backsql_search( Operation *op, SlapReply *rs )
{
	backsql_info		*bi = (backsql_info *)op->o_bd->be_private;
	SQLHDBC			dbh = SQL_NULL_HDBC;
	int			sres;
	Entry			user_entry = { 0 },
				base_entry = { 0 };
	int			manageDSAit = get_manageDSAit( op );
	time_t			stoptime = 0;
	backsql_srch_info	bsi = { 0 };
	backsql_entryID		*eid = NULL;
	struct berval		nbase = BER_BVNULL;
#ifndef BACKSQL_ARBITRARY_KEY
	ID			lastid = 0;
#endif /* ! BACKSQL_ARBITRARY_KEY */

	Debug( LDAP_DEBUG_TRACE, "==>backsql_search(): "
		"base=\"%s\", filter=\"%s\", scope=%d,", 
		op->o_req_ndn.bv_val,
		op->ors_filterstr.bv_val,
		op->ors_scope );
	Debug( LDAP_DEBUG_TRACE, " deref=%d, attrsonly=%d, "
		"attributes to load: %s\n",
		op->ors_deref,
		op->ors_attrsonly,
		op->ors_attrs == NULL ? "all" : "custom list" );

	if ( op->o_req_ndn.bv_len > BACKSQL_MAX_DN_LEN ) {
		Debug( LDAP_DEBUG_TRACE, "backsql_search(): "
			"search base length (%ld) exceeds max length (%d)\n", 
			op->o_req_ndn.bv_len, BACKSQL_MAX_DN_LEN );
		/*
		 * FIXME: a LDAP_NO_SUCH_OBJECT could be appropriate
		 * since it is impossible that such a long DN exists
		 * in the backend
		 */
		rs->sr_err = LDAP_ADMINLIMIT_EXCEEDED;
		send_ldap_result( op, rs );
		return 1;
	}

	sres = backsql_get_db_conn( op, &dbh );
	if ( sres != LDAP_SUCCESS ) {
		Debug( LDAP_DEBUG_TRACE, "backsql_search(): "
			"could not get connection handle - exiting\n" );
		rs->sr_err = sres;
		rs->sr_text = sres == LDAP_OTHER ?  "SQL-backend error" : NULL;
		send_ldap_result( op, rs );
		return 1;
	}

	/* compute it anyway; root does not use it */
	stoptime = op->o_time + op->ors_tlimit;

	/* init search */
	bsi.bsi_e = &base_entry;
	rs->sr_err = backsql_init_search( &bsi, &op->o_req_ndn,
			op->ors_scope,
			stoptime, op->ors_filter,
			dbh, op, rs, op->ors_attrs,
			( BACKSQL_ISF_MATCHED | BACKSQL_ISF_GET_ENTRY ) );
	switch ( rs->sr_err ) {
	case LDAP_SUCCESS:
		break;

	case LDAP_REFERRAL:
		if ( manageDSAit && !BER_BVISNULL( &bsi.bsi_e->e_nname ) &&
				dn_match( &op->o_req_ndn, &bsi.bsi_e->e_nname ) )
		{
			rs->sr_err = LDAP_SUCCESS;
			rs->sr_text = NULL;
			rs->sr_matched = NULL;
			if ( rs->sr_ref ) {
				ber_bvarray_free( rs->sr_ref );
				rs->sr_ref = NULL;
			}
			break;
		}

		/* an entry was created; free it */
		entry_clean( bsi.bsi_e );

		/* fall thru */

	default:
		if ( !BER_BVISNULL( &base_entry.e_nname )
				&& !access_allowed( op, &base_entry,
					slap_schema.si_ad_entry, NULL,
					ACL_DISCLOSE, NULL ) )
		{
			rs->sr_err = LDAP_NO_SUCH_OBJECT;
			if ( rs->sr_ref ) {
				ber_bvarray_free( rs->sr_ref );
				rs->sr_ref = NULL;
			}
			rs->sr_matched = NULL;
			rs->sr_text = NULL;
		}

		send_ldap_result( op, rs );

		if ( rs->sr_ref ) {
			ber_bvarray_free( rs->sr_ref );
			rs->sr_ref = NULL;
		}

		if ( !BER_BVISNULL( &base_entry.e_nname ) ) {
			entry_clean( &base_entry );
		}

		goto done;
	}
	/* NOTE: __NEW__ "search" access is required
	 * on searchBase object */
	{
		slap_mask_t	mask;
		
		if ( get_assert( op ) &&
				( test_filter( op, &base_entry, get_assertion( op ) )
				  != LDAP_COMPARE_TRUE ) )
		{
			rs->sr_err = LDAP_ASSERTION_FAILED;
			
		}
		if ( ! access_allowed_mask( op, &base_entry,
					slap_schema.si_ad_entry,
					NULL, ACL_SEARCH, NULL, &mask ) )
		{
			if ( rs->sr_err == LDAP_SUCCESS ) {
				rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
			}
		}

		if ( rs->sr_err != LDAP_SUCCESS ) {
			if ( !ACL_GRANT( mask, ACL_DISCLOSE ) ) {
				rs->sr_err = LDAP_NO_SUCH_OBJECT;
				rs->sr_text = NULL;
			}
			send_ldap_result( op, rs );
			goto done;
		}
	}

	bsi.bsi_e = NULL;

	bsi.bsi_n_candidates =
		( op->ors_limit == NULL	/* isroot == TRUE */ ? -2 : 
		( op->ors_limit->lms_s_unchecked == -1 ? -2 :
		( op->ors_limit->lms_s_unchecked ) ) );

#ifndef BACKSQL_ARBITRARY_KEY
	/* If paged results are in effect, check the paging cookie */
	if ( get_pagedresults( op ) > SLAP_CONTROL_IGNORED ) {
		rs->sr_err = parse_paged_cookie( op, rs );
		if ( rs->sr_err != LDAP_SUCCESS ) {
			send_ldap_result( op, rs );
			goto done;
		}
	}
#endif /* ! BACKSQL_ARBITRARY_KEY */

	switch ( bsi.bsi_scope ) {
	case LDAP_SCOPE_BASE:
	case BACKSQL_SCOPE_BASE_LIKE:
		/*
		 * probably already found...
		 */
		bsi.bsi_id_list = &bsi.bsi_base_id;
		bsi.bsi_id_listtail = &bsi.bsi_base_id.eid_next;
		break;

	case LDAP_SCOPE_SUBTREE:
		/*
		 * if baseObject is defined, and if it is the root 
		 * of the search, add it to the candidate list
		 */
		if ( bi->sql_baseObject && BACKSQL_IS_BASEOBJECT_ID( &bsi.bsi_base_id.eid_id ) )
		{
			bsi.bsi_id_list = &bsi.bsi_base_id;
			bsi.bsi_id_listtail = &bsi.bsi_base_id.eid_next;
		}

		/* FALLTHRU */
	default:

		/*
		 * for each objectclass we try to construct query which gets IDs
		 * of entries matching LDAP query filter and scope (or at least 
		 * candidates), and get the IDs. Do this in ID order for paging.
		 */
		ldap_avl_apply( bi->sql_oc_by_id, backsql_oc_get_candidates,
				&bsi, BACKSQL_AVL_STOP, AVL_INORDER );

		/* check for abandon */
		if ( op->o_abandon ) {
			eid = bsi.bsi_id_list;
			rs->sr_err = SLAPD_ABANDON;
			goto send_results;
		}
	}

	if ( op->ors_limit != NULL	/* isroot == FALSE */
			&& op->ors_limit->lms_s_unchecked != -1
			&& bsi.bsi_n_candidates == -1 )
	{
		rs->sr_err = LDAP_ADMINLIMIT_EXCEEDED;
		send_ldap_result( op, rs );
		goto done;
	}

	/*
	 * now we load candidate entries (only those attributes 
	 * mentioned in attrs and filter), test it against full filter 
	 * and then send to client; don't free entry_id if baseObject...
	 */
	for ( eid = bsi.bsi_id_list;
		eid != NULL; 
		eid = backsql_free_entryID( 
			eid, eid == &bsi.bsi_base_id ? 0 : 1, op->o_tmpmemctx ) )
	{
		int		rc;
		Attribute	*a_hasSubordinate = NULL,
				*a_entryUUID = NULL,
				*a_entryCSN = NULL,
				**ap = NULL;
		Entry		*e = NULL;

		/* check for abandon */
		if ( op->o_abandon ) {
			rs->sr_err = SLAPD_ABANDON;
			goto send_results;
		}

		/* check time limit */
		if ( op->ors_tlimit != SLAP_NO_LIMIT
				&& slap_get_time() > stoptime )
		{
			rs->sr_err = LDAP_TIMELIMIT_EXCEEDED;
			rs->sr_ctrls = NULL;
			rs->sr_ref = rs->sr_v2ref;
			goto send_results;
		}

		Debug(LDAP_DEBUG_TRACE, "backsql_search(): loading data "
			"for entry id=" BACKSQL_IDFMT " oc_id=" BACKSQL_IDNUMFMT ", keyval=" BACKSQL_IDFMT "\n",
			BACKSQL_IDARG(eid->eid_id),
			eid->eid_oc_id,
			BACKSQL_IDARG(eid->eid_keyval) );

		/* check scope */
		switch ( op->ors_scope ) {
		case LDAP_SCOPE_BASE:
		case BACKSQL_SCOPE_BASE_LIKE:
			if ( !dn_match( &eid->eid_ndn, &op->o_req_ndn ) ) {
				goto next_entry2;
			}
			break;

		case LDAP_SCOPE_ONE:
		{
			struct berval	rdn = eid->eid_ndn;

			rdn.bv_len -= op->o_req_ndn.bv_len + STRLENOF( "," );
			if ( !dnIsOneLevelRDN( &rdn ) ) {
				goto next_entry2;
			}
			/* fall thru */
		}

		case LDAP_SCOPE_SUBORDINATE:
			/* discard the baseObject entry */
			if ( dn_match( &eid->eid_ndn, &op->o_req_ndn ) ) {
				goto next_entry2;
			}
			/* FALLTHRU */
		case LDAP_SCOPE_SUBTREE:
			/* FIXME: this should never fail... */
			if ( !dnIsSuffix( &eid->eid_ndn, &op->o_req_ndn ) ) {
				goto next_entry2;
			}
			break;
		}

		if ( BACKSQL_IS_BASEOBJECT_ID( &eid->eid_id ) ) {
			/* don't recollect baseObject... */
			e = bi->sql_baseObject;

		} else if ( eid == &bsi.bsi_base_id ) {
			/* don't recollect searchBase object... */
			e = &base_entry;

		} else {
			bsi.bsi_e = &user_entry;
			rc = backsql_id2entry( &bsi, eid );
			if ( rc != LDAP_SUCCESS ) {
				Debug( LDAP_DEBUG_TRACE, "backsql_search(): "
					"error %d in backsql_id2entry() "
					"- skipping\n", rc );
				continue;
			}
			e = &user_entry;
		}

		if ( !manageDSAit &&
				op->ors_scope != LDAP_SCOPE_BASE &&
				op->ors_scope != BACKSQL_SCOPE_BASE_LIKE &&
				is_entry_referral( e ) )
		{
			BerVarray refs;

			refs = get_entry_referrals( op, e );
			if ( !refs ) {
				backsql_srch_info	bsi2 = { 0 };
				Entry			user_entry2 = { 0 };

				/* retry with the full entry... */
				bsi2.bsi_e = &user_entry2;
				rc = backsql_init_search( &bsi2,
						&e->e_nname,
						LDAP_SCOPE_BASE, 
						(time_t)(-1), NULL,
						dbh, op, rs, NULL,
						BACKSQL_ISF_GET_ENTRY );
				if ( rc == LDAP_SUCCESS ) {
					if ( is_entry_referral( &user_entry2 ) )
					{
						refs = get_entry_referrals( op,
								&user_entry2 );
					} else {
						rs->sr_err = LDAP_OTHER;
					}
					backsql_entry_clean( op, &user_entry2 );
				}
				if ( bsi2.bsi_attrs != NULL ) {
					op->o_tmpfree( bsi2.bsi_attrs,
							op->o_tmpmemctx );
				}
			}

			if ( refs ) {
				rs->sr_ref = referral_rewrite( refs,
						&e->e_name,
						&op->o_req_dn,
						op->ors_scope );
				ber_bvarray_free( refs );
			}

			if ( rs->sr_ref ) {
				rs->sr_err = LDAP_REFERRAL;

			} else {
				rs->sr_text = "bad referral object";
			}

			rs->sr_entry = e;
			rs->sr_matched = user_entry.e_name.bv_val;
			send_search_reference( op, rs );

			ber_bvarray_free( rs->sr_ref );
			rs->sr_ref = NULL;
			rs->sr_matched = NULL;
			rs->sr_entry = NULL;
			if ( rs->sr_err == LDAP_REFERRAL ) {
				rs->sr_err = LDAP_SUCCESS;
			}

			goto next_entry;
		}

		/*
		 * We use this flag since we need to parse the filter
		 * anyway; we should have used the frontend API function
		 * filter_has_subordinates()
		 */
		if ( bsi.bsi_flags & BSQL_SF_FILTER_HASSUBORDINATE ) {
			rc = backsql_has_children( op, dbh, &e->e_nname );

			switch ( rc ) {
			case LDAP_COMPARE_TRUE:
			case LDAP_COMPARE_FALSE:
				a_hasSubordinate = slap_operational_hasSubordinate( rc == LDAP_COMPARE_TRUE );
				if ( a_hasSubordinate != NULL ) {
					for ( ap = &user_entry.e_attrs; 
							*ap; 
							ap = &(*ap)->a_next );

					*ap = a_hasSubordinate;
				}
				rc = 0;
				break;

			default:
				Debug(LDAP_DEBUG_TRACE, 
					"backsql_search(): "
					"has_children failed( %d)\n", 
					rc );
				rc = 1;
				goto next_entry;
			}
		}

		if ( bsi.bsi_flags & BSQL_SF_FILTER_ENTRYUUID ) {
			a_entryUUID = backsql_operational_entryUUID( bi, eid );
			if ( a_entryUUID != NULL ) {
				if ( ap == NULL ) {
					ap = &user_entry.e_attrs;
				}

				for ( ; *ap; ap = &(*ap)->a_next );

				*ap = a_entryUUID;
			}
		}

#ifdef BACKSQL_SYNCPROV
		if ( bsi.bsi_flags & BSQL_SF_FILTER_ENTRYCSN ) {
			a_entryCSN = backsql_operational_entryCSN( op );
			if ( a_entryCSN != NULL ) {
				if ( ap == NULL ) {
					ap = &user_entry.e_attrs;
				}

				for ( ; *ap; ap = &(*ap)->a_next );

				*ap = a_entryCSN;
			}
		}
#endif /* BACKSQL_SYNCPROV */

		if ( test_filter( op, e, op->ors_filter ) == LDAP_COMPARE_TRUE )
		{
#ifndef BACKSQL_ARBITRARY_KEY
			/* If paged results are in effect, see if the page limit was exceeded */
			if ( get_pagedresults(op) > SLAP_CONTROL_IGNORED ) {
				if ( rs->sr_nentries >= ((PagedResultsState *)op->o_pagedresults_state)->ps_size )
				{
					e = NULL;
					send_paged_response( op, rs, &lastid );
					goto done;
				}
				lastid = SQL_TO_PAGECOOKIE( eid->eid_id, eid->eid_oc_id );
			}
#endif /* ! BACKSQL_ARBITRARY_KEY */
			rs->sr_attrs = op->ors_attrs;
			rs->sr_operational_attrs = NULL;
			rs->sr_entry = e;
			e->e_private = (void *)eid;
			rs->sr_flags = ( e == &user_entry ) ? REP_ENTRY_MODIFIABLE : 0;
			/* FIXME: need the whole entry (ITS#3480) */
			rs->sr_err = send_search_entry( op, rs );
			e->e_private = NULL;
			rs->sr_entry = NULL;
			rs->sr_attrs = NULL;
			rs->sr_operational_attrs = NULL;

			switch ( rs->sr_err ) {
			case LDAP_UNAVAILABLE:
				/*
				 * FIXME: send_search_entry failed;
				 * better stop
				 */
				Debug( LDAP_DEBUG_TRACE, "backsql_search(): "
					"connection lost\n" );
				goto end_of_search;

			case LDAP_SIZELIMIT_EXCEEDED:
			case LDAP_BUSY:
				goto send_results;
			}
		}

next_entry:;
		if ( e == &user_entry ) {
			backsql_entry_clean( op, &user_entry );
		}

next_entry2:;
	}

end_of_search:;
	if ( rs->sr_nentries > 0 ) {
		rs->sr_ref = rs->sr_v2ref;
		rs->sr_err = (rs->sr_v2ref == NULL) ? LDAP_SUCCESS
			: LDAP_REFERRAL;

	} else {
		rs->sr_err = bsi.bsi_status;
	}

send_results:;
	if ( rs->sr_err != SLAPD_ABANDON ) {
#ifndef BACKSQL_ARBITRARY_KEY
		if ( get_pagedresults(op) > SLAP_CONTROL_IGNORED ) {
			send_paged_response( op, rs, NULL );
		} else
#endif /* ! BACKSQL_ARBITRARY_KEY */
		{
			send_ldap_result( op, rs );
		}
	}

	/* cleanup in case of abandon */
	for ( ; eid != NULL; 
		eid = backsql_free_entryID(
			eid, eid == &bsi.bsi_base_id ? 0 : 1, op->o_tmpmemctx ) )
		;

	backsql_entry_clean( op, &base_entry );

	/* in case we got here accidentally */
	backsql_entry_clean( op, &user_entry );

	if ( rs->sr_v2ref ) {
		ber_bvarray_free( rs->sr_v2ref );
		rs->sr_v2ref = NULL;
	}

#ifdef BACKSQL_SYNCPROV
	if ( op->o_sync ) {
		Operation	op2 = *op;
		SlapReply	rs2 = { REP_RESULT };
		Entry		*e = entry_alloc();
		slap_callback	cb = { 0 };

		op2.o_tag = LDAP_REQ_ADD;
		op2.o_bd = select_backend( &op->o_bd->be_nsuffix[0], 0 );
		op2.ora_e = e;
		op2.o_callback = &cb;

		ber_dupbv( &e->e_name, op->o_bd->be_suffix );
		ber_dupbv( &e->e_nname, op->o_bd->be_nsuffix );

		cb.sc_response = slap_null_cb;

		op2.o_bd->be_add( &op2, &rs2 );

		if ( op2.ora_e == e )
			entry_free( e );
	}
#endif /* BACKSQL_SYNCPROV */

done:;
	(void)backsql_free_entryID( &bsi.bsi_base_id, 0, op->o_tmpmemctx );

	if ( bsi.bsi_attrs != NULL ) {
		op->o_tmpfree( bsi.bsi_attrs, op->o_tmpmemctx );
	}

	if ( !BER_BVISNULL( &nbase )
			&& nbase.bv_val != op->o_req_ndn.bv_val )
	{
		ch_free( nbase.bv_val );
	}

	/* restore scope ... FIXME: this should be done before ANY
	 * frontend call that uses op */
	if ( op->ors_scope == BACKSQL_SCOPE_BASE_LIKE ) {
		op->ors_scope = LDAP_SCOPE_BASE;
	}

	SQLTransact( SQL_NULL_HENV, dbh, SQL_ROLLBACK );
	Debug( LDAP_DEBUG_TRACE, "<==backsql_search()\n" );

	return rs->sr_err;
}