backsql_oc_get_candidates( void *v_oc, void *v_bsi )
{
	backsql_oc_map_rec	*oc = v_oc;
	backsql_srch_info	*bsi = v_bsi;
	Operation		*op = bsi->bsi_op;
	backsql_info		*bi = (backsql_info *)bsi->bsi_op->o_bd->be_private;
	struct berval		query;
	SQLHSTMT		sth = SQL_NULL_HSTMT;
	RETCODE			rc;
	int			res;
	BACKSQL_ROW_NTS		row;
	int			i;
	int			j;
	int			n_candidates = bsi->bsi_n_candidates;

	/* 
	 * + 1 because we need room for '%';
	 * + 1 because we need room for ',' for LDAP_SCOPE_SUBORDINATE;
	 * this makes a subtree
	 * search for a DN BACKSQL_MAX_DN_LEN long legal 
	 * if it returns that DN only
	 */
	char			tmp_base_ndn[ BACKSQL_MAX_DN_LEN + 1 + 1 ];

	bsi->bsi_status = LDAP_SUCCESS;
 
	Debug( LDAP_DEBUG_TRACE, "==>backsql_oc_get_candidates(): oc=\"%s\"\n",
			BACKSQL_OC_NAME( oc ) );

	/* check for abandon */
	if ( op->o_abandon ) {
		bsi->bsi_status = SLAPD_ABANDON;
		return BACKSQL_AVL_STOP;
	}

#ifndef BACKSQL_ARBITRARY_KEY
	/* If paged results have already completed this objectClass, skip it */
	if ( get_pagedresults(op) > SLAP_CONTROL_IGNORED ) {
		if ( oc->bom_id < PAGECOOKIE_TO_SQL_OC( ((PagedResultsState *)op->o_pagedresults_state)->ps_cookie ) )
		{
			return BACKSQL_AVL_CONTINUE;
		}
	}
#endif /* ! BACKSQL_ARBITRARY_KEY */

	if ( bsi->bsi_n_candidates == -1 ) {
		Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_candidates(): "
			"unchecked limit has been overcome\n" );
		/* should never get here */
		assert( 0 );
		bsi->bsi_status = LDAP_ADMINLIMIT_EXCEEDED;
		return BACKSQL_AVL_STOP;
	}
	
	bsi->bsi_oc = oc;
	res = backsql_srch_query( bsi, &query );
	if ( res ) {
		Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_candidates(): "
			"error while constructing query for objectclass \"%s\"\n",
			oc->bom_oc->soc_cname.bv_val );
		/*
		 * FIXME: need to separate errors from legally
		 * impossible filters
		 */
		switch ( bsi->bsi_status ) {
		case LDAP_SUCCESS:
		case LDAP_UNDEFINED_TYPE:
		case LDAP_NO_SUCH_OBJECT:
			/* we are conservative... */
		default:
			bsi->bsi_status = LDAP_SUCCESS;
			/* try next */
			return BACKSQL_AVL_CONTINUE;

		case LDAP_ADMINLIMIT_EXCEEDED:
		case LDAP_OTHER:
			/* don't try any more */
			return BACKSQL_AVL_STOP;
		}
	}

	if ( BER_BVISNULL( &query ) ) {
		Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_candidates(): "
			"could not construct query for objectclass \"%s\"\n",
			oc->bom_oc->soc_cname.bv_val );
		bsi->bsi_status = LDAP_SUCCESS;
		return BACKSQL_AVL_CONTINUE;
	}

	Debug( LDAP_DEBUG_TRACE, "Constructed query: %s\n", 
			query.bv_val );

	rc = backsql_Prepare( bsi->bsi_dbh, &sth, query.bv_val, 0 );
	bsi->bsi_op->o_tmpfree( query.bv_val, bsi->bsi_op->o_tmpmemctx );
	BER_BVZERO( &query );
	if ( rc != SQL_SUCCESS ) {
		Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_candidates(): "
			"error preparing query\n" );
		backsql_PrintErrors( bi->sql_db_env, bsi->bsi_dbh, sth, rc );
		bsi->bsi_status = LDAP_OTHER;
		return BACKSQL_AVL_CONTINUE;
	}
	
	Debug( LDAP_DEBUG_TRACE, "id: '" BACKSQL_IDNUMFMT "'\n",
		bsi->bsi_oc->bom_id );

	rc = backsql_BindParamNumID( sth, 1, SQL_PARAM_INPUT,
			&bsi->bsi_oc->bom_id );
	if ( rc != SQL_SUCCESS ) {
		Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_candidates(): "
			"error binding objectclass id parameter\n" );
		bsi->bsi_status = LDAP_OTHER;
		return BACKSQL_AVL_CONTINUE;
	}

	switch ( bsi->bsi_scope ) {
	case LDAP_SCOPE_BASE:
	case BACKSQL_SCOPE_BASE_LIKE:
		/*
		 * We do not accept DNs longer than BACKSQL_MAX_DN_LEN;
		 * however this should be handled earlier
		 */
		if ( bsi->bsi_base_ndn->bv_len > BACKSQL_MAX_DN_LEN ) {
			bsi->bsi_status = LDAP_OTHER;
			return BACKSQL_AVL_CONTINUE;
		}

		AC_MEMCPY( tmp_base_ndn, bsi->bsi_base_ndn->bv_val,
				bsi->bsi_base_ndn->bv_len + 1 );

		/* uppercase DN only if the stored DN can be uppercased
		 * for comparison */
		if ( BACKSQL_CANUPPERCASE( bi ) ) {
			ldap_pvt_str2upper( tmp_base_ndn );
		}

		Debug( LDAP_DEBUG_TRACE, "(base)dn: \"%s\"\n",
				tmp_base_ndn );

		rc = backsql_BindParamStr( sth, 2, SQL_PARAM_INPUT,
				tmp_base_ndn, BACKSQL_MAX_DN_LEN );
		if ( rc != SQL_SUCCESS ) {
         		Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_candidates(): "
				"error binding base_ndn parameter\n" );
			backsql_PrintErrors( bi->sql_db_env, bsi->bsi_dbh, 
					sth, rc );
			bsi->bsi_status = LDAP_OTHER;
			return BACKSQL_AVL_CONTINUE;
		}
		break;

	case LDAP_SCOPE_SUBORDINATE:
	case LDAP_SCOPE_SUBTREE:
	{
		/* if short-cutting the search base,
		 * don't bind any parameter */
		if ( bsi->bsi_use_subtree_shortcut ) {
			break;
		}
		
		/*
		 * We do not accept DNs longer than BACKSQL_MAX_DN_LEN;
		 * however this should be handled earlier
		 */
		if ( bsi->bsi_base_ndn->bv_len > BACKSQL_MAX_DN_LEN ) {
			bsi->bsi_status = LDAP_OTHER;
			return BACKSQL_AVL_CONTINUE;
		}

		/* 
		 * Sets the parameters for the SQL built earlier
		 * NOTE that all the databases could actually use 
		 * the TimesTen version, which would be cleaner 
		 * and would also eliminate the need for the
		 * subtree_cond line in the configuration file.  
		 * For now, I'm leaving it the way it is, 
		 * so non-TimesTen databases use the original code.
		 * But at some point this should get cleaned up.
		 *
		 * If "dn" is being used, do a suffix search.
		 * If "dn_ru" is being used, do a prefix search.
		 */
		if ( BACKSQL_HAS_LDAPINFO_DN_RU( bi ) ) {
			tmp_base_ndn[ 0 ] = '\0';

			for ( i = 0, j = bsi->bsi_base_ndn->bv_len - 1;
					j >= 0; i++, j--) {
				tmp_base_ndn[ i ] = bsi->bsi_base_ndn->bv_val[ j ];
			}

			if ( bsi->bsi_scope == LDAP_SCOPE_SUBORDINATE ) {
				tmp_base_ndn[ i++ ] = ',';
			}

			tmp_base_ndn[ i ] = '%';
			tmp_base_ndn[ i + 1 ] = '\0';

		} else {
			i = 0;

			tmp_base_ndn[ i++ ] = '%';

			if ( bsi->bsi_scope == LDAP_SCOPE_SUBORDINATE ) {
				tmp_base_ndn[ i++ ] = ',';
			}

			AC_MEMCPY( &tmp_base_ndn[ i ], bsi->bsi_base_ndn->bv_val,
				bsi->bsi_base_ndn->bv_len + 1 );
		}

		/* uppercase DN only if the stored DN can be uppercased
		 * for comparison */
		if ( BACKSQL_CANUPPERCASE( bi ) ) {
			ldap_pvt_str2upper( tmp_base_ndn );
		}

		if ( bsi->bsi_scope == LDAP_SCOPE_SUBORDINATE ) {
			Debug( LDAP_DEBUG_TRACE, "(children)dn: \"%s\"\n",
				tmp_base_ndn );
		} else {
			Debug( LDAP_DEBUG_TRACE, "(sub)dn: \"%s\"\n",
				tmp_base_ndn );
		}

		rc = backsql_BindParamStr( sth, 2, SQL_PARAM_INPUT,
				tmp_base_ndn, BACKSQL_MAX_DN_LEN );
		if ( rc != SQL_SUCCESS ) {
			Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_candidates(): "
				"error binding base_ndn parameter (2)\n" );
			backsql_PrintErrors( bi->sql_db_env, bsi->bsi_dbh, 
					sth, rc );
			bsi->bsi_status = LDAP_OTHER;
			return BACKSQL_AVL_CONTINUE;
		}
		break;
	}

 	case LDAP_SCOPE_ONELEVEL:
		assert( !BER_BVISNULL( &bsi->bsi_base_id.eid_ndn ) );

		Debug( LDAP_DEBUG_TRACE, "(one)id=" BACKSQL_IDFMT "\n",
			BACKSQL_IDARG(bsi->bsi_base_id.eid_id) );
		rc = backsql_BindParamID( sth, 2, SQL_PARAM_INPUT,
				&bsi->bsi_base_id.eid_id );
		if ( rc != SQL_SUCCESS ) {
			Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_candidates(): "
				"error binding base id parameter\n" );
			bsi->bsi_status = LDAP_OTHER;
			return BACKSQL_AVL_CONTINUE;
		}
		break;
	}
	
	rc = SQLExecute( sth );
	if ( !BACKSQL_SUCCESS( rc ) ) {
		Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_candidates(): "
			"error executing query\n" );
		backsql_PrintErrors( bi->sql_db_env, bsi->bsi_dbh, sth, rc );
		SQLFreeStmt( sth, SQL_DROP );
		bsi->bsi_status = LDAP_OTHER;
		return BACKSQL_AVL_CONTINUE;
	}

	backsql_BindRowAsStrings_x( sth, &row, bsi->bsi_op->o_tmpmemctx );
	rc = SQLFetch( sth );
	for ( ; BACKSQL_SUCCESS( rc ); rc = SQLFetch( sth ) ) {
		struct berval		dn, pdn, ndn;
		backsql_entryID		*c_id = NULL;
		int			ret;

		ber_str2bv( row.cols[ 3 ], 0, 0, &dn );

		if ( backsql_api_odbc2dn( bsi->bsi_op, bsi->bsi_rs, &dn ) ) {
			continue;
		}

		ret = dnPrettyNormal( NULL, &dn, &pdn, &ndn, op->o_tmpmemctx );
		if ( dn.bv_val != row.cols[ 3 ] ) {
			free( dn.bv_val );
		}

		if ( ret != LDAP_SUCCESS ) {
			continue;
		}

		if ( bi->sql_baseObject && dn_match( &ndn, &bi->sql_baseObject->e_nname ) ) {
			goto cleanup;
		}

		c_id = (backsql_entryID *)op->o_tmpcalloc( 1, 
				sizeof( backsql_entryID ), op->o_tmpmemctx );
#ifdef BACKSQL_ARBITRARY_KEY
		ber_str2bv_x( row.cols[ 0 ], 0, 1, &c_id->eid_id,
				op->o_tmpmemctx );
		ber_str2bv_x( row.cols[ 1 ], 0, 1, &c_id->eid_keyval,
				op->o_tmpmemctx );
#else /* ! BACKSQL_ARBITRARY_KEY */
		if ( BACKSQL_STR2ID( &c_id->eid_id, row.cols[ 0 ], 0 ) != 0 ) {
			goto cleanup;
		}
		if ( BACKSQL_STR2ID( &c_id->eid_keyval, row.cols[ 1 ], 0 ) != 0 ) {
			goto cleanup;
		}
#endif /* ! BACKSQL_ARBITRARY_KEY */
		c_id->eid_oc = bsi->bsi_oc;
		c_id->eid_oc_id = bsi->bsi_oc->bom_id;

		c_id->eid_dn = pdn;
		c_id->eid_ndn = ndn;

		/* append at end of list ... */
		c_id->eid_next = NULL;
		*bsi->bsi_id_listtail = c_id;
		bsi->bsi_id_listtail = &c_id->eid_next;

		Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_candidates(): "
			"added entry id=" BACKSQL_IDFMT " keyval=" BACKSQL_IDFMT " dn=\"%s\"\n",
			BACKSQL_IDARG(c_id->eid_id),
			BACKSQL_IDARG(c_id->eid_keyval),
			row.cols[ 3 ] );

		/* count candidates, for unchecked limit */
		bsi->bsi_n_candidates--;
		if ( bsi->bsi_n_candidates == -1 ) {
			break;
		}
		continue;

cleanup:;
		if ( !BER_BVISNULL( &pdn ) ) {
			op->o_tmpfree( pdn.bv_val, op->o_tmpmemctx );
		}
		if ( !BER_BVISNULL( &ndn ) ) {
			op->o_tmpfree( ndn.bv_val, op->o_tmpmemctx );
		}
		if ( c_id != NULL ) {
			ch_free( c_id );
		}
	}
	backsql_FreeRow_x( &row, bsi->bsi_op->o_tmpmemctx );
	SQLFreeStmt( sth, SQL_DROP );

	Debug( LDAP_DEBUG_TRACE, "<==backsql_oc_get_candidates(): %d\n",
			n_candidates - bsi->bsi_n_candidates );

	return ( bsi->bsi_n_candidates == -1 ? BACKSQL_AVL_STOP : BACKSQL_AVL_CONTINUE );
}