xref: /netbsd-src/external/bsd/openldap/dist/servers/slapd/back-sql/delete.c (revision b1c86f5f087524e68db12794ee9c3e3da1ab17a0)
1 /*	$NetBSD: delete.c,v 1.1.1.2 2010/03/08 02:14:20 lukem Exp $	*/
2 
3 /* OpenLDAP: pkg/ldap/servers/slapd/back-sql/delete.c,v 1.35.2.9 2009/01/22 00:01:11 kurt Exp */
4 /* This work is part of OpenLDAP Software <http://www.openldap.org/>.
5  *
6  * Copyright 1999-2009 The OpenLDAP Foundation.
7  * Portions Copyright 1999 Dmitry Kovalev.
8  * Portions Copyright 2002 Pierangelo Masarati.
9  * All rights reserved.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted only as authorized by the OpenLDAP
13  * Public License.
14  *
15  * A copy of this license is available in the file LICENSE in the
16  * top-level directory of the distribution or, alternatively, at
17  * <http://www.OpenLDAP.org/license.html>.
18  */
19 /* ACKNOWLEDGEMENTS:
20  * This work was initially developed by Dmitry Kovalev for inclusion
21  * by OpenLDAP Software.  Additional significant contributors include
22  * Pierangelo Masarati.
23  */
24 
25 #include "portable.h"
26 
27 #include <stdio.h>
28 #include <sys/types.h>
29 #include "ac/string.h"
30 
31 #include "slap.h"
32 #include "proto-sql.h"
33 
34 typedef struct backsql_delete_attr_t {
35 	Operation 		*op;
36 	SlapReply		*rs;
37 	SQLHDBC			dbh;
38 	backsql_entryID		*e_id;
39 } backsql_delete_attr_t;
40 
41 static int
42 backsql_delete_attr_f( void *v_at, void *v_bda )
43 {
44 	backsql_at_map_rec	*at = (backsql_at_map_rec *)v_at;
45 	backsql_delete_attr_t	*bda = (backsql_delete_attr_t *)v_bda;
46 	int			rc;
47 
48 	rc = backsql_modify_delete_all_values( bda->op,
49 			bda->rs, bda->dbh, bda->e_id, at );
50 
51 	if ( rc != LDAP_SUCCESS ) {
52 		return BACKSQL_AVL_STOP;
53 	}
54 
55 	return BACKSQL_AVL_CONTINUE;
56 }
57 
58 static int
59 backsql_delete_all_attrs(
60 	Operation 		*op,
61 	SlapReply		*rs,
62 	SQLHDBC			dbh,
63 	backsql_entryID		*eid )
64 {
65 	backsql_delete_attr_t	bda;
66 	int			rc;
67 
68 	bda.op = op;
69 	bda.rs = rs;
70 	bda.dbh = dbh;
71 	bda.e_id = eid;
72 
73 	rc = avl_apply( eid->eid_oc->bom_attrs, backsql_delete_attr_f, &bda,
74 			BACKSQL_AVL_STOP, AVL_INORDER );
75 	if ( rc == BACKSQL_AVL_STOP ) {
76 		return rs->sr_err;
77 	}
78 
79 	return LDAP_SUCCESS;
80 }
81 
82 static int
83 backsql_delete_int(
84 	Operation	*op,
85 	SlapReply	*rs,
86 	SQLHDBC		dbh,
87 	SQLHSTMT	*sthp,
88 	backsql_entryID	*eid,
89 	Entry		**ep )
90 {
91 	backsql_info 		*bi = (backsql_info*)op->o_bd->be_private;
92 	SQLHSTMT		sth = SQL_NULL_HSTMT;
93 	RETCODE			rc;
94 	int			prc = LDAP_SUCCESS;
95 	/* first parameter no */
96 	SQLUSMALLINT		pno = 0;
97 
98 	sth = *sthp;
99 
100 	/* avl_apply ... */
101 	rs->sr_err = backsql_delete_all_attrs( op, rs, dbh, eid );
102 	if ( rs->sr_err != LDAP_SUCCESS ) {
103 		goto done;
104 	}
105 
106 	rc = backsql_Prepare( dbh, &sth, eid->eid_oc->bom_delete_proc, 0 );
107 	if ( rc != SQL_SUCCESS ) {
108 		Debug( LDAP_DEBUG_TRACE,
109 			"   backsql_delete(): "
110 			"error preparing delete query\n",
111 			0, 0, 0 );
112 		backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc );
113 
114 		rs->sr_err = LDAP_OTHER;
115 		rs->sr_text = "SQL-backend error";
116 		*ep = NULL;
117 		goto done;
118 	}
119 
120 	if ( BACKSQL_IS_DEL( eid->eid_oc->bom_expect_return ) ) {
121 		pno = 1;
122 		rc = backsql_BindParamInt( sth, 1, SQL_PARAM_OUTPUT, &prc );
123 		if ( rc != SQL_SUCCESS ) {
124 			Debug( LDAP_DEBUG_TRACE,
125 				"   backsql_delete(): "
126 				"error binding output parameter for objectClass %s\n",
127 				eid->eid_oc->bom_oc->soc_cname.bv_val, 0, 0 );
128 			backsql_PrintErrors( bi->sql_db_env, dbh,
129 				sth, rc );
130 			SQLFreeStmt( sth, SQL_DROP );
131 
132 			rs->sr_text = "SQL-backend error";
133 			rs->sr_err = LDAP_OTHER;
134 			*ep = NULL;
135 			goto done;
136 		}
137 	}
138 
139 	rc = backsql_BindParamID( sth, pno + 1, SQL_PARAM_INPUT, &eid->eid_keyval );
140 	if ( rc != SQL_SUCCESS ) {
141 		Debug( LDAP_DEBUG_TRACE,
142 			"   backsql_delete(): "
143 			"error binding keyval parameter for objectClass %s\n",
144 			eid->eid_oc->bom_oc->soc_cname.bv_val, 0, 0 );
145 		backsql_PrintErrors( bi->sql_db_env, dbh,
146 			sth, rc );
147 		SQLFreeStmt( sth, SQL_DROP );
148 
149 		rs->sr_text = "SQL-backend error";
150 		rs->sr_err = LDAP_OTHER;
151 		*ep = NULL;
152 		goto done;
153 	}
154 
155 	rc = SQLExecute( sth );
156 	if ( rc == SQL_SUCCESS && prc == LDAP_SUCCESS ) {
157 		rs->sr_err = LDAP_SUCCESS;
158 
159 	} else {
160 		Debug( LDAP_DEBUG_TRACE, "   backsql_delete(): "
161 			"delete_proc execution failed (rc=%d, prc=%d)\n",
162 			rc, prc, 0 );
163 
164 
165 		if ( prc != LDAP_SUCCESS ) {
166 			/* SQL procedure executed fine
167 			 * but returned an error */
168 			rs->sr_err = BACKSQL_SANITIZE_ERROR( prc );
169 
170 		} else {
171 			backsql_PrintErrors( bi->sql_db_env, dbh,
172 					sth, rc );
173 			rs->sr_err = LDAP_OTHER;
174 		}
175 		SQLFreeStmt( sth, SQL_DROP );
176 		goto done;
177 	}
178 	SQLFreeStmt( sth, SQL_DROP );
179 
180 	/* delete "auxiliary" objectClasses, if any... */
181 	rc = backsql_Prepare( dbh, &sth, bi->sql_delobjclasses_stmt, 0 );
182 	if ( rc != SQL_SUCCESS ) {
183 		Debug( LDAP_DEBUG_TRACE,
184 			"   backsql_delete(): "
185 			"error preparing ldap_entry_objclasses delete query\n",
186 			0, 0, 0 );
187 		backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc );
188 
189 		rs->sr_err = LDAP_OTHER;
190 		rs->sr_text = "SQL-backend error";
191 		*ep = NULL;
192 		goto done;
193 	}
194 
195 	rc = backsql_BindParamID( sth, 1, SQL_PARAM_INPUT, &eid->eid_id );
196 	if ( rc != SQL_SUCCESS ) {
197 		Debug( LDAP_DEBUG_TRACE,
198 			"   backsql_delete(): "
199 			"error binding auxiliary objectClasses "
200 			"entry ID parameter for objectClass %s\n",
201 			eid->eid_oc->bom_oc->soc_cname.bv_val, 0, 0 );
202 		backsql_PrintErrors( bi->sql_db_env, dbh,
203 			sth, rc );
204 		SQLFreeStmt( sth, SQL_DROP );
205 
206 		rs->sr_text = "SQL-backend error";
207 		rs->sr_err = LDAP_OTHER;
208 		*ep = NULL;
209 		goto done;
210 	}
211 
212 	rc = SQLExecute( sth );
213 	switch ( rc ) {
214 	case SQL_NO_DATA:
215 		/* apparently there were no "auxiliary" objectClasses
216 		 * for this entry... */
217 	case SQL_SUCCESS:
218 		break;
219 
220 	default:
221 		Debug( LDAP_DEBUG_TRACE, "   backsql_delete(): "
222 			"failed to delete record from ldap_entry_objclasses\n",
223 			0, 0, 0 );
224 		backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc );
225 		SQLFreeStmt( sth, SQL_DROP );
226 		rs->sr_err = LDAP_OTHER;
227 		rs->sr_text = "SQL-backend error";
228 		*ep = NULL;
229 		goto done;
230 	}
231 	SQLFreeStmt( sth, SQL_DROP );
232 
233 	/* delete entry... */
234 	rc = backsql_Prepare( dbh, &sth, bi->sql_delentry_stmt, 0 );
235 	if ( rc != SQL_SUCCESS ) {
236 		Debug( LDAP_DEBUG_TRACE,
237 			"   backsql_delete(): "
238 			"error preparing ldap_entries delete query\n",
239 			0, 0, 0 );
240 		backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc );
241 
242 		rs->sr_err = LDAP_OTHER;
243 		rs->sr_text = "SQL-backend error";
244 		*ep = NULL;
245 		goto done;
246 	}
247 
248 	rc = backsql_BindParamID( sth, 1, SQL_PARAM_INPUT, &eid->eid_id );
249 	if ( rc != SQL_SUCCESS ) {
250 		Debug( LDAP_DEBUG_TRACE,
251 			"   backsql_delete(): "
252 			"error binding entry ID parameter "
253 			"for objectClass %s\n",
254 			eid->eid_oc->bom_oc->soc_cname.bv_val, 0, 0 );
255 		backsql_PrintErrors( bi->sql_db_env, dbh,
256 			sth, rc );
257 		SQLFreeStmt( sth, SQL_DROP );
258 
259 		rs->sr_text = "SQL-backend error";
260 		rs->sr_err = LDAP_OTHER;
261 		*ep = NULL;
262 		goto done;
263 	}
264 
265 	rc = SQLExecute( sth );
266 	if ( rc != SQL_SUCCESS ) {
267 		Debug( LDAP_DEBUG_TRACE, "   backsql_delete(): "
268 			"failed to delete record from ldap_entries\n",
269 			0, 0, 0 );
270 		backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc );
271 		SQLFreeStmt( sth, SQL_DROP );
272 		rs->sr_err = LDAP_OTHER;
273 		rs->sr_text = "SQL-backend error";
274 		*ep = NULL;
275 		goto done;
276 	}
277 	SQLFreeStmt( sth, SQL_DROP );
278 
279 	rs->sr_err = LDAP_SUCCESS;
280 	*ep = NULL;
281 
282 done:;
283 	*sthp = sth;
284 
285 	return rs->sr_err;
286 }
287 
288 typedef struct backsql_tree_delete_t {
289 	Operation	*btd_op;
290 	int		btd_rc;
291 	backsql_entryID	*btd_eid;
292 } backsql_tree_delete_t;
293 
294 static int
295 backsql_tree_delete_search_cb( Operation *op, SlapReply *rs )
296 {
297 	if ( rs->sr_type == REP_SEARCH ) {
298 		backsql_tree_delete_t	*btd;
299 		backsql_entryID		*eid;
300 
301 		btd = (backsql_tree_delete_t *)op->o_callback->sc_private;
302 
303 		if ( !access_allowed( btd->btd_op, rs->sr_entry,
304 			slap_schema.si_ad_entry, NULL, ACL_WDEL, NULL )
305 			|| !access_allowed( btd->btd_op, rs->sr_entry,
306 			slap_schema.si_ad_children, NULL, ACL_WDEL, NULL ) )
307 		{
308 			btd->btd_rc = LDAP_INSUFFICIENT_ACCESS;
309 			return rs->sr_err = LDAP_UNAVAILABLE;
310 		}
311 
312 		assert( rs->sr_entry != NULL );
313 		assert( rs->sr_entry->e_private != NULL );
314 
315 		eid = (backsql_entryID *)rs->sr_entry->e_private;
316 		assert( eid->eid_oc != NULL );
317 		if ( eid->eid_oc == NULL || eid->eid_oc->bom_delete_proc == NULL ) {
318 			btd->btd_rc = LDAP_UNWILLING_TO_PERFORM;
319 			return rs->sr_err = LDAP_UNAVAILABLE;
320 		}
321 
322 		eid = backsql_entryID_dup( eid, op->o_tmpmemctx );
323 		eid->eid_next = btd->btd_eid;
324 		btd->btd_eid = eid;
325 	}
326 
327 	return 0;
328 }
329 
330 static int
331 backsql_tree_delete(
332 	Operation	*op,
333 	SlapReply	*rs,
334 	SQLHDBC		dbh,
335 	SQLHSTMT	*sthp )
336 {
337 	Operation		op2 = *op;
338 	slap_callback		sc = { 0 };
339 	SlapReply		rs2 = { 0 };
340 	backsql_tree_delete_t	btd = { 0 };
341 
342 	int			rc;
343 
344 	/*
345 	 * - perform an internal subtree search as the rootdn
346 	 * - for each entry
347 	 *	- check access
348 	 *	- check objectClass and delete method(s)
349 	 * - for each entry
350 	 *	- delete
351 	 * - if successful, commit
352 	 */
353 
354 	op2.o_tag = LDAP_REQ_SEARCH;
355 	op2.o_protocol = LDAP_VERSION3;
356 
357 	btd.btd_op = op;
358 	sc.sc_private = &btd;
359 	sc.sc_response = backsql_tree_delete_search_cb;
360 	op2.o_callback = &sc;
361 
362 	op2.o_dn = op->o_bd->be_rootdn;
363 	op2.o_ndn = op->o_bd->be_rootndn;
364 
365 	op2.o_managedsait = SLAP_CONTROL_CRITICAL;
366 
367 	op2.ors_scope = LDAP_SCOPE_SUBTREE;
368 	op2.ors_deref = LDAP_DEREF_NEVER;
369 	op2.ors_slimit = SLAP_NO_LIMIT;
370 	op2.ors_tlimit = SLAP_NO_LIMIT;
371 	op2.ors_filter = (Filter *)slap_filter_objectClass_pres;
372 	op2.ors_filterstr = *slap_filterstr_objectClass_pres;
373 	op2.ors_attrs = slap_anlist_all_attributes;
374 	op2.ors_attrsonly = 0;
375 
376 	rc = op->o_bd->be_search( &op2, &rs2 );
377 	if ( rc != LDAP_SUCCESS ) {
378 		rc = rs->sr_err = btd.btd_rc;
379 		rs->sr_text = "subtree delete not possible";
380 		send_ldap_result( op, rs );
381 		goto clean;
382 	}
383 
384 	for ( ; btd.btd_eid != NULL;
385 		btd.btd_eid = backsql_free_entryID( btd.btd_eid,
386 			1, op->o_tmpmemctx ) )
387 	{
388 		Entry	*e = (void *)0xbad;
389 		rc = backsql_delete_int( op, rs, dbh, sthp, btd.btd_eid, &e );
390 		if ( rc != LDAP_SUCCESS ) {
391 			break;
392 		}
393 	}
394 
395 clean:;
396 	for ( ; btd.btd_eid != NULL;
397 		btd.btd_eid = backsql_free_entryID( btd.btd_eid,
398 			1, op->o_tmpmemctx ) )
399 		;
400 
401 	return rc;
402 }
403 
404 int
405 backsql_delete( Operation *op, SlapReply *rs )
406 {
407 	SQLHDBC 		dbh = SQL_NULL_HDBC;
408 	SQLHSTMT		sth = SQL_NULL_HSTMT;
409 	backsql_oc_map_rec	*oc = NULL;
410 	backsql_srch_info	bsi = { 0 };
411 	backsql_entryID		e_id = { 0 };
412 	Entry			d = { 0 }, p = { 0 }, *e = NULL;
413 	struct berval		pdn = BER_BVNULL;
414 	int			manageDSAit = get_manageDSAit( op );
415 
416 	Debug( LDAP_DEBUG_TRACE, "==>backsql_delete(): deleting entry \"%s\"\n",
417 			op->o_req_ndn.bv_val, 0, 0 );
418 
419 	rs->sr_err = backsql_get_db_conn( op, &dbh );
420 	if ( rs->sr_err != LDAP_SUCCESS ) {
421 		Debug( LDAP_DEBUG_TRACE, "   backsql_delete(): "
422 			"could not get connection handle - exiting\n",
423 			0, 0, 0 );
424 		rs->sr_text = ( rs->sr_err == LDAP_OTHER )
425 			? "SQL-backend error" : NULL;
426 		e = NULL;
427 		goto done;
428 	}
429 
430 	/*
431 	 * Get the entry
432 	 */
433 	bsi.bsi_e = &d;
434 	rs->sr_err = backsql_init_search( &bsi, &op->o_req_ndn,
435 			LDAP_SCOPE_BASE,
436 			(time_t)(-1), NULL, dbh, op, rs, slap_anlist_no_attrs,
437 			( BACKSQL_ISF_MATCHED | BACKSQL_ISF_GET_ENTRY | BACKSQL_ISF_GET_OC ) );
438 	switch ( rs->sr_err ) {
439 	case LDAP_SUCCESS:
440 		break;
441 
442 	case LDAP_REFERRAL:
443 		if ( manageDSAit && !BER_BVISNULL( &bsi.bsi_e->e_nname ) &&
444 				dn_match( &op->o_req_ndn, &bsi.bsi_e->e_nname ) )
445 		{
446 			rs->sr_err = LDAP_SUCCESS;
447 			rs->sr_text = NULL;
448 			rs->sr_matched = NULL;
449 			if ( rs->sr_ref ) {
450 				ber_bvarray_free( rs->sr_ref );
451 				rs->sr_ref = NULL;
452 			}
453 			break;
454 		}
455 		e = &d;
456 		/* fallthru */
457 
458 	default:
459 		Debug( LDAP_DEBUG_TRACE, "backsql_delete(): "
460 			"could not retrieve deleteDN ID - no such entry\n",
461 			0, 0, 0 );
462 		if ( !BER_BVISNULL( &d.e_nname ) ) {
463 			/* FIXME: should always be true! */
464 			e = &d;
465 
466 		} else {
467 			e = NULL;
468 		}
469 		goto done;
470 	}
471 
472 	if ( get_assert( op ) &&
473 			( test_filter( op, &d, get_assertion( op ) )
474 			  != LDAP_COMPARE_TRUE ) )
475 	{
476 		rs->sr_err = LDAP_ASSERTION_FAILED;
477 		e = &d;
478 		goto done;
479 	}
480 
481 	if ( !access_allowed( op, &d, slap_schema.si_ad_entry,
482 			NULL, ACL_WDEL, NULL ) )
483 	{
484 		Debug( LDAP_DEBUG_TRACE, "   backsql_delete(): "
485 			"no write access to entry\n",
486 			0, 0, 0 );
487 		rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
488 		e = &d;
489 		goto done;
490 	}
491 
492 	rs->sr_err = backsql_has_children( op, dbh, &op->o_req_ndn );
493 	switch ( rs->sr_err ) {
494 	case LDAP_COMPARE_FALSE:
495 		rs->sr_err = LDAP_SUCCESS;
496 		break;
497 
498 	case LDAP_COMPARE_TRUE:
499 #ifdef SLAP_CONTROL_X_TREE_DELETE
500 		if ( get_treeDelete( op ) ) {
501 			rs->sr_err = LDAP_SUCCESS;
502 			break;
503 		}
504 #endif /* SLAP_CONTROL_X_TREE_DELETE */
505 
506 		Debug( LDAP_DEBUG_TRACE, "   backsql_delete(): "
507 			"entry \"%s\" has children\n",
508 			op->o_req_dn.bv_val, 0, 0 );
509 		rs->sr_err = LDAP_NOT_ALLOWED_ON_NONLEAF;
510 		rs->sr_text = "subordinate objects must be deleted first";
511 		/* fallthru */
512 
513 	default:
514 		e = &d;
515 		goto done;
516 	}
517 
518 	assert( bsi.bsi_base_id.eid_oc != NULL );
519 	oc = bsi.bsi_base_id.eid_oc;
520 	if ( oc->bom_delete_proc == NULL ) {
521 		Debug( LDAP_DEBUG_TRACE, "   backsql_delete(): "
522 			"delete procedure is not defined "
523 			"for this objectclass - aborting\n", 0, 0, 0 );
524 		rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
525 		rs->sr_text = "operation not permitted within namingContext";
526 		e = NULL;
527 		goto done;
528 	}
529 
530 	/*
531 	 * Get the parent
532 	 */
533 	e_id = bsi.bsi_base_id;
534 	memset( &bsi.bsi_base_id, 0, sizeof( bsi.bsi_base_id ) );
535 	if ( !be_issuffix( op->o_bd, &op->o_req_ndn ) ) {
536 		dnParent( &op->o_req_ndn, &pdn );
537 		bsi.bsi_e = &p;
538 		rs->sr_err = backsql_init_search( &bsi, &pdn,
539 				LDAP_SCOPE_BASE,
540 				(time_t)(-1), NULL, dbh, op, rs,
541 				slap_anlist_no_attrs,
542 				BACKSQL_ISF_GET_ENTRY );
543 		if ( rs->sr_err != LDAP_SUCCESS ) {
544 			Debug( LDAP_DEBUG_TRACE, "backsql_delete(): "
545 				"could not retrieve deleteDN ID "
546 				"- no such entry\n",
547 				0, 0, 0 );
548 			e = &p;
549 			goto done;
550 		}
551 
552 		(void)backsql_free_entryID( &bsi.bsi_base_id, 0, op->o_tmpmemctx );
553 
554 		/* check parent for "children" acl */
555 		if ( !access_allowed( op, &p, slap_schema.si_ad_children,
556 				NULL, ACL_WDEL, NULL ) )
557 		{
558 			Debug( LDAP_DEBUG_TRACE, "   backsql_delete(): "
559 				"no write access to parent\n",
560 				0, 0, 0 );
561 			rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
562 			e = &p;
563 			goto done;
564 
565 		}
566 	}
567 
568 	e = &d;
569 #ifdef SLAP_CONTROL_X_TREE_DELETE
570 	if ( get_treeDelete( op ) ) {
571 		backsql_tree_delete( op, rs, dbh, &sth );
572 		if ( rs->sr_err == LDAP_OTHER || rs->sr_err == LDAP_SUCCESS )
573 		{
574 			e = NULL;
575 		}
576 
577 	} else
578 #endif /* SLAP_CONTROL_X_TREE_DELETE */
579 	{
580 		backsql_delete_int( op, rs, dbh, &sth, &e_id, &e );
581 	}
582 
583 	/*
584 	 * Commit only if all operations succeed
585 	 */
586 	if ( sth != SQL_NULL_HSTMT ) {
587 		SQLUSMALLINT	CompletionType = SQL_ROLLBACK;
588 
589 		if ( rs->sr_err == LDAP_SUCCESS && !op->o_noop ) {
590 			assert( e == NULL );
591 			CompletionType = SQL_COMMIT;
592 		}
593 
594 		SQLTransact( SQL_NULL_HENV, dbh, CompletionType );
595 	}
596 
597 done:;
598 	if ( e != NULL ) {
599 		if ( !access_allowed( op, e, slap_schema.si_ad_entry, NULL,
600 					ACL_DISCLOSE, NULL ) )
601 		{
602 			rs->sr_err = LDAP_NO_SUCH_OBJECT;
603 			rs->sr_text = NULL;
604 			rs->sr_matched = NULL;
605 			if ( rs->sr_ref ) {
606 				ber_bvarray_free( rs->sr_ref );
607 				rs->sr_ref = NULL;
608 			}
609 		}
610 	}
611 
612 	if ( op->o_noop && rs->sr_err == LDAP_SUCCESS ) {
613 		rs->sr_err = LDAP_X_NO_OPERATION;
614 	}
615 
616 	send_ldap_result( op, rs );
617 
618 	Debug( LDAP_DEBUG_TRACE, "<==backsql_delete()\n", 0, 0, 0 );
619 
620 	if ( !BER_BVISNULL( &e_id.eid_ndn ) ) {
621 		(void)backsql_free_entryID( &e_id, 0, op->o_tmpmemctx );
622 	}
623 
624 	if ( !BER_BVISNULL( &d.e_nname ) ) {
625 		backsql_entry_clean( op, &d );
626 	}
627 
628 	if ( !BER_BVISNULL( &p.e_nname ) ) {
629 		backsql_entry_clean( op, &p );
630 	}
631 
632 	if ( rs->sr_ref ) {
633 		ber_bvarray_free( rs->sr_ref );
634 		rs->sr_ref = NULL;
635 	}
636 
637 	return rs->sr_err;
638 }
639 
640