summaryrefslogtreecommitdiffstats
path: root/servers/slapd/back-sql
diff options
context:
space:
mode:
Diffstat (limited to 'servers/slapd/back-sql')
-rw-r--r--servers/slapd/back-sql/Makefile.in45
-rw-r--r--servers/slapd/back-sql/add.c1560
-rw-r--r--servers/slapd/back-sql/api.c211
-rw-r--r--servers/slapd/back-sql/back-sql.h631
-rw-r--r--servers/slapd/back-sql/bind.c115
-rw-r--r--servers/slapd/back-sql/compare.c194
-rw-r--r--servers/slapd/back-sql/config.c778
-rw-r--r--servers/slapd/back-sql/delete.c627
-rw-r--r--servers/slapd/back-sql/docs/bugs16
-rw-r--r--servers/slapd/back-sql/docs/concept1
-rw-r--r--servers/slapd/back-sql/docs/install86
-rw-r--r--servers/slapd/back-sql/docs/platforms8
-rw-r--r--servers/slapd/back-sql/docs/todo12
-rw-r--r--servers/slapd/back-sql/entry-id.c1092
-rw-r--r--servers/slapd/back-sql/init.c661
-rw-r--r--servers/slapd/back-sql/modify.c212
-rw-r--r--servers/slapd/back-sql/modrdn.c524
-rw-r--r--servers/slapd/back-sql/operational.c246
-rw-r--r--servers/slapd/back-sql/proto-sql.h313
-rw-r--r--servers/slapd/back-sql/rdbms_depend/README189
-rw-r--r--servers/slapd/back-sql/rdbms_depend/ibmdb2/backsql_create.sql59
-rw-r--r--servers/slapd/back-sql/rdbms_depend/ibmdb2/backsql_drop.sql5
-rw-r--r--servers/slapd/back-sql/rdbms_depend/ibmdb2/slapd.conf36
-rw-r--r--servers/slapd/back-sql/rdbms_depend/ibmdb2/testdb_create.sql75
-rw-r--r--servers/slapd/back-sql/rdbms_depend/ibmdb2/testdb_data.sql18
-rw-r--r--servers/slapd/back-sql/rdbms_depend/ibmdb2/testdb_drop.sql5
-rw-r--r--servers/slapd/back-sql/rdbms_depend/ibmdb2/testdb_metadata.sql123
-rw-r--r--servers/slapd/back-sql/rdbms_depend/mssql/backsql_create.sql100
-rw-r--r--servers/slapd/back-sql/rdbms_depend/mssql/backsql_drop.sql14
-rw-r--r--servers/slapd/back-sql/rdbms_depend/mssql/slapd.conf30
-rw-r--r--servers/slapd/back-sql/rdbms_depend/mssql/testdb_create.sql74
-rw-r--r--servers/slapd/back-sql/rdbms_depend/mssql/testdb_data.sql24
-rw-r--r--servers/slapd/back-sql/rdbms_depend/mssql/testdb_drop.sql39
-rw-r--r--servers/slapd/back-sql/rdbms_depend/mssql/testdb_metadata.sql198
-rw-r--r--servers/slapd/back-sql/rdbms_depend/mysql/backsql_create.sql58
-rw-r--r--servers/slapd/back-sql/rdbms_depend/mysql/backsql_drop.sql7
-rw-r--r--servers/slapd/back-sql/rdbms_depend/mysql/slapd.conf32
-rw-r--r--servers/slapd/back-sql/rdbms_depend/mysql/testdb_create.sql86
-rw-r--r--servers/slapd/back-sql/rdbms_depend/mysql/testdb_data.sql21
-rw-r--r--servers/slapd/back-sql/rdbms_depend/mysql/testdb_drop.sql5
-rw-r--r--servers/slapd/back-sql/rdbms_depend/mysql/testdb_metadata.sql125
-rw-r--r--servers/slapd/back-sql/rdbms_depend/oracle/backsql_create.sql90
-rw-r--r--servers/slapd/back-sql/rdbms_depend/oracle/backsql_drop.sql8
-rw-r--r--servers/slapd/back-sql/rdbms_depend/oracle/slapd.conf32
-rw-r--r--servers/slapd/back-sql/rdbms_depend/oracle/testdb_create.sql68
-rw-r--r--servers/slapd/back-sql/rdbms_depend/oracle/testdb_data.sql27
-rw-r--r--servers/slapd/back-sql/rdbms_depend/oracle/testdb_drop.sql25
-rw-r--r--servers/slapd/back-sql/rdbms_depend/oracle/testdb_metadata.sql252
-rw-r--r--servers/slapd/back-sql/rdbms_depend/pgsql/backsql_create.sql50
-rw-r--r--servers/slapd/back-sql/rdbms_depend/pgsql/backsql_drop.sql4
-rw-r--r--servers/slapd/back-sql/rdbms_depend/pgsql/slapd.conf35
-rw-r--r--servers/slapd/back-sql/rdbms_depend/pgsql/testdb_create.sql55
-rw-r--r--servers/slapd/back-sql/rdbms_depend/pgsql/testdb_data.sql21
-rw-r--r--servers/slapd/back-sql/rdbms_depend/pgsql/testdb_drop.sql13
-rw-r--r--servers/slapd/back-sql/rdbms_depend/pgsql/testdb_metadata.sql146
-rw-r--r--servers/slapd/back-sql/rdbms_depend/timesten/backsql_create.sql66
-rw-r--r--servers/slapd/back-sql/rdbms_depend/timesten/backsql_drop.sql9
-rwxr-xr-xservers/slapd/back-sql/rdbms_depend/timesten/create_schema.sh4
-rw-r--r--servers/slapd/back-sql/rdbms_depend/timesten/dnreverse/Makefile48
-rw-r--r--servers/slapd/back-sql/rdbms_depend/timesten/dnreverse/dnreverse.cpp387
-rw-r--r--servers/slapd/back-sql/rdbms_depend/timesten/slapd.conf31
-rw-r--r--servers/slapd/back-sql/rdbms_depend/timesten/testdb_create.sql36
-rw-r--r--servers/slapd/back-sql/rdbms_depend/timesten/testdb_data.sql16
-rw-r--r--servers/slapd/back-sql/rdbms_depend/timesten/testdb_drop.sql5
-rw-r--r--servers/slapd/back-sql/rdbms_depend/timesten/testdb_metadata.sql108
-rwxr-xr-xservers/slapd/back-sql/rdbms_depend/timesten/ttcreate_schema.sh4
-rw-r--r--servers/slapd/back-sql/rdbms_depend/timesten/tttestdb_create.sql42
-rw-r--r--servers/slapd/back-sql/rdbms_depend/timesten/tttestdb_data.sql20
-rw-r--r--servers/slapd/back-sql/rdbms_depend/timesten/tttestdb_drop.sql5
-rw-r--r--servers/slapd/back-sql/rdbms_depend/timesten/tttestdb_metadata.sql122
-rw-r--r--servers/slapd/back-sql/schema-map.c1012
-rw-r--r--servers/slapd/back-sql/search.c2874
-rw-r--r--servers/slapd/back-sql/sql-wrap.c529
-rw-r--r--servers/slapd/back-sql/util.c572
74 files changed, 15371 insertions, 0 deletions
diff --git a/servers/slapd/back-sql/Makefile.in b/servers/slapd/back-sql/Makefile.in
new file mode 100644
index 0000000..b90bb31
--- /dev/null
+++ b/servers/slapd/back-sql/Makefile.in
@@ -0,0 +1,45 @@
+# Makefile.in for back-sql
+# $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 1998-2022 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+
+SRCS = init.c config.c search.c bind.c compare.c operational.c \
+ entry-id.c schema-map.c sql-wrap.c modify.c util.c \
+ add.c delete.c modrdn.c api.c
+OBJS = init.lo config.lo search.lo bind.lo compare.lo operational.lo \
+ entry-id.lo schema-map.lo sql-wrap.lo modify.lo util.lo \
+ add.lo delete.lo modrdn.lo api.lo
+
+LDAP_INCDIR= ../../../include
+LDAP_LIBDIR= ../../../libraries
+
+BUILD_OPT = "--enable-sql"
+BUILD_MOD = @BUILD_SQL@
+
+mod_DEFS = -DSLAPD_IMPORT
+MOD_DEFS = $(@BUILD_SQL@_DEFS)
+
+shared_LDAP_LIBS = $(LDAP_LIBLDAP_LA) $(LDAP_LIBLBER_LA)
+NT_LINK_LIBS = -L.. -lslapd $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS)
+UNIX_LINK_LIBS = $(@BUILD_LIBS_DYNAMIC@_LDAP_LIBS) $(SLAPD_SQL_LIBS)
+
+LIBBASE = back_sql
+
+XINCPATH = -I.. -I$(srcdir)/.. $(SLAPD_SQL_INCLUDES)
+XDEFS = $(MODULES_CPPFLAGS)
+
+all-local-lib: ../.backend
+
+../.backend: lib$(LIBBASE).a
+ @touch $@
+
diff --git a/servers/slapd/back-sql/add.c b/servers/slapd/back-sql/add.c
new file mode 100644
index 0000000..3098521
--- /dev/null
+++ b/servers/slapd/back-sql/add.c
@@ -0,0 +1,1560 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1999 Dmitry Kovalev.
+ * Portions Copyright 2002 Pierangelo Masarati.
+ * Portions Copyright 2004 Mark Adamson.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Dmitry Kovalev for inclusion
+ * by OpenLDAP Software. Additional significant contributors include
+ * Pierangelo Masarati and Mark Adamson.
+
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <sys/types.h>
+#include "ac/string.h"
+
+#include "slap.h"
+#include "proto-sql.h"
+
+#ifdef BACKSQL_SYNCPROV
+#include <lutil.h>
+#endif /* BACKSQL_SYNCPROV */
+
+const char * processable_op_attrs[] = {
+ "pwdAccountLockedTime",
+ "pwdChangedTime",
+ "pwdFailureTime",
+ "pwdGraceUseTime",
+ "pwdHistory",
+ "pwdPolicySubentry",
+ "pwdReset",
+ "entryUUID"
+};
+
+#define processable_op_attrs_length (sizeof (processable_op_attrs) / sizeof (const char *))
+
+static int indexOf(const char *array[], int array_size, const char * value) {
+ for (int i = 0; i < array_size; ++i) {
+ if(strcmp(array[i], value) == 0) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+static int is_processable_opattr(const char * attr) {
+ return indexOf(processable_op_attrs, processable_op_attrs_length, attr) >= 0;
+}
+
+#define backsql_opattr_skip(ad) \
+ (is_at_operational( (ad)->ad_type ) && (ad) != slap_schema.si_ad_ref )
+
+/*
+ * Skip:
+ * - null values (e.g. delete modification)
+ * - single occurrence of objectClass, because it is already used
+ * to determine how to build the SQL entry
+ * - operational attributes (except those in processable_op_attrs)
+ * - empty attributes
+ */
+#define backsql_attr_skip(ad, vals) \
+ ( \
+ ( ( (ad) == slap_schema.si_ad_objectClass \
+ && (vals) && BER_BVISNULL( &((vals)[ 1 ]) ) ) \
+ || backsql_opattr_skip( (ad) ) \
+ || ( (vals) && BER_BVISNULL( &((vals)[ 0 ]) ) ) \
+ ) && !is_processable_opattr( ad->ad_cname.bv_val ) )
+
+int
+backsql_modify_delete_all_values(
+ Operation *op,
+ SlapReply *rs,
+ SQLHDBC dbh,
+ backsql_entryID *e_id,
+ backsql_at_map_rec *at )
+{
+ backsql_info *bi = (backsql_info *)op->o_bd->be_private;
+ RETCODE rc;
+ SQLHSTMT asth = SQL_NULL_HSTMT;
+ BACKSQL_ROW_NTS row;
+
+ assert( at != NULL );
+ if ( at->bam_delete_proc == NULL ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_delete_all_values(): "
+ "missing attribute value delete procedure "
+ "for attr \"%s\"\n",
+ at->bam_ad->ad_cname.bv_val );
+ if ( BACKSQL_FAIL_IF_NO_MAPPING( bi ) ) {
+ rs->sr_text = "SQL-backend error";
+ return rs->sr_err = LDAP_OTHER;
+ }
+
+ return LDAP_SUCCESS;
+ }
+
+ rc = backsql_Prepare( dbh, &asth, at->bam_query, 0 );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_delete_all_values(): "
+ "error preparing attribute value select query "
+ "\"%s\"\n",
+ at->bam_query );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ asth, rc );
+
+ rs->sr_text = "SQL-backend error";
+ return rs->sr_err = LDAP_OTHER;
+ }
+
+ rc = backsql_BindParamID( asth, 1, SQL_PARAM_INPUT, &e_id->eid_keyval );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_delete_all_values(): "
+ "error binding key value parameter "
+ "to attribute value select query\n" );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ asth, rc );
+ SQLFreeStmt( asth, SQL_DROP );
+
+ rs->sr_text = "SQL-backend error";
+ return rs->sr_err = LDAP_OTHER;
+ }
+
+ rc = SQLExecute( asth );
+ if ( !BACKSQL_SUCCESS( rc ) ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_delete_all_values(): "
+ "error executing attribute value select query\n" );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ asth, rc );
+ SQLFreeStmt( asth, SQL_DROP );
+
+ rs->sr_text = "SQL-backend error";
+ return rs->sr_err = LDAP_OTHER;
+ }
+
+ backsql_BindRowAsStrings_x( asth, &row, op->o_tmpmemctx );
+ for ( rc = SQLFetch( asth );
+ BACKSQL_SUCCESS( rc );
+ rc = SQLFetch( asth ) )
+ {
+ int i;
+ /* first parameter no, parameter order */
+ SQLUSMALLINT pno = 0,
+ po = 0;
+ /* procedure return code */
+ int prc = LDAP_SUCCESS;
+
+ for ( i = 0; i < row.ncols; i++ ) {
+ SQLHSTMT sth = SQL_NULL_HSTMT;
+ ber_len_t col_len;
+
+ rc = backsql_Prepare( dbh, &sth, at->bam_delete_proc, 0 );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_delete_all_values(): "
+ "error preparing attribute value "
+ "delete procedure "
+ "\"%s\"\n",
+ at->bam_delete_proc );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+
+ rs->sr_text = "SQL-backend error";
+ rs->sr_err = LDAP_OTHER;
+ goto done;
+ }
+
+ if ( BACKSQL_IS_DEL( at->bam_expect_return ) ) {
+ pno = 1;
+ rc = backsql_BindParamInt( sth, 1,
+ SQL_PARAM_OUTPUT, &prc );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_delete_all_values(): "
+ "error binding output parameter for %s[%d]\n",
+ at->bam_ad->ad_cname.bv_val, i );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+
+ rs->sr_text = "SQL-backend error";
+ rs->sr_err = LDAP_OTHER;
+ goto done;
+ }
+ }
+ po = ( BACKSQL_IS_DEL( at->bam_param_order ) ) > 0;
+ rc = backsql_BindParamID( sth, pno + 1 + po,
+ SQL_PARAM_INPUT, &e_id->eid_keyval );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_delete_all_values(): "
+ "error binding keyval parameter for %s[%d]\n",
+ at->bam_ad->ad_cname.bv_val, i );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+
+ rs->sr_text = "SQL-backend error";
+ rs->sr_err = LDAP_OTHER;
+ goto done;
+ }
+
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_delete_all_values() "
+ "arg(%d)=" BACKSQL_IDFMT "\n",
+ pno + 1 + po,
+ BACKSQL_IDARG(e_id->eid_keyval) );
+
+ /*
+ * check for syntax needed here
+ * maybe need binary bind?
+ */
+ col_len = strlen( row.cols[ i ] );
+ rc = backsql_BindParamStr( sth, pno + 2 - po,
+ SQL_PARAM_INPUT, row.cols[ i ], col_len );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_delete_all_values(): "
+ "error binding value parameter for %s[%d]\n",
+ at->bam_ad->ad_cname.bv_val, i );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+
+ rs->sr_text = "SQL-backend error";
+ rs->sr_err = LDAP_OTHER;
+ goto done;
+ }
+
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_delete_all_values(): "
+ "arg(%d)=%s; executing \"%s\"\n",
+ pno + 2 - po, row.cols[ i ],
+ at->bam_delete_proc );
+ rc = SQLExecute( sth );
+ if ( rc == SQL_SUCCESS && prc == LDAP_SUCCESS ) {
+ rs->sr_err = LDAP_SUCCESS;
+
+ } else {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_delete_all_values(): "
+ "delete_proc "
+ "execution failed (rc=%d, prc=%d)\n",
+ rc, prc );
+ if ( prc != LDAP_SUCCESS ) {
+ /* SQL procedure executed fine
+ * but returned an error */
+ rs->sr_err = BACKSQL_SANITIZE_ERROR( prc );
+
+ } else {
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+ rs->sr_err = LDAP_OTHER;
+ }
+ rs->sr_text = op->o_req_dn.bv_val;
+ SQLFreeStmt( sth, SQL_DROP );
+ goto done;
+ }
+ SQLFreeStmt( sth, SQL_DROP );
+ }
+ }
+
+ rs->sr_err = LDAP_SUCCESS;
+
+done:;
+ backsql_FreeRow_x( &row, op->o_tmpmemctx );
+ SQLFreeStmt( asth, SQL_DROP );
+
+ return rs->sr_err;
+}
+
+int
+backsql_modify_internal(
+ Operation *op,
+ SlapReply *rs,
+ SQLHDBC dbh,
+ backsql_oc_map_rec *oc,
+ backsql_entryID *e_id,
+ Modifications *modlist )
+{
+ backsql_info *bi = (backsql_info *)op->o_bd->be_private;
+ RETCODE rc;
+ Modifications *ml;
+
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_modify_internal(): "
+ "traversing modifications list\n" );
+
+ for ( ml = modlist; ml != NULL; ml = ml->sml_next ) {
+ AttributeDescription *ad;
+ int sm_op;
+ static char *sm_ops[] = { "add", "delete", "replace", "increment", NULL };
+
+ BerVarray sm_values;
+#if 0
+ /* NOTE: some day we'll have to pass
+ * the normalized values as well */
+ BerVarray sm_nvalues;
+#endif
+ backsql_at_map_rec *at = NULL;
+ struct berval *at_val;
+ int i;
+
+ ad = ml->sml_mod.sm_desc;
+ sm_op = ( ml->sml_mod.sm_op & LDAP_MOD_OP );
+ sm_values = ml->sml_mod.sm_values;
+#if 0
+ sm_nvalues = ml->sml_mod.sm_nvalues;
+#endif
+
+ Debug( LDAP_DEBUG_TRACE, " backsql_modify_internal(): "
+ "modifying attribute \"%s\" (%s) according to "
+ "mappings for objectClass \"%s\"\n",
+ ad->ad_cname.bv_val, sm_ops[ sm_op ], BACKSQL_OC_NAME( oc ) );
+
+ if ( backsql_attr_skip( ad, sm_values ) ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_modify_internal(): "
+ "skipping attribute \"%s\"\n",
+ ad->ad_cname.bv_val, 0, 0 );
+
+ continue;
+ }
+
+ at = backsql_ad2at( oc, ad );
+ if ( at == NULL ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_modify_internal(): "
+ "attribute \"%s\" is not registered "
+ "in objectClass \"%s\"\n",
+ ad->ad_cname.bv_val, BACKSQL_OC_NAME( oc ) );
+
+ if ( BACKSQL_FAIL_IF_NO_MAPPING( bi ) ) {
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "operation not permitted "
+ "within namingContext";
+ goto done;
+ }
+
+ continue;
+ }
+
+ switch ( sm_op ) {
+ case LDAP_MOD_REPLACE: {
+ Debug( LDAP_DEBUG_TRACE, " backsql_modify_internal(): "
+ "replacing values for attribute \"%s\"\n",
+ at->bam_ad->ad_cname.bv_val );
+
+ if ( at->bam_add_proc == NULL ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_internal(): "
+ "add procedure is not defined "
+ "for attribute \"%s\" "
+ "- unable to perform replacements\n",
+ at->bam_ad->ad_cname.bv_val );
+
+ if ( BACKSQL_FAIL_IF_NO_MAPPING( bi ) ) {
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "operation not permitted "
+ "within namingContext";
+ goto done;
+ }
+
+ break;
+ }
+
+ if ( at->bam_delete_proc == NULL ) {
+ if ( BACKSQL_FAIL_IF_NO_MAPPING( bi ) ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_internal(): "
+ "delete procedure is not defined "
+ "for attribute \"%s\"\n",
+ at->bam_ad->ad_cname.bv_val );
+
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "operation not permitted "
+ "within namingContext";
+ goto done;
+ }
+
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_internal(): "
+ "delete procedure is not defined "
+ "for attribute \"%s\" "
+ "- adding only\n",
+ at->bam_ad->ad_cname.bv_val );
+
+ goto add_only;
+ }
+
+del_all:
+ rs->sr_err = backsql_modify_delete_all_values( op, rs, dbh, e_id, at );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ goto done;
+ }
+
+ /* LDAP_MOD_DELETE gets here if all values must be deleted */
+ if ( sm_op == LDAP_MOD_DELETE ) {
+ break;
+ }
+ }
+
+ /*
+ * PASSTHROUGH - to add new attributes -- do NOT add break
+ */
+ case LDAP_MOD_ADD:
+ /* case SLAP_MOD_SOFTADD: */
+ /* case SLAP_MOD_ADD_IF_NOT_PRESENT: */
+add_only:;
+ if ( at->bam_add_proc == NULL ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_internal(): "
+ "add procedure is not defined "
+ "for attribute \"%s\"\n",
+ at->bam_ad->ad_cname.bv_val );
+
+ if ( BACKSQL_FAIL_IF_NO_MAPPING( bi ) ) {
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "operation not permitted "
+ "within namingContext";
+ goto done;
+ }
+
+ break;
+ }
+
+ Debug( LDAP_DEBUG_TRACE, " backsql_modify_internal(): "
+ "adding new values for attribute \"%s\"\n",
+ at->bam_ad->ad_cname.bv_val );
+
+ /* can't add a NULL val array */
+ assert( sm_values != NULL );
+
+ for ( i = 0, at_val = sm_values;
+ !BER_BVISNULL( at_val );
+ i++, at_val++ )
+ {
+ SQLHSTMT sth = SQL_NULL_HSTMT;
+ /* first parameter position, parameter order */
+ SQLUSMALLINT pno = 0,
+ po;
+ /* procedure return code */
+ int prc = LDAP_SUCCESS;
+
+ rc = backsql_Prepare( dbh, &sth, at->bam_add_proc, 0 );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_internal(): "
+ "error preparing add query\n" );
+ backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc );
+
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "SQL-backend error";
+ goto done;
+ }
+
+ if ( BACKSQL_IS_ADD( at->bam_expect_return ) ) {
+ pno = 1;
+ rc = backsql_BindParamInt( sth, 1,
+ SQL_PARAM_OUTPUT, &prc );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_internal(): "
+ "error binding output parameter for %s[%d]\n",
+ at->bam_ad->ad_cname.bv_val, i );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+
+ rs->sr_text = "SQL-backend error";
+ rs->sr_err = LDAP_OTHER;
+ goto done;
+ }
+ }
+ po = ( BACKSQL_IS_ADD( at->bam_param_order ) ) > 0;
+ rc = backsql_BindParamID( sth, pno + 1 + po,
+ SQL_PARAM_INPUT, &e_id->eid_keyval );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_internal(): "
+ "error binding keyval parameter for %s[%d]\n",
+ at->bam_ad->ad_cname.bv_val, i );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+
+ rs->sr_text = "SQL-backend error";
+ rs->sr_err = LDAP_OTHER;
+ goto done;
+ }
+
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_internal(): "
+ "arg(%d)=" BACKSQL_IDFMT "\n",
+ pno + 1 + po,
+ BACKSQL_IDARG(e_id->eid_keyval) );
+
+ /*
+ * check for syntax needed here
+ * maybe need binary bind?
+ */
+ rc = backsql_BindParamBerVal( sth, pno + 2 - po,
+ SQL_PARAM_INPUT, at_val );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_internal(): "
+ "error binding value parameter for %s[%d]\n",
+ at->bam_ad->ad_cname.bv_val, i );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+
+ rs->sr_text = "SQL-backend error";
+ rs->sr_err = LDAP_OTHER;
+ goto done;
+ }
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_internal(): "
+ "arg(%d)=\"%s\"; executing \"%s\"\n",
+ pno + 2 - po, at_val->bv_val,
+ at->bam_add_proc );
+
+ rc = SQLExecute( sth );
+ if ( rc == SQL_SUCCESS && prc == LDAP_SUCCESS ) {
+ rs->sr_err = LDAP_SUCCESS;
+
+ } else {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_internal(): "
+ "add_proc execution failed "
+ "(rc=%d, prc=%d)\n",
+ rc, prc );
+ if ( prc != LDAP_SUCCESS ) {
+ /* SQL procedure executed fine
+ * but returned an error */
+ SQLFreeStmt( sth, SQL_DROP );
+
+ rs->sr_err = BACKSQL_SANITIZE_ERROR( prc );
+ rs->sr_text = at->bam_ad->ad_cname.bv_val;
+ return rs->sr_err;
+
+ } else {
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+ if ( BACKSQL_FAIL_IF_NO_MAPPING( bi ) )
+ {
+ SQLFreeStmt( sth, SQL_DROP );
+
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "SQL-backend error";
+ goto done;
+ }
+ }
+ }
+ SQLFreeStmt( sth, SQL_DROP );
+ }
+ break;
+
+ case LDAP_MOD_DELETE:
+ /* case SLAP_MOD_SOFTDEL: */
+ if ( at->bam_delete_proc == NULL ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_internal(): "
+ "delete procedure is not defined "
+ "for attribute \"%s\"\n",
+ at->bam_ad->ad_cname.bv_val );
+
+ if ( BACKSQL_FAIL_IF_NO_MAPPING( bi ) ) {
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "operation not permitted "
+ "within namingContext";
+ goto done;
+ }
+
+ break;
+ }
+
+ if ( sm_values == NULL ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_internal(): "
+ "no values given to delete "
+ "for attribute \"%s\" "
+ "-- deleting all values\n",
+ at->bam_ad->ad_cname.bv_val );
+ goto del_all;
+ }
+
+ Debug( LDAP_DEBUG_TRACE, " backsql_modify_internal(): "
+ "deleting values for attribute \"%s\"\n",
+ at->bam_ad->ad_cname.bv_val );
+
+ for ( i = 0, at_val = sm_values;
+ !BER_BVISNULL( at_val );
+ i++, at_val++ )
+ {
+ SQLHSTMT sth = SQL_NULL_HSTMT;
+ /* first parameter position, parameter order */
+ SQLUSMALLINT pno = 0,
+ po;
+ /* procedure return code */
+ int prc = LDAP_SUCCESS;
+
+ rc = backsql_Prepare( dbh, &sth, at->bam_delete_proc, 0 );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_internal(): "
+ "error preparing delete query\n" );
+ backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc );
+
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "SQL-backend error";
+ goto done;
+ }
+
+ if ( BACKSQL_IS_DEL( at->bam_expect_return ) ) {
+ pno = 1;
+ rc = backsql_BindParamInt( sth, 1,
+ SQL_PARAM_OUTPUT, &prc );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_internal(): "
+ "error binding output parameter for %s[%d]\n",
+ at->bam_ad->ad_cname.bv_val, i );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+
+ rs->sr_text = "SQL-backend error";
+ rs->sr_err = LDAP_OTHER;
+ goto done;
+ }
+ }
+ po = ( BACKSQL_IS_DEL( at->bam_param_order ) ) > 0;
+ rc = backsql_BindParamID( sth, pno + 1 + po,
+ SQL_PARAM_INPUT, &e_id->eid_keyval );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_internal(): "
+ "error binding keyval parameter for %s[%d]\n",
+ at->bam_ad->ad_cname.bv_val, i );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+
+ rs->sr_text = "SQL-backend error";
+ rs->sr_err = LDAP_OTHER;
+ goto done;
+ }
+
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_internal(): "
+ "arg(%d)=" BACKSQL_IDFMT "\n",
+ pno + 1 + po,
+ BACKSQL_IDARG(e_id->eid_keyval) );
+
+ /*
+ * check for syntax needed here
+ * maybe need binary bind?
+ */
+ rc = backsql_BindParamBerVal( sth, pno + 2 - po,
+ SQL_PARAM_INPUT, at_val );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_internal(): "
+ "error binding value parameter for %s[%d]\n",
+ at->bam_ad->ad_cname.bv_val, i );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+
+ rs->sr_text = "SQL-backend error";
+ rs->sr_err = LDAP_OTHER;
+ goto done;
+ }
+
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_internal(): "
+ "executing \"%s\"\n",
+ at->bam_delete_proc );
+ rc = SQLExecute( sth );
+ if ( rc == SQL_SUCCESS && prc == LDAP_SUCCESS )
+ {
+ rs->sr_err = LDAP_SUCCESS;
+
+ } else {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modify_internal(): "
+ "delete_proc execution "
+ "failed (rc=%d, prc=%d)\n",
+ rc, prc );
+
+ if ( prc != LDAP_SUCCESS ) {
+ /* SQL procedure executed fine
+ * but returned an error */
+ rs->sr_err = BACKSQL_SANITIZE_ERROR( prc );
+ rs->sr_text = at->bam_ad->ad_cname.bv_val;
+ goto done;
+
+ } else {
+ backsql_PrintErrors( bi->sql_db_env,
+ dbh, sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = at->bam_ad->ad_cname.bv_val;
+ goto done;
+ }
+ }
+ SQLFreeStmt( sth, SQL_DROP );
+ }
+ break;
+
+ case LDAP_MOD_INCREMENT:
+ Debug( LDAP_DEBUG_TRACE, " backsql_modify_internal(): "
+ "increment not supported yet\n" );
+ if ( BACKSQL_FAIL_IF_NO_MAPPING( bi ) ) {
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "SQL-backend error";
+ goto done;
+ }
+ break;
+ }
+ }
+
+done:;
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_modify_internal(): %d%s%s\n",
+ rs->sr_err,
+ rs->sr_text ? ": " : "",
+ rs->sr_text ? rs->sr_text : "" );
+
+ /*
+ * FIXME: should fail in case one change fails?
+ */
+ return rs->sr_err;
+}
+
+static int
+backsql_add_attr(
+ Operation *op,
+ SlapReply *rs,
+ SQLHDBC dbh,
+ backsql_oc_map_rec *oc,
+ Attribute *at,
+ backsql_key_t new_keyval )
+{
+ backsql_info *bi = (backsql_info*)op->o_bd->be_private;
+ backsql_at_map_rec *at_rec = NULL;
+ struct berval *at_val;
+ unsigned long i;
+ RETCODE rc;
+ SQLUSMALLINT currpos;
+ SQLHSTMT sth = SQL_NULL_HSTMT;
+
+ at_rec = backsql_ad2at( oc, at->a_desc );
+
+ if ( at_rec == NULL ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_add_attr(\"%s\"): "
+ "attribute \"%s\" is not registered "
+ "in objectclass \"%s\"\n",
+ op->ora_e->e_name.bv_val,
+ at->a_desc->ad_cname.bv_val,
+ BACKSQL_OC_NAME( oc ) );
+
+ if ( BACKSQL_FAIL_IF_NO_MAPPING( bi ) ) {
+ rs->sr_text = "operation not permitted "
+ "within namingContext";
+ return rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ }
+
+ return LDAP_SUCCESS;
+ }
+
+ if ( at_rec->bam_add_proc == NULL ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_add_attr(\"%s\"): "
+ "add procedure is not defined "
+ "for attribute \"%s\" "
+ "of structuralObjectClass \"%s\"\n",
+ op->ora_e->e_name.bv_val,
+ at->a_desc->ad_cname.bv_val,
+ BACKSQL_OC_NAME( oc ) );
+
+ if ( BACKSQL_FAIL_IF_NO_MAPPING( bi ) ) {
+ rs->sr_text = "operation not permitted "
+ "within namingContext";
+ return rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ }
+
+ return LDAP_SUCCESS;
+ }
+
+ for ( i = 0, at_val = &at->a_vals[ i ];
+ !BER_BVISNULL( at_val );
+ i++, at_val = &at->a_vals[ i ] )
+ {
+ /* procedure return code */
+ int prc = LDAP_SUCCESS;
+ /* first parameter #, parameter order */
+ SQLUSMALLINT pno, po;
+
+ /*
+ * Do not deal with the objectClass that is used
+ * to build the entry
+ */
+ if ( at->a_desc == slap_schema.si_ad_objectClass ) {
+ if ( dn_match( at_val, &oc->bom_oc->soc_cname ) )
+ {
+ continue;
+ }
+ }
+
+ rc = backsql_Prepare( dbh, &sth, at_rec->bam_add_proc, 0 );
+ if ( rc != SQL_SUCCESS ) {
+ rs->sr_text = "SQL-backend error";
+ return rs->sr_err = LDAP_OTHER;
+ }
+
+ if ( BACKSQL_IS_ADD( at_rec->bam_expect_return ) ) {
+ pno = 1;
+ rc = backsql_BindParamInt( sth, 1, SQL_PARAM_OUTPUT, &prc );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_add_attr(): "
+ "error binding output parameter for %s[%lu]\n",
+ at_rec->bam_ad->ad_cname.bv_val, i );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+
+ rs->sr_text = "SQL-backend error";
+ return rs->sr_err = LDAP_OTHER;
+ }
+
+ } else {
+ pno = 0;
+ }
+
+ po = ( BACKSQL_IS_ADD( at_rec->bam_param_order ) ) > 0;
+ currpos = pno + 1 + po;
+ rc = backsql_BindParamNumID( sth, currpos,
+ SQL_PARAM_INPUT, &new_keyval );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_add_attr(): "
+ "error binding keyval parameter for %s[%lu]\n",
+ at_rec->bam_ad->ad_cname.bv_val, i );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+
+ rs->sr_text = "SQL-backend error";
+ return rs->sr_err = LDAP_OTHER;
+ }
+
+ currpos = pno + 2 - po;
+
+ /*
+ * check for syntax needed here
+ * maybe need binary bind?
+ */
+
+ rc = backsql_BindParamBerVal( sth, currpos, SQL_PARAM_INPUT, at_val );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_add_attr(): "
+ "error binding value parameter for %s[%lu]\n",
+ at_rec->bam_ad->ad_cname.bv_val, i );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+
+ rs->sr_text = "SQL-backend error";
+ return rs->sr_err = LDAP_OTHER;
+ }
+
+#ifdef LDAP_DEBUG
+ Debug(LDAP_DEBUG_TRACE,
+ " backsql_add_attr(\"%s\"): " "executing \"%s\" val[%lu], id=" BACKSQL_IDNUMFMT "\n",
+ op->ora_e->e_name.bv_val, at_rec->bam_add_proc,
+ i, new_keyval );
+#endif
+ rc = SQLExecute( sth );
+ if ( rc == SQL_SUCCESS && prc == LDAP_SUCCESS ) {
+ rs->sr_err = LDAP_SUCCESS;
+
+ } else {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_add_attr(\"%s\"): "
+ "add_proc execution failed (rc=%d, prc=%d)\n",
+ op->ora_e->e_name.bv_val, rc, prc );
+ if ( prc != LDAP_SUCCESS ) {
+ /* SQL procedure executed fine
+ * but returned an error */
+ rs->sr_err = BACKSQL_SANITIZE_ERROR( prc );
+ rs->sr_text = op->ora_e->e_name.bv_val;
+ SQLFreeStmt( sth, SQL_DROP );
+ return rs->sr_err;
+
+ } else {
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = op->ora_e->e_name.bv_val;
+ SQLFreeStmt( sth, SQL_DROP );
+ return rs->sr_err;
+ }
+ }
+ SQLFreeStmt( sth, SQL_DROP );
+ }
+
+ return LDAP_SUCCESS;
+}
+
+int
+backsql_add( Operation *op, SlapReply *rs )
+{
+ backsql_info *bi = (backsql_info*)op->o_bd->be_private;
+ SQLHDBC dbh = SQL_NULL_HDBC;
+ SQLHSTMT sth = SQL_NULL_HSTMT;
+ backsql_key_t new_keyval = 0;
+ RETCODE rc;
+ backsql_oc_map_rec *oc = NULL;
+ backsql_srch_info bsi = { 0 };
+ Entry p = { 0 }, *e = NULL;
+ Attribute *at,
+ *at_objectClass = NULL;
+ ObjectClass *soc = NULL;
+ struct berval scname = BER_BVNULL;
+ struct berval pdn;
+ struct berval realdn = BER_BVNULL;
+ int colnum;
+ slap_mask_t mask;
+
+ char textbuf[ SLAP_TEXT_BUFLEN ];
+ size_t textlen = sizeof( textbuf );
+
+#ifdef BACKSQL_SYNCPROV
+ /*
+ * NOTE: fake successful result to force contextCSN to be bumped up
+ */
+ if ( op->o_sync ) {
+ char buf[ LDAP_PVT_CSNSTR_BUFSIZE ];
+ struct berval csn;
+
+ csn.bv_val = buf;
+ csn.bv_len = sizeof( buf );
+ slap_get_csn( op, &csn, 1 );
+
+ rs->sr_err = LDAP_SUCCESS;
+ send_ldap_result( op, rs );
+
+ slap_graduate_commit_csn( op );
+
+ return 0;
+ }
+#endif /* BACKSQL_SYNCPROV */
+
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_add(\"%s\")\n",
+ op->ora_e->e_name.bv_val );
+
+ /* check schema */
+ if ( BACKSQL_CHECK_SCHEMA( bi ) ) {
+ char textbuf[ SLAP_TEXT_BUFLEN ] = { '\0' };
+
+ rs->sr_err = entry_schema_check( op, op->ora_e, NULL, 0, 1, NULL,
+ &rs->sr_text, textbuf, sizeof( textbuf ) );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): "
+ "entry failed schema check -- aborting\n",
+ op->ora_e->e_name.bv_val );
+ e = NULL;
+ goto done;
+ }
+ }
+
+ slap_add_opattrs( op, &rs->sr_text, textbuf, textlen, 1 );
+
+ if ( get_assert( op ) &&
+ ( test_filter( op, op->ora_e, get_assertion( op )) != LDAP_COMPARE_TRUE ))
+ {
+ Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): "
+ "assertion control failed -- aborting\n",
+ op->ora_e->e_name.bv_val );
+ e = NULL;
+ rs->sr_err = LDAP_ASSERTION_FAILED;
+ goto done;
+ }
+
+ /* search structuralObjectClass */
+ for ( at = op->ora_e->e_attrs; at != NULL; at = at->a_next ) {
+ if ( at->a_desc == slap_schema.si_ad_structuralObjectClass ) {
+ break;
+ }
+ }
+
+ /* there must exist */
+ if ( at == NULL ) {
+ char buf[ SLAP_TEXT_BUFLEN ];
+ const char *text;
+
+ /* search structuralObjectClass */
+ for ( at = op->ora_e->e_attrs; at != NULL; at = at->a_next ) {
+ if ( at->a_desc == slap_schema.si_ad_objectClass ) {
+ break;
+ }
+ }
+
+ if ( at == NULL ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): "
+ "no objectClass\n",
+ op->ora_e->e_name.bv_val );
+ rs->sr_err = LDAP_OBJECT_CLASS_VIOLATION;
+ e = NULL;
+ goto done;
+ }
+
+ rs->sr_err = structural_class( at->a_vals, &soc, NULL,
+ &text, buf, sizeof( buf ), op->o_tmpmemctx );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): "
+ "%s (%d)\n",
+ op->ora_e->e_name.bv_val, text, rs->sr_err );
+ e = NULL;
+ goto done;
+ }
+ scname = soc->soc_cname;
+
+ } else {
+ scname = at->a_vals[0];
+ }
+
+ /* I guess we should play with sub/supertypes to find a suitable oc */
+ oc = backsql_name2oc( bi, &scname );
+
+ if ( oc == NULL ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): "
+ "cannot map structuralObjectClass \"%s\" -- aborting\n",
+ op->ora_e->e_name.bv_val,
+ scname.bv_val );
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "operation not permitted within namingContext";
+ e = NULL;
+ goto done;
+ }
+
+ if ( oc->bom_create_proc == NULL ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): "
+ "create procedure is not defined "
+ "for structuralObjectClass \"%s\" - aborting\n",
+ op->ora_e->e_name.bv_val,
+ scname.bv_val );
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "operation not permitted within namingContext";
+ e = NULL;
+ goto done;
+
+ } else if ( BACKSQL_CREATE_NEEDS_SELECT( bi )
+ && oc->bom_create_keyval == NULL ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): "
+ "create procedure needs select procedure, "
+ "but none is defined for structuralObjectClass \"%s\" "
+ "- aborting\n",
+ op->ora_e->e_name.bv_val,
+ scname.bv_val );
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "operation not permitted within namingContext";
+ e = NULL;
+ goto done;
+ }
+
+ /* check write access */
+ if ( !access_allowed_mask( op, op->ora_e,
+ slap_schema.si_ad_entry,
+ NULL, ACL_WADD, NULL, &mask ) )
+ {
+ rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ e = op->ora_e;
+ goto done;
+ }
+
+ rs->sr_err = backsql_get_db_conn( op, &dbh );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): "
+ "could not get connection handle - exiting\n",
+ op->ora_e->e_name.bv_val );
+ rs->sr_text = ( rs->sr_err == LDAP_OTHER )
+ ? "SQL-backend error" : NULL;
+ e = NULL;
+ goto done;
+ }
+
+ /*
+ * Check if entry exists
+ *
+ * NOTE: backsql_api_dn2odbc() is called explicitly because
+ * we need the mucked DN to pass it to the create procedure.
+ */
+ realdn = op->ora_e->e_name;
+ if ( backsql_api_dn2odbc( op, rs, &realdn ) ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): "
+ "backsql_api_dn2odbc(\"%s\") failed\n",
+ op->ora_e->e_name.bv_val, realdn.bv_val );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "SQL-backend error";
+ e = NULL;
+ goto done;
+ }
+
+ rs->sr_err = backsql_dn2id( op, rs, dbh, &realdn, NULL, 0, 0 );
+ if ( rs->sr_err == LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): "
+ "entry exists\n",
+ op->ora_e->e_name.bv_val );
+ rs->sr_err = LDAP_ALREADY_EXISTS;
+ e = op->ora_e;
+ goto done;
+ }
+
+ /*
+ * Get the parent dn and see if the corresponding entry exists.
+ */
+ if ( be_issuffix( op->o_bd, &op->ora_e->e_nname ) ) {
+ pdn = slap_empty_bv;
+
+ } else {
+ dnParent( &op->ora_e->e_nname, &pdn );
+
+ /*
+ * Get the parent
+ */
+ bsi.bsi_e = &p;
+ rs->sr_err = backsql_init_search( &bsi, &pdn,
+ LDAP_SCOPE_BASE,
+ (time_t)(-1), NULL, dbh, op, rs, slap_anlist_no_attrs,
+ ( BACKSQL_ISF_MATCHED | BACKSQL_ISF_GET_ENTRY ) );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_add(): "
+ "could not retrieve addDN parent "
+ "\"%s\" ID - %s matched=\"%s\"\n",
+ pdn.bv_val,
+ rs->sr_err == LDAP_REFERRAL ? "referral" : "no such entry",
+ rs->sr_matched ? rs->sr_matched : "(null)" );
+ e = &p;
+ goto done;
+ }
+
+ /* check "children" pseudo-attribute access to parent */
+ if ( !access_allowed( op, &p, slap_schema.si_ad_children,
+ NULL, ACL_WADD, NULL ) )
+ {
+ rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ e = &p;
+ goto done;
+ }
+ }
+
+ /*
+ * create_proc is executed; if expect_return is set, then
+ * an output parameter is bound, which should contain
+ * the id of the added row; otherwise the procedure
+ * is expected to return the id as the first column of a select
+ */
+ rc = backsql_Prepare( dbh, &sth, oc->bom_create_proc, 0 );
+ if ( rc != SQL_SUCCESS ) {
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "SQL-backend error";
+ e = NULL;
+ goto done;
+ }
+
+ colnum = 1;
+ if ( BACKSQL_IS_ADD( oc->bom_expect_return ) ) {
+ rc = backsql_BindParamNumID( sth, 1, SQL_PARAM_OUTPUT, &new_keyval );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): "
+ "error binding keyval parameter "
+ "for objectClass %s\n",
+ op->ora_e->e_name.bv_val,
+ oc->bom_oc->soc_cname.bv_val );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+
+ rs->sr_text = "SQL-backend error";
+ rs->sr_err = LDAP_OTHER;
+ e = NULL;
+ goto done;
+ }
+ colnum++;
+ }
+
+ if ( oc->bom_create_hint ) {
+ at = attr_find( op->ora_e->e_attrs, oc->bom_create_hint );
+ if ( at && at->a_vals ) {
+ backsql_BindParamStr( sth, colnum, SQL_PARAM_INPUT,
+ at->a_vals[0].bv_val,
+ at->a_vals[0].bv_len );
+ Debug( LDAP_DEBUG_TRACE, "backsql_add(): "
+ "create_proc hint: param = '%s'\n",
+ at->a_vals[0].bv_val );
+
+ } else {
+ backsql_BindParamStr( sth, colnum, SQL_PARAM_INPUT,
+ "", 0 );
+ Debug( LDAP_DEBUG_TRACE, "backsql_add(): "
+ "create_proc hint (%s) not available\n",
+ oc->bom_create_hint->ad_cname.bv_val );
+ }
+ colnum++;
+ }
+
+ Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): executing \"%s\"\n",
+ op->ora_e->e_name.bv_val, oc->bom_create_proc );
+ rc = SQLExecute( sth );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): "
+ "create_proc execution failed\n",
+ op->ora_e->e_name.bv_val );
+ backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc);
+ SQLFreeStmt( sth, SQL_DROP );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "SQL-backend error";
+ e = NULL;
+ goto done;
+ }
+
+ /* FIXME: after SQLExecute(), the row is already inserted
+ * (at least with PostgreSQL and unixODBC); needs investigation */
+
+ if ( !BACKSQL_IS_ADD( oc->bom_expect_return ) ) {
+ SWORD ncols;
+ SQLLEN value_len;
+
+ if ( BACKSQL_CREATE_NEEDS_SELECT( bi ) ) {
+ SQLFreeStmt( sth, SQL_DROP );
+
+ rc = backsql_Prepare( dbh, &sth, oc->bom_create_keyval, 0 );
+ if ( rc != SQL_SUCCESS ) {
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "SQL-backend error";
+ e = NULL;
+ goto done;
+ }
+
+ rc = SQLExecute( sth );
+ if ( rc != SQL_SUCCESS ) {
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "SQL-backend error";
+ e = NULL;
+ goto done;
+ }
+ }
+
+ /*
+ * the query to know the id of the inserted entry
+ * must be embedded in the create procedure
+ */
+ rc = SQLNumResultCols( sth, &ncols );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): "
+ "create_proc result evaluation failed\n",
+ op->ora_e->e_name.bv_val );
+ backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc);
+ SQLFreeStmt( sth, SQL_DROP );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "SQL-backend error";
+ e = NULL;
+ goto done;
+
+ } else if ( ncols != 1 ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): "
+ "create_proc result is bogus (ncols=%d)\n",
+ op->ora_e->e_name.bv_val, ncols );
+ backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc);
+ SQLFreeStmt( sth, SQL_DROP );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "SQL-backend error";
+ e = NULL;
+ goto done;
+ }
+
+#if 0
+ {
+ SQLCHAR colname[ 64 ];
+ SQLSMALLINT name_len, col_type, col_scale, col_null;
+ UDWORD col_prec;
+
+ /*
+ * FIXME: check whether col_type is compatible,
+ * if it can be null and so on ...
+ */
+ rc = SQLDescribeCol( sth, (SQLUSMALLINT)1,
+ &colname[ 0 ],
+ (SQLUINTEGER)( sizeof( colname ) - 1 ),
+ &name_len, &col_type,
+ &col_prec, &col_scale, &col_null );
+ }
+#endif
+
+ rc = SQLBindCol( sth, (SQLUSMALLINT)1, SQL_C_ULONG,
+ (SQLPOINTER)&new_keyval,
+ (SQLINTEGER)sizeof( new_keyval ),
+ &value_len );
+
+ rc = SQLFetch( sth );
+
+ if ( value_len <= 0 ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): "
+ "create_proc result is empty?\n",
+ op->ora_e->e_name.bv_val );
+ backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc);
+ SQLFreeStmt( sth, SQL_DROP );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "SQL-backend error";
+ e = NULL;
+ goto done;
+ }
+ }
+
+ SQLFreeStmt( sth, SQL_DROP );
+
+ Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): "
+ "create_proc returned keyval=" BACKSQL_IDNUMFMT "\n",
+ op->ora_e->e_name.bv_val, new_keyval );
+
+ rc = backsql_Prepare( dbh, &sth, bi->sql_insentry_stmt, 0 );
+ if ( rc != SQL_SUCCESS ) {
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "SQL-backend error";
+ e = NULL;
+ goto done;
+ }
+
+ rc = backsql_BindParamBerVal( sth, 1, SQL_PARAM_INPUT, &realdn );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): "
+ "error binding DN parameter for objectClass %s\n",
+ op->ora_e->e_name.bv_val,
+ oc->bom_oc->soc_cname.bv_val );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+
+ rs->sr_text = "SQL-backend error";
+ rs->sr_err = LDAP_OTHER;
+ e = NULL;
+ goto done;
+ }
+
+ rc = backsql_BindParamNumID( sth, 2, SQL_PARAM_INPUT, &oc->bom_id );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): "
+ "error binding objectClass ID parameter "
+ "for objectClass %s\n",
+ op->ora_e->e_name.bv_val,
+ oc->bom_oc->soc_cname.bv_val );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+
+ rs->sr_text = "SQL-backend error";
+ rs->sr_err = LDAP_OTHER;
+ e = NULL;
+ goto done;
+ }
+
+ rc = backsql_BindParamID( sth, 3, SQL_PARAM_INPUT, &bsi.bsi_base_id.eid_id );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): "
+ "error binding parent ID parameter "
+ "for objectClass %s\n",
+ op->ora_e->e_name.bv_val,
+ oc->bom_oc->soc_cname.bv_val );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+
+ rs->sr_text = "SQL-backend error";
+ rs->sr_err = LDAP_OTHER;
+ e = NULL;
+ goto done;
+ }
+
+ rc = backsql_BindParamNumID( sth, 4, SQL_PARAM_INPUT, &new_keyval );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): "
+ "error binding entry ID parameter "
+ "for objectClass %s\n",
+ op->ora_e->e_name.bv_val,
+ oc->bom_oc->soc_cname.bv_val );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+
+ rs->sr_text = "SQL-backend error";
+ rs->sr_err = LDAP_OTHER;
+ e = NULL;
+ goto done;
+ }
+
+ Debug(LDAP_DEBUG_TRACE,
+ " backsql_add(): executing \"%s\" for dn=\"%s\" oc_map_id=" BACKSQL_IDNUMFMT " p_id=" BACKSQL_IDFMT " keyval=" BACKSQL_IDNUMFMT "\n",
+ bi->sql_insentry_stmt, op->ora_e->e_name.bv_val,
+ oc->bom_id, BACKSQL_IDARG(bsi.bsi_base_id.eid_id),
+ new_keyval );
+
+ rc = SQLExecute( sth );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_add(\"%s\"): "
+ "could not insert ldap_entries record\n",
+ op->ora_e->e_name.bv_val );
+ backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc );
+
+ /*
+ * execute delete_proc to delete data added !!!
+ */
+ SQLFreeStmt( sth, SQL_DROP );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "SQL-backend error";
+ e = NULL;
+ goto done;
+ }
+
+ SQLFreeStmt( sth, SQL_DROP );
+
+ for ( at = op->ora_e->e_attrs; at != NULL; at = at->a_next ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_add(): "
+ "adding attribute \"%s\"\n",
+ at->a_desc->ad_cname.bv_val );
+
+ /*
+ * Skip:
+ * - the first occurrence of objectClass, which is used
+ * to determine how to build the SQL entry (FIXME ?!?)
+ * - operational attributes
+ * - empty attributes (FIXME ?!?)
+ */
+ if ( backsql_attr_skip( at->a_desc, at->a_vals ) ) {
+ continue;
+ }
+
+ if ( at->a_desc == slap_schema.si_ad_objectClass ) {
+ at_objectClass = at;
+ continue;
+ }
+
+ rs->sr_err = backsql_add_attr( op, rs, dbh, oc, at, new_keyval );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ e = op->ora_e;
+ goto done;
+ }
+ }
+
+ if ( at_objectClass ) {
+ rs->sr_err = backsql_add_attr( op, rs, dbh, oc,
+ at_objectClass, new_keyval );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ e = op->ora_e;
+ goto done;
+ }
+ }
+
+done:;
+ /*
+ * Commit only if all operations succeed
+ */
+ if ( sth != SQL_NULL_HSTMT ) {
+ SQLUSMALLINT CompletionType = SQL_ROLLBACK;
+
+ if ( rs->sr_err == LDAP_SUCCESS && !op->o_noop ) {
+ assert( e == NULL );
+ CompletionType = SQL_COMMIT;
+ }
+
+ SQLTransact( SQL_NULL_HENV, dbh, CompletionType );
+ }
+
+ /*
+ * FIXME: NOOP does not work for add -- it works for all
+ * the other operations, and I don't get the reason :(
+ *
+ * hint: there might be some autocommit in Postgres
+ * so that when the unique id of the key table is
+ * automatically increased, there's no rollback.
+ * We might implement a "rollback" procedure consisting
+ * in deleting that row.
+ */
+
+ if ( e != NULL ) {
+ int disclose = 1;
+
+ if ( e == op->ora_e && !ACL_GRANT( mask, ACL_DISCLOSE ) ) {
+ /* mask already collected */
+ disclose = 0;
+
+ } else if ( e == &p && !access_allowed( op, &p,
+ slap_schema.si_ad_entry, NULL,
+ ACL_DISCLOSE, NULL ) )
+ {
+ disclose = 0;
+ }
+
+ if ( disclose == 0 ) {
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ rs->sr_text = NULL;
+ rs->sr_matched = NULL;
+ if ( rs->sr_ref ) {
+ ber_bvarray_free( rs->sr_ref );
+ rs->sr_ref = NULL;
+ }
+ }
+ }
+
+ if ( op->o_noop && rs->sr_err == LDAP_SUCCESS ) {
+ rs->sr_err = LDAP_X_NO_OPERATION;
+ }
+
+ send_ldap_result( op, rs );
+ slap_graduate_commit_csn( op );
+
+ if ( !BER_BVISNULL( &realdn )
+ && realdn.bv_val != op->ora_e->e_name.bv_val )
+ {
+ ch_free( realdn.bv_val );
+ }
+
+ if ( !BER_BVISNULL( &bsi.bsi_base_id.eid_ndn ) ) {
+ (void)backsql_free_entryID( &bsi.bsi_base_id, 0, op->o_tmpmemctx );
+ }
+
+ if ( !BER_BVISNULL( &p.e_nname ) ) {
+ backsql_entry_clean( op, &p );
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_add(\"%s\"): %d \"%s\"\n",
+ op->ora_e->e_name.bv_val,
+ rs->sr_err,
+ rs->sr_text ? rs->sr_text : "" );
+
+ rs->sr_text = NULL;
+ rs->sr_matched = NULL;
+ if ( rs->sr_ref ) {
+ ber_bvarray_free( rs->sr_ref );
+ rs->sr_ref = NULL;
+ }
+
+ return rs->sr_err;
+}
+
diff --git a/servers/slapd/back-sql/api.c b/servers/slapd/back-sql/api.c
new file mode 100644
index 0000000..7ef1d9f
--- /dev/null
+++ b/servers/slapd/back-sql/api.c
@@ -0,0 +1,211 @@
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1999 Dmitry Kovalev.
+ * Portions Copyright 2004 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Dmitry Kovalev for inclusion
+ * by OpenLDAP Software. Additional significant contributors include
+ * Pierangelo Masarati.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <sys/types.h>
+#include "ac/string.h"
+
+#include "slap.h"
+#include "proto-sql.h"
+
+static backsql_api *backsqlapi;
+
+int
+backsql_api_config( backsql_info *bi, const char *name, int argc, char *argv[] )
+{
+ backsql_api *ba;
+
+ assert( bi != NULL );
+ assert( name != NULL );
+
+ for ( ba = backsqlapi; ba; ba = ba->ba_next ) {
+ if ( strcasecmp( name, ba->ba_name ) == 0 ) {
+ backsql_api *ba2;
+
+ ba2 = ch_malloc( sizeof( backsql_api ) );
+ *ba2 = *ba;
+
+ if ( ba2->ba_config ) {
+ if ( ( *ba2->ba_config )( ba2, argc, argv ) ) {
+ ch_free( ba2 );
+ return 1;
+ }
+ ba2->ba_argc = argc;
+ if ( argc ) {
+ int i;
+ ba2->ba_argv = ch_malloc( argc * sizeof(char *));
+ for ( i=0; i<argc; i++ )
+ ba2->ba_argv[i] = ch_strdup( argv[i] );
+ }
+ }
+
+ ba2->ba_next = bi->sql_api;
+ bi->sql_api = ba2;
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+int
+backsql_api_destroy( backsql_info *bi )
+{
+ backsql_api *ba;
+
+ assert( bi != NULL );
+
+ ba = bi->sql_api;
+
+ if ( ba == NULL ) {
+ return 0;
+ }
+
+ for ( ; ba; ba = ba->ba_next ) {
+ if ( ba->ba_destroy ) {
+ (void)( *ba->ba_destroy )( ba );
+ }
+ }
+
+ return 0;
+}
+
+int
+backsql_api_register( backsql_api *ba )
+{
+ backsql_api *ba2;
+
+ assert( ba != NULL );
+ assert( ba->ba_private == NULL );
+
+ if ( ba->ba_name == NULL ) {
+ fprintf( stderr, "API module has no name\n" );
+ exit(EXIT_FAILURE);
+ }
+
+ for ( ba2 = backsqlapi; ba2; ba2 = ba2->ba_next ) {
+ if ( strcasecmp( ba->ba_name, ba2->ba_name ) == 0 ) {
+ fprintf( stderr, "API module \"%s\" already defined\n", ba->ba_name );
+ exit( EXIT_FAILURE );
+ }
+ }
+
+ ba->ba_next = backsqlapi;
+ backsqlapi = ba;
+
+ return 0;
+}
+
+int
+backsql_api_dn2odbc( Operation *op, SlapReply *rs, struct berval *dn )
+{
+ backsql_info *bi = (backsql_info *)op->o_bd->be_private;
+ backsql_api *ba;
+ int rc;
+ struct berval bv;
+
+ ba = bi->sql_api;
+
+ if ( ba == NULL ) {
+ return 0;
+ }
+
+ ber_dupbv( &bv, dn );
+
+ for ( ; ba; ba = ba->ba_next ) {
+ if ( ba->ba_dn2odbc ) {
+ /*
+ * The dn2odbc() helper is supposed to rewrite
+ * the contents of bv, freeing the original value
+ * with ch_free() if required and replacing it
+ * with a newly allocated one using ch_malloc()
+ * or companion functions.
+ *
+ * NOTE: it is supposed to __always__ free
+ * the value of bv in case of error, and reset
+ * it with BER_BVZERO() .
+ */
+ rc = ( *ba->ba_dn2odbc )( op, rs, &bv );
+
+ if ( rc ) {
+ /* in case of error, dn2odbc() must cleanup */
+ assert( BER_BVISNULL( &bv ) );
+
+ return rc;
+ }
+ }
+ }
+
+ assert( !BER_BVISNULL( &bv ) );
+
+ *dn = bv;
+
+ return 0;
+}
+
+int
+backsql_api_odbc2dn( Operation *op, SlapReply *rs, struct berval *dn )
+{
+ backsql_info *bi = (backsql_info *)op->o_bd->be_private;
+ backsql_api *ba;
+ int rc;
+ struct berval bv;
+
+ ba = bi->sql_api;
+
+ if ( ba == NULL ) {
+ return 0;
+ }
+
+ ber_dupbv( &bv, dn );
+
+ for ( ; ba; ba = ba->ba_next ) {
+ if ( ba->ba_dn2odbc ) {
+ rc = ( *ba->ba_odbc2dn )( op, rs, &bv );
+ /*
+ * The odbc2dn() helper is supposed to rewrite
+ * the contents of bv, freeing the original value
+ * with ch_free() if required and replacing it
+ * with a newly allocated one using ch_malloc()
+ * or companion functions.
+ *
+ * NOTE: it is supposed to __always__ free
+ * the value of bv in case of error, and reset
+ * it with BER_BVZERO() .
+ */
+ if ( rc ) {
+ /* in case of error, odbc2dn() must cleanup */
+ assert( BER_BVISNULL( &bv ) );
+
+ return rc;
+ }
+ }
+ }
+
+ assert( !BER_BVISNULL( &bv ) );
+
+ *dn = bv;
+
+ return 0;
+}
+
diff --git a/servers/slapd/back-sql/back-sql.h b/servers/slapd/back-sql/back-sql.h
new file mode 100644
index 0000000..556ea6f
--- /dev/null
+++ b/servers/slapd/back-sql/back-sql.h
@@ -0,0 +1,631 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1999 Dmitry Kovalev.
+ * Portions Copyright 2002 Pierangelo Mararati.
+ * Portions Copyright 2004 Mark Adamson.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Dmitry Kovalev for inclusion
+ * by OpenLDAP Software. Additional significant contributors include
+ * Pierangelo Masarati and Mark Adamson.
+ */
+/*
+ * The following changes have been addressed:
+ *
+ * Enhancements:
+ * - re-styled code for better readability
+ * - upgraded backend API to reflect recent changes
+ * - LDAP schema is checked when loading SQL/LDAP mapping
+ * - AttributeDescription/ObjectClass pointers used for more efficient
+ * mapping lookup
+ * - bervals used where string length is required often
+ * - atomized write operations by committing at the end of each operation
+ * and defaulting connection closure to rollback
+ * - added LDAP access control to write operations
+ * - fully implemented modrdn (with rdn attrs change, deleteoldrdn,
+ * access check, parent/children check and more)
+ * - added parent access control, children control to delete operation
+ * - added structuralObjectClass operational attribute check and
+ * value return on search
+ * - added hasSubordinate operational attribute on demand
+ * - search limits are appropriately enforced
+ * - function backsql_strcat() has been made more efficient
+ * - concat function has been made configurable by means of a pattern
+ * - added config switches:
+ * - fail_if_no_mapping write operations fail if there is no mapping
+ * - has_ldapinfo_dn_ru overrides autodetect
+ * - concat_pattern a string containing two '?' is used
+ * (note that "?||?" should be more portable
+ * than builtin function "CONCAT(?,?)")
+ * - strcast_func cast of string constants in "SELECT DISTINCT
+ * statements (needed by PostgreSQL)
+ * - upper_needs_cast cast the argument of upper when required
+ * (basically when building dn substring queries)
+ * - added noop control
+ * - added values return filter control
+ * - hasSubordinate can be used in search filters (with limitations)
+ * - eliminated oc->name; use oc->oc->soc_cname instead
+ *
+ * Todo:
+ * - add security checks for SQL statements that can be injected (?)
+ * - re-test with previously supported RDBMs
+ * - replace dn_ru and so with normalized dn (no need for upper() and so
+ * in dn match)
+ * - implement a backsql_normalize() function to replace the upper()
+ * conversion routines
+ * - note that subtree deletion, subtree renaming and so could be easily
+ * implemented (rollback and consistency checks are available :)
+ * - implement "lastmod" and other operational stuff (ldap_entries table ?)
+ * - check how to allow multiple operations with one statement, to remove
+ * BACKSQL_REALLOC_STMT from modify.c (a more recent unixODBC lib?)
+ */
+/*
+ * Improvements submitted by (ITS#3432)
+ *
+ * 1. id_query.patch applied (with changes)
+ * 2. shortcut.patch applied (reworked)
+ * 3. create_hint.patch applied
+ * 4. count_query.patch applied (reworked)
+ * 5. returncodes.patch applied (with sanity checks)
+ * 6. connpool.patch under evaluation
+ * 7. modoc.patch under evaluation (requires
+ * manageDSAit and "manage"
+ * access privileges)
+ * 8. miscfixes.patch applied (reworked; other
+ * operations need to load the
+ * entire entry for ACL purposes;
+ * see ITS#3480, now fixed)
+ *
+ * original description:
+
+ Changes that were made to the SQL backend.
+
+The patches were made against 2.2.18 and can be applied individually,
+but would best be applied in the numerical order of the file names.
+A synopsis of each patch is given here:
+
+
+1. Added an option to set SQL query for the "id_query" operation.
+
+2. Added an option to the SQL backend called "use_subtree_shortcut".
+When a search is performed, the SQL query includes a WHERE clause
+which says the DN must be "LIKE %<searchbase>". The LIKE operation
+can be slow in an RDBM. This shortcut option says that if the
+searchbase of the LDAP search is the root DN of the SQL backend,
+and thus all objects will match the LIKE operator, do not include
+the "LIKE %<searchbase>" clause in the SQL query (it is replaced
+instead by the always true "1=1" clause to keep the "AND"'s
+working correctly). This option is off by default, and should be
+turned on only if all objects to be found in the RDBM are under the
+same root DN. Multiple backends working within the same RDBM table
+space would encounter problems. LDAP searches whose searchbase are
+not at the root DN will bypass this shortcut and employ the LIKE
+clause.
+
+3. Added a "create_hint" column to ldap_oc_mappings table. Allows
+taking the value of an attr named in "create_hint" and passing it to
+the create_proc procedure. This is necessary for when an objectClass's
+table is partition indexed by some indexing column and thus the value
+in that indexing column cannot change after the row is created. The
+value for the indexed column is passed into the create_proc, which
+uses it to fill in the indexed column as the new row is created.
+
+4. When loading the values of an attribute, the count(*) of the number
+of values is fetched first and memory is allocated for the array of
+values and normalized values. The old system of loading the values one
+by one and running realloc() on the array of values and normalized
+values each time was badly fragmenting memory. The array of values and
+normalized values would be side by side in memory, and realloc()'ing
+them over and over would force them to leapfrog each other through all
+of available memory. Attrs with a large number of values could not be
+loaded without crashing the slapd daemon.
+
+5. Added code to interpret the value returned by stored procedures
+which have expect_return set. Returned value is interpreted as an LDAP
+return code. This allows the distinction between the SQL failing to
+execute and the SQL running to completion and returning an error code
+which can indicate a policy violation.
+
+6. Added RDBM connection pooling. Once an operation is finished the
+connection to the RDBM is returned to a pool rather than closing.
+Allows the next operation to skip the initialization and authentication
+phases of contacting the RDBM. Also, if licensing with ODBC places
+a limit on the number of connections, an LDAP thread can block waiting
+for another thread to finish, so that no LDAP errors are returned
+for having more LDAP connections than allowed RDBM connections. An
+RDBM connection which receives an SQL error is marked as "tainted"
+so that it will be closed rather than returned to the pool.
+ Also, RDBM connections must be bound to a given LDAP connection AND
+operation number, and NOT just the connection number. Asynchronous
+LDAP clients can have multiple simultaneous LDAP operations which
+should not share the same RDBM connection. A given LDAP operation can
+even make multiple SQL operations (e.g. a BIND operation which
+requires SASL to perform an LDAP search to convert the SASL ID to an
+LDAP DN), so each RDBM connection now has a refcount that must reach
+zero before the connection is returned to the free pool.
+
+7. Added ability to change the objectClass of an object. Required
+considerable work to copy all attributes out of old object and into
+new object. Does a schema check before proceeding. Creates a new
+object, fills it in, deletes the old object, then changes the
+oc_map_id and keyval of the entry in the "ldap_entries" table.
+
+8. Generic fixes. Includes initializing pointers before they
+get used in error branch cases, pointer checks before dereferencing,
+resetting a return code to success after a COMPARE op, sealing
+memory leaks, and in search.c, changing some of the "1=1" tests to
+"2=2", "3=3", etc so that when reading slapd trace output, the
+location in the source code where the x=x test was added to the SQL
+can be easily distinguished.
+ */
+
+#ifndef __BACKSQL_H__
+#define __BACKSQL_H__
+
+/* former sql-types.h */
+#include <sql.h>
+#include <sqlext.h>
+
+typedef struct {
+ SWORD ncols;
+ BerVarray col_names;
+ UDWORD *col_prec;
+ SQLSMALLINT *col_type;
+ char **cols;
+ SQLLEN *value_len;
+} BACKSQL_ROW_NTS;
+
+/*
+ * Better use the standard length of 8192 (as of slap.h)?
+ *
+ * NOTE: must be consistent with definition in ldap_entries table
+ */
+/* #define BACKSQL_MAX_DN_LEN SLAP_LDAPDN_MAXLEN */
+#define BACKSQL_MAX_DN_LEN 255
+
+/*
+ * define to enable very extensive trace logging (debug only)
+ */
+#undef BACKSQL_TRACE
+
+/*
+ * define if using MS SQL and workaround needed (see sql-wrap.c)
+ */
+#undef BACKSQL_MSSQL_WORKAROUND
+
+/*
+ * define to enable values counting for attributes
+ */
+#define BACKSQL_COUNTQUERY
+
+/*
+ * define to enable prettification/validation of values
+ */
+#define BACKSQL_PRETTY_VALIDATE
+
+/*
+ * define to enable varchars as unique keys in user tables
+ *
+ * by default integers are used (and recommended)
+ * for performances. Integers are used anyway in back-sql
+ * related tables.
+ */
+#undef BACKSQL_ARBITRARY_KEY
+
+/*
+ * type used for keys
+ */
+#if defined(HAVE_LONG_LONG) && defined(SQL_C_UBIGINT) && \
+ ( defined(HAVE_STRTOULL) || defined(HAVE_STRTOUQ) )
+typedef unsigned long long backsql_key_t;
+#define BACKSQL_C_NUMID SQL_C_UBIGINT
+#define BACKSQL_IDNUMFMT "%llu"
+#define BACKSQL_STR2ID lutil_atoullx
+#else /* ! HAVE_LONG_LONG || ! SQL_C_UBIGINT */
+typedef unsigned long backsql_key_t;
+#define BACKSQL_C_NUMID SQL_C_ULONG
+#define BACKSQL_IDNUMFMT "%lu"
+#define BACKSQL_STR2ID lutil_atoulx
+#endif /* ! HAVE_LONG_LONG */
+
+/*
+ * define to enable support for syncprov overlay
+ */
+#define BACKSQL_SYNCPROV
+
+/*
+ * define to the appropriate aliasing string
+ *
+ * some RDBMSes tolerate (or require) that " AS " is not used
+ * when aliasing tables/columns
+ */
+#define BACKSQL_ALIASING "AS "
+/* #define BACKSQL_ALIASING "" */
+
+/*
+ * define to the appropriate quoting char
+ *
+ * some RDBMSes tolerate/require that the aliases be enclosed
+ * in quotes. This is especially true for those that do not
+ * allow keywords used as aliases.
+ */
+#define BACKSQL_ALIASING_QUOTE ""
+/* #define BACKSQL_ALIASING_QUOTE "\"" */
+/* #define BACKSQL_ALIASING_QUOTE "'" */
+
+/*
+ * API
+ *
+ * a simple mechanism to allow DN mucking between the LDAP
+ * and the stored string representation.
+ */
+typedef struct backsql_api {
+ char *ba_name;
+ int (*ba_config)( struct backsql_api *self, int argc, char *argv[] );
+ int (*ba_destroy)( struct backsql_api *self );
+
+ int (*ba_dn2odbc)( Operation *op, SlapReply *rs, struct berval *dn );
+ int (*ba_odbc2dn)( Operation *op, SlapReply *rs, struct berval *dn );
+
+ void *ba_private;
+ struct backsql_api *ba_next;
+ char **ba_argv;
+ int ba_argc;
+} backsql_api;
+
+/*
+ * "structural" objectClass mapping structure
+ */
+typedef struct backsql_oc_map_rec {
+ /*
+ * Structure of corresponding LDAP objectClass definition
+ */
+ ObjectClass *bom_oc;
+#define BACKSQL_OC_NAME(ocmap) ((ocmap)->bom_oc->soc_cname.bv_val)
+
+ struct berval bom_keytbl;
+ struct berval bom_keycol;
+ /* expected to return keyval of newly created entry */
+ char *bom_create_proc;
+ /* in case create_proc does not return the keyval of the newly
+ * created row */
+ char *bom_create_keyval;
+ /* supposed to expect keyval as parameter and delete
+ * all the attributes as well */
+ char *bom_delete_proc;
+ /* flags whether delete_proc is a function (whether back-sql
+ * should bind first parameter as output for return code) */
+ int bom_expect_return;
+ backsql_key_t bom_id;
+ Avlnode *bom_attrs;
+ AttributeDescription *bom_create_hint;
+} backsql_oc_map_rec;
+
+/*
+ * attributeType mapping structure
+ */
+typedef struct backsql_at_map_rec {
+ /* Description of corresponding LDAP attribute type */
+ AttributeDescription *bam_ad;
+ AttributeDescription *bam_true_ad;
+ /* ObjectClass if bam_ad is objectClass */
+ ObjectClass *bam_oc;
+
+ struct berval bam_from_tbls;
+ struct berval bam_join_where;
+ struct berval bam_sel_expr;
+
+ /* TimesTen, or, if a uppercase function is defined,
+ * an uppercased version of bam_sel_expr */
+ struct berval bam_sel_expr_u;
+
+ /* supposed to expect 2 binded values: entry keyval
+ * and attr. value to add, like "add_name(?,?,?)" */
+ char *bam_add_proc;
+ /* supposed to expect 2 binded values: entry keyval
+ * and attr. value to delete */
+ char *bam_delete_proc;
+ /* for optimization purposes attribute load query
+ * is preconstructed from parts on schemamap load time */
+ char *bam_query;
+#ifdef BACKSQL_COUNTQUERY
+ char *bam_countquery;
+#endif /* BACKSQL_COUNTQUERY */
+ /* following flags are bitmasks (first bit used for add_proc,
+ * second - for delete_proc) */
+ /* order of parameters for procedures above;
+ * 1 means "data then keyval", 0 means "keyval then data" */
+ int bam_param_order;
+ /* flags whether one or more of procedures is a function
+ * (whether back-sql should bind first parameter as output
+ * for return code) */
+ int bam_expect_return;
+
+ /* next mapping for attribute */
+ struct backsql_at_map_rec *bam_next;
+} backsql_at_map_rec;
+
+#define BACKSQL_AT_MAP_REC_INIT { NULL, NULL, BER_BVC(""), BER_BVC(""), BER_BVNULL, BER_BVNULL, NULL, NULL, NULL, 0, 0, NULL }
+
+/* define to uppercase filters only if the matching rule requires it
+ * (currently broken) */
+/* #define BACKSQL_UPPERCASE_FILTER */
+
+#define BACKSQL_AT_CANUPPERCASE(at) ( !BER_BVISNULL( &(at)->bam_sel_expr_u ) )
+
+/* defines to support bitmasks above */
+#define BACKSQL_ADD 0x1
+#define BACKSQL_DEL 0x2
+
+#define BACKSQL_IS_ADD(x) ( ( BACKSQL_ADD & (x) ) == BACKSQL_ADD )
+#define BACKSQL_IS_DEL(x) ( ( BACKSQL_DEL & (x) ) == BACKSQL_DEL )
+
+#define BACKSQL_NCMP(v1,v2) ber_bvcmp((v1),(v2))
+
+#define BACKSQL_CONCAT
+/*
+ * berbuf structure: a berval with a buffer size associated
+ */
+typedef struct berbuf {
+ struct berval bb_val;
+ ber_len_t bb_len;
+} BerBuffer;
+
+#define BB_NULL { BER_BVNULL, 0 }
+
+/*
+ * Entry ID structure
+ */
+typedef struct backsql_entryID {
+ /* #define BACKSQL_ARBITRARY_KEY to allow a non-numeric key.
+ * It is required by some special applications that use
+ * strings as keys for the main table.
+ * In this case, #define BACKSQL_MAX_KEY_LEN consistently
+ * with the key size definition */
+#ifdef BACKSQL_ARBITRARY_KEY
+ struct berval eid_id;
+ struct berval eid_keyval;
+#define BACKSQL_MAX_KEY_LEN 64
+#else /* ! BACKSQL_ARBITRARY_KEY */
+ /* The original numeric key is maintained as default. */
+ backsql_key_t eid_id;
+ backsql_key_t eid_keyval;
+#endif /* ! BACKSQL_ARBITRARY_KEY */
+
+ backsql_key_t eid_oc_id;
+ backsql_oc_map_rec *eid_oc;
+ struct berval eid_dn;
+ struct berval eid_ndn;
+ struct backsql_entryID *eid_next;
+} backsql_entryID;
+
+#ifdef BACKSQL_ARBITRARY_KEY
+#define BACKSQL_ENTRYID_INIT { BER_BVNULL, BER_BVNULL, 0, NULL, BER_BVNULL, BER_BVNULL, NULL }
+#else /* ! BACKSQL_ARBITRARY_KEY */
+#define BACKSQL_ENTRYID_INIT { 0, 0, 0, NULL, BER_BVNULL, BER_BVNULL, NULL }
+#endif /* BACKSQL_ARBITRARY_KEY */
+
+/* the function must collect the entry associated to nbase */
+#define BACKSQL_ISF_GET_ID 0x1U
+#define BACKSQL_ISF_GET_ENTRY ( 0x2U | BACKSQL_ISF_GET_ID )
+#define BACKSQL_ISF_GET_OC ( 0x4U | BACKSQL_ISF_GET_ID )
+#define BACKSQL_ISF_MATCHED 0x8U
+#define BACKSQL_IS_GET_ID(f) \
+ ( ( (f) & BACKSQL_ISF_GET_ID ) == BACKSQL_ISF_GET_ID )
+#define BACKSQL_IS_GET_ENTRY(f) \
+ ( ( (f) & BACKSQL_ISF_GET_ENTRY ) == BACKSQL_ISF_GET_ENTRY )
+#define BACKSQL_IS_GET_OC(f) \
+ ( ( (f) & BACKSQL_ISF_GET_OC ) == BACKSQL_ISF_GET_OC )
+#define BACKSQL_IS_MATCHED(f) \
+ ( ( (f) & BACKSQL_ISF_MATCHED ) == BACKSQL_ISF_MATCHED )
+typedef struct backsql_srch_info {
+ Operation *bsi_op;
+ SlapReply *bsi_rs;
+
+ unsigned bsi_flags;
+#define BSQL_SF_NONE 0x0000U
+#define BSQL_SF_ALL_USER 0x0001U
+#define BSQL_SF_ALL_OPER 0x0002U
+#define BSQL_SF_ALL_ATTRS (BSQL_SF_ALL_USER|BSQL_SF_ALL_OPER)
+#define BSQL_SF_FILTER_HASSUBORDINATE 0x0010U
+#define BSQL_SF_FILTER_ENTRYUUID 0x0020U
+#define BSQL_SF_FILTER_ENTRYCSN 0x0040U
+#define BSQL_SF_RETURN_ENTRYUUID (BSQL_SF_FILTER_ENTRYUUID << 8)
+#define BSQL_ISF(bsi, f) ( ( (bsi)->bsi_flags & f ) == f )
+#define BSQL_ISF_ALL_USER(bsi) BSQL_ISF(bsi, BSQL_SF_ALL_USER)
+#define BSQL_ISF_ALL_OPER(bsi) BSQL_ISF(bsi, BSQL_SF_ALL_OPER)
+#define BSQL_ISF_ALL_ATTRS(bsi) BSQL_ISF(bsi, BSQL_SF_ALL_ATTRS)
+
+ struct berval *bsi_base_ndn;
+ int bsi_use_subtree_shortcut;
+ backsql_entryID bsi_base_id;
+ int bsi_scope;
+/* BACKSQL_SCOPE_BASE_LIKE can be set by API in ors_scope
+ * whenever the search base DN contains chars that cannot
+ * be mapped into the charset used in the RDBMS; so they're
+ * turned into '%' and an approximate ('LIKE') condition
+ * is used */
+#define BACKSQL_SCOPE_BASE_LIKE ( LDAP_SCOPE_BASE | 0x1000 )
+ Filter *bsi_filter;
+ time_t bsi_stoptime;
+
+ backsql_entryID *bsi_id_list,
+ **bsi_id_listtail,
+ *bsi_c_eid;
+ int bsi_n_candidates;
+ int bsi_status;
+
+ backsql_oc_map_rec *bsi_oc;
+ struct berbuf bsi_sel,
+ bsi_from,
+ bsi_join_where,
+ bsi_flt_where;
+ ObjectClass *bsi_filter_oc;
+ SQLHDBC bsi_dbh;
+ AttributeName *bsi_attrs;
+
+ Entry *bsi_e;
+} backsql_srch_info;
+
+/*
+ * Backend private data structure
+ */
+typedef struct backsql_info {
+ char *sql_dbhost;
+ int sql_dbport;
+ char *sql_dbuser;
+ char *sql_dbpasswd;
+ char *sql_dbname;
+
+ /*
+ * SQL condition for subtree searches differs in syntax:
+ * "LIKE CONCAT('%',?)" or "LIKE '%'+?" or "LIKE '%'||?"
+ * or smtg else
+ */
+ struct berval sql_subtree_cond;
+ struct berval sql_children_cond;
+ struct berval sql_dn_match_cond;
+ char *sql_oc_query;
+ char *sql_at_query;
+ char *sql_insentry_stmt;
+ char *sql_delentry_stmt;
+ char *sql_renentry_stmt;
+ char *sql_delobjclasses_stmt;
+ char *sql_id_query;
+ char *sql_has_children_query;
+ char *sql_list_children_query;
+
+ MatchingRule *sql_caseIgnoreMatch;
+ MatchingRule *sql_telephoneNumberMatch;
+
+ struct berval sql_upper_func;
+ struct berval sql_upper_func_open;
+ struct berval sql_upper_func_close;
+ struct berval sql_strcast_func;
+ BerVarray sql_concat_func;
+ char *sql_concat_patt;
+
+ struct berval sql_aliasing;
+ struct berval sql_aliasing_quote;
+ struct berval sql_dn_oc_aliasing;
+
+ AttributeName *sql_anlist;
+
+ unsigned int sql_flags;
+#define BSQLF_SCHEMA_LOADED 0x0001
+#define BSQLF_UPPER_NEEDS_CAST 0x0002
+#define BSQLF_CREATE_NEEDS_SELECT 0x0004
+#define BSQLF_FAIL_IF_NO_MAPPING 0x0008
+#define BSQLF_HAS_LDAPINFO_DN_RU 0x0010
+#define BSQLF_DONTCHECK_LDAPINFO_DN_RU 0x0020
+#define BSQLF_USE_REVERSE_DN 0x0040
+#define BSQLF_ALLOW_ORPHANS 0x0080
+#define BSQLF_USE_SUBTREE_SHORTCUT 0x0100
+#define BSQLF_FETCH_ALL_USERATTRS 0x0200
+#define BSQLF_FETCH_ALL_OPATTRS 0x0400
+#define BSQLF_FETCH_ALL_ATTRS (BSQLF_FETCH_ALL_USERATTRS|BSQLF_FETCH_ALL_OPATTRS)
+#define BSQLF_CHECK_SCHEMA 0x0800
+#define BSQLF_AUTOCOMMIT_ON 0x1000
+
+#define BACKSQL_ISF(si, f) \
+ (((si)->sql_flags & f) == f)
+
+#define BACKSQL_SCHEMA_LOADED(si) \
+ BACKSQL_ISF(si, BSQLF_SCHEMA_LOADED)
+#define BACKSQL_UPPER_NEEDS_CAST(si) \
+ BACKSQL_ISF(si, BSQLF_UPPER_NEEDS_CAST)
+#define BACKSQL_CREATE_NEEDS_SELECT(si) \
+ BACKSQL_ISF(si, BSQLF_CREATE_NEEDS_SELECT)
+#define BACKSQL_FAIL_IF_NO_MAPPING(si) \
+ BACKSQL_ISF(si, BSQLF_FAIL_IF_NO_MAPPING)
+#define BACKSQL_HAS_LDAPINFO_DN_RU(si) \
+ BACKSQL_ISF(si, BSQLF_HAS_LDAPINFO_DN_RU)
+#define BACKSQL_DONTCHECK_LDAPINFO_DN_RU(si) \
+ BACKSQL_ISF(si, BSQLF_DONTCHECK_LDAPINFO_DN_RU)
+#define BACKSQL_USE_REVERSE_DN(si) \
+ BACKSQL_ISF(si, BSQLF_USE_REVERSE_DN)
+#define BACKSQL_CANUPPERCASE(si) \
+ (!BER_BVISNULL( &(si)->sql_upper_func ))
+#define BACKSQL_ALLOW_ORPHANS(si) \
+ BACKSQL_ISF(si, BSQLF_ALLOW_ORPHANS)
+#define BACKSQL_USE_SUBTREE_SHORTCUT(si) \
+ BACKSQL_ISF(si, BSQLF_USE_SUBTREE_SHORTCUT)
+#define BACKSQL_FETCH_ALL_USERATTRS(si) \
+ BACKSQL_ISF(si, BSQLF_FETCH_ALL_USERATTRS)
+#define BACKSQL_FETCH_ALL_OPATTRS(si) \
+ BACKSQL_ISF(si, BSQLF_FETCH_ALL_OPATTRS)
+#define BACKSQL_FETCH_ALL_ATTRS(si) \
+ BACKSQL_ISF(si, BSQLF_FETCH_ALL_ATTRS)
+#define BACKSQL_CHECK_SCHEMA(si) \
+ BACKSQL_ISF(si, BSQLF_CHECK_SCHEMA)
+#define BACKSQL_AUTOCOMMIT_ON(si) \
+ BACKSQL_ISF(si, BSQLF_AUTOCOMMIT_ON)
+
+ Entry *sql_baseObject;
+ char *sql_base_ob_file;
+#ifdef BACKSQL_ARBITRARY_KEY
+#define BACKSQL_BASEOBJECT_IDSTR "baseObject"
+#define BACKSQL_BASEOBJECT_KEYVAL BACKSQL_BASEOBJECT_IDSTR
+#define BACKSQL_IS_BASEOBJECT_ID(id) (bvmatch((id), &backsql_baseObject_bv))
+#else /* ! BACKSQL_ARBITRARY_KEY */
+#define BACKSQL_BASEOBJECT_ID 0
+#define BACKSQL_BASEOBJECT_IDSTR LDAP_XSTRING(BACKSQL_BASEOBJECT_ID)
+#define BACKSQL_BASEOBJECT_KEYVAL 0
+#define BACKSQL_IS_BASEOBJECT_ID(id) (*(id) == BACKSQL_BASEOBJECT_ID)
+#endif /* ! BACKSQL_ARBITRARY_KEY */
+#define BACKSQL_BASEOBJECT_OC 0
+
+ Avlnode *sql_db_conns;
+ SQLHDBC sql_dbh;
+ ldap_pvt_thread_mutex_t sql_dbconn_mutex;
+ Avlnode *sql_oc_by_oc;
+ Avlnode *sql_oc_by_id;
+ ldap_pvt_thread_mutex_t sql_schema_mutex;
+ SQLHENV sql_db_env;
+
+ backsql_api *sql_api;
+} backsql_info;
+
+#define BACKSQL_SUCCESS( rc ) \
+ ( (rc) == SQL_SUCCESS || (rc) == SQL_SUCCESS_WITH_INFO )
+
+#define BACKSQL_AVL_STOP 0
+#define BACKSQL_AVL_CONTINUE 1
+
+/* see ldap.h for the meaning of the macros and of the values */
+#define BACKSQL_LEGAL_ERROR( rc ) \
+ ( LDAP_RANGE( (rc), 0x00, 0x0e ) \
+ || LDAP_ATTR_ERROR( (rc) ) \
+ || LDAP_NAME_ERROR( (rc) ) \
+ || LDAP_SECURITY_ERROR( (rc) ) \
+ || LDAP_SERVICE_ERROR( (rc) ) \
+ || LDAP_UPDATE_ERROR( (rc) ) )
+#define BACKSQL_SANITIZE_ERROR( rc ) \
+ ( BACKSQL_LEGAL_ERROR( (rc) ) ? (rc) : LDAP_OTHER )
+
+#define BACKSQL_IS_BINARY(ct) \
+ ( (ct) == SQL_BINARY \
+ || (ct) == SQL_VARBINARY \
+ || (ct) == SQL_LONGVARBINARY)
+
+#ifdef BACKSQL_ARBITRARY_KEY
+#define BACKSQL_IDFMT "%s"
+#define BACKSQL_IDARG(arg) ((arg).bv_val)
+#else /* ! BACKSQL_ARBITRARY_KEY */
+#define BACKSQL_IDFMT BACKSQL_IDNUMFMT
+#define BACKSQL_IDARG(arg) (arg)
+#endif /* ! BACKSQL_ARBITRARY_KEY */
+
+#endif /* __BACKSQL_H__ */
+
diff --git a/servers/slapd/back-sql/bind.c b/servers/slapd/back-sql/bind.c
new file mode 100644
index 0000000..ac78fc8
--- /dev/null
+++ b/servers/slapd/back-sql/bind.c
@@ -0,0 +1,115 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1999 Dmitry Kovalev.
+ * Portions Copyright 2002 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Dmitry Kovalev for inclusion
+ * by OpenLDAP Software. Additional significant contributors include
+ * Pierangelo Masarati.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <sys/types.h>
+
+#include "slap.h"
+#include "proto-sql.h"
+
+int
+backsql_bind( Operation *op, SlapReply *rs )
+{
+ SQLHDBC dbh = SQL_NULL_HDBC;
+ Entry e = { 0 };
+ Attribute *a;
+ backsql_srch_info bsi = { 0 };
+ AttributeName anlist[2];
+ int rc;
+
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_bind()\n" );
+
+ switch ( be_rootdn_bind( op, rs ) ) {
+ case SLAP_CB_CONTINUE:
+ break;
+
+ default:
+ /* in case of success, front end will send result;
+ * otherwise, be_rootdn_bind() did */
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_bind(%d)\n",
+ rs->sr_err );
+ return rs->sr_err;
+ }
+
+ rs->sr_err = backsql_get_db_conn( op, &dbh );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_bind(): "
+ "could not get connection handle - exiting\n" );
+
+ rs->sr_text = ( rs->sr_err == LDAP_OTHER )
+ ? "SQL-backend error" : NULL;
+ goto error_return;
+ }
+
+ anlist[0].an_name = slap_schema.si_ad_userPassword->ad_cname;
+ anlist[0].an_desc = slap_schema.si_ad_userPassword;
+ anlist[1].an_name.bv_val = NULL;
+
+ bsi.bsi_e = &e;
+ rc = backsql_init_search( &bsi, &op->o_req_ndn, LDAP_SCOPE_BASE,
+ (time_t)(-1), NULL, dbh, op, rs, anlist,
+ BACKSQL_ISF_GET_ENTRY );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_bind(): "
+ "could not retrieve bindDN ID - no such entry\n" );
+ rs->sr_err = LDAP_INVALID_CREDENTIALS;
+ goto error_return;
+ }
+
+ a = attr_find( e.e_attrs, slap_schema.si_ad_userPassword );
+ if ( a == NULL ) {
+ rs->sr_err = LDAP_INVALID_CREDENTIALS;
+ goto error_return;
+ }
+
+ if ( slap_passwd_check( op, &e, a, &op->oq_bind.rb_cred,
+ &rs->sr_text ) != 0 )
+ {
+ rs->sr_err = LDAP_INVALID_CREDENTIALS;
+ goto error_return;
+ }
+
+error_return:;
+ if ( !BER_BVISNULL( &bsi.bsi_base_id.eid_ndn ) ) {
+ (void)backsql_free_entryID( &bsi.bsi_base_id, 0, op->o_tmpmemctx );
+ }
+
+ if ( !BER_BVISNULL( &e.e_nname ) ) {
+ backsql_entry_clean( op, &e );
+ }
+
+ if ( bsi.bsi_attrs != NULL ) {
+ op->o_tmpfree( bsi.bsi_attrs, op->o_tmpmemctx );
+ }
+
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ send_ldap_result( op, rs );
+ }
+
+ SQLTransact( SQL_NULL_HENV, dbh, SQL_ROLLBACK );
+ Debug( LDAP_DEBUG_TRACE,"<==backsql_bind()\n" );
+
+ return rs->sr_err;
+}
+
diff --git a/servers/slapd/back-sql/compare.c b/servers/slapd/back-sql/compare.c
new file mode 100644
index 0000000..d457085
--- /dev/null
+++ b/servers/slapd/back-sql/compare.c
@@ -0,0 +1,194 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1999 Dmitry Kovalev.
+ * Portions Copyright 2002 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Dmitry Kovalev for inclusion
+ * by OpenLDAP Software. Additional significant contributors include
+ * Pierangelo Masarati.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <sys/types.h>
+
+#include "slap.h"
+#include "proto-sql.h"
+
+int
+backsql_compare( Operation *op, SlapReply *rs )
+{
+ SQLHDBC dbh = SQL_NULL_HDBC;
+ Entry e = { 0 };
+ Attribute *a = NULL;
+ backsql_srch_info bsi = { 0 };
+ int rc;
+ int manageDSAit = get_manageDSAit( op );
+ AttributeName anlist[2];
+
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_compare()\n" );
+
+ rs->sr_err = backsql_get_db_conn( op, &dbh );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_compare(): "
+ "could not get connection handle - exiting\n" );
+
+ rs->sr_text = ( rs->sr_err == LDAP_OTHER )
+ ? "SQL-backend error" : NULL;
+ goto return_results;
+ }
+
+ anlist[ 0 ].an_name = op->oq_compare.rs_ava->aa_desc->ad_cname;
+ anlist[ 0 ].an_desc = op->oq_compare.rs_ava->aa_desc;
+ BER_BVZERO( &anlist[ 1 ].an_name );
+
+ /*
+ * Get the entry
+ */
+ bsi.bsi_e = &e;
+ rc = backsql_init_search( &bsi, &op->o_req_ndn, LDAP_SCOPE_BASE,
+ (time_t)(-1), NULL, dbh, op, rs, anlist,
+ ( BACKSQL_ISF_MATCHED | BACKSQL_ISF_GET_ENTRY ) );
+ switch ( rc ) {
+ 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;
+ }
+ /* fallthru */
+
+ default:
+ Debug( LDAP_DEBUG_TRACE, "backsql_compare(): "
+ "could not retrieve compareDN ID - no such entry\n" );
+ goto return_results;
+ }
+
+ if ( get_assert( op ) &&
+ ( test_filter( op, &e, get_assertion( op ) )
+ != LDAP_COMPARE_TRUE ) )
+ {
+ rs->sr_err = LDAP_ASSERTION_FAILED;
+ goto return_results;
+ }
+
+ if ( is_at_operational( op->oq_compare.rs_ava->aa_desc->ad_type ) ) {
+ SlapReply nrs = { REP_SEARCH };
+ Attribute **ap;
+
+ for ( ap = &e.e_attrs; *ap; ap = &(*ap)->a_next )
+ ;
+
+ nrs.sr_attrs = anlist;
+ nrs.sr_entry = &e;
+ nrs.sr_attr_flags = SLAP_OPATTRS_NO;
+ nrs.sr_operational_attrs = NULL;
+
+ rs->sr_err = backsql_operational( op, &nrs );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ goto return_results;
+ }
+
+ *ap = nrs.sr_operational_attrs;
+ }
+
+ if ( ! access_allowed( op, &e, op->oq_compare.rs_ava->aa_desc,
+ &op->oq_compare.rs_ava->aa_value,
+ ACL_COMPARE, NULL ) )
+ {
+ rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ goto return_results;
+ }
+
+ rs->sr_err = LDAP_NO_SUCH_ATTRIBUTE;
+ for ( a = attrs_find( e.e_attrs, op->oq_compare.rs_ava->aa_desc );
+ a != NULL;
+ a = attrs_find( a->a_next, op->oq_compare.rs_ava->aa_desc ) )
+ {
+ rs->sr_err = LDAP_COMPARE_FALSE;
+ if ( attr_valfind( a,
+ SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH |
+ SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH,
+ &op->oq_compare.rs_ava->aa_value, NULL,
+ op->o_tmpmemctx ) == 0 )
+ {
+ rs->sr_err = LDAP_COMPARE_TRUE;
+ break;
+ }
+ }
+
+return_results:;
+ switch ( rs->sr_err ) {
+ case LDAP_COMPARE_TRUE:
+ case LDAP_COMPARE_FALSE:
+ break;
+
+ default:
+ if ( !BER_BVISNULL( &e.e_nname ) &&
+ ! access_allowed( op, &e,
+ slap_schema.si_ad_entry, NULL,
+ ACL_DISCLOSE, NULL ) )
+ {
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ rs->sr_text = NULL;
+ }
+ break;
+ }
+
+ send_ldap_result( op, rs );
+
+ if ( rs->sr_matched ) {
+ rs->sr_matched = NULL;
+ }
+
+ if ( rs->sr_ref ) {
+ ber_bvarray_free( rs->sr_ref );
+ rs->sr_ref = NULL;
+ }
+
+ if ( !BER_BVISNULL( &bsi.bsi_base_id.eid_ndn ) ) {
+ (void)backsql_free_entryID( &bsi.bsi_base_id, 0, op->o_tmpmemctx );
+ }
+
+ if ( !BER_BVISNULL( &e.e_nname ) ) {
+ backsql_entry_clean( op, &e );
+ }
+
+ if ( bsi.bsi_attrs != NULL ) {
+ op->o_tmpfree( bsi.bsi_attrs, op->o_tmpmemctx );
+ }
+
+ Debug(LDAP_DEBUG_TRACE,"<==backsql_compare()\n" );
+ switch ( rs->sr_err ) {
+ case LDAP_COMPARE_TRUE:
+ case LDAP_COMPARE_FALSE:
+ return LDAP_SUCCESS;
+
+ default:
+ return rs->sr_err;
+ }
+}
+
diff --git a/servers/slapd/back-sql/config.c b/servers/slapd/back-sql/config.c
new file mode 100644
index 0000000..51a6be3
--- /dev/null
+++ b/servers/slapd/back-sql/config.c
@@ -0,0 +1,778 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1999 Dmitry Kovalev.
+ * Portions Copyright 2002 Pierangelo Masarati.
+ * Portions Copyright 2004 Mark Adamson.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Dmitry Kovalev for inclusion
+ * by OpenLDAP Software. Additional significant contributors include
+ * Pierangelo Masarati.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include "ac/string.h"
+#include <sys/types.h>
+
+#include "slap.h"
+#include "slap-config.h"
+#include "ldif.h"
+#include "lutil.h"
+#include "proto-sql.h"
+
+static int
+create_baseObject(
+ BackendDB *be,
+ const char *fname,
+ int lineno );
+
+static int
+read_baseObject(
+ BackendDB *be,
+ const char *fname );
+
+static ConfigDriver sql_cf_gen;
+
+enum {
+ BSQL_CONCAT_PATT = 1,
+ BSQL_CREATE_NEEDS_SEL,
+ BSQL_UPPER_NEEDS_CAST,
+ BSQL_HAS_LDAPINFO_DN_RU,
+ BSQL_FAIL_IF_NO_MAPPING,
+ BSQL_ALLOW_ORPHANS,
+ BSQL_BASE_OBJECT,
+ BSQL_LAYER,
+ BSQL_SUBTREE_SHORTCUT,
+ BSQL_FETCH_ALL_ATTRS,
+ BSQL_FETCH_ATTRS,
+ BSQL_CHECK_SCHEMA,
+ BSQL_ALIASING_KEYWORD,
+ BSQL_AUTOCOMMIT
+};
+
+static ConfigTable sqlcfg[] = {
+ { "dbhost", "hostname", 2, 2, 0, ARG_STRING|ARG_OFFSET,
+ (void *)offsetof(struct backsql_info, sql_dbhost),
+ "( OLcfgDbAt:6.1 NAME 'olcDbHost' "
+ "DESC 'Hostname of SQL server' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "dbname", "name", 2, 2, 0, ARG_STRING|ARG_OFFSET,
+ (void *)offsetof(struct backsql_info, sql_dbname),
+ "( OLcfgDbAt:6.2 NAME 'olcDbName' "
+ "DESC 'Name of SQL database' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "dbuser", "username", 2, 2, 0, ARG_STRING|ARG_OFFSET,
+ (void *)offsetof(struct backsql_info, sql_dbuser),
+ "( OLcfgDbAt:6.3 NAME 'olcDbUser' "
+ "DESC 'Username for SQL session' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "dbpasswd", "password", 2, 2, 0, ARG_STRING|ARG_OFFSET,
+ (void *)offsetof(struct backsql_info, sql_dbpasswd),
+ "( OLcfgDbAt:6.4 NAME 'olcDbPass' "
+ "DESC 'Password for SQL session' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "concat_pattern", "pattern", 2, 2, 0,
+ ARG_STRING|ARG_MAGIC|BSQL_CONCAT_PATT, (void *)sql_cf_gen,
+ "( OLcfgDbAt:6.20 NAME 'olcSqlConcatPattern' "
+ "DESC 'Pattern used to concatenate strings' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "subtree_cond", "SQL expression", 2, 0, 0, ARG_BERVAL|ARG_QUOTE|ARG_OFFSET,
+ (void *)offsetof(struct backsql_info, sql_subtree_cond),
+ "( OLcfgDbAt:6.21 NAME 'olcSqlSubtreeCond' "
+ "DESC 'Where-clause template for a subtree search condition' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "children_cond", "SQL expression", 2, 0, 0, ARG_BERVAL|ARG_QUOTE|ARG_OFFSET,
+ (void *)offsetof(struct backsql_info, sql_children_cond),
+ "( OLcfgDbAt:6.22 NAME 'olcSqlChildrenCond' "
+ "DESC 'Where-clause template for a children search condition' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "dn_match_cond", "SQL expression", 2, 0, 0, ARG_BERVAL|ARG_QUOTE|ARG_OFFSET,
+ (void *)offsetof(struct backsql_info, sql_dn_match_cond),
+ "( OLcfgDbAt:6.23 NAME 'olcSqlDnMatchCond' "
+ "DESC 'Where-clause template for a DN match search condition' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "oc_query", "SQL expression", 2, 0, 0, ARG_STRING|ARG_QUOTE|ARG_OFFSET,
+ (void *)offsetof(struct backsql_info, sql_oc_query),
+ "( OLcfgDbAt:6.24 NAME 'olcSqlOcQuery' "
+ "DESC 'Query used to collect objectClass mapping data' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "at_query", "SQL expression", 2, 0, 0, ARG_STRING|ARG_QUOTE|ARG_OFFSET,
+ (void *)offsetof(struct backsql_info, sql_at_query),
+ "( OLcfgDbAt:6.25 NAME 'olcSqlAtQuery' "
+ "DESC 'Query used to collect attributeType mapping data' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "insentry_stmt", "SQL expression", 2, 0, 0, ARG_STRING|ARG_QUOTE|ARG_OFFSET,
+ (void *)offsetof(struct backsql_info, sql_insentry_stmt),
+ "( OLcfgDbAt:6.26 NAME 'olcSqlInsEntryStmt' "
+ "DESC 'Statement used to insert a new entry' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "create_needs_select", "yes|no", 2, 2, 0,
+ ARG_ON_OFF|ARG_MAGIC|BSQL_CREATE_NEEDS_SEL, (void *)sql_cf_gen,
+ "( OLcfgDbAt:6.27 NAME 'olcSqlCreateNeedsSelect' "
+ "DESC 'Whether entry creation needs a subsequent select' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "upper_func", "SQL function name", 2, 2, 0, ARG_BERVAL|ARG_OFFSET,
+ (void *)offsetof(struct backsql_info, sql_upper_func),
+ "( OLcfgDbAt:6.28 NAME 'olcSqlUpperFunc' "
+ "DESC 'Function that converts a value to uppercase' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "upper_needs_cast", "yes|no", 2, 2, 0,
+ ARG_ON_OFF|ARG_MAGIC|BSQL_UPPER_NEEDS_CAST, (void *)sql_cf_gen,
+ "( OLcfgDbAt:6.29 NAME 'olcSqlUpperNeedsCast' "
+ "DESC 'Whether olcSqlUpperFunc needs an explicit cast' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "strcast_func", "SQL function name", 2, 2, 0, ARG_BERVAL|ARG_OFFSET,
+ (void *)offsetof(struct backsql_info, sql_strcast_func),
+ "( OLcfgDbAt:6.30 NAME 'olcSqlStrcastFunc' "
+ "DESC 'Function that converts a value to a string' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "delentry_stmt", "SQL expression", 2, 0, 0, ARG_STRING|ARG_QUOTE|ARG_OFFSET,
+ (void *)offsetof(struct backsql_info, sql_delentry_stmt),
+ "( OLcfgDbAt:6.31 NAME 'olcSqlDelEntryStmt' "
+ "DESC 'Statement used to delete an existing entry' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "renentry_stmt", "SQL expression", 2, 0, 0, ARG_STRING|ARG_QUOTE|ARG_OFFSET,
+ (void *)offsetof(struct backsql_info, sql_renentry_stmt),
+ "( OLcfgDbAt:6.32 NAME 'olcSqlRenEntryStmt' "
+ "DESC 'Statement used to rename an entry' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "delobjclasses_stmt", "SQL expression", 2, 0, 0, ARG_STRING|ARG_QUOTE|ARG_OFFSET,
+ (void *)offsetof(struct backsql_info, sql_delobjclasses_stmt),
+ "( OLcfgDbAt:6.33 NAME 'olcSqlDelObjclassesStmt' "
+ "DESC 'Statement used to delete the ID of an entry' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "has_ldapinfo_dn_ru", "yes|no", 2, 2, 0,
+ ARG_ON_OFF|ARG_MAGIC|BSQL_HAS_LDAPINFO_DN_RU, (void *)sql_cf_gen,
+ "( OLcfgDbAt:6.34 NAME 'olcSqlHasLDAPinfoDnRu' "
+ "DESC 'Whether the dn_ru column is present' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "fail_if_no_mapping", "yes|no", 2, 2, 0,
+ ARG_ON_OFF|ARG_MAGIC|BSQL_FAIL_IF_NO_MAPPING, (void *)sql_cf_gen,
+ "( OLcfgDbAt:6.35 NAME 'olcSqlFailIfNoMapping' "
+ "DESC 'Whether to fail on unknown attribute mappings' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "allow_orphans", "yes|no", 2, 2, 0,
+ ARG_ON_OFF|ARG_MAGIC|BSQL_ALLOW_ORPHANS, (void *)sql_cf_gen,
+ "( OLcfgDbAt:6.36 NAME 'olcSqlAllowOrphans' "
+ "DESC 'Whether to allow adding entries with no parent' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "baseobject", "[file]", 1, 2, 0,
+ ARG_STRING|ARG_MAGIC|BSQL_BASE_OBJECT, (void *)sql_cf_gen,
+ "( OLcfgDbAt:6.37 NAME 'olcSqlBaseObject' "
+ "DESC 'Manage an in-memory baseObject entry' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "sqllayer", "name", 2, 0, 0,
+ ARG_MAGIC|BSQL_LAYER, (void *)sql_cf_gen,
+ "( OLcfgDbAt:6.38 NAME 'olcSqlLayer' "
+ "DESC 'Helper used to map DNs between LDAP and SQL' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString )", NULL, NULL },
+ { "use_subtree_shortcut", "yes|no", 2, 2, 0,
+ ARG_ON_OFF|ARG_MAGIC|BSQL_SUBTREE_SHORTCUT, (void *)sql_cf_gen,
+ "( OLcfgDbAt:6.39 NAME 'olcSqlUseSubtreeShortcut' "
+ "DESC 'Collect all entries when searchBase is DB suffix' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "fetch_all_attrs", "yes|no", 2, 2, 0,
+ ARG_ON_OFF|ARG_MAGIC|BSQL_FETCH_ALL_ATTRS, (void *)sql_cf_gen,
+ "( OLcfgDbAt:6.40 NAME 'olcSqlFetchAllAttrs' "
+ "DESC 'Require all attributes to always be loaded' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "fetch_attrs", "attrlist", 2, 0, 0,
+ ARG_MAGIC|BSQL_FETCH_ATTRS, (void *)sql_cf_gen,
+ "( OLcfgDbAt:6.41 NAME 'olcSqlFetchAttrs' "
+ "DESC 'Set of attributes to always fetch' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "check_schema", "yes|no", 2, 2, 0,
+ ARG_ON_OFF|ARG_MAGIC|BSQL_CHECK_SCHEMA, (void *)sql_cf_gen,
+ "( OLcfgDbAt:6.42 NAME 'olcSqlCheckSchema' "
+ "DESC 'Check schema after modifications' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "aliasing_keyword", "string", 2, 2, 0,
+ ARG_STRING|ARG_MAGIC|BSQL_ALIASING_KEYWORD, (void *)sql_cf_gen,
+ "( OLcfgDbAt:6.43 NAME 'olcSqlAliasingKeyword' "
+ "DESC 'The aliasing keyword' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "aliasing_quote", "string", 2, 2, 0, ARG_BERVAL|ARG_OFFSET,
+ (void *)offsetof(struct backsql_info, sql_aliasing_quote),
+ "( OLcfgDbAt:6.44 NAME 'olcSqlAliasingQuote' "
+ "DESC 'Quoting char of the aliasing keyword' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { "autocommit", "yes|no", 2, 2, 0,
+ ARG_ON_OFF|ARG_MAGIC|BSQL_AUTOCOMMIT, (void *)sql_cf_gen,
+ "( OLcfgDbAt:6.45 NAME 'olcSqlAutocommit' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean SINGLE-VALUE )", NULL, NULL },
+ { "id_query", "SQL expression", 2, 0, 0, ARG_STRING|ARG_QUOTE|ARG_OFFSET,
+ (void *)offsetof(struct backsql_info, sql_id_query),
+ "( OLcfgDbAt:6.46 NAME 'olcSqlIdQuery' "
+ "DESC 'Query used to collect entryID mapping data' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString SINGLE-VALUE )", NULL, NULL },
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED,
+ NULL, NULL, NULL, NULL }
+};
+
+static ConfigOCs sqlocs[] = {
+ {
+ "( OLcfgDbOc:6.1 "
+ "NAME 'olcSqlConfig' "
+ "DESC 'SQL backend configuration' "
+ "SUP olcDatabaseConfig "
+ "MUST olcDbName "
+ "MAY ( olcDbHost $ olcDbUser $ olcDbPass $ olcSqlConcatPattern $ "
+ "olcSqlSubtreeCond $ olcsqlChildrenCond $ olcSqlDnMatchCond $ "
+ "olcSqlOcQuery $ olcSqlAtQuery $ olcSqlInsEntryStmt $ "
+ "olcSqlCreateNeedsSelect $ olcSqlUpperFunc $ olcSqlUpperNeedsCast $ "
+ "olcSqlStrCastFunc $ olcSqlDelEntryStmt $ olcSqlRenEntryStmt $ "
+ "olcSqlDelObjClassesStmt $ olcSqlHasLDAPInfoDnRu $ "
+ "olcSqlFailIfNoMapping $ olcSqlAllowOrphans $ olcSqlBaseObject $ "
+ "olcSqlLayer $ olcSqlUseSubtreeShortcut $ olcSqlFetchAllAttrs $ "
+ "olcSqlFetchAttrs $ olcSqlCheckSchema $ olcSqlAliasingKeyword $ "
+ "olcSqlAliasingQuote $ olcSqlAutocommit $ olcSqlIdQuery ) )",
+ Cft_Database, sqlcfg },
+ { NULL, Cft_Abstract, NULL }
+};
+
+static int
+sql_cf_gen( ConfigArgs *c )
+{
+ backsql_info *bi = (backsql_info *)c->be->be_private;
+ int rc = 0;
+
+ if ( c->op == SLAP_CONFIG_EMIT ) {
+ switch( c->type ) {
+ case BSQL_CONCAT_PATT:
+ if ( bi->sql_concat_patt ) {
+ c->value_string = ch_strdup( bi->sql_concat_patt );
+ } else {
+ rc = 1;
+ }
+ break;
+ case BSQL_CREATE_NEEDS_SEL:
+ if ( bi->sql_flags & BSQLF_CREATE_NEEDS_SELECT )
+ c->value_int = 1;
+ break;
+ case BSQL_UPPER_NEEDS_CAST:
+ if ( bi->sql_flags & BSQLF_UPPER_NEEDS_CAST )
+ c->value_int = 1;
+ break;
+ case BSQL_HAS_LDAPINFO_DN_RU:
+ if ( !(bi->sql_flags & BSQLF_DONTCHECK_LDAPINFO_DN_RU) )
+ return 1;
+ if ( bi->sql_flags & BSQLF_HAS_LDAPINFO_DN_RU )
+ c->value_int = 1;
+ break;
+ case BSQL_FAIL_IF_NO_MAPPING:
+ if ( bi->sql_flags & BSQLF_FAIL_IF_NO_MAPPING )
+ c->value_int = 1;
+ break;
+ case BSQL_ALLOW_ORPHANS:
+ if ( bi->sql_flags & BSQLF_ALLOW_ORPHANS )
+ c->value_int = 1;
+ break;
+ case BSQL_SUBTREE_SHORTCUT:
+ if ( bi->sql_flags & BSQLF_USE_SUBTREE_SHORTCUT )
+ c->value_int = 1;
+ break;
+ case BSQL_FETCH_ALL_ATTRS:
+ if ( bi->sql_flags & BSQLF_FETCH_ALL_ATTRS )
+ c->value_int = 1;
+ break;
+ case BSQL_CHECK_SCHEMA:
+ if ( bi->sql_flags & BSQLF_CHECK_SCHEMA )
+ c->value_int = 1;
+ break;
+ case BSQL_AUTOCOMMIT:
+ if ( bi->sql_flags & BSQLF_AUTOCOMMIT_ON )
+ c->value_int = 1;
+ break;
+ case BSQL_BASE_OBJECT:
+ if ( bi->sql_base_ob_file ) {
+ c->value_string = ch_strdup( bi->sql_base_ob_file );
+ } else if ( bi->sql_baseObject ) {
+ c->value_string = ch_strdup( "TRUE" );
+ } else {
+ rc = 1;
+ }
+ break;
+ case BSQL_LAYER:
+ if ( bi->sql_api ) {
+ backsql_api *ba;
+ struct berval bv;
+ char *ptr;
+ int i;
+ for ( ba = bi->sql_api; ba; ba = ba->ba_next ) {
+ bv.bv_len = strlen( ba->ba_name );
+ if ( ba->ba_argc ) {
+ for ( i = 0; i<ba->ba_argc; i++ )
+ bv.bv_len += strlen( ba->ba_argv[i] ) + 3;
+ }
+ bv.bv_val = ch_malloc( bv.bv_len + 1 );
+ ptr = lutil_strcopy( bv.bv_val, ba->ba_name );
+ if ( ba->ba_argc ) {
+ for ( i = 0; i<ba->ba_argc; i++ ) {
+ *ptr++ = ' ';
+ *ptr++ = '"';
+ ptr = lutil_strcopy( ptr, ba->ba_argv[i] );
+ *ptr++ = '"';
+ }
+ }
+ ber_bvarray_add( &c->rvalue_vals, &bv );
+ }
+ } else {
+ rc = 1;
+ }
+ break;
+ case BSQL_ALIASING_KEYWORD:
+ if ( !BER_BVISNULL( &bi->sql_aliasing )) {
+ struct berval bv;
+ bv = bi->sql_aliasing;
+ bv.bv_len--;
+ value_add_one( &c->rvalue_vals, &bv );
+ } else {
+ rc = 1;
+ }
+ break;
+ case BSQL_FETCH_ATTRS:
+ if ( bi->sql_anlist ||
+ ( bi->sql_flags & (BSQLF_FETCH_ALL_USERATTRS|
+ BSQLF_FETCH_ALL_OPATTRS)))
+ {
+ char buf[BUFSIZ*2], *ptr;
+ struct berval bv;
+# define WHATSLEFT ((ber_len_t) (&buf[sizeof( buf )] - ptr))
+ ptr = buf;
+ if ( bi->sql_anlist ) {
+ ptr = anlist_unparse( bi->sql_anlist, ptr, WHATSLEFT );
+ if ( ptr == NULL )
+ return 1;
+ }
+ if ( bi->sql_flags & BSQLF_FETCH_ALL_USERATTRS ) {
+ if ( WHATSLEFT <= STRLENOF( ",*" )) return 1;
+ if ( ptr != buf ) *ptr++ = ',';
+ *ptr++ = '*';
+ }
+ if ( bi->sql_flags & BSQLF_FETCH_ALL_OPATTRS ) {
+ if ( WHATSLEFT <= STRLENOF( ",+" )) return 1;
+ if ( ptr != buf ) *ptr++ = ',';
+ *ptr++ = '+';
+ }
+ bv.bv_val = buf;
+ bv.bv_len = ptr - buf;
+ value_add_one( &c->rvalue_vals, &bv );
+ }
+ break;
+ }
+ return rc;
+ } else if ( c->op == LDAP_MOD_DELETE ) { /* FIXME */
+ return -1;
+ }
+
+ switch( c->type ) {
+ case BSQL_CONCAT_PATT:
+ if ( backsql_split_pattern( c->argv[ 1 ], &bi->sql_concat_func, 2 ) ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%s: unable to parse pattern \"%s\"",
+ c->log, c->argv[ 1 ] );
+ Debug( LDAP_DEBUG_ANY, "%s\n", c->cr_msg );
+ return -1;
+ }
+ bi->sql_concat_patt = c->value_string;
+ break;
+ case BSQL_CREATE_NEEDS_SEL:
+ if ( c->value_int )
+ bi->sql_flags |= BSQLF_CREATE_NEEDS_SELECT;
+ else
+ bi->sql_flags &= ~BSQLF_CREATE_NEEDS_SELECT;
+ break;
+ case BSQL_UPPER_NEEDS_CAST:
+ if ( c->value_int )
+ bi->sql_flags |= BSQLF_UPPER_NEEDS_CAST;
+ else
+ bi->sql_flags &= ~BSQLF_UPPER_NEEDS_CAST;
+ break;
+ case BSQL_HAS_LDAPINFO_DN_RU:
+ bi->sql_flags |= BSQLF_DONTCHECK_LDAPINFO_DN_RU;
+ if ( c->value_int )
+ bi->sql_flags |= BSQLF_HAS_LDAPINFO_DN_RU;
+ else
+ bi->sql_flags &= ~BSQLF_HAS_LDAPINFO_DN_RU;
+ break;
+ case BSQL_FAIL_IF_NO_MAPPING:
+ if ( c->value_int )
+ bi->sql_flags |= BSQLF_FAIL_IF_NO_MAPPING;
+ else
+ bi->sql_flags &= ~BSQLF_FAIL_IF_NO_MAPPING;
+ break;
+ case BSQL_ALLOW_ORPHANS:
+ if ( c->value_int )
+ bi->sql_flags |= BSQLF_ALLOW_ORPHANS;
+ else
+ bi->sql_flags &= ~BSQLF_ALLOW_ORPHANS;
+ break;
+ case BSQL_SUBTREE_SHORTCUT:
+ if ( c->value_int )
+ bi->sql_flags |= BSQLF_USE_SUBTREE_SHORTCUT;
+ else
+ bi->sql_flags &= ~BSQLF_USE_SUBTREE_SHORTCUT;
+ break;
+ case BSQL_FETCH_ALL_ATTRS:
+ if ( c->value_int )
+ bi->sql_flags |= BSQLF_FETCH_ALL_ATTRS;
+ else
+ bi->sql_flags &= ~BSQLF_FETCH_ALL_ATTRS;
+ break;
+ case BSQL_CHECK_SCHEMA:
+ if ( c->value_int )
+ bi->sql_flags |= BSQLF_CHECK_SCHEMA;
+ else
+ bi->sql_flags &= ~BSQLF_CHECK_SCHEMA;
+ break;
+ case BSQL_AUTOCOMMIT:
+ if ( c->value_int )
+ bi->sql_flags |= BSQLF_AUTOCOMMIT_ON;
+ else
+ bi->sql_flags &= ~BSQLF_AUTOCOMMIT_ON;
+ break;
+ case BSQL_BASE_OBJECT:
+ if ( c->be->be_nsuffix == NULL ) {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%s: suffix must be set", c->log );
+ Debug( LDAP_DEBUG_ANY, "%s\n", c->cr_msg );
+ rc = ARG_BAD_CONF;
+ break;
+ }
+ if ( bi->sql_baseObject ) {
+ Debug( LDAP_DEBUG_CONFIG,
+ "%s: "
+ "\"baseObject\" already provided (will be overwritten)\n",
+ c->log );
+ entry_free( bi->sql_baseObject );
+ }
+ if ( c->argc == 2 && !strcmp( c->argv[1], "TRUE" ))
+ c->argc = 1;
+ switch( c->argc ) {
+ case 1:
+ return create_baseObject( c->be, c->fname, c->lineno );
+
+ case 2:
+ rc = read_baseObject( c->be, c->argv[ 1 ] );
+ if ( rc == 0 ) {
+ ch_free( bi->sql_base_ob_file );
+ bi->sql_base_ob_file = c->value_string;
+ }
+ return rc;
+
+ default:
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%s: trailing values in directive", c->log );
+ Debug( LDAP_DEBUG_ANY, "%s\n", c->cr_msg );
+ return 1;
+ }
+ break;
+ case BSQL_LAYER:
+ if ( backsql_api_config( bi, c->argv[ 1 ], c->argc - 2, &c->argv[ 2 ] ) )
+ {
+ snprintf( c->cr_msg, sizeof( c->cr_msg ),
+ "%s: unable to load sql layer", c->log );
+ Debug( LDAP_DEBUG_ANY, "%s \"%s\"\n",
+ c->cr_msg, c->argv[1] );
+ return 1;
+ }
+ break;
+ case BSQL_ALIASING_KEYWORD:
+ if ( ! BER_BVISNULL( &bi->sql_aliasing ) ) {
+ ch_free( bi->sql_aliasing.bv_val );
+ }
+
+ ber_str2bv( c->argv[ 1 ], strlen( c->argv[ 1 ] ) + 1, 1,
+ &bi->sql_aliasing );
+ /* add a trailing space... */
+ bi->sql_aliasing.bv_val[ bi->sql_aliasing.bv_len - 1] = ' ';
+ break;
+ case BSQL_FETCH_ATTRS: {
+ char *str, *s, *next;
+ const char *delimstr = ",";
+
+ str = ch_strdup( c->argv[ 1 ] );
+ for ( s = ldap_pvt_strtok( str, delimstr, &next );
+ s != NULL;
+ s = ldap_pvt_strtok( NULL, delimstr, &next ) )
+ {
+ if ( strlen( s ) == 1 ) {
+ if ( *s == '*' ) {
+ bi->sql_flags |= BSQLF_FETCH_ALL_USERATTRS;
+ c->argv[ 1 ][ s - str ] = ',';
+
+ } else if ( *s == '+' ) {
+ bi->sql_flags |= BSQLF_FETCH_ALL_OPATTRS;
+ c->argv[ 1 ][ s - str ] = ',';
+ }
+ }
+ }
+ ch_free( str );
+ bi->sql_anlist = str2anlist( bi->sql_anlist, c->argv[ 1 ], delimstr );
+ if ( bi->sql_anlist == NULL ) {
+ return -1;
+ }
+ }
+ break;
+ }
+ return rc;
+}
+
+/*
+ * Read the entries specified in fname and merge the attributes
+ * to the user defined baseObject entry. Note that if we find any errors
+ * what so ever, we will discard the entire entries, print an
+ * error message and return.
+ */
+static int
+read_baseObject(
+ BackendDB *be,
+ const char *fname )
+{
+ backsql_info *bi = (backsql_info *)be->be_private;
+ LDIFFP *fp;
+ int rc = 0, lmax = 0, ldifrc;
+ unsigned long lineno = 0;
+ char *buf = NULL;
+
+ assert( fname != NULL );
+
+ fp = ldif_open( fname, "r" );
+ if ( fp == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "could not open back-sql baseObject "
+ "attr file \"%s\" - absolute path?\n",
+ fname );
+ perror( fname );
+ return LDAP_OTHER;
+ }
+
+ bi->sql_baseObject = entry_alloc();
+ if ( bi->sql_baseObject == NULL ) {
+ Debug( LDAP_DEBUG_ANY,
+ "read_baseObject_file: entry_alloc failed" );
+ ldif_close( fp );
+ return LDAP_NO_MEMORY;
+ }
+ bi->sql_baseObject->e_name = be->be_suffix[0];
+ bi->sql_baseObject->e_nname = be->be_nsuffix[0];
+ bi->sql_baseObject->e_attrs = NULL;
+
+ while (( ldifrc = ldif_read_record( fp, &lineno, &buf, &lmax )) > 0 ) {
+ Entry *e = str2entry( buf );
+ Attribute *a;
+
+ if( e == NULL ) {
+ fprintf( stderr, "back-sql baseObject: "
+ "could not parse entry (line=%lu)\n",
+ lineno );
+ rc = LDAP_OTHER;
+ break;
+ }
+
+ /* make sure the DN is the database's suffix */
+ if ( !be_issuffix( be, &e->e_nname ) ) {
+ fprintf( stderr,
+ "back-sql: invalid baseObject - "
+ "dn=\"%s\" (line=%lu)\n",
+ e->e_name.bv_val, lineno );
+ entry_free( e );
+ rc = LDAP_OTHER;
+ break;
+ }
+
+ /*
+ * we found a valid entry, so walk thru all the attributes in the
+ * entry, and add each attribute type and description to baseObject
+ */
+ for ( a = e->e_attrs; a != NULL; a = a->a_next ) {
+ if ( attr_merge( bi->sql_baseObject, a->a_desc,
+ a->a_vals,
+ ( a->a_nvals == a->a_vals ) ?
+ NULL : a->a_nvals ) )
+ {
+ rc = LDAP_OTHER;
+ break;
+ }
+ }
+
+ entry_free( e );
+ if ( rc ) {
+ break;
+ }
+ }
+
+ if ( ldifrc < 0 )
+ rc = LDAP_OTHER;
+
+ if ( rc ) {
+ entry_free( bi->sql_baseObject );
+ bi->sql_baseObject = NULL;
+ }
+
+ ch_free( buf );
+
+ ldif_close( fp );
+
+ Debug( LDAP_DEBUG_CONFIG, "back-sql baseObject file \"%s\" read.\n",
+ fname );
+
+ return rc;
+}
+
+static int
+create_baseObject(
+ BackendDB *be,
+ const char *fname,
+ int lineno )
+{
+ backsql_info *bi = (backsql_info *)be->be_private;
+ LDAPRDN rdn;
+ char *p;
+ int rc, iAVA;
+ char buf[1024];
+
+ snprintf( buf, sizeof(buf),
+ "dn: %s\n"
+ "objectClass: extensibleObject\n"
+ "description: builtin baseObject for back-sql\n"
+ "description: all entries mapped "
+ "in table \"ldap_entries\" "
+ "must have "
+ "\"" BACKSQL_BASEOBJECT_IDSTR "\" "
+ "in the \"parent\" column",
+ be->be_suffix[0].bv_val );
+
+ bi->sql_baseObject = str2entry( buf );
+ if ( bi->sql_baseObject == NULL ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "<==backsql_db_config (%s line %d): "
+ "unable to parse baseObject entry\n",
+ fname, lineno );
+ return 1;
+ }
+
+ if ( BER_BVISEMPTY( &be->be_suffix[ 0 ] ) ) {
+ return 0;
+ }
+
+ rc = ldap_bv2rdn( &be->be_suffix[ 0 ], &rdn, (char **)&p,
+ LDAP_DN_FORMAT_LDAP );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug(LDAP_DEBUG_TRACE,
+ "<==backsql_db_config (%s line %d): unable to extract RDN " "from baseObject DN \"%s\" (%d: %s)\n",
+ fname, lineno, be->be_suffix[0].bv_val, rc,
+ ldap_err2string(rc) );
+ return 1;
+ }
+
+ for ( iAVA = 0; rdn[ iAVA ]; iAVA++ ) {
+ LDAPAVA *ava = rdn[ iAVA ];
+ AttributeDescription *ad = NULL;
+ slap_syntax_transform_func *transf = NULL;
+ struct berval bv = BER_BVNULL;
+ const char *text = NULL;
+
+ assert( ava != NULL );
+
+ rc = slap_bv2ad( &ava->la_attr, &ad, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug(LDAP_DEBUG_TRACE,
+ "<==backsql_db_config (%s line %d): AttributeDescription of naming " "attribute #%d from baseObject " "DN \"%s\": %d: %s\n",
+ fname, lineno, iAVA, be->be_suffix[0].bv_val,
+ rc, ldap_err2string(rc) );
+ return 1;
+ }
+
+ transf = ad->ad_type->sat_syntax->ssyn_pretty;
+ if ( transf ) {
+ /*
+ * transform value by pretty function
+ * if value is empty, use empty_bv
+ */
+ rc = ( *transf )( ad->ad_type->sat_syntax,
+ ava->la_value.bv_len
+ ? &ava->la_value
+ : (struct berval *) &slap_empty_bv,
+ &bv, NULL );
+
+ if ( rc != LDAP_SUCCESS ) {
+ Debug(LDAP_DEBUG_TRACE,
+ "<==backsql_db_config (%s line %d): " "prettying of attribute #%d " "from baseObject " "DN \"%s\" failed: %d: %s\n",
+ fname, lineno, iAVA,
+ be->be_suffix[0].bv_val, rc,
+ ldap_err2string(rc) );
+ return 1;
+ }
+ }
+
+ if ( !BER_BVISNULL( &bv ) ) {
+ if ( ava->la_flags & LDAP_AVA_FREE_VALUE ) {
+ ber_memfree( ava->la_value.bv_val );
+ }
+ ava->la_value = bv;
+ ava->la_flags |= LDAP_AVA_FREE_VALUE;
+ }
+
+ attr_merge_normalize_one( bi->sql_baseObject,
+ ad, &ava->la_value, NULL );
+ }
+
+ ldap_rdnfree( rdn );
+
+ return 0;
+}
+
+int backsql_init_cf( BackendInfo *bi )
+{
+ int rc;
+
+ bi->bi_cf_ocs = sqlocs;
+ rc = config_register_schema( sqlcfg, sqlocs );
+ if ( rc ) return rc;
+ return 0;
+}
diff --git a/servers/slapd/back-sql/delete.c b/servers/slapd/back-sql/delete.c
new file mode 100644
index 0000000..489d4d3
--- /dev/null
+++ b/servers/slapd/back-sql/delete.c
@@ -0,0 +1,627 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1999 Dmitry Kovalev.
+ * Portions Copyright 2002 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Dmitry Kovalev for inclusion
+ * by OpenLDAP Software. Additional significant contributors include
+ * Pierangelo Masarati.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <sys/types.h>
+#include "ac/string.h"
+
+#include "slap.h"
+#include "proto-sql.h"
+
+typedef struct backsql_delete_attr_t {
+ Operation *op;
+ SlapReply *rs;
+ SQLHDBC dbh;
+ backsql_entryID *e_id;
+} backsql_delete_attr_t;
+
+static int
+backsql_delete_attr_f( void *v_at, void *v_bda )
+{
+ backsql_at_map_rec *at = (backsql_at_map_rec *)v_at;
+ backsql_delete_attr_t *bda = (backsql_delete_attr_t *)v_bda;
+ int rc;
+
+ rc = backsql_modify_delete_all_values( bda->op,
+ bda->rs, bda->dbh, bda->e_id, at );
+
+ if ( rc != LDAP_SUCCESS ) {
+ return BACKSQL_AVL_STOP;
+ }
+
+ return BACKSQL_AVL_CONTINUE;
+}
+
+static int
+backsql_delete_all_attrs(
+ Operation *op,
+ SlapReply *rs,
+ SQLHDBC dbh,
+ backsql_entryID *eid )
+{
+ backsql_delete_attr_t bda;
+ int rc;
+
+ bda.op = op;
+ bda.rs = rs;
+ bda.dbh = dbh;
+ bda.e_id = eid;
+
+ rc = ldap_avl_apply( eid->eid_oc->bom_attrs, backsql_delete_attr_f, &bda,
+ BACKSQL_AVL_STOP, AVL_INORDER );
+ if ( rc == BACKSQL_AVL_STOP ) {
+ return rs->sr_err;
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static int
+backsql_delete_int(
+ Operation *op,
+ SlapReply *rs,
+ SQLHDBC dbh,
+ SQLHSTMT *sthp,
+ backsql_entryID *eid,
+ Entry **ep )
+{
+ backsql_info *bi = (backsql_info*)op->o_bd->be_private;
+ SQLHSTMT sth = SQL_NULL_HSTMT;
+ RETCODE rc;
+ int prc = LDAP_SUCCESS;
+ /* first parameter no */
+ SQLUSMALLINT pno = 0;
+
+ sth = *sthp;
+
+ /* ldap_avl_apply ... */
+ rs->sr_err = backsql_delete_all_attrs( op, rs, dbh, eid );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ goto done;
+ }
+
+ rc = backsql_Prepare( dbh, &sth, eid->eid_oc->bom_delete_proc, 0 );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_delete(): "
+ "error preparing delete query\n" );
+ backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc );
+
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "SQL-backend error";
+ *ep = NULL;
+ goto done;
+ }
+
+ if ( BACKSQL_IS_DEL( eid->eid_oc->bom_expect_return ) ) {
+ pno = 1;
+ rc = backsql_BindParamInt( sth, 1, SQL_PARAM_OUTPUT, &prc );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_delete(): "
+ "error binding output parameter for objectClass %s\n",
+ eid->eid_oc->bom_oc->soc_cname.bv_val );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+
+ rs->sr_text = "SQL-backend error";
+ rs->sr_err = LDAP_OTHER;
+ *ep = NULL;
+ goto done;
+ }
+ }
+
+ rc = backsql_BindParamID( sth, pno + 1, SQL_PARAM_INPUT, &eid->eid_keyval );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_delete(): "
+ "error binding keyval parameter for objectClass %s\n",
+ eid->eid_oc->bom_oc->soc_cname.bv_val );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+
+ rs->sr_text = "SQL-backend error";
+ rs->sr_err = LDAP_OTHER;
+ *ep = NULL;
+ goto done;
+ }
+
+ rc = SQLExecute( sth );
+ if ( rc == SQL_SUCCESS && prc == LDAP_SUCCESS ) {
+ rs->sr_err = LDAP_SUCCESS;
+
+ } else {
+ Debug( LDAP_DEBUG_TRACE, " backsql_delete(): "
+ "delete_proc execution failed (rc=%d, prc=%d)\n",
+ rc, prc );
+
+
+ if ( prc != LDAP_SUCCESS ) {
+ /* SQL procedure executed fine
+ * but returned an error */
+ rs->sr_err = BACKSQL_SANITIZE_ERROR( prc );
+
+ } else {
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+ rs->sr_err = LDAP_OTHER;
+ }
+ SQLFreeStmt( sth, SQL_DROP );
+ goto done;
+ }
+ SQLFreeStmt( sth, SQL_DROP );
+
+ /* delete "auxiliary" objectClasses, if any... */
+ rc = backsql_Prepare( dbh, &sth, bi->sql_delobjclasses_stmt, 0 );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_delete(): "
+ "error preparing ldap_entry_objclasses delete query\n" );
+ backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc );
+
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "SQL-backend error";
+ *ep = NULL;
+ goto done;
+ }
+
+ rc = backsql_BindParamID( sth, 1, SQL_PARAM_INPUT, &eid->eid_id );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_delete(): "
+ "error binding auxiliary objectClasses "
+ "entry ID parameter for objectClass %s\n",
+ eid->eid_oc->bom_oc->soc_cname.bv_val );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+
+ rs->sr_text = "SQL-backend error";
+ rs->sr_err = LDAP_OTHER;
+ *ep = NULL;
+ goto done;
+ }
+
+ rc = SQLExecute( sth );
+ switch ( rc ) {
+ case SQL_NO_DATA:
+ /* apparently there were no "auxiliary" objectClasses
+ * for this entry... */
+ case SQL_SUCCESS:
+ break;
+
+ default:
+ Debug( LDAP_DEBUG_TRACE, " backsql_delete(): "
+ "failed to delete record from ldap_entry_objclasses\n" );
+ backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "SQL-backend error";
+ *ep = NULL;
+ goto done;
+ }
+ SQLFreeStmt( sth, SQL_DROP );
+
+ /* delete entry... */
+ rc = backsql_Prepare( dbh, &sth, bi->sql_delentry_stmt, 0 );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_delete(): "
+ "error preparing ldap_entries delete query\n" );
+ backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc );
+
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "SQL-backend error";
+ *ep = NULL;
+ goto done;
+ }
+
+ rc = backsql_BindParamID( sth, 1, SQL_PARAM_INPUT, &eid->eid_id );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_delete(): "
+ "error binding entry ID parameter "
+ "for objectClass %s\n",
+ eid->eid_oc->bom_oc->soc_cname.bv_val );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+
+ rs->sr_text = "SQL-backend error";
+ rs->sr_err = LDAP_OTHER;
+ *ep = NULL;
+ goto done;
+ }
+
+ rc = SQLExecute( sth );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_delete(): "
+ "failed to delete record from ldap_entries\n" );
+ backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "SQL-backend error";
+ *ep = NULL;
+ goto done;
+ }
+ SQLFreeStmt( sth, SQL_DROP );
+
+ rs->sr_err = LDAP_SUCCESS;
+ *ep = NULL;
+
+done:;
+ *sthp = sth;
+
+ return rs->sr_err;
+}
+
+typedef struct backsql_tree_delete_t {
+ Operation *btd_op;
+ int btd_rc;
+ backsql_entryID *btd_eid;
+} backsql_tree_delete_t;
+
+static int
+backsql_tree_delete_search_cb( Operation *op, SlapReply *rs )
+{
+ if ( rs->sr_type == REP_SEARCH ) {
+ backsql_tree_delete_t *btd;
+ backsql_entryID *eid;
+
+ btd = (backsql_tree_delete_t *)op->o_callback->sc_private;
+
+ if ( !access_allowed( btd->btd_op, rs->sr_entry,
+ slap_schema.si_ad_entry, NULL, ACL_WDEL, NULL )
+ || !access_allowed( btd->btd_op, rs->sr_entry,
+ slap_schema.si_ad_children, NULL, ACL_WDEL, NULL ) )
+ {
+ btd->btd_rc = LDAP_INSUFFICIENT_ACCESS;
+ return rs->sr_err = LDAP_UNAVAILABLE;
+ }
+
+ assert( rs->sr_entry != NULL );
+ assert( rs->sr_entry->e_private != NULL );
+
+ eid = (backsql_entryID *)rs->sr_entry->e_private;
+ assert( eid->eid_oc != NULL );
+ if ( eid->eid_oc == NULL || eid->eid_oc->bom_delete_proc == NULL ) {
+ btd->btd_rc = LDAP_UNWILLING_TO_PERFORM;
+ return rs->sr_err = LDAP_UNAVAILABLE;
+ }
+
+ eid = backsql_entryID_dup( eid, op->o_tmpmemctx );
+ eid->eid_next = btd->btd_eid;
+ btd->btd_eid = eid;
+ }
+
+ return 0;
+}
+
+static int
+backsql_tree_delete(
+ Operation *op,
+ SlapReply *rs,
+ SQLHDBC dbh,
+ SQLHSTMT *sthp )
+{
+ Operation op2 = *op;
+ slap_callback sc = { 0 };
+ SlapReply rs2 = { REP_RESULT };
+ backsql_tree_delete_t btd = { 0 };
+
+ int rc;
+
+ /*
+ * - perform an internal subtree search as the rootdn
+ * - for each entry
+ * - check access
+ * - check objectClass and delete method(s)
+ * - for each entry
+ * - delete
+ * - if successful, commit
+ */
+
+ op2.o_tag = LDAP_REQ_SEARCH;
+ op2.o_protocol = LDAP_VERSION3;
+
+ btd.btd_op = op;
+ sc.sc_private = &btd;
+ sc.sc_response = backsql_tree_delete_search_cb;
+ op2.o_callback = &sc;
+
+ op2.o_dn = op->o_bd->be_rootdn;
+ op2.o_ndn = op->o_bd->be_rootndn;
+
+ op2.o_managedsait = SLAP_CONTROL_CRITICAL;
+
+ op2.ors_scope = LDAP_SCOPE_SUBTREE;
+ op2.ors_deref = LDAP_DEREF_NEVER;
+ op2.ors_slimit = SLAP_NO_LIMIT;
+ op2.ors_tlimit = SLAP_NO_LIMIT;
+ op2.ors_filter = (Filter *)slap_filter_objectClass_pres;
+ op2.ors_filterstr = *slap_filterstr_objectClass_pres;
+ op2.ors_attrs = slap_anlist_all_attributes;
+ op2.ors_attrsonly = 0;
+
+ rc = op->o_bd->be_search( &op2, &rs2 );
+ if ( rc != LDAP_SUCCESS ) {
+ rc = rs->sr_err = btd.btd_rc;
+ rs->sr_text = "subtree delete not possible";
+ send_ldap_result( op, rs );
+ goto clean;
+ }
+
+ for ( ; btd.btd_eid != NULL;
+ btd.btd_eid = backsql_free_entryID( btd.btd_eid,
+ 1, op->o_tmpmemctx ) )
+ {
+ Entry *e = (void *)0xbad;
+ rc = backsql_delete_int( op, rs, dbh, sthp, btd.btd_eid, &e );
+ if ( rc != LDAP_SUCCESS ) {
+ break;
+ }
+ }
+
+clean:;
+ for ( ; btd.btd_eid != NULL;
+ btd.btd_eid = backsql_free_entryID( btd.btd_eid,
+ 1, op->o_tmpmemctx ) )
+ ;
+
+ return rc;
+}
+
+int
+backsql_delete( Operation *op, SlapReply *rs )
+{
+ SQLHDBC dbh = SQL_NULL_HDBC;
+ SQLHSTMT sth = SQL_NULL_HSTMT;
+ backsql_oc_map_rec *oc = NULL;
+ backsql_srch_info bsi = { 0 };
+ backsql_entryID e_id = { 0 };
+ Entry d = { 0 }, p = { 0 }, *e = NULL;
+ struct berval pdn = BER_BVNULL;
+ int manageDSAit = get_manageDSAit( op );
+
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_delete(): deleting entry \"%s\"\n",
+ op->o_req_ndn.bv_val );
+
+ rs->sr_err = backsql_get_db_conn( op, &dbh );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_delete(): "
+ "could not get connection handle - exiting\n" );
+ rs->sr_text = ( rs->sr_err == LDAP_OTHER )
+ ? "SQL-backend error" : NULL;
+ e = NULL;
+ goto done;
+ }
+
+ /*
+ * Get the entry
+ */
+ bsi.bsi_e = &d;
+ rs->sr_err = backsql_init_search( &bsi, &op->o_req_ndn,
+ LDAP_SCOPE_BASE,
+ (time_t)(-1), NULL, dbh, op, rs, slap_anlist_no_attrs,
+ ( BACKSQL_ISF_MATCHED | BACKSQL_ISF_GET_ENTRY | BACKSQL_ISF_GET_OC ) );
+ 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;
+ }
+ e = &d;
+ /* fallthru */
+
+ default:
+ Debug( LDAP_DEBUG_TRACE, "backsql_delete(): "
+ "could not retrieve deleteDN ID - no such entry\n" );
+ if ( !BER_BVISNULL( &d.e_nname ) ) {
+ /* FIXME: should always be true! */
+ e = &d;
+
+ } else {
+ e = NULL;
+ }
+ goto done;
+ }
+
+ if ( get_assert( op ) &&
+ ( test_filter( op, &d, get_assertion( op ) )
+ != LDAP_COMPARE_TRUE ) )
+ {
+ rs->sr_err = LDAP_ASSERTION_FAILED;
+ e = &d;
+ goto done;
+ }
+
+ if ( !access_allowed( op, &d, slap_schema.si_ad_entry,
+ NULL, ACL_WDEL, NULL ) )
+ {
+ Debug( LDAP_DEBUG_TRACE, " backsql_delete(): "
+ "no write access to entry\n" );
+ rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ e = &d;
+ goto done;
+ }
+
+ rs->sr_err = backsql_has_children( op, dbh, &op->o_req_ndn );
+ switch ( rs->sr_err ) {
+ case LDAP_COMPARE_FALSE:
+ rs->sr_err = LDAP_SUCCESS;
+ break;
+
+ case LDAP_COMPARE_TRUE:
+#ifdef SLAP_CONTROL_X_TREE_DELETE
+ if ( get_treeDelete( op ) ) {
+ rs->sr_err = LDAP_SUCCESS;
+ break;
+ }
+#endif /* SLAP_CONTROL_X_TREE_DELETE */
+
+ Debug( LDAP_DEBUG_TRACE, " backsql_delete(): "
+ "entry \"%s\" has children\n",
+ op->o_req_dn.bv_val );
+ rs->sr_err = LDAP_NOT_ALLOWED_ON_NONLEAF;
+ rs->sr_text = "subordinate objects must be deleted first";
+ /* fallthru */
+
+ default:
+ e = &d;
+ goto done;
+ }
+
+ assert( bsi.bsi_base_id.eid_oc != NULL );
+ oc = bsi.bsi_base_id.eid_oc;
+ if ( oc->bom_delete_proc == NULL ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_delete(): "
+ "delete procedure is not defined "
+ "for this objectclass - aborting\n" );
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "operation not permitted within namingContext";
+ e = NULL;
+ goto done;
+ }
+
+ /*
+ * Get the parent
+ */
+ e_id = bsi.bsi_base_id;
+ memset( &bsi.bsi_base_id, 0, sizeof( bsi.bsi_base_id ) );
+ if ( !be_issuffix( op->o_bd, &op->o_req_ndn ) ) {
+ dnParent( &op->o_req_ndn, &pdn );
+ bsi.bsi_e = &p;
+ rs->sr_err = backsql_init_search( &bsi, &pdn,
+ LDAP_SCOPE_BASE,
+ (time_t)(-1), NULL, dbh, op, rs,
+ slap_anlist_no_attrs,
+ BACKSQL_ISF_GET_ENTRY );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_delete(): "
+ "could not retrieve deleteDN ID "
+ "- no such entry\n" );
+ e = &p;
+ goto done;
+ }
+
+ (void)backsql_free_entryID( &bsi.bsi_base_id, 0, op->o_tmpmemctx );
+
+ /* check parent for "children" acl */
+ if ( !access_allowed( op, &p, slap_schema.si_ad_children,
+ NULL, ACL_WDEL, NULL ) )
+ {
+ Debug( LDAP_DEBUG_TRACE, " backsql_delete(): "
+ "no write access to parent\n" );
+ rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ e = &p;
+ goto done;
+
+ }
+ }
+
+ e = &d;
+#ifdef SLAP_CONTROL_X_TREE_DELETE
+ if ( get_treeDelete( op ) ) {
+ backsql_tree_delete( op, rs, dbh, &sth );
+ if ( rs->sr_err == LDAP_OTHER || rs->sr_err == LDAP_SUCCESS )
+ {
+ e = NULL;
+ }
+
+ } else
+#endif /* SLAP_CONTROL_X_TREE_DELETE */
+ {
+ backsql_delete_int( op, rs, dbh, &sth, &e_id, &e );
+ }
+
+ /*
+ * Commit only if all operations succeed
+ */
+ if ( sth != SQL_NULL_HSTMT ) {
+ SQLUSMALLINT CompletionType = SQL_ROLLBACK;
+
+ if ( rs->sr_err == LDAP_SUCCESS && !op->o_noop ) {
+ assert( e == NULL );
+ CompletionType = SQL_COMMIT;
+ }
+
+ SQLTransact( SQL_NULL_HENV, dbh, CompletionType );
+ }
+
+done:;
+ if ( e != NULL ) {
+ if ( !access_allowed( op, e, slap_schema.si_ad_entry, NULL,
+ ACL_DISCLOSE, NULL ) )
+ {
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ rs->sr_text = NULL;
+ rs->sr_matched = NULL;
+ if ( rs->sr_ref ) {
+ ber_bvarray_free( rs->sr_ref );
+ rs->sr_ref = NULL;
+ }
+ }
+ }
+
+ if ( op->o_noop && rs->sr_err == LDAP_SUCCESS ) {
+ rs->sr_err = LDAP_X_NO_OPERATION;
+ }
+
+ send_ldap_result( op, rs );
+
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_delete()\n" );
+
+ if ( !BER_BVISNULL( &e_id.eid_ndn ) ) {
+ (void)backsql_free_entryID( &e_id, 0, op->o_tmpmemctx );
+ }
+
+ if ( !BER_BVISNULL( &d.e_nname ) ) {
+ backsql_entry_clean( op, &d );
+ }
+
+ if ( !BER_BVISNULL( &p.e_nname ) ) {
+ backsql_entry_clean( op, &p );
+ }
+
+ if ( rs->sr_ref ) {
+ ber_bvarray_free( rs->sr_ref );
+ rs->sr_ref = NULL;
+ }
+
+ return rs->sr_err;
+}
+
diff --git a/servers/slapd/back-sql/docs/bugs b/servers/slapd/back-sql/docs/bugs
new file mode 100644
index 0000000..4b2f3c7
--- /dev/null
+++ b/servers/slapd/back-sql/docs/bugs
@@ -0,0 +1,16 @@
+1) driver name comparison for MS SQL Server workaround is really kinda dirty
+ hack, but for now i don't know how to code it more carefully
+2) another dirty hack: length of LONGVARCHAR and LONGVARBINARY fields is
+ currently set to MAX_ATTR_LEN. Maybe such fields must be handled with
+ SQLGetData() instead of SQLBindCol(), but it is said in documentation,
+ that it is guaranteed to work only when such column goes after last bound
+ column. Or should we get ALL columns with SQLGetData (then something like
+ _SQLFetchAsStrings() wrapper would do SQLGetData() for all columns)...
+3) in some cases (particularly, when using OpenLink Generic ODBC driver with
+ MS SQL Server), it returns "Function sequence error" after all records are
+ fetched. I really don't know what it means, and after all
+ - it works with any other driver I tried
+4) ldapsearch sometimes refuses to show some attributes ("NOT PRINTABLE" diags)
+ on Win32 (on linux everything's fine)
+5) back-sql crashes on invalid filters (to be fixed ASAP)
+ \ No newline at end of file
diff --git a/servers/slapd/back-sql/docs/concept b/servers/slapd/back-sql/docs/concept
new file mode 100644
index 0000000..ed29047
--- /dev/null
+++ b/servers/slapd/back-sql/docs/concept
@@ -0,0 +1 @@
+The SQL backend is described in the slapd-sql(5) manual page.
diff --git a/servers/slapd/back-sql/docs/install b/servers/slapd/back-sql/docs/install
new file mode 100644
index 0000000..230bf0a
--- /dev/null
+++ b/servers/slapd/back-sql/docs/install
@@ -0,0 +1,86 @@
+PLEASE READ THIS WHOLE FILE AND CONCEPT, BECAUSE THEY COVER SEVERAL STICKY
+ISSUES THAT YOU WILL PROBABLY STUMBLE ACROSS ANYWAY
+
+1. Build
+To build slapd with back-sql under Unix you need to build and install
+iODBC 2.50.3 (later versions should probably work, but not earlier),
+or unixODBC (you will have to change -liodbc to -lodbc then).
+Then, at top of OpenLDAP source tree, run
+"configure <other options you need> --enable-sql", then "make" -
+this should build back-sql-enabled slapd, provided that you have iODBC/unixODBC
+libraries and include files in include/library paths, "make install"...
+In other words, follow installation procedure described in OpenLDAP
+Administrators Guide, adding --enable-sql option to configure, and
+having iODBC/unixODBC libraries installed an accessible by compiler.
+
+Under Win32/MSVC++, I modified the workspace so that back-sql is built into
+slapd automatically, since MS ODBC manager, odbc32.dll, is included in
+standard library pack, and it does no bad even if you don't plan to use it.
+I also could provide precompiled executables for those who don't have MSVC.
+Note that Win32 port of OpenLDAP itself is experimental, and thus doesn't
+provide very convenient build environment (yet).
+
+2. Tune datasources and slapd.conf
+Next, you need to define ODBC datasource with data you want to publish
+with help of back-sql. Assuming that you have your data in some SQL-compliant
+RDBMS, and have installed proper ODBC driver for this RDBMS, this is as simple
+as adding a record into odbc.ini (for iODBC/unixODBC), or using ODBC wizard in
+Control Panel (for odbc32).
+Next, you need to add appropriate "database" record to your slapd.conf.
+See samples provided in "back-sql/RDBMS_DEPENDENT/" subdirectory.
+
+Several things worth noting about ODBC:
+- "dbname" directive stands for ODBC datasource name (DSN),
+ not the name of your database in RDBMS context
+- ODBC under Unix is not so common as under Windows, so you could have
+ problems with Unix drivers for your RDBMS. Visit http://www.openlinksw.com,
+ they provide a multitier solution which allows connecting to DBMSes on
+ different platforms, proxying and other connectivity and integration issues.
+ They also support iODBC, and have good free customer service through
+ newsserver (at news.openlinksw.com).
+ Also worth noting are: ODBC-ODBC bridge by EasySoft (which was claimed
+ by several people to be far more effective and stable than OpenLink),
+ OpenRDA package etc.
+- be careful defining RDBMS connection parameters, you'll probably need only
+ "dbname" directive - all the rest can be defined in datasource. Every other
+ directive is used to override value stored in datasource definition.
+ Maybe you will want to use dbuser/dbpasswd to override credentials defined in datasource
+- full list of configuration directives supported is available in file "guide",
+ you may also analyze output of 'slapd -d 5' to find out some useful
+ directives for redefining default queries
+
+3. Creating and using back-sql metatables
+Read the file "concept" to understand, what metainformation you need to add,
+and what for... ;)
+See SQL scripts and slapd.conf files in samples directory.
+Find subdirectory in "rdbms_depend/" corresponding to your RDBMS (Oracle,
+MS SQL Server and mySQL are listed there currently), or copy and edit
+any of these to conform to SQL dialect of your RDBMS (please be sure to send
+me scripts and notes for new RDBMSes ;).
+
+Execute "backsql_create.sql" from that subdirectory (or edited one),
+so that the tables it creates appear in the same
+context with the data you want to export through LDAP (under same DB/user,
+or whatever is needed in RDBMS you use). You can use something like
+"mysql < xxx.sql" for mySQL, Query Analyzer+Open query file for MS SQL,
+sqlplus and "@xxx.sql" for Oracle.
+
+You may well want to try it with test data first, and see how metatables
+are used. Create test data and metadata by running testdb_create.sql,
+testdb_data.sql, and testdb_metadata.sql scripts (again, adopted for your
+RDBMS, and in the same context as metatables you created before), and
+tune slapd.conf to use your test DB.
+
+4. Testing
+To diagnose back-sql, run slapd with debug level TRACE ("slapd -d 5" will go).
+Then, use some LDAP client to query corresponding subtree (for test database,
+you could for instance search one level from "o=sql,c=RU"). I personally used
+saucer, which is included in OpenLDAP package (it builds automatically under
+Unix/GNU configure and for MSVC I added appropriate project to workspace).
+And also Java LDAP browser-editor (see link somewhere on OpenLDAP site) to
+test ADD/DELETE/MODIFY operations on Oracle and MS SQL.
+
+See file "platforms" if you encounter connection problems - you may find
+a hint for your RDBMS or OS there. If you are stuck - please contact me at
+mit@openldap.org, or (better) post an issue through OpenLDAP's Issue Tracking
+System (see http:/www.openldap.org/its).
diff --git a/servers/slapd/back-sql/docs/platforms b/servers/slapd/back-sql/docs/platforms
new file mode 100644
index 0000000..65e326a
--- /dev/null
+++ b/servers/slapd/back-sql/docs/platforms
@@ -0,0 +1,8 @@
+Platforms and configurations it has been tested on:
+
+General:
+ - ODBC managers: iODBC,unixODBC under unixes, odbc32.dll under Win32 family
+ - OSes: Linux/glibc, FreeBSD, OpenBSD, Solaris 2.6, Win98, WinNT, Win2000 server
+ - RDBMSes: Oracle 7/8/8i, MS SQL Server 6.5/7, mySQL
+ - access suites: OpenLink DAS, EasySoft OOB, various win32 drivers
+
diff --git a/servers/slapd/back-sql/docs/todo b/servers/slapd/back-sql/docs/todo
new file mode 100644
index 0000000..9d8736f
--- /dev/null
+++ b/servers/slapd/back-sql/docs/todo
@@ -0,0 +1,12 @@
+1) must add alias handling
+2) [sizelimit moved to frontend]
+ must set time limit when preparing all queries, and check size limit
+3) there was expressed a need to have access to IP in while constructing
+ queries, to have response alter in accordance to client IP. Will add
+ preprocessor for values in metatables, which would substitute things
+ like "$IP$".
+4) must handle NOT filters (see ITS#2652)
+5) must map attribute types and syntaxes between LDAP and SQL types (e.g.
+ use BLOBs for octet streams)
+6) must define another mech to add auxiliary objectClass to all entries
+ according to ldap_at_mappings (ldap_entry_objclasses has limitations)
diff --git a/servers/slapd/back-sql/entry-id.c b/servers/slapd/back-sql/entry-id.c
new file mode 100644
index 0000000..c2d78a7
--- /dev/null
+++ b/servers/slapd/back-sql/entry-id.c
@@ -0,0 +1,1092 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1999 Dmitry Kovalev.
+ * Portions Copyright 2002 Pierangelo Masarati.
+ * Portions Copyright 2004 Mark Adamson.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Dmitry Kovalev for inclusion
+ * by OpenLDAP Software. Additional significant contributors include
+ * Pierangelo Masarati and Mark Adamson.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <sys/types.h>
+#include "ac/string.h"
+
+#include "lutil.h"
+#include "slap.h"
+#include "proto-sql.h"
+
+#ifdef BACKSQL_ARBITRARY_KEY
+struct berval backsql_baseObject_bv = BER_BVC( BACKSQL_BASEOBJECT_IDSTR );
+#endif /* BACKSQL_ARBITRARY_KEY */
+
+backsql_entryID *
+backsql_entryID_dup( backsql_entryID *src, void *ctx )
+{
+ backsql_entryID *dst;
+
+ if ( src == NULL ) return NULL;
+
+ dst = slap_sl_calloc( 1, sizeof( backsql_entryID ), ctx );
+ ber_dupbv_x( &dst->eid_ndn, &src->eid_ndn, ctx );
+ if ( src->eid_dn.bv_val == src->eid_ndn.bv_val ) {
+ dst->eid_dn = dst->eid_ndn;
+ } else {
+ ber_dupbv_x( &dst->eid_dn, &src->eid_dn, ctx );
+ }
+
+#ifdef BACKSQL_ARBITRARY_KEY
+ ber_dupbv_x( &dst->eid_id, &src->eid_id, ctx );
+ ber_dupbv_x( &dst->eid_keyval, &src->eid_keyval, ctx );
+#else /* ! BACKSQL_ARBITRARY_KEY */
+ dst->eid_id = src->eid_id;
+ dst->eid_keyval = src->eid_keyval;
+#endif /* ! BACKSQL_ARBITRARY_KEY */
+
+ dst->eid_oc = src->eid_oc;
+ dst->eid_oc_id = src->eid_oc_id;
+
+ return dst;
+}
+
+backsql_entryID *
+backsql_free_entryID( backsql_entryID *id, int freeit, void *ctx )
+{
+ backsql_entryID *next;
+
+ assert( id != NULL );
+
+ next = id->eid_next;
+
+ if ( !BER_BVISNULL( &id->eid_ndn ) ) {
+ if ( !BER_BVISNULL( &id->eid_dn )
+ && id->eid_dn.bv_val != id->eid_ndn.bv_val )
+ {
+ slap_sl_free( id->eid_dn.bv_val, ctx );
+ BER_BVZERO( &id->eid_dn );
+ }
+
+ slap_sl_free( id->eid_ndn.bv_val, ctx );
+ BER_BVZERO( &id->eid_ndn );
+ }
+
+#ifdef BACKSQL_ARBITRARY_KEY
+ if ( !BER_BVISNULL( &id->eid_id ) ) {
+ slap_sl_free( id->eid_id.bv_val, ctx );
+ BER_BVZERO( &id->eid_id );
+ }
+
+ if ( !BER_BVISNULL( &id->eid_keyval ) ) {
+ slap_sl_free( id->eid_keyval.bv_val, ctx );
+ BER_BVZERO( &id->eid_keyval );
+ }
+#endif /* BACKSQL_ARBITRARY_KEY */
+
+ if ( freeit ) {
+ slap_sl_free( id, ctx );
+ }
+
+ return next;
+}
+
+/*
+ * NOTE: the dn must be normalized
+ */
+int
+backsql_dn2id(
+ Operation *op,
+ SlapReply *rs,
+ SQLHDBC dbh,
+ struct berval *ndn,
+ backsql_entryID *id,
+ int matched,
+ int muck )
+{
+ backsql_info *bi = op->o_bd->be_private;
+ SQLHSTMT sth = SQL_NULL_HSTMT;
+ BACKSQL_ROW_NTS row = { 0 };
+ RETCODE rc;
+ int res;
+ struct berval realndn = BER_BVNULL;
+
+ /* TimesTen */
+ char upperdn[ BACKSQL_MAX_DN_LEN + 1 ];
+ struct berval tbbDN;
+ int i, j;
+
+ /*
+ * NOTE: id can be NULL; in this case, the function
+ * simply checks whether the DN can be successfully
+ * turned into an ID, returning LDAP_SUCCESS for
+ * positive cases, or the most appropriate error
+ */
+
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_dn2id(\"%s\")%s%s\n",
+ ndn->bv_val, id == NULL ? " (no ID expected)" : "",
+ matched ? " matched expected" : "" );
+
+ if ( id ) {
+ /* NOTE: trap inconsistencies */
+ assert( BER_BVISNULL( &id->eid_ndn ) );
+ }
+
+ if ( ndn->bv_len > BACKSQL_MAX_DN_LEN ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_dn2id(\"%s\"): DN length=%ld "
+ "exceeds max DN length %d:\n",
+ ndn->bv_val, ndn->bv_len, BACKSQL_MAX_DN_LEN );
+ return LDAP_OTHER;
+ }
+
+ /* return baseObject if available and matches */
+ /* FIXME: if ndn is already mucked, we cannot check this */
+ if ( bi->sql_baseObject != NULL &&
+ dn_match( ndn, &bi->sql_baseObject->e_nname ) )
+ {
+ if ( id != NULL ) {
+#ifdef BACKSQL_ARBITRARY_KEY
+ ber_dupbv_x( &id->eid_id, &backsql_baseObject_bv,
+ op->o_tmpmemctx );
+ ber_dupbv_x( &id->eid_keyval, &backsql_baseObject_bv,
+ op->o_tmpmemctx );
+#else /* ! BACKSQL_ARBITRARY_KEY */
+ id->eid_id = BACKSQL_BASEOBJECT_ID;
+ id->eid_keyval = BACKSQL_BASEOBJECT_KEYVAL;
+#endif /* ! BACKSQL_ARBITRARY_KEY */
+ id->eid_oc_id = BACKSQL_BASEOBJECT_OC;
+
+ ber_dupbv_x( &id->eid_ndn, &bi->sql_baseObject->e_nname,
+ op->o_tmpmemctx );
+ ber_dupbv_x( &id->eid_dn, &bi->sql_baseObject->e_name,
+ op->o_tmpmemctx );
+
+ id->eid_next = NULL;
+ }
+
+ return LDAP_SUCCESS;
+ }
+
+ /* begin TimesTen */
+ assert( bi->sql_id_query != NULL );
+ Debug( LDAP_DEBUG_TRACE, " backsql_dn2id(\"%s\"): id_query \"%s\"\n",
+ ndn->bv_val, bi->sql_id_query );
+ rc = backsql_Prepare( dbh, &sth, bi->sql_id_query, 0 );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_dn2id(\"%s\"): "
+ "error preparing SQL:\n %s",
+ ndn->bv_val, bi->sql_id_query );
+ backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc );
+ res = LDAP_OTHER;
+ goto done;
+ }
+
+ realndn = *ndn;
+ if ( muck ) {
+ if ( backsql_api_dn2odbc( op, rs, &realndn ) ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_dn2id(\"%s\"): "
+ "backsql_api_dn2odbc(\"%s\") failed\n",
+ ndn->bv_val, realndn.bv_val );
+ res = LDAP_OTHER;
+ goto done;
+ }
+ }
+
+ if ( BACKSQL_HAS_LDAPINFO_DN_RU( bi ) ) {
+ /*
+ * Prepare an upper cased, byte reversed version
+ * that can be searched using indexes
+ */
+
+ for ( i = 0, j = realndn.bv_len - 1; realndn.bv_val[ i ]; i++, j--)
+ {
+ upperdn[ i ] = realndn.bv_val[ j ];
+ }
+ upperdn[ i ] = '\0';
+ ldap_pvt_str2upper( upperdn );
+
+ Debug( LDAP_DEBUG_TRACE, " backsql_dn2id(\"%s\"): "
+ "upperdn=\"%s\"\n",
+ ndn->bv_val, upperdn );
+ ber_str2bv( upperdn, 0, 0, &tbbDN );
+
+ } else {
+ if ( BACKSQL_USE_REVERSE_DN( bi ) ) {
+ AC_MEMCPY( upperdn, realndn.bv_val, realndn.bv_len + 1 );
+ ldap_pvt_str2upper( upperdn );
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_dn2id(\"%s\"): "
+ "upperdn=\"%s\"\n",
+ ndn->bv_val, upperdn );
+ ber_str2bv( upperdn, 0, 0, &tbbDN );
+
+ } else {
+ tbbDN = realndn;
+ }
+ }
+
+ rc = backsql_BindParamBerVal( sth, 1, SQL_PARAM_INPUT, &tbbDN );
+ if ( rc != SQL_SUCCESS) {
+ /* end TimesTen */
+ Debug( LDAP_DEBUG_TRACE, " backsql_dn2id(\"%s\"): "
+ "error binding dn=\"%s\" parameter:\n",
+ ndn->bv_val, tbbDN.bv_val );
+ backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc );
+ res = LDAP_OTHER;
+ goto done;
+ }
+
+ rc = SQLExecute( sth );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_dn2id(\"%s\"): "
+ "error executing query (\"%s\", \"%s\"):\n",
+ ndn->bv_val, bi->sql_id_query, tbbDN.bv_val );
+ backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc );
+ res = LDAP_OTHER;
+ goto done;
+ }
+
+ backsql_BindRowAsStrings_x( sth, &row, op->o_tmpmemctx );
+ rc = SQLFetch( sth );
+ if ( BACKSQL_SUCCESS( rc ) ) {
+#ifdef LDAP_DEBUG
+ Debug(LDAP_DEBUG_TRACE,
+ " backsql_dn2id(\"%s\"): id=%s keyval=%s oc_id=%s dn=%s\n",
+ ndn->bv_val, row.cols[0], row.cols[1], row.cols[2],
+ row.cols[3] );
+#endif /* LDAP_DEBUG */
+
+ res = LDAP_SUCCESS;
+ if ( id != NULL ) {
+ struct berval dn;
+
+ id->eid_next = NULL;
+
+#ifdef BACKSQL_ARBITRARY_KEY
+ ber_str2bv_x( row.cols[ 0 ], 0, 1, &id->eid_id,
+ op->o_tmpmemctx );
+ ber_str2bv_x( row.cols[ 1 ], 0, 1, &id->eid_keyval,
+ op->o_tmpmemctx );
+#else /* ! BACKSQL_ARBITRARY_KEY */
+ if ( BACKSQL_STR2ID( &id->eid_id, row.cols[ 0 ], 0 ) != 0 ) {
+ res = LDAP_OTHER;
+ goto done;
+ }
+ if ( BACKSQL_STR2ID( &id->eid_keyval, row.cols[ 1 ], 0 ) != 0 ) {
+ res = LDAP_OTHER;
+ goto done;
+ }
+#endif /* ! BACKSQL_ARBITRARY_KEY */
+ if ( BACKSQL_STR2ID( &id->eid_oc_id, row.cols[ 2 ], 0 ) != 0 ) {
+ res = LDAP_OTHER;
+ goto done;
+ }
+
+ ber_str2bv( row.cols[ 3 ], 0, 0, &dn );
+
+ if ( backsql_api_odbc2dn( op, rs, &dn ) ) {
+ res = LDAP_OTHER;
+ goto done;
+ }
+
+ res = dnPrettyNormal( NULL, &dn,
+ &id->eid_dn, &id->eid_ndn,
+ op->o_tmpmemctx );
+ if ( res != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_dn2id(\"%s\"): "
+ "dnPrettyNormal failed (%d: %s)\n",
+ realndn.bv_val, res,
+ ldap_err2string( res ) );
+
+ /* cleanup... */
+ (void)backsql_free_entryID( id, 0, op->o_tmpmemctx );
+ }
+
+ if ( dn.bv_val != row.cols[ 3 ] ) {
+ free( dn.bv_val );
+ }
+ }
+
+ } else {
+ res = LDAP_NO_SUCH_OBJECT;
+ if ( matched ) {
+ struct berval pdn = *ndn;
+
+ /*
+ * Look for matched
+ */
+ rs->sr_matched = NULL;
+ while ( !be_issuffix( op->o_bd, &pdn ) ) {
+ char *matchedDN = NULL;
+
+ dnParent( &pdn, &pdn );
+
+ /*
+ * Empty DN ("") defaults to LDAP_SUCCESS
+ */
+ rs->sr_err = backsql_dn2id( op, rs, dbh, &pdn, id, 0, 1 );
+ switch ( rs->sr_err ) {
+ case LDAP_NO_SUCH_OBJECT:
+ /* try another one */
+ break;
+
+ case LDAP_SUCCESS:
+ matchedDN = pdn.bv_val;
+ /* fail over to next case */
+
+ default:
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ rs->sr_matched = matchedDN;
+ goto done;
+ }
+ }
+ }
+ }
+
+done:;
+ backsql_FreeRow_x( &row, op->o_tmpmemctx );
+
+ Debug( LDAP_DEBUG_TRACE,
+ "<==backsql_dn2id(\"%s\"): err=%d\n",
+ ndn->bv_val, res );
+ if ( sth != SQL_NULL_HSTMT ) {
+ SQLFreeStmt( sth, SQL_DROP );
+ }
+
+ if ( !BER_BVISNULL( &realndn ) && realndn.bv_val != ndn->bv_val ) {
+ ch_free( realndn.bv_val );
+ }
+
+ return res;
+}
+
+int
+backsql_count_children(
+ Operation *op,
+ SQLHDBC dbh,
+ struct berval *dn,
+ unsigned long *nchildren )
+{
+ backsql_info *bi = (backsql_info *)op->o_bd->be_private;
+ SQLHSTMT sth = SQL_NULL_HSTMT;
+ BACKSQL_ROW_NTS row;
+ RETCODE rc;
+ int res = LDAP_SUCCESS;
+
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_count_children(): dn=\"%s\"\n",
+ dn->bv_val );
+
+ if ( dn->bv_len > BACKSQL_MAX_DN_LEN ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "backsql_count_children(): DN \"%s\" (%ld bytes) "
+ "exceeds max DN length (%d):\n",
+ dn->bv_val, dn->bv_len, BACKSQL_MAX_DN_LEN );
+ return LDAP_OTHER;
+ }
+
+ /* begin TimesTen */
+ assert( bi->sql_has_children_query != NULL );
+ Debug(LDAP_DEBUG_TRACE, "children id query \"%s\"\n",
+ bi->sql_has_children_query );
+ rc = backsql_Prepare( dbh, &sth, bi->sql_has_children_query, 0 );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "backsql_count_children(): error preparing SQL:\n%s",
+ bi->sql_has_children_query );
+ backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+ return LDAP_OTHER;
+ }
+
+ rc = backsql_BindParamBerVal( sth, 1, SQL_PARAM_INPUT, dn );
+ if ( rc != SQL_SUCCESS) {
+ /* end TimesTen */
+ Debug( LDAP_DEBUG_TRACE, "backsql_count_children(): "
+ "error binding dn=\"%s\" parameter:\n",
+ dn->bv_val );
+ backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+ return LDAP_OTHER;
+ }
+
+ rc = SQLExecute( sth );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_count_children(): "
+ "error executing query (\"%s\", \"%s\"):\n",
+ bi->sql_has_children_query, dn->bv_val );
+ backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+ return LDAP_OTHER;
+ }
+
+ backsql_BindRowAsStrings_x( sth, &row, op->o_tmpmemctx );
+
+ rc = SQLFetch( sth );
+ if ( BACKSQL_SUCCESS( rc ) ) {
+ char *end;
+
+ *nchildren = strtol( row.cols[ 0 ], &end, 0 );
+ if ( end == row.cols[ 0 ] ) {
+ res = LDAP_OTHER;
+
+ } else {
+ switch ( end[ 0 ] ) {
+ case '\0':
+ break;
+
+ case '.': {
+ unsigned long ul;
+
+ /* FIXME: braindead RDBMSes return
+ * a fractional number from COUNT!
+ */
+ if ( lutil_atoul( &ul, end + 1 ) != 0 || ul != 0 ) {
+ res = LDAP_OTHER;
+ }
+ } break;
+
+ default:
+ res = LDAP_OTHER;
+ }
+ }
+
+ } else {
+ res = LDAP_OTHER;
+ }
+ backsql_FreeRow_x( &row, op->o_tmpmemctx );
+
+ SQLFreeStmt( sth, SQL_DROP );
+
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_count_children(): %lu\n",
+ *nchildren );
+
+ return res;
+}
+
+int
+backsql_has_children(
+ Operation *op,
+ SQLHDBC dbh,
+ struct berval *dn )
+{
+ unsigned long nchildren;
+ int rc;
+
+ rc = backsql_count_children( op, dbh, dn, &nchildren );
+
+ if ( rc == LDAP_SUCCESS ) {
+ return nchildren > 0 ? LDAP_COMPARE_TRUE : LDAP_COMPARE_FALSE;
+ }
+
+ return rc;
+}
+
+static int
+backsql_get_attr_vals( void *v_at, void *v_bsi )
+{
+ backsql_at_map_rec *at = v_at;
+ backsql_srch_info *bsi = v_bsi;
+ backsql_info *bi;
+ RETCODE rc;
+ SQLHSTMT sth = SQL_NULL_HSTMT;
+ BACKSQL_ROW_NTS row;
+ unsigned long i,
+ k = 0,
+ oldcount = 0,
+ res = 0;
+#ifdef BACKSQL_COUNTQUERY
+ unsigned count,
+ j,
+ append = 0;
+ SQLLEN countsize = sizeof( count );
+ Attribute *attr = NULL;
+
+ slap_mr_normalize_func *normfunc = NULL;
+#endif /* BACKSQL_COUNTQUERY */
+#ifdef BACKSQL_PRETTY_VALIDATE
+ slap_syntax_validate_func *validate = NULL;
+ slap_syntax_transform_func *pretty = NULL;
+#endif /* BACKSQL_PRETTY_VALIDATE */
+
+ assert( at != NULL );
+ assert( bsi != NULL );
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_get_attr_vals(): "
+ "oc=\"%s\" attr=\"%s\" keyval=" BACKSQL_IDFMT "\n",
+ BACKSQL_OC_NAME( bsi->bsi_oc ), at->bam_ad->ad_cname.bv_val,
+ BACKSQL_IDARG(bsi->bsi_c_eid->eid_keyval) );
+
+ bi = (backsql_info *)bsi->bsi_op->o_bd->be_private;
+
+#ifdef BACKSQL_PRETTY_VALIDATE
+ validate = at->bam_true_ad->ad_type->sat_syntax->ssyn_validate;
+ pretty = at->bam_true_ad->ad_type->sat_syntax->ssyn_pretty;
+
+ if ( validate == NULL && pretty == NULL ) {
+ return 1;
+ }
+#endif /* BACKSQL_PRETTY_VALIDATE */
+
+#ifdef BACKSQL_COUNTQUERY
+ if ( at->bam_true_ad->ad_type->sat_equality ) {
+ normfunc = at->bam_true_ad->ad_type->sat_equality->smr_normalize;
+ }
+
+ /* Count how many rows will be returned. This avoids memory
+ * fragmentation that can result from loading the values in
+ * one by one and using realloc()
+ */
+ rc = backsql_Prepare( bsi->bsi_dbh, &sth, at->bam_countquery, 0 );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_get_attr_vals(): "
+ "error preparing count query: %s\n",
+ at->bam_countquery );
+ backsql_PrintErrors( bi->sql_db_env, bsi->bsi_dbh, sth, rc );
+ return 1;
+ }
+
+ rc = backsql_BindParamID( sth, 1, SQL_PARAM_INPUT,
+ &bsi->bsi_c_eid->eid_keyval );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_get_attr_vals(): "
+ "error binding key value parameter\n" );
+ SQLFreeStmt( sth, SQL_DROP );
+ return 1;
+ }
+
+ rc = SQLExecute( sth );
+ if ( ! BACKSQL_SUCCESS( rc ) ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_get_attr_vals(): "
+ "error executing attribute count query '%s'\n",
+ at->bam_countquery );
+ backsql_PrintErrors( bi->sql_db_env, bsi->bsi_dbh, sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+ return 1;
+ }
+
+ SQLBindCol( sth, (SQLUSMALLINT)1, SQL_C_LONG,
+ (SQLPOINTER)&count,
+ (SQLINTEGER)sizeof( count ),
+ &countsize );
+
+ rc = SQLFetch( sth );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_get_attr_vals(): "
+ "error fetch results of count query: %s\n",
+ at->bam_countquery );
+ backsql_PrintErrors( bi->sql_db_env, bsi->bsi_dbh, sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+ return 1;
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "backsql_get_attr_vals(): "
+ "number of values in query: %u\n", count );
+ SQLFreeStmt( sth, SQL_DROP );
+ if ( count == 0 ) {
+ return 1;
+ }
+
+ attr = attr_find( bsi->bsi_e->e_attrs, at->bam_true_ad );
+ if ( attr != NULL ) {
+ BerVarray tmp;
+
+ if ( attr->a_vals != NULL ) {
+ oldcount = attr->a_numvals;
+ }
+
+ tmp = ch_realloc( attr->a_vals, ( oldcount + count + 1 ) * sizeof( struct berval ) );
+ if ( tmp == NULL ) {
+ return 1;
+ }
+ attr->a_vals = tmp;
+ memset( &attr->a_vals[ oldcount ], 0, ( count + 1 ) * sizeof( struct berval ) );
+
+ if ( normfunc ) {
+ tmp = ch_realloc( attr->a_nvals, ( oldcount + count + 1 ) * sizeof( struct berval ) );
+ if ( tmp == NULL ) {
+ return 1;
+ }
+ attr->a_nvals = tmp;
+ memset( &attr->a_nvals[ oldcount ], 0, ( count + 1 ) * sizeof( struct berval ) );
+
+ } else {
+ attr->a_nvals = attr->a_vals;
+ }
+ attr->a_numvals += count;
+
+ } else {
+ append = 1;
+
+ /* Make space for the array of values */
+ attr = attr_alloc( at->bam_true_ad );
+ attr->a_numvals = count;
+ attr->a_vals = ch_calloc( count + 1, sizeof( struct berval ) );
+ if ( attr->a_vals == NULL ) {
+ Debug( LDAP_DEBUG_TRACE, "Out of memory!\n" );
+ ch_free( attr );
+ return 1;
+ }
+ if ( normfunc ) {
+ attr->a_nvals = ch_calloc( count + 1, sizeof( struct berval ) );
+ if ( attr->a_nvals == NULL ) {
+ ch_free( attr->a_vals );
+ ch_free( attr );
+ return 1;
+
+ }
+
+ } else {
+ attr->a_nvals = attr->a_vals;
+ }
+ }
+#endif /* BACKSQL_COUNTQUERY */
+
+ rc = backsql_Prepare( bsi->bsi_dbh, &sth, at->bam_query, 0 );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_get_attr_vals(): "
+ "error preparing query: %s\n", at->bam_query );
+ backsql_PrintErrors( bi->sql_db_env, bsi->bsi_dbh, sth, rc );
+#ifdef BACKSQL_COUNTQUERY
+ if ( append ) {
+ attr_free( attr );
+ }
+#endif /* BACKSQL_COUNTQUERY */
+ return 1;
+ }
+
+ rc = backsql_BindParamID( sth, 1, SQL_PARAM_INPUT,
+ &bsi->bsi_c_eid->eid_keyval );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_get_attr_vals(): "
+ "error binding key value parameter\n" );
+#ifdef BACKSQL_COUNTQUERY
+ if ( append ) {
+ attr_free( attr );
+ }
+#endif /* BACKSQL_COUNTQUERY */
+ return 1;
+ }
+
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE, "backsql_get_attr_vals(): "
+ "query=\"%s\" keyval=" BACKSQL_IDFMT "\n", at->bam_query,
+ BACKSQL_IDARG(bsi->bsi_c_eid->eid_keyval) );
+#endif /* BACKSQL_TRACE */
+
+ rc = SQLExecute( sth );
+ if ( ! BACKSQL_SUCCESS( rc ) ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_get_attr_vals(): "
+ "error executing attribute query \"%s\"\n",
+ at->bam_query );
+ backsql_PrintErrors( bi->sql_db_env, bsi->bsi_dbh, sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+#ifdef BACKSQL_COUNTQUERY
+ if ( append ) {
+ attr_free( attr );
+ }
+#endif /* BACKSQL_COUNTQUERY */
+ return 1;
+ }
+
+ backsql_BindRowAsStrings_x( sth, &row, bsi->bsi_op->o_tmpmemctx );
+#ifdef BACKSQL_COUNTQUERY
+ j = oldcount;
+#endif /* BACKSQL_COUNTQUERY */
+ for ( rc = SQLFetch( sth ), k = 0;
+ BACKSQL_SUCCESS( rc );
+ rc = SQLFetch( sth ), k++ )
+ {
+ for ( i = 0; i < (unsigned long)row.ncols; i++ ) {
+
+ if ( row.value_len[ i ] > 0 ) {
+ struct berval bv;
+ int retval;
+#ifdef BACKSQL_TRACE
+ AttributeDescription *ad = NULL;
+ const char *text;
+
+ retval = slap_bv2ad( &row.col_names[ i ], &ad, &text );
+ if ( retval != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY,
+ "==>backsql_get_attr_vals(\"%s\"): "
+ "unable to find AttributeDescription %s "
+ "in schema (%d)\n",
+ bsi->bsi_e->e_name.bv_val,
+ row.col_names[ i ].bv_val, retval );
+ res = 1;
+ goto done;
+ }
+
+ if ( ad != at->bam_ad ) {
+ Debug( LDAP_DEBUG_ANY,
+ "==>backsql_get_attr_vals(\"%s\"): "
+ "column name %s differs from "
+ "AttributeDescription %s\n",
+ bsi->bsi_e->e_name.bv_val,
+ ad->ad_cname.bv_val,
+ at->bam_ad->ad_cname.bv_val );
+ res = 1;
+ goto done;
+ }
+#endif /* BACKSQL_TRACE */
+
+ /* ITS#3386, ITS#3113 - 20070308
+ * If a binary is fetched?
+ * must use the actual size read
+ * from the database.
+ */
+ if ( BACKSQL_IS_BINARY( row.col_type[ i ] ) ) {
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_ANY,
+ "==>backsql_get_attr_vals(\"%s\"): "
+ "column name %s: data is binary; "
+ "using database size %ld\n",
+ bsi->bsi_e->e_name.bv_val,
+ ad->ad_cname.bv_val,
+ row.value_len[ i ] );
+#endif /* BACKSQL_TRACE */
+ bv.bv_val = row.cols[ i ];
+ bv.bv_len = row.value_len[ i ];
+
+ } else {
+ ber_str2bv( row.cols[ i ], 0, 0, &bv );
+ }
+
+#ifdef BACKSQL_PRETTY_VALIDATE
+ if ( pretty ) {
+ struct berval pbv;
+
+ retval = pretty( at->bam_true_ad->ad_type->sat_syntax,
+ &bv, &pbv, bsi->bsi_op->o_tmpmemctx );
+ bv = pbv;
+
+ } else {
+ retval = validate( at->bam_true_ad->ad_type->sat_syntax,
+ &bv );
+ }
+
+ if ( retval != LDAP_SUCCESS ) {
+ /* FIXME: we're ignoring invalid values,
+ * but we're accepting the attributes;
+ * should we fail at all? */
+ Debug(LDAP_DEBUG_TRACE,
+ "==>backsql_get_attr_vals(\"%s\"): " "unable to %s value #%lu " "of AttributeDescription %s (%d)\n",
+ bsi->bsi_e->e_name.bv_val,
+ pretty ? "prettify" : "validate",
+ k - oldcount,
+ at->bam_ad->ad_cname.bv_val,
+ retval );
+ continue;
+ }
+#endif /* BACKSQL_PRETTY_VALIDATE */
+
+#ifndef BACKSQL_COUNTQUERY
+ (void)backsql_entry_addattr( bsi->bsi_e,
+ at->bam_true_ad, &bv,
+ bsi->bsi_op->o_tmpmemctx );
+
+#else /* BACKSQL_COUNTQUERY */
+ if ( normfunc ) {
+ struct berval nbv;
+
+ retval = (*normfunc)( SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX,
+ at->bam_true_ad->ad_type->sat_syntax,
+ at->bam_true_ad->ad_type->sat_equality,
+ &bv, &nbv,
+ bsi->bsi_op->o_tmpmemctx );
+
+ if ( retval != LDAP_SUCCESS ) {
+ /* FIXME: we're ignoring invalid values,
+ * but we're accepting the attributes;
+ * should we fail at all? */
+ Debug(LDAP_DEBUG_TRACE,
+ "==>backsql_get_attr_vals(\"%s\"): " "unable to normalize value #%lu " "of AttributeDescription %s (%d)\n",
+ bsi->bsi_e->e_name.bv_val,
+ k - oldcount,
+ at->bam_ad->ad_cname.bv_val,
+ retval );
+
+#ifdef BACKSQL_PRETTY_VALIDATE
+ if ( pretty ) {
+ bsi->bsi_op->o_tmpfree( bv.bv_val,
+ bsi->bsi_op->o_tmpmemctx );
+ }
+#endif /* BACKSQL_PRETTY_VALIDATE */
+
+ continue;
+ }
+ ber_dupbv( &attr->a_nvals[ j ], &nbv );
+ bsi->bsi_op->o_tmpfree( nbv.bv_val,
+ bsi->bsi_op->o_tmpmemctx );
+ }
+
+ ber_dupbv( &attr->a_vals[ j ], &bv );
+
+ assert( j < oldcount + count );
+ j++;
+#endif /* BACKSQL_COUNTQUERY */
+
+#ifdef BACKSQL_PRETTY_VALIDATE
+ if ( pretty ) {
+ bsi->bsi_op->o_tmpfree( bv.bv_val,
+ bsi->bsi_op->o_tmpmemctx );
+ }
+#endif /* BACKSQL_PRETTY_VALIDATE */
+
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE, "prec=%d\n",
+ (int)row.col_prec[ i ] );
+
+ } else {
+ Debug( LDAP_DEBUG_TRACE, "NULL value "
+ "in this row for attribute \"%s\"\n",
+ row.col_names[ i ].bv_val );
+#endif /* BACKSQL_TRACE */
+ }
+ }
+ }
+
+#ifdef BACKSQL_COUNTQUERY
+ if ( BER_BVISNULL( &attr->a_vals[ 0 ] ) ) {
+ /* don't leave around attributes with no values */
+ attr_free( attr );
+
+ } else if ( append ) {
+ Attribute **ap;
+
+ for ( ap = &bsi->bsi_e->e_attrs; (*ap) != NULL; ap = &(*ap)->a_next )
+ /* goto last */ ;
+ *ap = attr;
+ }
+#endif /* BACKSQL_COUNTQUERY */
+
+ SQLFreeStmt( sth, SQL_DROP );
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_get_attr_vals()\n" );
+
+ if ( at->bam_next ) {
+ res = backsql_get_attr_vals( at->bam_next, v_bsi );
+ } else {
+ res = 1;
+ }
+
+#ifdef BACKSQL_TRACE
+done:;
+#endif /* BACKSQL_TRACE */
+ backsql_FreeRow_x( &row, bsi->bsi_op->o_tmpmemctx );
+
+ return res;
+}
+
+int
+backsql_id2entry( backsql_srch_info *bsi, backsql_entryID *eid )
+{
+ Operation *op = bsi->bsi_op;
+ backsql_info *bi = (backsql_info *)op->o_bd->be_private;
+ int i;
+ int rc;
+
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_id2entry()\n" );
+
+ assert( bsi->bsi_e != NULL );
+
+ memset( bsi->bsi_e, 0, sizeof( Entry ) );
+
+ if ( bi->sql_baseObject && BACKSQL_IS_BASEOBJECT_ID( &eid->eid_id ) ) {
+ (void)entry_dup2( bsi->bsi_e, bi->sql_baseObject );
+ goto done;
+ }
+
+ bsi->bsi_e->e_attrs = NULL;
+ bsi->bsi_e->e_private = NULL;
+
+ if ( eid->eid_oc == NULL ) {
+ eid->eid_oc = backsql_id2oc( bsi->bsi_op->o_bd->be_private,
+ eid->eid_oc_id );
+ if ( eid->eid_oc == NULL ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "backsql_id2entry(): unable to fetch objectClass with id=" BACKSQL_IDNUMFMT " for entry id=" BACKSQL_IDFMT " dn=\"%s\"\n",
+ eid->eid_oc_id, BACKSQL_IDARG(eid->eid_id),
+ eid->eid_dn.bv_val );
+ return LDAP_OTHER;
+ }
+ }
+ bsi->bsi_oc = eid->eid_oc;
+ bsi->bsi_c_eid = eid;
+
+ ber_dupbv_x( &bsi->bsi_e->e_name, &eid->eid_dn, op->o_tmpmemctx );
+ ber_dupbv_x( &bsi->bsi_e->e_nname, &eid->eid_ndn, op->o_tmpmemctx );
+
+#ifndef BACKSQL_ARBITRARY_KEY
+ /* FIXME: unused */
+ bsi->bsi_e->e_id = eid->eid_id;
+#endif /* ! BACKSQL_ARBITRARY_KEY */
+
+ rc = attr_merge_normalize_one( bsi->bsi_e,
+ slap_schema.si_ad_objectClass,
+ &bsi->bsi_oc->bom_oc->soc_cname,
+ bsi->bsi_op->o_tmpmemctx );
+ if ( rc != LDAP_SUCCESS ) {
+ backsql_entry_clean( op, bsi->bsi_e );
+ return rc;
+ }
+
+ if ( bsi->bsi_attrs == NULL || ( bsi->bsi_flags & BSQL_SF_ALL_USER ) )
+ {
+ Debug( LDAP_DEBUG_TRACE, "backsql_id2entry(): "
+ "retrieving all attributes\n" );
+ ldap_avl_apply( bsi->bsi_oc->bom_attrs, backsql_get_attr_vals,
+ bsi, 0, AVL_INORDER );
+
+ } else {
+ Debug( LDAP_DEBUG_TRACE, "backsql_id2entry(): "
+ "custom attribute list\n" );
+ for ( i = 0; !BER_BVISNULL( &bsi->bsi_attrs[ i ].an_name ); i++ ) {
+ backsql_at_map_rec **vat;
+ AttributeName *an = &bsi->bsi_attrs[ i ];
+ int j;
+
+ /* if one of the attributes listed here is
+ * a subtype of another, it must be ignored,
+ * because subtypes are already dealt with
+ * by backsql_supad2at()
+ */
+ for ( j = 0; !BER_BVISNULL( &bsi->bsi_attrs[ j ].an_name ); j++ ) {
+ /* skip self */
+ if ( j == i ) {
+ continue;
+ }
+
+ /* skip subtypes */
+ if ( is_at_subtype( an->an_desc->ad_type,
+ bsi->bsi_attrs[ j ].an_desc->ad_type ) )
+ {
+ goto next;
+ }
+ }
+
+ rc = backsql_supad2at( bsi->bsi_oc, an->an_desc, &vat );
+ if ( rc != 0 || vat == NULL ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_id2entry(): "
+ "attribute \"%s\" is not defined "
+ "for objectclass \"%s\"\n",
+ an->an_name.bv_val,
+ BACKSQL_OC_NAME( bsi->bsi_oc ) );
+ continue;
+ }
+
+ for ( j = 0; vat[j]; j++ ) {
+ backsql_get_attr_vals( vat[j], bsi );
+ }
+
+ ch_free( vat );
+
+next:;
+ }
+ }
+
+ if ( bsi->bsi_flags & BSQL_SF_RETURN_ENTRYUUID ) {
+ Attribute *a_entryUUID,
+ **ap;
+
+ a_entryUUID = backsql_operational_entryUUID( bi, eid );
+ if ( a_entryUUID != NULL ) {
+ for ( ap = &bsi->bsi_e->e_attrs;
+ *ap;
+ ap = &(*ap)->a_next );
+
+ *ap = a_entryUUID;
+ }
+ }
+
+ if ( ( bsi->bsi_flags & BSQL_SF_ALL_OPER )
+ || an_find( bsi->bsi_attrs, slap_bv_all_operational_attrs )
+ || an_find( bsi->bsi_attrs, &slap_schema.si_ad_structuralObjectClass->ad_cname ) )
+ {
+ ObjectClass *soc = NULL;
+
+ if ( BACKSQL_CHECK_SCHEMA( bi ) ) {
+ Attribute *a;
+ const char *text = NULL;
+ char textbuf[ 1024 ];
+ size_t textlen = sizeof( textbuf );
+ struct berval bv[ 2 ],
+ *nvals;
+ int rc = LDAP_SUCCESS;
+
+ a = attr_find( bsi->bsi_e->e_attrs,
+ slap_schema.si_ad_objectClass );
+ if ( a != NULL ) {
+ nvals = a->a_nvals;
+
+ } else {
+ bv[ 0 ] = bsi->bsi_oc->bom_oc->soc_cname;
+ BER_BVZERO( &bv[ 1 ] );
+ nvals = bv;
+ }
+
+ rc = structural_class( nvals, &soc, NULL,
+ &text, textbuf, textlen, op->o_tmpmemctx );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_id2entry(%s): "
+ "structural_class() failed %d (%s)\n",
+ bsi->bsi_e->e_name.bv_val,
+ rc, text ? text : "" );
+ backsql_entry_clean( op, bsi->bsi_e );
+ return rc;
+ }
+
+ if ( !bvmatch( &soc->soc_cname, &bsi->bsi_oc->bom_oc->soc_cname ) ) {
+ if ( !is_object_subclass( bsi->bsi_oc->bom_oc, soc ) ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_id2entry(%s): "
+ "computed structuralObjectClass %s "
+ "does not match objectClass %s associated "
+ "to entry\n",
+ bsi->bsi_e->e_name.bv_val, soc->soc_cname.bv_val,
+ bsi->bsi_oc->bom_oc->soc_cname.bv_val );
+ backsql_entry_clean( op, bsi->bsi_e );
+ return rc;
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "backsql_id2entry(%s): "
+ "computed structuralObjectClass %s "
+ "is subclass of objectClass %s associated "
+ "to entry\n",
+ bsi->bsi_e->e_name.bv_val, soc->soc_cname.bv_val,
+ bsi->bsi_oc->bom_oc->soc_cname.bv_val );
+ }
+
+ } else {
+ soc = bsi->bsi_oc->bom_oc;
+ }
+
+ rc = attr_merge_normalize_one( bsi->bsi_e,
+ slap_schema.si_ad_structuralObjectClass,
+ &soc->soc_cname,
+ bsi->bsi_op->o_tmpmemctx );
+ if ( rc != LDAP_SUCCESS ) {
+ backsql_entry_clean( op, bsi->bsi_e );
+ return rc;
+ }
+ }
+
+done:;
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_id2entry()\n" );
+
+ return LDAP_SUCCESS;
+}
+
diff --git a/servers/slapd/back-sql/init.c b/servers/slapd/back-sql/init.c
new file mode 100644
index 0000000..1b45f8f
--- /dev/null
+++ b/servers/slapd/back-sql/init.c
@@ -0,0 +1,661 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1999 Dmitry Kovalev.
+ * Portions Copyright 2002 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Dmitry Kovalev for inclusion
+ * by OpenLDAP Software. Additional significant contributors include
+ * Pierangelo Masarati.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <sys/types.h>
+#include "ac/string.h"
+
+#include "slap.h"
+#include "slap-config.h"
+#include "proto-sql.h"
+
+int
+sql_back_initialize(
+ BackendInfo *bi )
+{
+ static char *controls[] = {
+ LDAP_CONTROL_ASSERT,
+ LDAP_CONTROL_MANAGEDSAIT,
+ LDAP_CONTROL_NOOP,
+#ifdef SLAP_CONTROL_X_TREE_DELETE
+ SLAP_CONTROL_X_TREE_DELETE,
+#endif /* SLAP_CONTROL_X_TREE_DELETE */
+#ifndef BACKSQL_ARBITRARY_KEY
+ LDAP_CONTROL_PAGEDRESULTS,
+#endif /* ! BACKSQL_ARBITRARY_KEY */
+ NULL
+ };
+ int rc;
+
+ bi->bi_controls = controls;
+
+ bi->bi_flags |=
+#if 0
+ SLAP_BFLAG_INCREMENT |
+#endif
+ SLAP_BFLAG_REFERRALS;
+
+ Debug( LDAP_DEBUG_TRACE,"==>sql_back_initialize()\n" );
+
+ bi->bi_db_init = backsql_db_init;
+ bi->bi_db_config = config_generic_wrapper;
+ bi->bi_db_open = backsql_db_open;
+ bi->bi_db_close = backsql_db_close;
+ bi->bi_db_destroy = backsql_db_destroy;
+
+ bi->bi_op_abandon = 0;
+ bi->bi_op_compare = backsql_compare;
+ bi->bi_op_bind = backsql_bind;
+ bi->bi_op_unbind = 0;
+ bi->bi_op_search = backsql_search;
+ bi->bi_op_modify = backsql_modify;
+ bi->bi_op_modrdn = backsql_modrdn;
+ bi->bi_op_add = backsql_add;
+ bi->bi_op_delete = backsql_delete;
+
+ bi->bi_chk_referrals = 0;
+ bi->bi_operational = backsql_operational;
+ bi->bi_entry_get_rw = backsql_entry_get;
+ bi->bi_entry_release_rw = backsql_entry_release;
+
+ bi->bi_connection_init = 0;
+
+ rc = backsql_init_cf( bi );
+ Debug( LDAP_DEBUG_TRACE,"<==sql_back_initialize()\n" );
+ return rc;
+}
+
+int
+backsql_destroy(
+ BackendInfo *bi )
+{
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_destroy()\n" );
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_destroy()\n" );
+ return 0;
+}
+
+int
+backsql_db_init(
+ BackendDB *bd,
+ ConfigReply *cr )
+{
+ backsql_info *bi;
+ int rc = 0;
+
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_db_init()\n" );
+
+ bi = (backsql_info *)ch_calloc( 1, sizeof( backsql_info ) );
+ ldap_pvt_thread_mutex_init( &bi->sql_dbconn_mutex );
+ ldap_pvt_thread_mutex_init( &bi->sql_schema_mutex );
+
+ if ( backsql_init_db_env( bi ) != SQL_SUCCESS ) {
+ rc = -1;
+ }
+
+ bd->be_private = bi;
+ bd->be_cf_ocs = bd->bd_info->bi_cf_ocs;
+
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_db_init()\n" );
+
+ return rc;
+}
+
+int
+backsql_db_destroy(
+ BackendDB *bd,
+ ConfigReply *cr )
+{
+ backsql_info *bi = (backsql_info*)bd->be_private;
+
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_db_destroy()\n" );
+
+ backsql_free_db_env( bi );
+ ldap_pvt_thread_mutex_destroy( &bi->sql_dbconn_mutex );
+ backsql_destroy_schema_map( bi );
+ ldap_pvt_thread_mutex_destroy( &bi->sql_schema_mutex );
+
+ if ( bi->sql_dbname ) {
+ ch_free( bi->sql_dbname );
+ }
+ if ( bi->sql_dbuser ) {
+ ch_free( bi->sql_dbuser );
+ }
+ if ( bi->sql_dbpasswd ) {
+ ch_free( bi->sql_dbpasswd );
+ }
+ if ( bi->sql_dbhost ) {
+ ch_free( bi->sql_dbhost );
+ }
+ if ( bi->sql_upper_func.bv_val ) {
+ ch_free( bi->sql_upper_func.bv_val );
+ ch_free( bi->sql_upper_func_open.bv_val );
+ ch_free( bi->sql_upper_func_close.bv_val );
+ }
+ if ( bi->sql_concat_func ) {
+ ber_bvarray_free( bi->sql_concat_func );
+ }
+ if ( !BER_BVISNULL( &bi->sql_strcast_func ) ) {
+ ch_free( bi->sql_strcast_func.bv_val );
+ }
+ if ( !BER_BVISNULL( &bi->sql_children_cond ) ) {
+ ch_free( bi->sql_children_cond.bv_val );
+ }
+ if ( !BER_BVISNULL( &bi->sql_dn_match_cond ) ) {
+ ch_free( bi->sql_dn_match_cond.bv_val );
+ }
+ if ( !BER_BVISNULL( &bi->sql_subtree_cond ) ) {
+ ch_free( bi->sql_subtree_cond.bv_val );
+ }
+ if ( !BER_BVISNULL( &bi->sql_dn_oc_aliasing ) ) {
+ ch_free( bi->sql_dn_oc_aliasing.bv_val );
+ }
+ if ( bi->sql_oc_query ) {
+ ch_free( bi->sql_oc_query );
+ }
+ if ( bi->sql_at_query ) {
+ ch_free( bi->sql_at_query );
+ }
+ if ( bi->sql_id_query ) {
+ ch_free( bi->sql_id_query );
+ }
+ if ( bi->sql_has_children_query ) {
+ ch_free( bi->sql_has_children_query );
+ }
+ if ( bi->sql_insentry_stmt ) {
+ ch_free( bi->sql_insentry_stmt );
+ }
+ if ( bi->sql_delentry_stmt ) {
+ ch_free( bi->sql_delentry_stmt );
+ }
+ if ( bi->sql_renentry_stmt ) {
+ ch_free( bi->sql_renentry_stmt );
+ }
+ if ( bi->sql_delobjclasses_stmt ) {
+ ch_free( bi->sql_delobjclasses_stmt );
+ }
+ if ( !BER_BVISNULL( &bi->sql_aliasing ) ) {
+ ch_free( bi->sql_aliasing.bv_val );
+ }
+ if ( !BER_BVISNULL( &bi->sql_aliasing_quote ) ) {
+ ch_free( bi->sql_aliasing_quote.bv_val );
+ }
+
+ if ( bi->sql_anlist ) {
+ int i;
+
+ for ( i = 0; !BER_BVISNULL( &bi->sql_anlist[ i ].an_name ); i++ )
+ {
+ ch_free( bi->sql_anlist[ i ].an_name.bv_val );
+ }
+ ch_free( bi->sql_anlist );
+ }
+
+ if ( bi->sql_baseObject ) {
+ entry_free( bi->sql_baseObject );
+ }
+
+ ch_free( bi );
+
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_db_destroy()\n" );
+ return 0;
+}
+
+int
+backsql_db_open(
+ BackendDB *bd,
+ ConfigReply *cr )
+{
+ backsql_info *bi = (backsql_info*)bd->be_private;
+ struct berbuf bb = BB_NULL;
+
+ Connection conn = { 0 };
+ OperationBuffer opbuf;
+ Operation* op;
+ SQLHDBC dbh = SQL_NULL_HDBC;
+ void *thrctx = ldap_pvt_thread_pool_context();
+
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_db_open(): "
+ "testing RDBMS connection\n" );
+ if ( bi->sql_dbname == NULL ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): "
+ "datasource name not specified "
+ "(use \"dbname\" directive in slapd.conf)\n" );
+ return 1;
+ }
+
+ if ( bi->sql_concat_func == NULL ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): "
+ "concat func not specified (use \"concat_pattern\" "
+ "directive in slapd.conf)\n" );
+
+ if ( backsql_split_pattern( backsql_def_concat_func,
+ &bi->sql_concat_func, 2 ) ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): "
+ "unable to parse pattern \"%s\"",
+ backsql_def_concat_func );
+ return 1;
+ }
+ }
+
+ /*
+ * see back-sql.h for default values
+ */
+ if ( BER_BVISNULL( &bi->sql_aliasing ) ) {
+ ber_str2bv( BACKSQL_ALIASING,
+ STRLENOF( BACKSQL_ALIASING ),
+ 1, &bi->sql_aliasing );
+ }
+
+ if ( BER_BVISNULL( &bi->sql_aliasing_quote ) ) {
+ ber_str2bv( BACKSQL_ALIASING_QUOTE,
+ STRLENOF( BACKSQL_ALIASING_QUOTE ),
+ 1, &bi->sql_aliasing_quote );
+ }
+
+ /*
+ * Prepare cast string as required
+ */
+ if ( bi->sql_upper_func.bv_val ) {
+ char buf[1024];
+
+ if ( BACKSQL_UPPER_NEEDS_CAST( bi ) ) {
+ snprintf( buf, sizeof( buf ),
+ "%s(cast (" /* ? as varchar(%d))) */ ,
+ bi->sql_upper_func.bv_val );
+ ber_str2bv( buf, 0, 1, &bi->sql_upper_func_open );
+
+ snprintf( buf, sizeof( buf ),
+ /* (cast(? */ " as varchar(%d)))",
+ BACKSQL_MAX_DN_LEN );
+ ber_str2bv( buf, 0, 1, &bi->sql_upper_func_close );
+
+ } else {
+ snprintf( buf, sizeof( buf ), "%s(" /* ?) */ ,
+ bi->sql_upper_func.bv_val );
+ ber_str2bv( buf, 0, 1, &bi->sql_upper_func_open );
+
+ ber_str2bv( /* (? */ ")", 0, 1, &bi->sql_upper_func_close );
+ }
+ }
+
+ /* normalize filter values only if necessary */
+ bi->sql_caseIgnoreMatch = mr_find( "caseIgnoreMatch" );
+ assert( bi->sql_caseIgnoreMatch != NULL );
+
+ bi->sql_telephoneNumberMatch = mr_find( "telephoneNumberMatch" );
+ assert( bi->sql_telephoneNumberMatch != NULL );
+
+ if ( bi->sql_dbuser == NULL ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): "
+ "user name not specified "
+ "(use \"dbuser\" directive in slapd.conf)\n" );
+ return 1;
+ }
+
+ if ( BER_BVISNULL( &bi->sql_subtree_cond ) ) {
+ /*
+ * Prepare concat function for subtree search condition
+ */
+ struct berval concat;
+ struct berval values[] = {
+ BER_BVC( "'%'" ),
+ BER_BVC( "?" ),
+ BER_BVNULL
+ };
+ struct berbuf bb = BB_NULL;
+
+ Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): "
+ "subtree search SQL condition not specified "
+ "(use \"subtree_cond\" directive in slapd.conf); "
+ "preparing default\n" );
+
+ if ( backsql_prepare_pattern( bi->sql_concat_func, values,
+ &concat ) ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): "
+ "unable to prepare CONCAT pattern for subtree search" );
+ return 1;
+ }
+
+ if ( bi->sql_upper_func.bv_val ) {
+
+ /*
+ * UPPER(ldap_entries.dn) LIKE UPPER(CONCAT('%',?))
+ */
+
+ backsql_strfcat_x( &bb, NULL, "blbbb",
+ &bi->sql_upper_func,
+ (ber_len_t)STRLENOF( "(ldap_entries.dn) LIKE " ),
+ "(ldap_entries.dn) LIKE ",
+ &bi->sql_upper_func_open,
+ &concat,
+ &bi->sql_upper_func_close );
+
+ } else {
+
+ /*
+ * ldap_entries.dn LIKE CONCAT('%',?)
+ */
+
+ backsql_strfcat_x( &bb, NULL, "lb",
+ (ber_len_t)STRLENOF( "ldap_entries.dn LIKE " ),
+ "ldap_entries.dn LIKE ",
+ &concat );
+ }
+
+ ch_free( concat.bv_val );
+
+ bi->sql_subtree_cond = bb.bb_val;
+
+ Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): "
+ "setting \"%s\" as default \"subtree_cond\"\n",
+ bi->sql_subtree_cond.bv_val );
+ }
+
+ if ( bi->sql_children_cond.bv_val == NULL ) {
+ /*
+ * Prepare concat function for children search condition
+ */
+ struct berval concat;
+ struct berval values[] = {
+ BER_BVC( "'%,'" ),
+ BER_BVC( "?" ),
+ BER_BVNULL
+ };
+ struct berbuf bb = BB_NULL;
+
+ Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): "
+ "children search SQL condition not specified "
+ "(use \"children_cond\" directive in slapd.conf); "
+ "preparing default\n" );
+
+ if ( backsql_prepare_pattern( bi->sql_concat_func, values,
+ &concat ) ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): "
+ "unable to prepare CONCAT pattern for children search" );
+ return 1;
+ }
+
+ if ( bi->sql_upper_func.bv_val ) {
+
+ /*
+ * UPPER(ldap_entries.dn) LIKE UPPER(CONCAT('%,',?))
+ */
+
+ backsql_strfcat_x( &bb, NULL, "blbbb",
+ &bi->sql_upper_func,
+ (ber_len_t)STRLENOF( "(ldap_entries.dn) LIKE " ),
+ "(ldap_entries.dn) LIKE ",
+ &bi->sql_upper_func_open,
+ &concat,
+ &bi->sql_upper_func_close );
+
+ } else {
+
+ /*
+ * ldap_entries.dn LIKE CONCAT('%,',?)
+ */
+
+ backsql_strfcat_x( &bb, NULL, "lb",
+ (ber_len_t)STRLENOF( "ldap_entries.dn LIKE " ),
+ "ldap_entries.dn LIKE ",
+ &concat );
+ }
+
+ ch_free( concat.bv_val );
+
+ bi->sql_children_cond = bb.bb_val;
+
+ Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): "
+ "setting \"%s\" as default \"children_cond\"\n",
+ bi->sql_children_cond.bv_val );
+ }
+
+ if ( bi->sql_dn_match_cond.bv_val == NULL ) {
+ /*
+ * Prepare concat function for dn match search condition
+ */
+ struct berbuf bb = BB_NULL;
+
+ Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): "
+ "DN match search SQL condition not specified "
+ "(use \"dn_match_cond\" directive in slapd.conf); "
+ "preparing default\n" );
+
+ if ( bi->sql_upper_func.bv_val ) {
+
+ /*
+ * UPPER(ldap_entries.dn)=?
+ */
+
+ backsql_strfcat_x( &bb, NULL, "blbcb",
+ &bi->sql_upper_func,
+ (ber_len_t)STRLENOF( "(ldap_entries.dn)=" ),
+ "(ldap_entries.dn)=",
+ &bi->sql_upper_func_open,
+ '?',
+ &bi->sql_upper_func_close );
+
+ } else {
+
+ /*
+ * ldap_entries.dn=?
+ */
+
+ backsql_strfcat_x( &bb, NULL, "l",
+ (ber_len_t)STRLENOF( "ldap_entries.dn=?" ),
+ "ldap_entries.dn=?" );
+ }
+
+ bi->sql_dn_match_cond = bb.bb_val;
+
+ Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): "
+ "setting \"%s\" as default \"dn_match_cond\"\n",
+ bi->sql_dn_match_cond.bv_val );
+ }
+
+ if ( bi->sql_oc_query == NULL ) {
+ if ( BACKSQL_CREATE_NEEDS_SELECT( bi ) ) {
+ bi->sql_oc_query =
+ ch_strdup( backsql_def_needs_select_oc_query );
+
+ } else {
+ bi->sql_oc_query = ch_strdup( backsql_def_oc_query );
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): "
+ "objectclass mapping SQL statement not specified "
+ "(use \"oc_query\" directive in slapd.conf)\n" );
+ Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): "
+ "setting \"%s\" by default\n", bi->sql_oc_query );
+ }
+
+ if ( bi->sql_at_query == NULL ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): "
+ "attribute mapping SQL statement not specified "
+ "(use \"at_query\" directive in slapd.conf)\n" );
+ Debug(LDAP_DEBUG_TRACE, "backsql_db_open(): "
+ "setting \"%s\" by default\n",
+ backsql_def_at_query );
+ bi->sql_at_query = ch_strdup( backsql_def_at_query );
+ }
+
+ if ( bi->sql_insentry_stmt == NULL ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): "
+ "entry insertion SQL statement not specified "
+ "(use \"insentry_stmt\" directive in slapd.conf)\n" );
+ Debug(LDAP_DEBUG_TRACE, "backsql_db_open(): "
+ "setting \"%s\" by default\n",
+ backsql_def_insentry_stmt );
+ bi->sql_insentry_stmt = ch_strdup( backsql_def_insentry_stmt );
+ }
+
+ if ( bi->sql_delentry_stmt == NULL ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): "
+ "entry deletion SQL statement not specified "
+ "(use \"delentry_stmt\" directive in slapd.conf)\n" );
+ Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): "
+ "setting \"%s\" by default\n",
+ backsql_def_delentry_stmt );
+ bi->sql_delentry_stmt = ch_strdup( backsql_def_delentry_stmt );
+ }
+
+ if ( bi->sql_renentry_stmt == NULL ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): "
+ "entry deletion SQL statement not specified "
+ "(use \"renentry_stmt\" directive in slapd.conf)\n" );
+ Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): "
+ "setting \"%s\" by default\n",
+ backsql_def_renentry_stmt );
+ bi->sql_renentry_stmt = ch_strdup( backsql_def_renentry_stmt );
+ }
+
+ if ( bi->sql_delobjclasses_stmt == NULL ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): "
+ "objclasses deletion SQL statement not specified "
+ "(use \"delobjclasses_stmt\" directive in slapd.conf)\n" );
+ Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): "
+ "setting \"%s\" by default\n",
+ backsql_def_delobjclasses_stmt );
+ bi->sql_delobjclasses_stmt = ch_strdup( backsql_def_delobjclasses_stmt );
+ }
+
+ /* This should just be to force schema loading */
+ connection_fake_init2( &conn, &opbuf, thrctx, 0 );
+ op = &opbuf.ob_op;
+ op->o_bd = bd;
+ if ( backsql_get_db_conn( op, &dbh ) != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): "
+ "connection failed, exiting\n" );
+ return 1;
+ }
+ if ( backsql_load_schema_map( bi, dbh ) != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): "
+ "schema mapping failed, exiting\n" );
+ return 1;
+ }
+ if ( backsql_free_db_conn( op, dbh ) != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): "
+ "connection free failed\n" );
+ }
+ if ( !BACKSQL_SCHEMA_LOADED( bi ) ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_db_open(): "
+ "test failed, schema map not loaded - exiting\n" );
+ return 1;
+ }
+
+ /*
+ * Prepare ID selection query
+ */
+ if ( bi->sql_id_query == NULL ) {
+ /* no custom id_query provided */
+ if ( bi->sql_upper_func.bv_val == NULL ) {
+ backsql_strcat_x( &bb, NULL, backsql_id_query, "dn=?", NULL );
+
+ } else {
+ if ( BACKSQL_HAS_LDAPINFO_DN_RU( bi ) ) {
+ backsql_strcat_x( &bb, NULL, backsql_id_query,
+ "dn_ru=?", NULL );
+ } else {
+ if ( BACKSQL_USE_REVERSE_DN( bi ) ) {
+ backsql_strfcat_x( &bb, NULL, "sbl",
+ backsql_id_query,
+ &bi->sql_upper_func,
+ (ber_len_t)STRLENOF( "(dn)=?" ), "(dn)=?" );
+ } else {
+ backsql_strfcat_x( &bb, NULL, "sblbcb",
+ backsql_id_query,
+ &bi->sql_upper_func,
+ (ber_len_t)STRLENOF( "(dn)=" ), "(dn)=",
+ &bi->sql_upper_func_open,
+ '?',
+ &bi->sql_upper_func_close );
+ }
+ }
+ }
+ bi->sql_id_query = bb.bb_val.bv_val;
+ }
+
+ /*
+ * Prepare children count query
+ */
+ BER_BVZERO( &bb.bb_val );
+ bb.bb_len = 0;
+ backsql_strfcat_x( &bb, NULL, "sbsb",
+ "SELECT COUNT(distinct subordinates.id) "
+ "FROM ldap_entries,ldap_entries ",
+ &bi->sql_aliasing, "subordinates "
+ "WHERE subordinates.parent=ldap_entries.id AND ",
+ &bi->sql_dn_match_cond );
+ bi->sql_has_children_query = bb.bb_val.bv_val;
+
+ /*
+ * Prepare DN and objectClass aliasing bit of query
+ */
+ BER_BVZERO( &bb.bb_val );
+ bb.bb_len = 0;
+ backsql_strfcat_x( &bb, NULL, "sbbsbsbbsb",
+ " ", &bi->sql_aliasing, &bi->sql_aliasing_quote,
+ "objectClass", &bi->sql_aliasing_quote,
+ ",ldap_entries.dn ", &bi->sql_aliasing,
+ &bi->sql_aliasing_quote, "dn", &bi->sql_aliasing_quote );
+ bi->sql_dn_oc_aliasing = bb.bb_val;
+
+ /* should never happen! */
+ assert( bd->be_nsuffix != NULL );
+
+ if ( BER_BVISNULL( &bd->be_nsuffix[ 1 ] ) ) {
+ /* enable if only one suffix is defined */
+ bi->sql_flags |= BSQLF_USE_SUBTREE_SHORTCUT;
+ }
+
+ bi->sql_flags |= BSQLF_CHECK_SCHEMA;
+
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_db_open(): "
+ "test succeeded, schema map loaded\n" );
+ return 0;
+}
+
+int
+backsql_db_close(
+ BackendDB *bd,
+ ConfigReply *cr )
+{
+ backsql_info *bi = (backsql_info*)bd->be_private;
+
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_db_close()\n" );
+
+ backsql_conn_destroy( bi );
+
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_db_close()\n" );
+
+ return 0;
+}
+
+#if SLAPD_SQL == SLAPD_MOD_DYNAMIC
+
+/* conditionally define the init_module() function */
+SLAP_BACKEND_INIT_MODULE( sql )
+
+#endif /* SLAPD_SQL == SLAPD_MOD_DYNAMIC */
+
diff --git a/servers/slapd/back-sql/modify.c b/servers/slapd/back-sql/modify.c
new file mode 100644
index 0000000..83afec3
--- /dev/null
+++ b/servers/slapd/back-sql/modify.c
@@ -0,0 +1,212 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1999 Dmitry Kovalev.
+ * Portions Copyright 2002 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Dmitry Kovalev for inclusion
+ * by OpenLDAP Software. Additional significant contributors include
+ * Pierangelo Masarati.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <sys/types.h>
+#include "ac/string.h"
+
+#include "slap.h"
+#include "proto-sql.h"
+
+int
+backsql_modify( Operation *op, SlapReply *rs )
+{
+ backsql_info *bi = (backsql_info*)op->o_bd->be_private;
+ SQLHDBC dbh = SQL_NULL_HDBC;
+ backsql_oc_map_rec *oc = NULL;
+ backsql_srch_info bsi = { 0 };
+ Entry m = { 0 }, *e = NULL;
+ int manageDSAit = get_manageDSAit( op );
+ SQLUSMALLINT CompletionType = SQL_ROLLBACK;
+
+ /*
+ * FIXME: in case part of the operation cannot be performed
+ * (missing mapping, SQL write fails or so) the entire operation
+ * should be rolled-back
+ */
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_modify(): modifying entry \"%s\"\n",
+ op->o_req_ndn.bv_val );
+
+ rs->sr_err = backsql_get_db_conn( op, &dbh );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_modify(): "
+ "could not get connection handle - exiting\n" );
+ /*
+ * FIXME: we don't want to send back
+ * excessively detailed messages
+ */
+ rs->sr_text = ( rs->sr_err == LDAP_OTHER )
+ ? "SQL-backend error" : NULL;
+ goto done;
+ }
+
+ bsi.bsi_e = &m;
+ rs->sr_err = backsql_init_search( &bsi, &op->o_req_ndn,
+ LDAP_SCOPE_BASE,
+ (time_t)(-1), NULL, dbh, op, rs,
+ slap_anlist_all_attributes,
+ ( BACKSQL_ISF_MATCHED | BACKSQL_ISF_GET_ENTRY | BACKSQL_ISF_GET_OC ) );
+ 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;
+ }
+ e = &m;
+ /* fallthru */
+
+ default:
+ Debug( LDAP_DEBUG_TRACE, "backsql_modify(): "
+ "could not retrieve modifyDN ID - no such entry\n" );
+ if ( !BER_BVISNULL( &m.e_nname ) ) {
+ /* FIXME: should always be true! */
+ e = &m;
+
+ } else {
+ e = NULL;
+ }
+ goto done;
+ }
+
+ Debug( LDAP_DEBUG_TRACE, " backsql_modify(): "
+ "modifying entry \"%s\" (id=" BACKSQL_IDFMT ")\n",
+ bsi.bsi_base_id.eid_dn.bv_val,
+ BACKSQL_IDARG(bsi.bsi_base_id.eid_id) );
+
+ if ( get_assert( op ) &&
+ ( test_filter( op, &m, get_assertion( op ) )
+ != LDAP_COMPARE_TRUE ))
+ {
+ rs->sr_err = LDAP_ASSERTION_FAILED;
+ e = &m;
+ goto done;
+ }
+
+ slap_mods_opattrs( op, &op->orm_modlist, 1 );
+
+ assert( bsi.bsi_base_id.eid_oc != NULL );
+ oc = bsi.bsi_base_id.eid_oc;
+
+ if ( !acl_check_modlist( op, &m, op->orm_modlist ) ) {
+ rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ e = &m;
+ goto done;
+ }
+
+ rs->sr_err = backsql_modify_internal( op, rs, dbh, oc,
+ &bsi.bsi_base_id, op->orm_modlist );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ e = &m;
+ goto do_transact;
+ }
+
+ if ( BACKSQL_CHECK_SCHEMA( bi ) ) {
+ char textbuf[ SLAP_TEXT_BUFLEN ] = { '\0' };
+
+ backsql_entry_clean( op, &m );
+
+ bsi.bsi_e = &m;
+ rs->sr_err = backsql_id2entry( &bsi, &bsi.bsi_base_id );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ e = &m;
+ goto do_transact;
+ }
+
+ rs->sr_err = entry_schema_check( op, &m, NULL, 0, 0, NULL,
+ &rs->sr_text, textbuf, sizeof( textbuf ) );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_modify(\"%s\"): "
+ "entry failed schema check -- aborting\n",
+ m.e_name.bv_val );
+ e = NULL;
+ goto do_transact;
+ }
+ }
+
+do_transact:;
+ /*
+ * Commit only if all operations succeed
+ */
+ if ( rs->sr_err == LDAP_SUCCESS && !op->o_noop ) {
+ assert( e == NULL );
+ CompletionType = SQL_COMMIT;
+ }
+
+ SQLTransact( SQL_NULL_HENV, dbh, CompletionType );
+
+done:;
+ if ( e != NULL ) {
+ if ( !access_allowed( op, e, slap_schema.si_ad_entry, NULL,
+ ACL_DISCLOSE, NULL ) )
+ {
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ rs->sr_text = NULL;
+ rs->sr_matched = NULL;
+ if ( rs->sr_ref ) {
+ ber_bvarray_free( rs->sr_ref );
+ rs->sr_ref = NULL;
+ }
+ }
+ }
+
+ if ( op->o_noop && rs->sr_err == LDAP_SUCCESS ) {
+ rs->sr_err = LDAP_X_NO_OPERATION;
+ }
+
+ send_ldap_result( op, rs );
+ slap_graduate_commit_csn( op );
+
+ if ( !BER_BVISNULL( &bsi.bsi_base_id.eid_ndn ) ) {
+ (void)backsql_free_entryID( &bsi.bsi_base_id, 0, op->o_tmpmemctx );
+ }
+
+ if ( !BER_BVISNULL( &m.e_nname ) ) {
+ backsql_entry_clean( op, &m );
+ }
+
+ if ( bsi.bsi_attrs != NULL ) {
+ op->o_tmpfree( bsi.bsi_attrs, op->o_tmpmemctx );
+ }
+
+ if ( rs->sr_ref ) {
+ ber_bvarray_free( rs->sr_ref );
+ rs->sr_ref = NULL;
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_modify()\n" );
+
+ return rs->sr_err;
+}
+
diff --git a/servers/slapd/back-sql/modrdn.c b/servers/slapd/back-sql/modrdn.c
new file mode 100644
index 0000000..1299923
--- /dev/null
+++ b/servers/slapd/back-sql/modrdn.c
@@ -0,0 +1,524 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1999 Dmitry Kovalev.
+ * Portions Copyright 2002 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Dmitry Kovalev for inclusion
+ * by OpenLDAP Software. Additional significant contributors include
+ * Pierangelo Masarati.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <sys/types.h>
+#include "ac/string.h"
+
+#include "slap.h"
+#include "proto-sql.h"
+
+int
+backsql_modrdn( Operation *op, SlapReply *rs )
+{
+ backsql_info *bi = (backsql_info*)op->o_bd->be_private;
+ SQLHDBC dbh = SQL_NULL_HDBC;
+ SQLHSTMT sth = SQL_NULL_HSTMT;
+ RETCODE rc;
+ backsql_entryID e_id = BACKSQL_ENTRYID_INIT,
+ n_id = BACKSQL_ENTRYID_INIT;
+ backsql_srch_info bsi = { 0 };
+ backsql_oc_map_rec *oc = NULL;
+ struct berval pdn = BER_BVNULL, pndn = BER_BVNULL,
+ *new_pdn = NULL, *new_npdn = NULL,
+ new_dn = BER_BVNULL, new_ndn = BER_BVNULL,
+ realnew_dn = BER_BVNULL;
+ Entry r = { 0 },
+ p = { 0 },
+ n = { 0 },
+ *e = NULL;
+ int manageDSAit = get_manageDSAit( op );
+ struct berval *newSuperior = op->oq_modrdn.rs_newSup;
+
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_modrdn() renaming entry \"%s\", "
+ "newrdn=\"%s\", newSuperior=\"%s\"\n",
+ op->o_req_dn.bv_val, op->oq_modrdn.rs_newrdn.bv_val,
+ newSuperior ? newSuperior->bv_val : "(NULL)" );
+
+ rs->sr_err = backsql_get_db_conn( op, &dbh );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_modrdn(): "
+ "could not get connection handle - exiting\n" );
+ rs->sr_text = ( rs->sr_err == LDAP_OTHER )
+ ? "SQL-backend error" : NULL;
+ e = NULL;
+ goto done;
+ }
+
+ bsi.bsi_e = &r;
+ rs->sr_err = backsql_init_search( &bsi, &op->o_req_ndn,
+ LDAP_SCOPE_BASE,
+ (time_t)(-1), NULL, dbh, op, rs,
+ slap_anlist_all_attributes,
+ ( BACKSQL_ISF_MATCHED | BACKSQL_ISF_GET_ENTRY | BACKSQL_ISF_GET_OC ) );
+ 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;
+ }
+ e = &r;
+ /* fallthru */
+
+ default:
+ Debug( LDAP_DEBUG_TRACE, "backsql_modrdn(): "
+ "could not retrieve modrdnDN ID - no such entry\n" );
+ if ( !BER_BVISNULL( &r.e_nname ) ) {
+ /* FIXME: should always be true! */
+ e = &r;
+
+ } else {
+ e = NULL;
+ }
+ goto done;
+ }
+
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modrdn(): entry id=" BACKSQL_IDFMT "\n",
+ BACKSQL_IDARG(e_id.eid_id) );
+
+ if ( get_assert( op ) &&
+ ( test_filter( op, &r, get_assertion( op ) )
+ != LDAP_COMPARE_TRUE ) )
+ {
+ rs->sr_err = LDAP_ASSERTION_FAILED;
+ e = &r;
+ goto done;
+ }
+
+ if ( backsql_has_children( op, dbh, &op->o_req_ndn ) == LDAP_COMPARE_TRUE ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_modrdn(): "
+ "entry \"%s\" has children\n",
+ op->o_req_dn.bv_val );
+ rs->sr_err = LDAP_NOT_ALLOWED_ON_NONLEAF;
+ rs->sr_text = "subtree rename not supported";
+ e = &r;
+ goto done;
+ }
+
+ /*
+ * Check for entry access to target
+ */
+ if ( !access_allowed( op, &r, slap_schema.si_ad_entry,
+ NULL, ACL_WRITE, NULL ) ) {
+ Debug( LDAP_DEBUG_TRACE, " no access to entry\n" );
+ rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ goto done;
+ }
+
+ dnParent( &op->o_req_dn, &pdn );
+ dnParent( &op->o_req_ndn, &pndn );
+
+ /*
+ * namingContext "" is not supported
+ */
+ if ( BER_BVISEMPTY( &pdn ) ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_modrdn(): "
+ "parent is \"\" - aborting\n" );
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "not allowed within namingContext";
+ e = NULL;
+ goto done;
+ }
+
+ /*
+ * Check for children access to parent
+ */
+ bsi.bsi_e = &p;
+ e_id = bsi.bsi_base_id;
+ memset( &bsi.bsi_base_id, 0, sizeof( bsi.bsi_base_id ) );
+ rs->sr_err = backsql_init_search( &bsi, &pndn,
+ LDAP_SCOPE_BASE,
+ (time_t)(-1), NULL, dbh, op, rs,
+ slap_anlist_all_attributes,
+ BACKSQL_ISF_GET_ENTRY );
+
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modrdn(): old parent entry id is " BACKSQL_IDFMT "\n",
+ BACKSQL_IDARG(bsi.bsi_base_id.eid_id) );
+
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_modrdn(): "
+ "could not retrieve renameDN ID - no such entry\n" );
+ e = &p;
+ goto done;
+ }
+
+ if ( !access_allowed( op, &p, slap_schema.si_ad_children, NULL,
+ newSuperior ? ACL_WDEL : ACL_WRITE, NULL ) )
+ {
+ Debug( LDAP_DEBUG_TRACE, " no access to parent\n" );
+ rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ goto done;
+ }
+
+ if ( newSuperior ) {
+ (void)backsql_free_entryID( &bsi.bsi_base_id, 0, op->o_tmpmemctx );
+
+ /*
+ * namingContext "" is not supported
+ */
+ if ( BER_BVISEMPTY( newSuperior ) ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_modrdn(): "
+ "newSuperior is \"\" - aborting\n" );
+ rs->sr_err = LDAP_UNWILLING_TO_PERFORM;
+ rs->sr_text = "not allowed within namingContext";
+ e = NULL;
+ goto done;
+ }
+
+ new_pdn = newSuperior;
+ new_npdn = op->oq_modrdn.rs_nnewSup;
+
+ /*
+ * Check for children access to new parent
+ */
+ bsi.bsi_e = &n;
+ rs->sr_err = backsql_init_search( &bsi, new_npdn,
+ LDAP_SCOPE_BASE,
+ (time_t)(-1), NULL, dbh, op, rs,
+ slap_anlist_all_attributes,
+ ( BACKSQL_ISF_MATCHED | BACKSQL_ISF_GET_ENTRY ) );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_modrdn(): "
+ "could not retrieve renameDN ID - no such entry\n" );
+ e = &n;
+ goto done;
+ }
+
+ n_id = bsi.bsi_base_id;
+
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modrdn(): new parent entry id=" BACKSQL_IDFMT "\n",
+ BACKSQL_IDARG(n_id.eid_id) );
+
+ if ( !access_allowed( op, &n, slap_schema.si_ad_children,
+ NULL, ACL_WADD, NULL ) ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_modrdn(): "
+ "no access to new parent \"%s\"\n",
+ new_pdn->bv_val );
+ rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+ e = &n;
+ goto done;
+ }
+
+ } else {
+ n_id = bsi.bsi_base_id;
+ new_pdn = &pdn;
+ new_npdn = &pndn;
+ }
+
+ memset( &bsi.bsi_base_id, 0, sizeof( bsi.bsi_base_id ) );
+
+ if ( newSuperior && dn_match( &pndn, new_npdn ) ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_modrdn(): "
+ "newSuperior is equal to old parent - ignored\n" );
+ newSuperior = NULL;
+ }
+
+ if ( newSuperior && dn_match( &op->o_req_ndn, new_npdn ) ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_modrdn(): "
+ "newSuperior is equal to entry being moved "
+ "- aborting\n" );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "newSuperior is equal to old DN";
+ e = &r;
+ goto done;
+ }
+
+ build_new_dn( &new_dn, new_pdn, &op->oq_modrdn.rs_newrdn,
+ op->o_tmpmemctx );
+ build_new_dn( &new_ndn, new_npdn, &op->oq_modrdn.rs_nnewrdn,
+ op->o_tmpmemctx );
+
+ Debug( LDAP_DEBUG_TRACE, " backsql_modrdn(): new entry dn is \"%s\"\n",
+ new_dn.bv_val );
+
+ realnew_dn = new_dn;
+ if ( backsql_api_dn2odbc( op, rs, &realnew_dn ) ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_modrdn(\"%s\"): "
+ "backsql_api_dn2odbc(\"%s\") failed\n",
+ op->o_req_dn.bv_val, realnew_dn.bv_val );
+ SQLFreeStmt( sth, SQL_DROP );
+
+ rs->sr_text = "SQL-backend error";
+ rs->sr_err = LDAP_OTHER;
+ e = NULL;
+ goto done;
+ }
+
+ Debug( LDAP_DEBUG_TRACE, " backsql_modrdn(): "
+ "executing renentry_stmt\n" );
+
+ rc = backsql_Prepare( dbh, &sth, bi->sql_renentry_stmt, 0 );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modrdn(): "
+ "error preparing renentry_stmt\n" );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+
+ rs->sr_text = "SQL-backend error";
+ rs->sr_err = LDAP_OTHER;
+ e = NULL;
+ goto done;
+ }
+
+ rc = backsql_BindParamBerVal( sth, 1, SQL_PARAM_INPUT, &realnew_dn );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modrdn(): "
+ "error binding DN parameter for objectClass %s\n",
+ oc->bom_oc->soc_cname.bv_val );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+
+ rs->sr_text = "SQL-backend error";
+ rs->sr_err = LDAP_OTHER;
+ e = NULL;
+ goto done;
+ }
+
+ rc = backsql_BindParamID( sth, 2, SQL_PARAM_INPUT, &n_id.eid_id );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modrdn(): "
+ "error binding parent ID parameter for objectClass %s\n",
+ oc->bom_oc->soc_cname.bv_val );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+
+ rs->sr_text = "SQL-backend error";
+ rs->sr_err = LDAP_OTHER;
+ e = NULL;
+ goto done;
+ }
+
+ rc = backsql_BindParamID( sth, 3, SQL_PARAM_INPUT, &e_id.eid_keyval );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modrdn(): "
+ "error binding entry ID parameter for objectClass %s\n",
+ oc->bom_oc->soc_cname.bv_val );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+
+ rs->sr_text = "SQL-backend error";
+ rs->sr_err = LDAP_OTHER;
+ e = NULL;
+ goto done;
+ }
+
+ rc = backsql_BindParamID( sth, 4, SQL_PARAM_INPUT, &e_id.eid_id );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE,
+ " backsql_modrdn(): "
+ "error binding ID parameter for objectClass %s\n",
+ oc->bom_oc->soc_cname.bv_val );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+
+ rs->sr_text = "SQL-backend error";
+ rs->sr_err = LDAP_OTHER;
+ e = NULL;
+ goto done;
+ }
+
+ rc = SQLExecute( sth );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_modrdn(): "
+ "could not rename ldap_entries record\n" );
+ backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+ rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "SQL-backend error";
+ e = NULL;
+ goto done;
+ }
+ SQLFreeStmt( sth, SQL_DROP );
+
+ slap_mods_opattrs( op, &op->orr_modlist, 1 );
+
+ assert( e_id.eid_oc != NULL );
+ oc = e_id.eid_oc;
+
+ if ( op->orr_modlist != NULL ) {
+ rs->sr_err = backsql_modify_internal( op, rs, dbh, oc, &e_id, op->orr_modlist );
+ slap_graduate_commit_csn( op );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ e = &r;
+ goto done;
+ }
+ }
+
+ if ( BACKSQL_CHECK_SCHEMA( bi ) ) {
+ char textbuf[ SLAP_TEXT_BUFLEN ] = { '\0' };
+
+ backsql_entry_clean( op, &r );
+ (void)backsql_free_entryID( &e_id, 0, op->o_tmpmemctx );
+
+ bsi.bsi_e = &r;
+ rs->sr_err = backsql_init_search( &bsi, &new_ndn,
+ LDAP_SCOPE_BASE,
+ (time_t)(-1), NULL, dbh, op, rs,
+ slap_anlist_all_attributes,
+ ( 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( &new_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;
+ }
+ e = &r;
+ /* fallthru */
+
+ default:
+ Debug( LDAP_DEBUG_TRACE, "backsql_modrdn(): "
+ "could not retrieve modrdnDN ID - no such entry\n" );
+ if ( !BER_BVISNULL( &r.e_nname ) ) {
+ /* FIXME: should always be true! */
+ e = &r;
+
+ } else {
+ e = NULL;
+ }
+ goto done;
+ }
+
+ e_id = bsi.bsi_base_id;
+
+ rs->sr_err = entry_schema_check( op, &r, NULL, 0, 0, NULL,
+ &rs->sr_text, textbuf, sizeof( textbuf ) );
+ if ( rs->sr_err != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, " backsql_modrdn(\"%s\"): "
+ "entry failed schema check -- aborting\n",
+ r.e_name.bv_val );
+ e = NULL;
+ goto done;
+ }
+ }
+
+done:;
+ if ( e != NULL ) {
+ if ( !access_allowed( op, e, slap_schema.si_ad_entry, NULL,
+ ACL_DISCLOSE, NULL ) )
+ {
+ rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ rs->sr_text = NULL;
+ rs->sr_matched = NULL;
+ if ( rs->sr_ref ) {
+ ber_bvarray_free( rs->sr_ref );
+ rs->sr_ref = NULL;
+ }
+ }
+ }
+
+ /*
+ * Commit only if all operations succeed
+ */
+ if ( sth != SQL_NULL_HSTMT ) {
+ SQLUSMALLINT CompletionType = SQL_ROLLBACK;
+
+ if ( rs->sr_err == LDAP_SUCCESS && !op->o_noop ) {
+ CompletionType = SQL_COMMIT;
+ }
+
+ SQLTransact( SQL_NULL_HENV, dbh, CompletionType );
+ }
+
+ if ( op->o_noop && rs->sr_err == LDAP_SUCCESS ) {
+ rs->sr_err = LDAP_X_NO_OPERATION;
+ }
+
+ send_ldap_result( op, rs );
+ slap_graduate_commit_csn( op );
+
+ if ( !BER_BVISNULL( &realnew_dn ) && realnew_dn.bv_val != new_dn.bv_val ) {
+ ch_free( realnew_dn.bv_val );
+ }
+
+ if ( !BER_BVISNULL( &new_dn ) ) {
+ slap_sl_free( new_dn.bv_val, op->o_tmpmemctx );
+ }
+
+ if ( !BER_BVISNULL( &new_ndn ) ) {
+ slap_sl_free( new_ndn.bv_val, op->o_tmpmemctx );
+ }
+
+ if ( !BER_BVISNULL( &e_id.eid_ndn ) ) {
+ (void)backsql_free_entryID( &e_id, 0, op->o_tmpmemctx );
+ }
+
+ if ( !BER_BVISNULL( &n_id.eid_ndn ) ) {
+ (void)backsql_free_entryID( &n_id, 0, op->o_tmpmemctx );
+ }
+
+ if ( !BER_BVISNULL( &r.e_nname ) ) {
+ backsql_entry_clean( op, &r );
+ }
+
+ if ( !BER_BVISNULL( &p.e_nname ) ) {
+ backsql_entry_clean( op, &p );
+ }
+
+ if ( !BER_BVISNULL( &n.e_nname ) ) {
+ backsql_entry_clean( op, &n );
+ }
+
+ if ( rs->sr_ref ) {
+ ber_bvarray_free( rs->sr_ref );
+ rs->sr_ref = NULL;
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_modrdn()\n" );
+
+ return rs->sr_err;
+}
+
diff --git a/servers/slapd/back-sql/operational.c b/servers/slapd/back-sql/operational.c
new file mode 100644
index 0000000..3eb1ec2
--- /dev/null
+++ b/servers/slapd/back-sql/operational.c
@@ -0,0 +1,246 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1999 Dmitry Kovalev.
+ * Portions Copyright 2002 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Dmitry Kovalev for inclusion
+ * by OpenLDAP Software. Additional significant contributors include
+ * Pierangelo Masarati.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <sys/types.h>
+
+#include "slap.h"
+#include "proto-sql.h"
+#include "lutil.h"
+
+/*
+ * sets the supported operational attributes (if required)
+ */
+
+Attribute *
+backsql_operational_entryUUID( backsql_info *bi, backsql_entryID *id )
+{
+ int rc;
+ struct berval val, nval;
+ AttributeDescription *desc = slap_schema.si_ad_entryUUID;
+ Attribute *a;
+
+ backsql_entryUUID( bi, id, &val, NULL );
+
+ rc = (*desc->ad_type->sat_equality->smr_normalize)(
+ SLAP_MR_VALUE_OF_ATTRIBUTE_SYNTAX,
+ desc->ad_type->sat_syntax,
+ desc->ad_type->sat_equality,
+ &val, &nval, NULL );
+ if ( rc != LDAP_SUCCESS ) {
+ ber_memfree( val.bv_val );
+ return NULL;
+ }
+
+ a = attr_alloc( desc );
+
+ a->a_numvals = 1;
+ a->a_vals = (BerVarray) ch_malloc( 2 * sizeof( struct berval ) );
+ a->a_vals[ 0 ] = val;
+ BER_BVZERO( &a->a_vals[ 1 ] );
+
+ a->a_nvals = (BerVarray) ch_malloc( 2 * sizeof( struct berval ) );
+ a->a_nvals[ 0 ] = nval;
+ BER_BVZERO( &a->a_nvals[ 1 ] );
+
+ return a;
+}
+
+Attribute *
+backsql_operational_entryCSN( Operation *op )
+{
+ char csnbuf[ LDAP_PVT_CSNSTR_BUFSIZE ];
+ struct berval entryCSN;
+ Attribute *a;
+
+ a = attr_alloc( slap_schema.si_ad_entryCSN );
+ a->a_numvals = 1;
+ a->a_vals = ch_malloc( 2 * sizeof( struct berval ) );
+ BER_BVZERO( &a->a_vals[ 1 ] );
+
+#ifdef BACKSQL_SYNCPROV
+ if ( op->o_sync && op->o_tag == LDAP_REQ_SEARCH && op->o_private != NULL ) {
+ assert( op->o_private != NULL );
+
+ entryCSN = *((struct berval *)op->o_private);
+
+ } else
+#endif /* BACKSQL_SYNCPROV */
+ {
+ entryCSN.bv_val = csnbuf;
+ entryCSN.bv_len = sizeof( csnbuf );
+ slap_get_csn( op, &entryCSN, 0 );
+ }
+
+ ber_dupbv( &a->a_vals[ 0 ], &entryCSN );
+
+ a->a_nvals = a->a_vals;
+
+ return a;
+}
+
+int
+backsql_operational(
+ Operation *op,
+ SlapReply *rs )
+{
+
+ backsql_info *bi = (backsql_info*)op->o_bd->be_private;
+ SQLHDBC dbh = SQL_NULL_HDBC;
+ int rc = 0;
+ Attribute **ap;
+ enum {
+ BACKSQL_OP_HASSUBORDINATES = 0,
+ BACKSQL_OP_ENTRYUUID,
+ BACKSQL_OP_ENTRYCSN,
+
+ BACKSQL_OP_LAST
+ };
+ int get_conn = BACKSQL_OP_LAST,
+ got[ BACKSQL_OP_LAST ] = { 0 };
+
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_operational(): entry \"%s\"\n",
+ rs->sr_entry->e_nname.bv_val );
+
+ for ( ap = &rs->sr_entry->e_attrs; *ap; ap = &(*ap)->a_next ) {
+ if ( (*ap)->a_desc == slap_schema.si_ad_hasSubordinates ) {
+ get_conn--;
+ got[ BACKSQL_OP_HASSUBORDINATES ] = 1;
+
+ } else if ( (*ap)->a_desc == slap_schema.si_ad_entryUUID ) {
+ get_conn--;
+ got[ BACKSQL_OP_ENTRYUUID ] = 1;
+
+ } else if ( (*ap)->a_desc == slap_schema.si_ad_entryCSN ) {
+ get_conn--;
+ got[ BACKSQL_OP_ENTRYCSN ] = 1;
+ }
+ }
+
+ for ( ap = &rs->sr_operational_attrs; *ap; ap = &(*ap)->a_next ) {
+ if ( !got[ BACKSQL_OP_HASSUBORDINATES ] &&
+ (*ap)->a_desc == slap_schema.si_ad_hasSubordinates )
+ {
+ get_conn--;
+ got[ BACKSQL_OP_HASSUBORDINATES ] = 1;
+
+ } else if ( !got[ BACKSQL_OP_ENTRYUUID ] &&
+ (*ap)->a_desc == slap_schema.si_ad_entryUUID )
+ {
+ get_conn--;
+ got[ BACKSQL_OP_ENTRYUUID ] = 1;
+
+ } else if ( !got[ BACKSQL_OP_ENTRYCSN ] &&
+ (*ap)->a_desc == slap_schema.si_ad_entryCSN )
+ {
+ get_conn--;
+ got[ BACKSQL_OP_ENTRYCSN ] = 1;
+ }
+ }
+
+ if ( !get_conn ) {
+ return 0;
+ }
+
+ rc = backsql_get_db_conn( op, &dbh );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_operational(): "
+ "could not get connection handle - exiting\n" );
+ return 1;
+ }
+
+ if ( ( SLAP_OPATTRS( rs->sr_attr_flags ) || ad_inlist( slap_schema.si_ad_hasSubordinates, rs->sr_attrs ) )
+ && !got[ BACKSQL_OP_HASSUBORDINATES ]
+ && attr_find( rs->sr_entry->e_attrs, slap_schema.si_ad_hasSubordinates ) == NULL )
+ {
+ rc = backsql_has_children( op, dbh, &rs->sr_entry->e_nname );
+
+ switch( rc ) {
+ case LDAP_COMPARE_TRUE:
+ case LDAP_COMPARE_FALSE:
+ *ap = slap_operational_hasSubordinate( rc == LDAP_COMPARE_TRUE );
+ assert( *ap != NULL );
+ ap = &(*ap)->a_next;
+ rc = 0;
+ break;
+
+ default:
+ Debug( LDAP_DEBUG_TRACE, "backsql_operational(): "
+ "has_children failed( %d)\n", rc );
+ return 1;
+ }
+ }
+
+ if ( ( SLAP_OPATTRS( rs->sr_attr_flags ) || ad_inlist( slap_schema.si_ad_entryUUID, rs->sr_attrs ) )
+ && !got[ BACKSQL_OP_ENTRYUUID ]
+ && attr_find( rs->sr_entry->e_attrs, slap_schema.si_ad_entryUUID ) == NULL )
+ {
+ backsql_srch_info bsi = { 0 };
+
+ rc = backsql_init_search( &bsi, &rs->sr_entry->e_nname,
+ LDAP_SCOPE_BASE,
+ (time_t)(-1), NULL, dbh, op, rs, NULL,
+ BACKSQL_ISF_GET_ID );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_operational(): "
+ "could not retrieve entry ID - no such entry\n" );
+ return 1;
+ }
+
+ *ap = backsql_operational_entryUUID( bi, &bsi.bsi_base_id );
+
+ (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 ( *ap == NULL ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_operational(): "
+ "could not retrieve entryUUID\n" );
+ return 1;
+ }
+
+ ap = &(*ap)->a_next;
+ }
+
+ if ( ( SLAP_OPATTRS( rs->sr_attr_flags ) || ad_inlist( slap_schema.si_ad_entryCSN, rs->sr_attrs ) )
+ && !got[ BACKSQL_OP_ENTRYCSN ]
+ && attr_find( rs->sr_entry->e_attrs, slap_schema.si_ad_entryCSN ) == NULL )
+ {
+ *ap = backsql_operational_entryCSN( op );
+ if ( *ap == NULL ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_operational(): "
+ "could not retrieve entryCSN\n" );
+ return 1;
+ }
+
+ ap = &(*ap)->a_next;
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_operational(%d)\n", rc );
+
+ return rc;
+}
+
diff --git a/servers/slapd/back-sql/proto-sql.h b/servers/slapd/back-sql/proto-sql.h
new file mode 100644
index 0000000..169be76
--- /dev/null
+++ b/servers/slapd/back-sql/proto-sql.h
@@ -0,0 +1,313 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1999 Dmitry Kovalev.
+ * Portions Copyright 2002 Pierangelo Mararati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Dmitry Kovalev for inclusion
+ * by OpenLDAP Software. Additional significant contributors include
+ * Pierangelo Masarati
+ */
+
+/*
+ * The following changes have been addressed:
+ *
+ * Enhancements:
+ * - re-styled code for better readability
+ * - upgraded backend API to reflect recent changes
+ * - LDAP schema is checked when loading SQL/LDAP mapping
+ * - AttributeDescription/ObjectClass pointers used for more efficient
+ * mapping lookup
+ * - bervals used where string length is required often
+ * - atomized write operations by committing at the end of each operation
+ * and defaulting connection closure to rollback
+ * - added LDAP access control to write operations
+ * - fully implemented modrdn (with rdn attrs change, deleteoldrdn,
+ * access check, parent/children check and more)
+ * - added parent access control, children control to delete operation
+ * - added structuralObjectClass operational attribute check and
+ * value return on search
+ * - added hasSubordinate operational attribute on demand
+ * - search limits are appropriately enforced
+ * - function backsql_strcat() has been made more efficient
+ * - concat function has been made configurable by means of a pattern
+ * - added config switches:
+ * - fail_if_no_mapping write operations fail if there is no mapping
+ * - has_ldapinfo_dn_ru overrides autodetect
+ * - concat_pattern a string containing two '?' is used
+ * (note that "?||?" should be more portable
+ * than builtin function "CONCAT(?,?)")
+ * - strcast_func cast of string constants in "SELECT DISTINCT
+ * statements (needed by PostgreSQL)
+ * - upper_needs_cast cast the argument of upper when required
+ * (basically when building dn substring queries)
+ * - added noop control
+ * - added values return filter control
+ * - hasSubordinate can be used in search filters (with limitations)
+ * - eliminated oc->name; use oc->oc->soc_cname instead
+ *
+ * Todo:
+ * - add security checks for SQL statements that can be injected (?)
+ * - re-test with previously supported RDBMs
+ * - replace dn_ru and so with normalized dn (no need for upper() and so
+ * in dn match)
+ * - implement a backsql_normalize() function to replace the upper()
+ * conversion routines
+ * - note that subtree deletion, subtree renaming and so could be easily
+ * implemented (rollback and consistency checks are available :)
+ * - implement "lastmod" and other operational stuff (ldap_entries table ?)
+ * - check how to allow multiple operations with one statement, to remove
+ * BACKSQL_REALLOC_STMT from modify.c (a more recent unixODBC lib?)
+ */
+
+#ifndef PROTO_SQL_H
+#define PROTO_SQL_H
+
+#include "back-sql.h"
+
+/*
+ * add.c
+ */
+int backsql_modify_delete_all_values(
+ Operation *op,
+ SlapReply *rs,
+ SQLHDBC dbh,
+ backsql_entryID *e_id,
+ backsql_at_map_rec *at );
+
+int backsql_modify_internal(
+ Operation *op,
+ SlapReply *rs,
+ SQLHDBC dbh,
+ backsql_oc_map_rec *oc,
+ backsql_entryID *e_id,
+ Modifications *modlist );
+
+/*
+ * api.c
+ */
+int backsql_api_config( backsql_info *bi, const char *name,
+ int argc, char *argv[] );
+int backsql_api_destroy( backsql_info *bi );
+int backsql_api_register( backsql_api *ba );
+int backsql_api_dn2odbc( Operation *op, SlapReply *rs, struct berval *dn );
+int backsql_api_odbc2dn( Operation *op, SlapReply *rs, struct berval *dn );
+
+/*
+ * entry-id.c
+ */
+#ifdef BACKSQL_ARBITRARY_KEY
+extern struct berval backsql_baseObject_bv;
+#endif /* BACKSQL_ARBITRARY_KEY */
+
+/* stores in *id the ID in table ldap_entries corresponding to DN, if any */
+extern int
+backsql_dn2id( Operation *op, SlapReply *rs, SQLHDBC dbh,
+ struct berval *ndn, backsql_entryID *id,
+ int matched, int muck );
+
+/* stores in *nchildren the count of children for an entry */
+extern int
+backsql_count_children( Operation *op, SQLHDBC dbh,
+ struct berval *dn, unsigned long *nchildren );
+
+/* returns LDAP_COMPARE_TRUE/LDAP_COMPARE_FALSE if the entry corresponding
+ * to DN has/has not children */
+extern int
+backsql_has_children( Operation *op, SQLHDBC dbh, struct berval *dn );
+
+/* free *id and return next in list */
+extern backsql_entryID *
+backsql_free_entryID( backsql_entryID *id, int freeit, void *ctx );
+
+/* turn an ID into an entry */
+extern int
+backsql_id2entry( backsql_srch_info *bsi, backsql_entryID *id );
+
+/* duplicate an entryID */
+extern backsql_entryID *
+backsql_entryID_dup( backsql_entryID *eid, void *ctx );
+
+/*
+ * operational.c
+ */
+
+Attribute *backsql_operational_entryUUID( backsql_info *bi, backsql_entryID *id );
+
+Attribute *backsql_operational_entryCSN( Operation *op );
+
+/*
+ * schema-map.c
+ */
+
+int backsql_load_schema_map( backsql_info *si, SQLHDBC dbh );
+
+backsql_oc_map_rec *backsql_oc2oc( backsql_info *si, ObjectClass *oc );
+
+backsql_oc_map_rec *backsql_id2oc( backsql_info *si, unsigned long id );
+
+backsql_oc_map_rec * backsql_name2oc( backsql_info *si,
+ struct berval *oc_name );
+
+backsql_at_map_rec *backsql_ad2at( backsql_oc_map_rec *objclass,
+ AttributeDescription *ad );
+
+int backsql_supad2at( backsql_oc_map_rec *objclass,
+ AttributeDescription *supad, backsql_at_map_rec ***pret );
+
+int backsql_destroy_schema_map( backsql_info *si );
+
+/*
+ * search.c
+ */
+
+int backsql_init_search( backsql_srch_info *bsi,
+ struct berval *nbase, int scope,
+ time_t stoptime, Filter *filter, SQLHDBC dbh,
+ Operation *op, SlapReply *rs, AttributeName *attrs,
+ unsigned flags );
+
+void backsql_entry_clean( Operation *op, Entry *e );
+
+/*
+ * sql-wrap.h
+ */
+
+RETCODE backsql_Prepare( SQLHDBC dbh, SQLHSTMT *sth, const char* query, int timeout );
+
+#define backsql_BindParamStr( sth, par_ind, io, str, maxlen ) \
+ SQLBindParameter( (sth), (SQLUSMALLINT)(par_ind), \
+ (io), SQL_C_CHAR, SQL_VARCHAR, \
+ (SQLULEN)(maxlen), 0, (SQLPOINTER)(str), \
+ (SQLLEN)(maxlen), NULL )
+
+#define backsql_BindParamBerVal( sth, par_ind, io, bv ) \
+ SQLBindParameter( (sth), (SQLUSMALLINT)(par_ind), \
+ (io), SQL_C_CHAR, SQL_VARCHAR, \
+ (SQLULEN)(bv)->bv_len, 0, \
+ (SQLPOINTER)(bv)->bv_val, \
+ (SQLLEN)(bv)->bv_len, NULL )
+
+#define backsql_BindParamInt( sth, par_ind, io, val ) \
+ SQLBindParameter( (sth), (SQLUSMALLINT)(par_ind), \
+ (io), SQL_C_ULONG, SQL_INTEGER, \
+ 0, 0, (SQLPOINTER)(val), 0, (SQLLEN*)NULL )
+
+#define backsql_BindParamNumID( sth, par_ind, io, val ) \
+ SQLBindParameter( (sth), (SQLUSMALLINT)(par_ind), \
+ (io), BACKSQL_C_NUMID, SQL_INTEGER, \
+ 0, 0, (SQLPOINTER)(val), 0, (SQLLEN*)NULL )
+
+#ifdef BACKSQL_ARBITRARY_KEY
+#define backsql_BindParamID( sth, par_ind, io, id ) \
+ backsql_BindParamBerVal( (sth), (par_ind), (io), (id) )
+#else /* ! BACKSQL_ARBITRARY_KEY */
+#define backsql_BindParamID( sth, par_ind, io, id ) \
+ backsql_BindParamNumID( (sth), (par_ind), (io), (id) )
+#endif /* ! BACKSQL_ARBITRARY_KEY */
+
+RETCODE backsql_BindRowAsStrings_x( SQLHSTMT sth, BACKSQL_ROW_NTS *row, void *ctx );
+
+RETCODE backsql_BindRowAsStrings( SQLHSTMT sth, BACKSQL_ROW_NTS *row );
+
+RETCODE backsql_FreeRow_x( BACKSQL_ROW_NTS *row, void *ctx );
+
+RETCODE backsql_FreeRow( BACKSQL_ROW_NTS *row );
+
+void backsql_PrintErrors( SQLHENV henv, SQLHDBC hdbc, SQLHSTMT sth, int rc );
+
+int backsql_conn_destroy( backsql_info *bi );
+
+int backsql_init_db_env( backsql_info *si );
+
+int backsql_free_db_env( backsql_info *si );
+
+int backsql_get_db_conn( Operation *op, SQLHDBC *dbh );
+
+int backsql_free_db_conn( Operation *op, SQLHDBC dbh );
+
+/*
+ * util.c
+ */
+
+extern const char
+ backsql_def_oc_query[],
+ backsql_def_needs_select_oc_query[],
+ backsql_def_at_query[],
+ backsql_def_delentry_stmt[],
+ backsql_def_renentry_stmt[],
+ backsql_def_insentry_stmt[],
+ backsql_def_delobjclasses_stmt[],
+ backsql_def_subtree_cond[],
+ backsql_def_upper_subtree_cond[],
+ backsql_id_query[],
+ backsql_def_concat_func[],
+ backsql_check_dn_ru_query[];
+
+struct berbuf * backsql_strcat_x( struct berbuf *dest, void *memctx, ... );
+struct berbuf * backsql_strfcat_x( struct berbuf *dest, void *memctx, const char *fmt, ... );
+
+int backsql_entry_addattr( Entry *e, AttributeDescription *ad,
+ struct berval *at_val, void *memctx );
+
+int backsql_merge_from_clause( backsql_info *bi, struct berbuf *dest_from,
+ struct berval *src_from );
+
+int backsql_split_pattern( const char *pattern, BerVarray *split_pattern,
+ int expected );
+
+int backsql_prepare_pattern( BerVarray split_pattern, BerVarray values,
+ struct berval *res );
+
+int backsql_entryUUID( backsql_info *bi, backsql_entryID *id,
+ struct berval *entryUUID, void *memctx );
+int backsql_entryUUID_decode( struct berval *entryUUID, unsigned long *oc_id,
+#ifdef BACKSQL_ARBITRARY_KEY
+ struct berval *keyval
+#else /* ! BACKSQL_ARBITRARY_KEY */
+ unsigned long *keyval
+#endif /* ! BACKSQL_ARBITRARY_KEY */
+ );
+
+/*
+ * former external.h
+ */
+
+extern BI_init sql_back_initialize;
+
+extern BI_destroy backsql_destroy;
+
+extern BI_db_init backsql_db_init;
+extern BI_db_open backsql_db_open;
+extern BI_db_close backsql_db_close;
+extern BI_db_destroy backsql_db_destroy;
+extern BI_db_config backsql_db_config;
+
+extern BI_op_bind backsql_bind;
+extern BI_op_search backsql_search;
+extern BI_op_compare backsql_compare;
+extern BI_op_modify backsql_modify;
+extern BI_op_modrdn backsql_modrdn;
+extern BI_op_add backsql_add;
+extern BI_op_delete backsql_delete;
+
+extern BI_operational backsql_operational;
+extern BI_entry_get_rw backsql_entry_get;
+extern BI_entry_release_rw backsql_entry_release;
+
+extern BI_connection_destroy backsql_connection_destroy;
+
+int backsql_init_cf( BackendInfo * bi );
+
+#endif /* PROTO_SQL_H */
diff --git a/servers/slapd/back-sql/rdbms_depend/README b/servers/slapd/back-sql/rdbms_depend/README
new file mode 100644
index 0000000..2b281f6
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/README
@@ -0,0 +1,189 @@
+Author: Pierangelo Masarati <ando@OpenLDAP.org>
+
+Back-sql can be tested with sql-test000-read; it requires a bit of work
+to get everything up and running appropriately.
+
+This document briefly describes the steps that are required to prepare
+a quick'n'dirty installation of back-sql and of the related RDBMS
+and ODBC; Examples are provided, but by no means they pretent
+to represent an exhaustive source of info about how to setup the ODBC;
+refer to the docs for any problem or detail.
+
+Currently, the system has been tested with IBM db2, PostgreSQL and MySQL;
+basic support and test data for other RDBMSes is in place, but as of
+today (November 2004) it's totally untested. If you succeed in running
+any of the other RDBMSes, please provide feedback about any required
+change either in the code or in the test scripts by means of OpenLDAP's
+Issue Tracking System (http://www.openldap.org/its/).
+
+1) slapd must be compiled with back-sql support, i.e. configure
+with --enable-sql switch. This requires an implementation of the ODBC
+to be installed.
+
+2) The ODBC must be set up appropriately, by editing the odbc.ini file
+in /etc/ (or wherever your installation puts it) and, if appropriate,
+the odbcinst.ini file. Note: you can also use custom odbc.ini and
+odbcinst.ini files, provided you export in ODBCINI the full path to the
+odbc.ini file, and in ODBCSYSINI the directory where the odbcinst.ini
+file resides.
+Relevant info for our test setup is highlighted with '<===' on the right.
+
+2.1) PostgreSQL
+
+2.1.1) Add to the odbc.ini file a block of the form
+
+[example] <===
+Description = Example for OpenLDAP's back-sql
+Driver = PostgreSQL
+Trace = No
+Database = example <===
+Servername = localhost
+UserName = manager <===
+Password = secret <===
+Port = 5432
+;Protocol = 6.4
+ReadOnly = No
+RowVersioning = No
+ShowSystemTables = No
+ShowOidColumn = No
+FakeOidIndex = No
+ConnSettings =
+
+2.1.2) Add to the odbcinst.ini file a block of the form
+
+[PostgreSQL]
+Description = ODBC for PostgreSQL
+Driver = /usr/lib/libodbcpsql.so
+Setup = /usr/lib/libodbcpsqlS.so
+FileUsage = 1
+
+2.2) MySQL
+
+2.2.1) Add to the odbc.ini file a block of the form
+
+[example] <===
+Description = Example for OpenLDAP's back-sql
+Driver = MySQL
+Trace = No
+Database = example <===
+Servername = localhost
+UserName = manager <===
+Password = secret <===
+ReadOnly = No
+RowVersioning = No
+ShowSystemTables = No
+ShowOidColumn = No
+FakeOidIndex = No
+ConnSettings =
+SOCKET = /var/lib/mysql/mysql.sock
+
+2.2.2) Add to the odbcinst.ini file a block of the form
+
+[MySQL]
+Description = ODBC for MySQL
+Driver = /usr/lib/libmyodbc.so
+FileUsage = 1
+
+2.3) IBM db2
+[n.a.]
+
+3) The RDBMS must be setup; examples are provided for my installations
+of PostgreSQL and MySQL, but details may change; other RDBMSes should
+be configured in a similar manner, you need to find out the details by
+reading their documentation.
+
+3.1) PostgreSQL
+
+3.1.1) Start the server
+on RedHat:
+[root@localhost]# service postgresql start
+on other systems: read the docs...
+
+3.1.2) Create the database:
+[root@localhost]# su - postgres
+[postgres@localhost]$ createdb example
+
+3.1.3) Create the user:
+[root@localhost]# su - postgres
+[postgres@localhost]$ psql example
+example=> create user manager with password 'secret';
+example=> <control-D>
+
+3.1.4) Populate the database:
+[root@localhost]# cd $SOURCES/servers/slapd/back-sql/rdbms_depend/pgsql/
+[root@localhost]# psql -U manager -W example
+example=> <control-D>
+[root@localhost]# psql -U manager example < backsql_create.sql
+[root@localhost]# psql -U manager example < testdb_create.sql
+[root@localhost]# psql -U manager example < testdb_data.sql
+[root@localhost]# psql -U manager example < testdb_metadata.sql
+
+3.1.5) Run the test:
+[root@localhost]# cd $SOURCES/tests
+[root@localhost]# SLAPD_USE_SQL=pgsql ./run sql-test000
+
+3.2) MySQL
+
+3.2.1) Start the server
+on RedHat:
+[root@localhost]# service mysqld start
+on other systems: read the docs...
+
+3.2.2) Create the database:
+[root@localhost]# mysqladmin -u root -p create example
+(hit <return> for the empty password).
+
+3.2.3) Create the user:
+[root@localhost]# mysql -u root -p example
+(hit <return> for the empty password)
+mysql> grant all privileges on *.* \
+ to 'manager'@'localhost' identified by 'secret' with grant option;
+mysql> exit;
+
+3.2.4) Populate the database:
+[root@localhost]# cd $SOURCES/servers/slapd/back-sql/rdbms_depend/mysql/
+[root@localhost]# mysql -u manager -p example < backsql_create.sql
+[root@localhost]# mysql -u manager -p example < testdb_create.sql
+[root@localhost]# mysql -u manager -p example < testdb_data.sql
+[root@localhost]# mysql -u manager -p example < testdb_metadata.sql
+
+3.2.5) Run the test:
+[root@localhost]# cd $SOURCES/tests
+[root@localhost]# SLAPD_USE_SQL=mysql ./run sql-test000
+
+3.3) IBM db2
+[n.a.]
+
+3.3.1) Start the server:
+
+3.3.2) Create the database:
+
+3.3.3) Create the user:
+
+3.3.4) Populate the database:
+connect to the database as user manager, and execute the test files
+in auto-commit mode (-c)
+[root@localhost]# su - manager
+[manager@localhost]$ db2 "connect to example user manager using secret"
+[manager@localhost]$ db2 -ctvf backsql_create.sql
+[manager@localhost]$ db2 -ctvf testdb_create.sql
+[manager@localhost]$ db2 -ctvf testdb_data.sql
+[manager@localhost]$ db2 -ctvf testdb_metadata.sql
+[manager@localhost]$ db2 "connect reset"
+
+3.3.5) Run the test:
+[root@localhost]# cd $SOURCES/tests
+[root@localhost]# SLAPD_USE_SQL=ibmdb2 ./run sql-test000
+
+4) Cleanup:
+The test is basically readonly; this can be performed by all RDBMSes
+(listed above).
+
+There is another test, sql-test900-write, which is currently enabled
+only for PostgreSQL and IBM db2. Note that after a successful run
+of the write test, the database is no longer in the correct state
+to restart either of the tests, and step 3.X.4 needs to be re-run first.
+
+More tests are to come; PostgreSQL is known to allow a full reload
+of the test database starting from an empty database.
+
diff --git a/servers/slapd/back-sql/rdbms_depend/ibmdb2/backsql_create.sql b/servers/slapd/back-sql/rdbms_depend/ibmdb2/backsql_create.sql
new file mode 100644
index 0000000..cb2856b
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/ibmdb2/backsql_create.sql
@@ -0,0 +1,59 @@
+drop table ldap_oc_mappings;
+create table ldap_oc_mappings
+ (
+ id integer not null primary key,
+ name varchar(64) not null,
+ keytbl varchar(64) not null,
+ keycol varchar(64) not null,
+ create_proc varchar(255),
+ create_keyval varchar(255),
+ delete_proc varchar(255),
+ expect_return integer not null
+);
+
+drop table ldap_attr_mappings;
+create table ldap_attr_mappings
+ (
+ id integer not null primary key,
+ oc_map_id integer not null references ldap_oc_mappings(id),
+ name varchar(255) not null,
+ sel_expr varchar(255) not null,
+ sel_expr_u varchar(255),
+ from_tbls varchar(255) not null,
+ join_where varchar(255),
+ add_proc varchar(255),
+ delete_proc varchar(255),
+ param_order integer not null,
+ expect_return integer not null
+);
+
+drop table ldap_entries;
+create table ldap_entries
+ (
+ id integer not null primary key,
+ dn varchar(255) not null,
+ oc_map_id integer not null references ldap_oc_mappings(id),
+ parent int NOT NULL ,
+ keyval int NOT NULL
+);
+
+alter table ldap_entries add
+ constraint unq1_ldap_entries unique
+ (
+ oc_map_id,
+ keyval
+ );
+
+alter table ldap_entries add
+ constraint unq2_ldap_entries unique
+ (
+ dn
+ );
+
+drop table ldap_entry_objclasses;
+create table ldap_entry_objclasses
+ (
+ entry_id integer not null references ldap_entries(id),
+ oc_name varchar(64)
+ );
+
diff --git a/servers/slapd/back-sql/rdbms_depend/ibmdb2/backsql_drop.sql b/servers/slapd/back-sql/rdbms_depend/ibmdb2/backsql_drop.sql
new file mode 100644
index 0000000..49e7e3a
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/ibmdb2/backsql_drop.sql
@@ -0,0 +1,5 @@
+DROP TABLE ldap_referrals;
+DROP TABLE ldap_entry_objclasses;
+DROP TABLE ldap_attr_mappings;
+DROP TABLE ldap_entries;
+DROP TABLE ldap_oc_mappings;
diff --git a/servers/slapd/back-sql/rdbms_depend/ibmdb2/slapd.conf b/servers/slapd/back-sql/rdbms_depend/ibmdb2/slapd.conf
new file mode 100644
index 0000000..f6c1613
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/ibmdb2/slapd.conf
@@ -0,0 +1,36 @@
+# $OpenLDAP$
+#
+# See slapd.conf(5) for details on configuration options.
+# This file should NOT be world readable.
+#
+include /usr/local/etc/openldap/schema/core.schema
+include /usr/local/etc/openldap/schema/cosine.schema
+include /usr/local/etc/openldap/schema/inetorgperson.schema
+
+# Define global ACLs to disable default read access.
+
+# Do not enable referrals until AFTER you have a working directory
+# service AND an understanding of referrals.
+#referral ldap://root.openldap.org
+
+pidfile /usr/local/var/slapd.pid
+argsfile /usr/local/var/slapd.args
+
+#######################################################################
+# sql database definitions
+#######################################################################
+
+database sql
+suffix "o=sql,c=RU"
+rootdn "cn=root,o=sql,c=RU"
+rootpw secret
+dbname ldap_db2
+dbuser db2inst1
+dbpasswd ibmdb2
+subtree_cond "upper(ldap_entries.dn) LIKE CONCAT('%',?)"
+insentry_stmt "insert into ldap_entries (id,dn,oc_map_id,parent,keyval) values ((select max(id)+1 from ldap_entries),?,?,?,?)"
+upper_func "upper"
+upper_needs_cast "yes"
+create_needs_select "yes"
+has_ldapinfo_dn_ru "no"
+
diff --git a/servers/slapd/back-sql/rdbms_depend/ibmdb2/testdb_create.sql b/servers/slapd/back-sql/rdbms_depend/ibmdb2/testdb_create.sql
new file mode 100644
index 0000000..b6e850c
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/ibmdb2/testdb_create.sql
@@ -0,0 +1,75 @@
+drop table persons;
+CREATE TABLE persons (
+ id int NOT NULL,
+ name varchar(255) NOT NULL,
+ surname varchar(255) NOT NULL,
+ password varchar(64)
+);
+
+drop table institutes;
+CREATE TABLE institutes (
+ id int NOT NULL,
+ name varchar(255)
+);
+
+drop table documents;
+CREATE TABLE documents (
+ id int NOT NULL,
+ title varchar(255) NOT NULL,
+ abstract varchar(255)
+);
+
+drop table authors_docs;
+CREATE TABLE authors_docs (
+ pers_id int NOT NULL,
+ doc_id int NOT NULL
+);
+
+drop table phones;
+CREATE TABLE phones (
+ id int NOT NULL ,
+ phone varchar(255) NOT NULL ,
+ pers_id int NOT NULL
+);
+
+drop table referrals;
+CREATE TABLE referrals (
+ id int NOT NULL,
+ name varchar(255) NOT NULL,
+ url varchar(255) NOT NULL
+);
+
+
+
+ALTER TABLE authors_docs ADD
+ CONSTRAINT PK_authors_docs PRIMARY KEY
+ (
+ pers_id,
+ doc_id
+ );
+
+ALTER TABLE documents ADD
+ CONSTRAINT PK_documents PRIMARY KEY
+ (
+ id
+ );
+
+ALTER TABLE institutes ADD
+ CONSTRAINT PK_institutes PRIMARY KEY
+ (
+ id
+ );
+
+
+ALTER TABLE persons ADD
+ CONSTRAINT PK_persons PRIMARY KEY
+ (
+ id
+ );
+
+ALTER TABLE phones ADD
+ CONSTRAINT PK_phones PRIMARY KEY
+ (
+ id
+ );
+
diff --git a/servers/slapd/back-sql/rdbms_depend/ibmdb2/testdb_data.sql b/servers/slapd/back-sql/rdbms_depend/ibmdb2/testdb_data.sql
new file mode 100644
index 0000000..7bef374
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/ibmdb2/testdb_data.sql
@@ -0,0 +1,18 @@
+insert into institutes (id,name) values (1,'Example');
+
+insert into persons (id,name,surname,password) values (1,'Mitya','Kovalev','mit');
+insert into persons (id,name,surname) values (2,'Torvlobnor','Puzdoy');
+insert into persons (id,name,surname) values (3,'Akakiy','Zinberstein');
+
+insert into phones (id,phone,pers_id) values (1,'332-2334',1);
+insert into phones (id,phone,pers_id) values (2,'222-3234',1);
+insert into phones (id,phone,pers_id) values (3,'545-4563',2);
+
+insert into documents (id,abstract,title) values (1,'abstract1','book1');
+insert into documents (id,abstract,title) values (2,'abstract2','book2');
+
+insert into authors_docs (pers_id,doc_id) values (1,1);
+insert into authors_docs (pers_id,doc_id) values (1,2);
+insert into authors_docs (pers_id,doc_id) values (2,1);
+
+insert into referrals (id,name,url) values (1,'Referral','ldap://localhost:9012/');
diff --git a/servers/slapd/back-sql/rdbms_depend/ibmdb2/testdb_drop.sql b/servers/slapd/back-sql/rdbms_depend/ibmdb2/testdb_drop.sql
new file mode 100644
index 0000000..17b12af
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/ibmdb2/testdb_drop.sql
@@ -0,0 +1,5 @@
+DROP TABLE persons;
+DROP TABLE institutes;
+DROP TABLE documents;
+DROP TABLE authors_docs;
+DROP TABLE phones;
diff --git a/servers/slapd/back-sql/rdbms_depend/ibmdb2/testdb_metadata.sql b/servers/slapd/back-sql/rdbms_depend/ibmdb2/testdb_metadata.sql
new file mode 100644
index 0000000..0b0d1c2
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/ibmdb2/testdb_metadata.sql
@@ -0,0 +1,123 @@
+--mappings
+
+-- objectClass mappings: these may be viewed as structuralObjectClass, the ones that are used to decide how to build an entry
+-- id a unique number identifying the objectClass
+-- name the name of the objectClass; it MUST match the name of an objectClass that is loaded in slapd's schema
+-- keytbl the name of the table that is referenced for the primary key of an entry
+-- keycol the name of the column in "keytbl" that contains the primary key of an entry; the pair "keytbl.keycol" uniquely identifies an entry of objectClass "id"
+-- create_proc a procedure to create the entry
+-- create_keyval a query that returns the id of the last inserted entry
+-- delete_proc a procedure to delete the entry; it takes "keytbl.keycol" of the row to be deleted
+-- expect_return a bitmap that marks whether create_proc (1) and delete_proc (2) return a value or not
+insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,create_keyval,delete_proc,expect_return)
+values (1,'inetOrgPerson','persons','id','INSERT INTO persons (id,name,surname) VALUES ((SELECT max(id)+1 FROM persons),'''','''')',
+ 'SELECT max(id) FROM persons','DELETE FROM persons WHERE id=?',0);
+
+insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,create_keyval,delete_proc,expect_return)
+values (2,'document','documents','id','INSERT INTO documents (id,title,abstract) VALUES ((SELECT max(id)+1 FROM documents),'''','''')',
+ 'SELECT max(id) FROM documents','DELETE FROM documents WHERE id=?',0);
+
+insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,create_keyval,delete_proc,expect_return)
+values (3,'organization','institutes','id','INSERT INTO institutes (id,name) VALUES ((SELECT max(id)+1 FROM institutes),'''')',
+ 'SELECT max(id) FROM institutes','DELETE FROM institutes WHERE id=?',0);
+
+insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,create_keyval,delete_proc,expect_return)
+values (4,'referral','referrals','id','INSERT INTO referrals (id,name,url) VALUES ((SELECT max(id)+1 FROM referrals),'''','''')',
+ 'SELECT max(id) FROM referrals','DELETE FROM referrals WHERE id=?',0);
+
+-- attributeType mappings: describe how an attributeType for a certain objectClass maps to the SQL data.
+-- id a unique number identifying the attribute
+-- oc_map_id the value of "ldap_oc_mappings.id" that identifies the objectClass this attributeType is defined for
+-- name the name of the attributeType; it MUST match the name of an attributeType that is loaded in slapd's schema
+-- sel_expr the expression that is used to select this attribute (the "select <sel_expr> from ..." portion)
+-- from_tbls the expression that defines the table(s) this attribute is taken from (the "select ... from <from_tbls> where ..." portion)
+-- join_where the expression that defines the condition to select this attribute (the "select ... where <join_where> ..." portion)
+-- add_proc a procedure to insert the attribute; it takes the value of the attribute that is added, and the "keytbl.keycol" of the entry it is associated to
+-- delete_proc a procedure to delete the attribute; it takes the value of the attribute that is added, and the "keytbl.keycol" of the entry it is associated to
+-- param_order a mask that marks if the "keytbl.keycol" value comes before or after the value in add_proc (1) and delete_proc (2)
+-- expect_return a mask that marks whether add_proc (1) and delete_proc(2) are expected to return a value or not
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (1,1,'cn','persons.name||'' ''||persons.surname','persons',NULL,NULL,NULL,3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (2,1,'telephoneNumber','phones.phone','persons,phones',
+ 'phones.pers_id=persons.id','INSERT INTO phones (id,phone,pers_id) VALUES ((SELECT max(id)+1 FROM phones),?,?)',
+ 'DELETE FROM phones WHERE phone=? AND pers_id=?',3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (4,1,'givenName','persons.name','persons',NULL,'UPDATE persons SET name=? WHERE id=?',NULL,3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (3,1,'sn','persons.surname','persons',NULL,'UPDATE persons SET surname=? WHERE id=?',NULL,3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (5,1,'userPassword','persons.password','persons','persons.password IS NOT NULL','UPDATE persons SET password=? WHERE id=?',
+ 'UPDATE persons SET password=NULL WHERE password=? AND id=?',3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (6,1,'seeAlso','seeAlso.dn','ldap_entries AS seeAlso,documents,authors_docs,persons',
+ 'seeAlso.keyval=documents.id AND seeAlso.oc_map_id=2 AND authors_docs.doc_id=documents.id AND authors_docs.pers_id=persons.id',
+ NULL,NULL,3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (7,2,'description','documents.abstract','documents',NULL,'UPDATE documents SET abstract=? WHERE id=?',NULL,3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (8,2,'documentTitle','documents.title','documents',NULL,'UPDATE documents SET title=? WHERE id=?',NULL,3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (9,2,'documentAuthor','documentAuthor.dn','ldap_entries AS documentAuthor,documents,authors_docs,persons',
+ 'documentAuthor.keyval=persons.id AND documentAuthor.oc_map_id=1 AND authors_docs.doc_id=documents.id AND authors_docs.pers_id=persons.id',
+ 'INSERT INTO authors_docs (pers_id,doc_id) VALUES ((SELECT keyval FROM ldap_entries WHERE ucase(cast(? AS VARCHAR(255)))=ucase(dn)),?)',
+ 'DELETE FROM authors_docs WHERE pers_id=(SELECT keyval FROM ldap_entries WHERE ucase(cast(? AS VARCHAR(255))=ucase(dn)) AND doc_id=?',3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (10,2,'documentIdentifier','''document ''||rtrim(cast(documents.id AS CHAR(16)))','documents',NULL,NULL,NULL,3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (11,3,'o','institutes.name','institutes',NULL,'UPDATE institutes SET name=? WHERE id=?',NULL,3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (12,3,'dc','lcase(institutes.name)','institutes,ldap_entries AS dcObject,ldap_entry_objclasses as auxObjectClass',
+ 'institutes.id=dcObject.keyval AND dcObject.oc_map_id=3 AND dcObject.id=auxObjectClass.entry_id AND auxObjectClass.oc_name=''dcObject''',
+ NULL,NULL,3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (13,4,'ou','referrals.name','referrals',NULL,'UPDATE referrals SET name=? WHERE id=?',NULL,3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (14,4,'ref','referrals.url','referrals',NULL,'UPDATE referrals SET url=? WHERE id=?',NULL,3,0);
+
+-- entries mapping: each entry must appear in this table, with a unique DN rooted at the database naming context
+-- id a unique number > 0 identifying the entry
+-- dn the DN of the entry, in "pretty" form
+-- oc_map_id the "ldap_oc_mappings.id" of the main objectClass of this entry (view it as the structuralObjectClass)
+-- parent the "ldap_entries.id" of the parent of this objectClass; 0 if it is the "suffix" of the database
+-- keyval the value of the "keytbl.keycol" defined for this objectClass
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (1,'dc=example,dc=com',3,0,1);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (2,'cn=Mitya Kovalev,dc=example,dc=com',1,1,1);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (3,'cn=Torvlobnor Puzdoy,dc=example,dc=com',1,1,2);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (4,'cn=Akakiy Zinberstein,dc=example,dc=com',1,1,3);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (5,'documentTitle=book1,dc=example,dc=com',2,1,1);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (6,'documentTitle=book2,dc=example,dc=com',2,1,2);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (7,'ou=Referral,dc=example,dc=com',4,1,1);
+
+-- objectClass mapping: entries that have multiple objectClass instances are listed here with the objectClass name (view them as auxiliary objectClass)
+-- entry_id the "ldap_entries.id" of the entry this objectClass value must be added
+-- oc_name the name of the objectClass; it MUST match the name of an objectClass that is loaded in slapd's schema
+insert into ldap_entry_objclasses (entry_id,oc_name) values (1,'dcObject');
+
+insert into ldap_entry_objclasses (entry_id,oc_name) values (7,'extensibleObject');
diff --git a/servers/slapd/back-sql/rdbms_depend/mssql/backsql_create.sql b/servers/slapd/back-sql/rdbms_depend/mssql/backsql_create.sql
new file mode 100644
index 0000000..1f1f6d2
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/mssql/backsql_create.sql
@@ -0,0 +1,100 @@
+create table ldap_oc_mappings (
+ id int identity (1, 1) not null ,
+ name varchar (64) not null ,
+ keytbl varchar (64) not null ,
+ keycol varchar (64) not null ,
+ create_proc varchar (255) NULL ,
+ delete_proc varchar (255) NULL,
+ expect_return int not null
+)
+GO
+
+alter table ldap_oc_mappings add
+ constraint pk_ldap_oc_mappings primary key
+ (
+ id
+ )
+GO
+
+
+alter table ldap_oc_mappings add
+ constraint unq1_ldap_oc_mappings unique
+ (
+ name
+ )
+GO
+
+
+create table ldap_attr_mappings (
+ id int identity (1, 1) not null ,
+ oc_map_id int not null references ldap_oc_mappings(id),
+ name varchar (255) not null ,
+ sel_expr varchar (255) not null ,
+ sel_expr_u varchar(255),
+ from_tbls varchar (255) not null ,
+ join_where varchar (255) NULL ,
+ add_proc varchar (255) NULL ,
+ delete_proc varchar (255) NULL ,
+ param_order int not null,
+ expect_return int not null
+)
+GO
+
+alter table ldap_attr_mappings add
+ constraint pk_ldap_attr_mappings primary key
+ (
+ id
+ )
+GO
+
+
+create table ldap_entries (
+ id int identity (1, 1) not null ,
+ dn varchar (255) not null ,
+ oc_map_id int not null references ldap_oc_mappings(id),
+ parent int not null ,
+ keyval int not null
+)
+GO
+
+
+alter table ldap_entries add
+ constraint pk_ldap_entries primary key
+ (
+ id
+ )
+GO
+
+alter table ldap_entries add
+ constraint unq1_ldap_entries unique
+ (
+ oc_map_id,
+ keyval
+ )
+GO
+
+alter table ldap_entries add
+ constraint unq2_ldap_entries unique
+ (
+ dn
+ )
+GO
+
+
+create table ldap_referrals
+ (
+ entry_id int not null references ldap_entries(id),
+ url text not null
+)
+GO
+
+create index entry_idx on ldap_referrals(entry_id);
+
+create table ldap_entry_objclasses
+ (
+ entry_id int not null references ldap_entries(id),
+ oc_name varchar(64)
+ )
+GO
+
+create index entry_idx on ldap_entry_objclasses(entry_id);
diff --git a/servers/slapd/back-sql/rdbms_depend/mssql/backsql_drop.sql b/servers/slapd/back-sql/rdbms_depend/mssql/backsql_drop.sql
new file mode 100644
index 0000000..0e888b3
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/mssql/backsql_drop.sql
@@ -0,0 +1,14 @@
+drop table ldap_attr_mappings
+GO
+
+drop table ldap_referrals
+GO
+
+drop table ldap_entry_objclasses
+GO
+
+drop table ldap_entries
+GO
+
+drop table ldap_oc_mappings
+GO
diff --git a/servers/slapd/back-sql/rdbms_depend/mssql/slapd.conf b/servers/slapd/back-sql/rdbms_depend/mssql/slapd.conf
new file mode 100644
index 0000000..c3032f2
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/mssql/slapd.conf
@@ -0,0 +1,30 @@
+# $OpenLDAP$
+#
+# See slapd.conf(5) for details on configuration options.
+# This file should NOT be world readable.
+#
+include ./schema/core.schema
+include ./schema/cosine.schema
+include ./schema/inetorgperson.schema
+
+# Define global ACLs to disable default read access.
+
+# Do not enable referrals until AFTER you have a working directory
+# service AND an understanding of referrals.
+#referral ldap://root.openldap.org
+
+pidfile ./slapd.pid
+argsfile ./slapd.args
+
+#######################################################################
+# sql database definitions
+#######################################################################
+
+database sql
+suffix "o=sql,c=RU"
+rootdn "cn=root,o=sql,c=RU"
+rootpw secret
+dbname ldap_mssql
+dbuser ldap
+dbpasswd ldap
+subtree_cond "ldap_entries.dn LIKE '%'+?"
diff --git a/servers/slapd/back-sql/rdbms_depend/mssql/testdb_create.sql b/servers/slapd/back-sql/rdbms_depend/mssql/testdb_create.sql
new file mode 100644
index 0000000..2034afd
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/mssql/testdb_create.sql
@@ -0,0 +1,74 @@
+
+CREATE TABLE authors_docs (
+ pers_id int NOT NULL ,
+ doc_id int NOT NULL
+)
+GO
+
+CREATE TABLE documents (
+ id int IDENTITY (1, 1) NOT NULL ,
+ abstract varchar (255) NULL ,
+ title varchar (255) NULL ,
+ body binary (255) NULL
+)
+GO
+
+CREATE TABLE institutes (
+ id int IDENTITY (1, 1) NOT NULL ,
+ name varchar (255) NOT NULL
+)
+GO
+
+
+CREATE TABLE persons (
+ id int IDENTITY (1, 1) NOT NULL ,
+ name varchar (255) NULL ,
+ surname varchar (255) NULL ,
+ password varchar (64) NULL
+)
+GO
+
+CREATE TABLE phones (
+ id int IDENTITY (1, 1) NOT NULL ,
+ phone varchar (255) NOT NULL ,
+ pers_id int NOT NULL
+)
+GO
+
+ALTER TABLE authors_docs WITH NOCHECK ADD
+ CONSTRAINT PK_authors_docs PRIMARY KEY
+ (
+ pers_id,
+ doc_id
+ )
+GO
+
+ALTER TABLE documents WITH NOCHECK ADD
+ CONSTRAINT PK_documents PRIMARY KEY
+ (
+ id
+ )
+GO
+
+ALTER TABLE institutes WITH NOCHECK ADD
+ CONSTRAINT PK_institutes PRIMARY KEY
+ (
+ id
+ )
+GO
+
+
+ALTER TABLE persons WITH NOCHECK ADD
+ CONSTRAINT PK_persons PRIMARY KEY
+ (
+ id
+ )
+GO
+
+ALTER TABLE phones WITH NOCHECK ADD
+ CONSTRAINT PK_phones PRIMARY KEY
+ (
+ id
+ )
+GO
+
diff --git a/servers/slapd/back-sql/rdbms_depend/mssql/testdb_data.sql b/servers/slapd/back-sql/rdbms_depend/mssql/testdb_data.sql
new file mode 100644
index 0000000..21a51ef
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/mssql/testdb_data.sql
@@ -0,0 +1,24 @@
+set IDENTITY_INSERT institutes ON
+insert into institutes (id,name) values (1,'Example')
+set IDENTITY_INSERT institutes OFF
+
+set IDENTITY_INSERT persons ON
+insert into persons (id,name,surname,password) values (1,'Mitya','Kovalev','mit')
+insert into persons (id,name,surname) values (2,'Torvlobnor','Puzdoy')
+insert into persons (id,name,surname) values (3,'Akakiy','Zinberstein')
+set IDENTITY_INSERT persons OFF
+
+set IDENTITY_INSERT phones ON
+insert into phones (id,phone,pers_id) values (1,'332-2334',1)
+insert into phones (id,phone,pers_id) values (2,'222-3234',1)
+insert into phones (id,phone,pers_id) values (3,'545-4563',2)
+set IDENTITY_INSERT phones OFF
+
+set IDENTITY_INSERT documents ON
+insert into documents (id,abstract,title) values (1,'abstract1','book1')
+insert into documents (id,abstract,title) values (2,'abstract2','book2')
+set IDENTITY_INSERT documents OFF
+
+insert into authors_docs (pers_id,doc_id) values (1,1)
+insert into authors_docs (pers_id,doc_id) values (1,2)
+insert into authors_docs (pers_id,doc_id) values (2,1)
diff --git a/servers/slapd/back-sql/rdbms_depend/mssql/testdb_drop.sql b/servers/slapd/back-sql/rdbms_depend/mssql/testdb_drop.sql
new file mode 100644
index 0000000..4842ed8
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/mssql/testdb_drop.sql
@@ -0,0 +1,39 @@
+drop procedure create_person
+drop procedure set_person_name
+drop procedure delete_phone
+drop procedure add_phone
+drop procedure make_doc_link
+drop procedure del_doc_link
+drop procedure delete_person
+
+drop procedure create_org
+drop procedure set_org_name
+drop procedure delete_org
+
+drop procedure create_document
+drop procedure set_doc_title
+drop procedure set_doc_abstract
+drop procedure make_author_link
+drop procedure del_author_link
+drop procedure delete_document
+
+if exists (select * from sysobjects where id = object_id(N'authors_docs') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
+drop table authors_docs
+GO
+
+if exists (select * from sysobjects where id = object_id(N'documents') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
+drop table documents
+GO
+
+if exists (select * from sysobjects where id = object_id(N'institutes') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
+drop table institutes
+GO
+
+if exists (select * from sysobjects where id = object_id(N'persons') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
+drop table persons
+GO
+
+if exists (select * from sysobjects where id = object_id(N'phones') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
+drop table phones
+GO
+
diff --git a/servers/slapd/back-sql/rdbms_depend/mssql/testdb_metadata.sql b/servers/slapd/back-sql/rdbms_depend/mssql/testdb_metadata.sql
new file mode 100644
index 0000000..e087523
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/mssql/testdb_metadata.sql
@@ -0,0 +1,198 @@
+-- mappings
+
+
+SET IDENTITY_INSERT ldap_oc_mappings ON
+insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,delete_proc,expect_return)
+values (1,'inetOrgPerson','persons','id','{call create_person(?)}','{call delete_person(?)}',0)
+
+insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,delete_proc,expect_return)
+values (2,'document','documents','id','{call create_document(?)}','{call delete_document(?)}',0)
+
+insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,delete_proc,expect_return)
+values (3,'organization','institutes','id','{call create_org(?)}','{call delete_org(?)}',0)
+SET IDENTITY_INSERT ldap_oc_mappings OFF
+
+
+SET IDENTITY_INSERT ldap_attr_mappings ON
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (1,1,'cn','persons.name+'' ''+persons.surname','persons',NULL,
+ NULL,NULL,0,0)
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (2,1,'telephoneNumber','phones.phone','persons,phones',
+ 'phones.pers_id=persons.id','{call add_phone(?,?)}',
+ '{call delete_phone(?,?)}',0,0)
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (3,1,'givenName','persons.name','persons',NULL,
+ '{call set_person_name(?,?)}',NULL,0,0)
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (4,1,'sn','persons.surname','persons',NULL,
+ '{call set_person_surname(?,?)}',NULL,0,0)
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (5,1,'userPassword','persons.password','persons','persons.password IS NOT NULL',
+ '{call set_person_password(?,?)}','call del_person_password(?,?)',0,0)
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (6,1,'seeAlso','seeAlso.dn','ldap_entries AS seeAlso,documents,authors_docs,persons',
+ 'seeAlso.keyval=documents.id AND seeAlso.oc_map_id=2 AND authors_docs.doc_id=documents.id AND authors_docs.pers_id=persons.id',
+ NULL,NULL,0,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (7,2,'description','documents.abstract','documents',NULL,'{call set_doc_abstract(?,?)}',
+ NULL,0,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (8,2,'documentTitle','documents.title','documents',NULL, '{call set_doc_title(?,?)}',
+ NULL,0,0)
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (9,2,'documentAuthor','documentAuthor.dn','ldap_entries AS documentAuthor,documents,authors_docs,persons',
+ 'documentAuthor.keyval=persons.id AND documentAuthor.oc_map_id=1 AND authors_docs.doc_id=documents.id AND authors_docs.pers_id=persons.id',
+ 'INSERT INTO authors_docs (pers_id,doc_id) VALUES ((SELECT ldap_entries.keyval FROM ldap_entries WHERE upper(?)=upper(ldap_entries.dn)),?)',
+ 'DELETE FROM authors_docs WHERE authors_docs.pers_id=(SELECT ldap_entries.keyval FROM ldap_entries WHERE upper(?)=upper(ldap_entries.dn)) AND authors_docs.doc_id=?',3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (10,2,'documentIdentifier','''document ''+text(documents.id)','documents',
+ NULL,NULL,NULL,0,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (11,3,'o','institutes.name','institutes',NULL,NULL,NULL,3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (12,3,'dc','lower(institutes.name)','institutes,ldap_entries AS dcObject,ldap_entry_objclasses AS auxObjectClass',
+ 'institutes.id=dcObject.keyval AND dcObject.oc_map_id=3 AND dcObject.id=auxObjectClass.entry_id AND auxObjectClass.oc_name=''dcObject''',
+ '{call set_org_name(?,?)}',NULL,3,0);
+
+SET IDENTITY_INSERT ldap_attr_mappings OFF
+
+-- entries
+
+SET IDENTITY_INSERT ldap_entries ON
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (1,'dc=example,dc=com',3,0,1)
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (2,'cn=Mitya Kovalev,dc=example,dc=com',1,1,1)
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (3,'cn=Torvlobnor Puzdoy,dc=example,dc=com',1,1,2)
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (4,'cn=Akakiy Zinberstein,dc=example,dc=com',1,1,3)
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (5,'documentTitle=book1,dc=example,dc=com',2,1,1)
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (6,'documentTitle=book2,dc=example,dc=com',2,1,2)
+
+SET IDENTITY_INSERT ldap_entries OFF
+
+-- referrals
+insert into ldap_entry_objclasses (entry_id,oc_name)
+values (1,'dcObject');
+
+insert into ldap_entry_objclasses (entry_id,oc_name)
+values (4,'referral');
+
+insert into ldap_referrals (entry_id,url)
+values (4,'ldap://localhost:9012/');
+
+-- support procedures
+
+SET QUOTED_IDENTIFIER OFF SET ANSI_NULLS ON
+GO
+
+
+CREATE PROCEDURE create_person @@keyval int OUTPUT AS
+INSERT INTO example.persons (name) VALUES ('');
+set @@keyval=(SELECT MAX(id) FROM example.persons)
+GO
+
+CREATE PROCEDURE delete_person @keyval int AS
+DELETE FROM example.phones WHERE pers_id=@keyval;
+DELETE FROM example.authors_docs WHERE pers_id=@keyval;
+DELETE FROM example.persons WHERE id=@keyval;
+GO
+
+CREATE PROCEDURE create_org @@keyval int OUTPUT AS
+INSERT INTO example.institutes (name) VALUES ('');
+set @@keyval=(SELECT MAX(id) FROM example.institutes)
+GO
+
+CREATE PROCEDURE delete_org @keyval int AS
+DELETE FROM example.institutes WHERE id=@keyval;
+GO
+
+CREATE PROCEDURE create_document @@keyval int OUTPUT AS
+INSERT INTO example.documents (title) VALUES ('');
+set @@keyval=(SELECT MAX(id) FROM example.documents)
+GO
+
+CREATE PROCEDURE delete_document @keyval int AS
+DELETE FROM example.authors_docs WHERE doc_id=@keyval;
+DELETE FROM example.documents WHERE id=@keyval;
+GO
+
+CREATE PROCEDURE add_phone @pers_id int, @phone varchar(255) AS
+INSERT INTO example.phones (pers_id,phone) VALUES (@pers_id,@phone)
+GO
+
+CREATE PROCEDURE delete_phone @keyval int,@phone varchar(64) AS
+DELETE FROM example.phones WHERE pers_id=@keyval AND phone=@phone;
+GO
+
+CREATE PROCEDURE set_person_name @keyval int, @new_name varchar(255) AS
+UPDATE example.persons SET name=@new_name WHERE id=@keyval;
+GO
+
+CREATE PROCEDURE set_person_surname @keyval int, @new_surname varchar(255) AS
+UPDATE example.persons SET surname=@new_surname WHERE id=@keyval;
+GO
+
+CREATE PROCEDURE set_org_name @keyval int, @new_name varchar(255) AS
+UPDATE example.institutes SET name=@new_name WHERE id=@keyval;
+GO
+
+CREATE PROCEDURE set_doc_title @keyval int, @new_title varchar(255) AS
+UPDATE example.documents SET title=@new_title WHERE id=@keyval;
+GO
+
+CREATE PROCEDURE set_doc_abstract @keyval int, @new_abstract varchar(255) AS
+UPDATE example.documents SET abstract=@new_abstract WHERE id=@keyval;
+GO
+
+CREATE PROCEDURE make_author_link @keyval int, @author_dn varchar(255) AS
+DECLARE @per_id int;
+SET @per_id=(SELECT keyval FROM example.ldap_entries
+ WHERE oc_map_id=1 AND dn=@author_dn);
+IF NOT (@per_id IS NULL)
+ INSERT INTO example.authors_docs (doc_id,pers_id) VALUES (@keyval,@per_id);
+GO
+
+CREATE PROCEDURE make_doc_link @keyval int, @doc_dn varchar(255) AS
+DECLARE @doc_id int;
+SET @doc_id=(SELECT keyval FROM example.ldap_entries
+ WHERE oc_map_id=2 AND dn=@doc_dn);
+IF NOT (@doc_id IS NULL)
+ INSERT INTO example.authors_docs (pers_id,doc_id) VALUES (@keyval,@doc_id);
+GO
+
+CREATE PROCEDURE del_doc_link @keyval int, @doc_dn varchar(255) AS
+DECLARE @doc_id int;
+SET @doc_id=(SELECT keyval FROM example.ldap_entries
+ WHERE oc_map_id=2 AND dn=@doc_dn);
+IF NOT (@doc_id IS NULL)
+DELETE FROM example.authors_docs WHERE pers_id=@keyval AND doc_id=@doc_id;
+GO
+
+CREATE PROCEDURE del_author_link @keyval int, @author_dn varchar(255) AS
+DECLARE @per_id int;
+SET @per_id=(SELECT keyval FROM example.ldap_entries
+ WHERE oc_map_id=1 AND dn=@author_dn);
+IF NOT (@per_id IS NULL)
+ DELETE FROM example.authors_docs WHERE doc_id=@keyval AND pers_id=@per_id;
+GO
diff --git a/servers/slapd/back-sql/rdbms_depend/mysql/backsql_create.sql b/servers/slapd/back-sql/rdbms_depend/mysql/backsql_create.sql
new file mode 100644
index 0000000..b2029c4
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/mysql/backsql_create.sql
@@ -0,0 +1,58 @@
+drop table if exists ldap_oc_mappings;
+create table ldap_oc_mappings
+ (
+ id integer unsigned not null primary key auto_increment,
+ name varchar(64) not null,
+ keytbl varchar(64) not null,
+ keycol varchar(64) not null,
+ create_proc varchar(255),
+ delete_proc varchar(255),
+ expect_return tinyint not null
+);
+
+drop table if exists ldap_attr_mappings;
+create table ldap_attr_mappings
+ (
+ id integer unsigned not null primary key auto_increment,
+ oc_map_id integer unsigned not null references ldap_oc_mappings(id),
+ name varchar(255) not null,
+ sel_expr varchar(255) not null,
+ sel_expr_u varchar(255),
+ from_tbls varchar(255) not null,
+ join_where varchar(255),
+ add_proc varchar(255),
+ delete_proc varchar(255),
+ param_order tinyint not null,
+ expect_return tinyint not null
+);
+
+drop table if exists ldap_entries;
+create table ldap_entries
+ (
+ id integer unsigned not null primary key auto_increment,
+ dn varchar(255) not null,
+ oc_map_id integer unsigned not null references ldap_oc_mappings(id),
+ parent int NOT NULL ,
+ keyval int NOT NULL
+);
+
+alter table ldap_entries add
+ constraint unq1_ldap_entries unique
+ (
+ oc_map_id,
+ keyval
+ );
+
+alter table ldap_entries add
+ constraint unq2_ldap_entries unique
+ (
+ dn
+ );
+
+drop table if exists ldap_entry_objclasses;
+create table ldap_entry_objclasses
+ (
+ entry_id integer unsigned not null references ldap_entries(id),
+ oc_name varchar(64)
+ );
+
diff --git a/servers/slapd/back-sql/rdbms_depend/mysql/backsql_drop.sql b/servers/slapd/back-sql/rdbms_depend/mysql/backsql_drop.sql
new file mode 100644
index 0000000..a81fa8b
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/mysql/backsql_drop.sql
@@ -0,0 +1,7 @@
+DROP TABLE IF EXISTS ldap_entry_objclasses;
+
+DROP TABLE IF EXISTS ldap_attr_mappings;
+
+DROP TABLE IF EXISTS ldap_entries;
+
+DROP TABLE IF EXISTS ldap_oc_mappings;
diff --git a/servers/slapd/back-sql/rdbms_depend/mysql/slapd.conf b/servers/slapd/back-sql/rdbms_depend/mysql/slapd.conf
new file mode 100644
index 0000000..8f6e4e1
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/mysql/slapd.conf
@@ -0,0 +1,32 @@
+# $OpenLDAP$
+#
+# See slapd.conf(5) for details on configuration options.
+# This file should NOT be world readable.
+#
+include /usr/local/etc/openldap/schema/core.schema
+include /usr/local/etc/openldap/schema/cosine.schema
+include /usr/local/etc/openldap/schema/inetorgperson.schema
+
+# Define global ACLs to disable default read access.
+
+# Do not enable referrals until AFTER you have a working directory
+# service AND an understanding of referrals.
+#referral ldap://root.openldap.org
+
+pidfile /usr/local/var/slapd.pid
+argsfile /usr/local/var/slapd.args
+
+#######################################################################
+# sql database definitions
+#######################################################################
+
+database sql
+suffix "o=sql,c=RU"
+rootdn "cn=root,o=sql,c=RU"
+rootpw secret
+dbname ldap_mysql
+dbuser root
+dbpasswd
+subtree_cond "ldap_entries.dn LIKE CONCAT('%',?)"
+insentry_stmt "INSERT INTO ldap_entries (dn,oc_map_id,parent,keyval) VALUES (?,?,?,?)"
+has_ldapinfo_dn_ru no
diff --git a/servers/slapd/back-sql/rdbms_depend/mysql/testdb_create.sql b/servers/slapd/back-sql/rdbms_depend/mysql/testdb_create.sql
new file mode 100644
index 0000000..b35261b
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/mysql/testdb_create.sql
@@ -0,0 +1,86 @@
+drop table if exists persons;
+CREATE TABLE persons (
+ id int NOT NULL,
+ name varchar(255) NOT NULL,
+ surname varchar(255) NOT NULL,
+ password varchar(64)
+);
+
+drop table if exists institutes;
+CREATE TABLE institutes (
+ id int NOT NULL,
+ name varchar(255)
+);
+
+drop table if exists documents;
+CREATE TABLE documents (
+ id int NOT NULL,
+ title varchar(255) NOT NULL,
+ abstract varchar(255)
+);
+
+drop table if exists authors_docs;
+CREATE TABLE authors_docs (
+ pers_id int NOT NULL,
+ doc_id int NOT NULL
+);
+
+drop table if exists phones;
+CREATE TABLE phones (
+ id int NOT NULL ,
+ phone varchar(255) NOT NULL ,
+ pers_id int NOT NULL
+);
+
+drop table if exists certs;
+CREATE TABLE certs (
+ id int NOT NULL ,
+ cert LONGBLOB NOT NULL,
+ pers_id int NOT NULL
+);
+
+ALTER TABLE authors_docs ADD
+ CONSTRAINT PK_authors_docs PRIMARY KEY
+ (
+ pers_id,
+ doc_id
+ );
+
+ALTER TABLE documents ADD
+ CONSTRAINT PK_documents PRIMARY KEY
+ (
+ id
+ );
+
+ALTER TABLE institutes ADD
+ CONSTRAINT PK_institutes PRIMARY KEY
+ (
+ id
+ );
+
+
+ALTER TABLE persons ADD
+ CONSTRAINT PK_persons PRIMARY KEY
+ (
+ id
+ );
+
+ALTER TABLE phones ADD
+ CONSTRAINT PK_phones PRIMARY KEY
+ (
+ id
+ );
+
+ALTER TABLE certs ADD
+ CONSTRAINT PK_certs PRIMARY KEY
+ (
+ id
+ );
+
+drop table if exists referrals;
+CREATE TABLE referrals (
+ id int NOT NULL,
+ name varchar(255) NOT NULL,
+ url varchar(255) NOT NULL
+);
+
diff --git a/servers/slapd/back-sql/rdbms_depend/mysql/testdb_data.sql b/servers/slapd/back-sql/rdbms_depend/mysql/testdb_data.sql
new file mode 100644
index 0000000..0ccbfb7
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/mysql/testdb_data.sql
@@ -0,0 +1,21 @@
+insert into institutes (id,name) values (1,'Example');
+
+insert into persons (id,name,surname,password) values (1,'Mitya','Kovalev','mit');
+insert into persons (id,name,surname) values (2,'Torvlobnor','Puzdoy');
+insert into persons (id,name,surname) values (3,'Akakiy','Zinberstein');
+
+insert into phones (id,phone,pers_id) values (1,'332-2334',1);
+insert into phones (id,phone,pers_id) values (2,'222-3234',1);
+insert into phones (id,phone,pers_id) values (3,'545-4563',2);
+
+insert into documents (id,abstract,title) values (1,'abstract1','book1');
+insert into documents (id,abstract,title) values (2,'abstract2','book2');
+
+insert into authors_docs (pers_id,doc_id) values (1,1);
+insert into authors_docs (pers_id,doc_id) values (1,2);
+insert into authors_docs (pers_id,doc_id) values (2,1);
+
+insert into referrals (id,name,url) values (1,'Referral','ldap://localhost:9012/');
+
+insert into certs (id,cert,pers_id) values (1,UNHEX('3082036b308202d4a003020102020102300d06092a864886f70d01010405003077310b3009060355040613025553311330110603550408130a43616c69666f726e6961311f301d060355040a13164f70656e4c444150204578616d706c652c204c74642e311330110603550403130a4578616d706c65204341311d301b06092a864886f70d010901160e6361406578616d706c652e636f6d301e170d3033313031373136333331395a170d3034313031363136333331395a307e310b3009060355040613025553311330110603550408130a43616c69666f726e6961311f301d060355040a13164f70656e4c444150204578616d706c652c204c74642e311830160603550403130f557273756c612048616d7073746572311f301d06092a864886f70d01090116107568616d406578616d706c652e636f6d30819f300d06092a864886f70d010101050003818d0030818902818100eec60a7910b57d2e687158ca55eea738d36f10413dfecf31435e1aeeb9713b8e2da7dd2dde6bc6cec03b4987eaa7b037b9eb50e11c71e58088cc282883122cd8329c6f24f6045e6be9d21b9190c8292998267a5f7905292de936262747ab4b76a88a63872c41629a69d32e894d44c896a8d06fab0a1bc7de343c6c1458478f290203010001a381ff3081fc30090603551d1304023000302c06096086480186f842010d041f161d4f70656e53534c2047656e657261746564204365727469666963617465301d0603551d0e04160414a323de136c19ae0c479450e882dfb10ad147f45e3081a10603551d2304819930819680144b6f211a3624d290f943b053472d7de1c0e69823a17ba4793077310b3009060355040613025553311330110603550408130a43616c69666f726e6961311f301d060355040a13164f70656e4c444150204578616d706c652c204c74642e311330110603550403130a4578616d706c65204341311d301b06092a864886f70d010901160e6361406578616d706c652e636f6d820100300d06092a864886f70d010104050003818100881470045bdce95660d6e6af59e6a844aec4b9f5eaea88d4eb7a5a47080afa64750f81a3e47d00fd39c69a17a1c66d29d36f06edc537107f8c592239c2d4da55fb3f1d488e7b2387ad2a551cbd1ceb070ae9e020a9467275cb28798abb4cbfff98ddb3f1e7689b067072392511bb08125b5bec2bc207b7b6b275c47248f29acd'),3);
+
diff --git a/servers/slapd/back-sql/rdbms_depend/mysql/testdb_drop.sql b/servers/slapd/back-sql/rdbms_depend/mysql/testdb_drop.sql
new file mode 100644
index 0000000..7c5e9e7
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/mysql/testdb_drop.sql
@@ -0,0 +1,5 @@
+DROP TABLE IF EXISTS persons;
+DROP TABLE IF EXISTS institutes;
+DROP TABLE IF EXISTS documents;
+DROP TABLE IF EXISTS authors_docs;
+DROP TABLE IF EXISTS phones;
diff --git a/servers/slapd/back-sql/rdbms_depend/mysql/testdb_metadata.sql b/servers/slapd/back-sql/rdbms_depend/mysql/testdb_metadata.sql
new file mode 100644
index 0000000..d7e88e4
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/mysql/testdb_metadata.sql
@@ -0,0 +1,125 @@
+-- mappings
+
+-- objectClass mappings: these may be viewed as structuralObjectClass, the ones that are used to decide how to build an entry
+-- id a unique number identifying the objectClass
+-- name the name of the objectClass; it MUST match the name of an objectClass that is loaded in slapd's schema
+-- keytbl the name of the table that is referenced for the primary key of an entry
+-- keycol the name of the column in "keytbl" that contains the primary key of an entry; the pair "keytbl.keycol" uniquely identifies an entry of objectClass "id"
+-- create_proc a procedure to create the entry
+-- delete_proc a procedure to delete the entry; it takes "keytbl.keycol" of the row to be deleted
+-- expect_return a bitmap that marks whether create_proc (1) and delete_proc (2) return a value or not
+insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,delete_proc,expect_return)
+values (1,'inetOrgPerson','persons','id',NULL,NULL,0);
+
+insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,delete_proc,expect_return)
+values (2,'document','documents','id',NULL,NULL,0);
+
+insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,delete_proc,expect_return)
+values (3,'organization','institutes','id',NULL,NULL,0);
+
+insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,delete_proc,expect_return)
+values (4,'referral','referrals','id',NULL,NULL,0);
+
+-- attributeType mappings: describe how an attributeType for a certain objectClass maps to the SQL data.
+-- id a unique number identifying the attribute
+-- oc_map_id the value of "ldap_oc_mappings.id" that identifies the objectClass this attributeType is defined for
+-- name the name of the attributeType; it MUST match the name of an attributeType that is loaded in slapd's schema
+-- sel_expr the expression that is used to select this attribute (the "select <sel_expr> from ..." portion)
+-- from_tbls the expression that defines the table(s) this attribute is taken from (the "select ... from <from_tbls> where ..." portion)
+-- join_where the expression that defines the condition to select this attribute (the "select ... where <join_where> ..." portion)
+-- add_proc a procedure to insert the attribute; it takes the value of the attribute that is added, and the "keytbl.keycol" of the entry it is associated to
+-- delete_proc a procedure to delete the attribute; it takes the value of the attribute that is added, and the "keytbl.keycol" of the entry it is associated to
+-- param_order a mask that marks if the "keytbl.keycol" value comes before or after the value in add_proc (1) and delete_proc (2)
+-- expect_return a mask that marks whether add_proc (1) and delete_proc(2) are expected to return a value or not
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (1,1,'cn',"concat(persons.name,' ',persons.surname)",'persons',NULL,NULL,NULL,3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (2,1,'telephoneNumber','phones.phone','persons,phones',
+ 'phones.pers_id=persons.id',NULL,NULL,3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (3,1,'givenName','persons.name','persons',NULL,NULL,NULL,3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (4,1,'sn','persons.surname','persons',NULL,NULL,NULL,3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (5,1,'userPassword','persons.password','persons','persons.password IS NOT NULL',NULL,NULL,3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (6,1,'seeAlso','seeAlso.dn','ldap_entries AS seeAlso,documents,authors_docs,persons',
+ 'seeAlso.keyval=documents.id AND seeAlso.oc_map_id=2 AND authors_docs.doc_id=documents.id AND authors_docs.pers_id=persons.id',
+ NULL,NULL,3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (7,2,'description','documents.abstract','documents',NULL,NULL,NULL,3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (8,2,'documentTitle','documents.title','documents',NULL,NULL,NULL,3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (9,2,'documentAuthor','documentAuthor.dn','ldap_entries AS documentAuthor,documents,authors_docs,persons',
+ 'documentAuthor.keyval=persons.id AND documentAuthor.oc_map_id=1 AND authors_docs.doc_id=documents.id AND authors_docs.pers_id=persons.id',
+ NULL,NULL,3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (10,2,'documentIdentifier','concat(''document '',documents.id)','documents',NULL,NULL,NULL,3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (11,3,'o','institutes.name','institutes',NULL,NULL,NULL,3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (12,3,'dc','lower(institutes.name)','institutes,ldap_entries AS dcObject,ldap_entry_objclasses as auxObjectClass',
+ 'institutes.id=dcObject.keyval AND dcObject.oc_map_id=3 AND dcObject.id=auxObjectClass.entry_id AND auxObjectClass.oc_name=''dcObject''',
+ NULL,NULL,3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (13,4,'ou','referrals.name','referrals',NULL,NULL,NULL,3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (14,4,'ref','referrals.url','referrals',NULL,NULL,NULL,3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (15,1,'userCertificate','certs.cert','persons,certs',
+ 'certs.pers_id=persons.id',NULL,NULL,3,0);
+
+-- entries mapping: each entry must appear in this table, with a unique DN rooted at the database naming context
+-- id a unique number > 0 identifying the entry
+-- dn the DN of the entry, in "pretty" form
+-- oc_map_id the "ldap_oc_mappings.id" of the main objectClass of this entry (view it as the structuralObjectClass)
+-- parent the "ldap_entries.id" of the parent of this objectClass; 0 if it is the "suffix" of the database
+-- keyval the value of the "keytbl.keycol" defined for this objectClass
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (1,'dc=example,dc=com',3,0,1);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (2,'cn=Mitya Kovalev,dc=example,dc=com',1,1,1);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (3,'cn=Torvlobnor Puzdoy,dc=example,dc=com',1,1,2);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (4,'cn=Akakiy Zinberstein,dc=example,dc=com',1,1,3);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (5,'documentTitle=book1,dc=example,dc=com',2,1,1);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (6,'documentTitle=book2,dc=example,dc=com',2,1,2);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (7,'ou=Referral,dc=example,dc=com',4,1,1);
+
+-- objectClass mapping: entries that have multiple objectClass instances are listed here with the objectClass name (view them as auxiliary objectClass)
+-- entry_id the "ldap_entries.id" of the entry this objectClass value must be added
+-- oc_name the name of the objectClass; it MUST match the name of an objectClass that is loaded in slapd's schema
+insert into ldap_entry_objclasses (entry_id,oc_name)
+values (1,'dcObject');
+
+insert into ldap_entry_objclasses (entry_id,oc_name)
+values (4,'pkiUser');
+
+insert into ldap_entry_objclasses (entry_id,oc_name)
+values (7,'extensibleObject');
+
diff --git a/servers/slapd/back-sql/rdbms_depend/oracle/backsql_create.sql b/servers/slapd/back-sql/rdbms_depend/oracle/backsql_create.sql
new file mode 100644
index 0000000..2e4e6ec
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/oracle/backsql_create.sql
@@ -0,0 +1,90 @@
+create table ldap_oc_mappings (
+ id number not null ,
+ name varchar2(64) not null ,
+ keytbl varchar2(64) not null ,
+ keycol varchar2(64) not null ,
+ create_proc varchar2(255),
+ delete_proc varchar2(255),
+ expect_return number not null
+);
+
+alter table ldap_oc_mappings add
+ constraint PK_ldap_oc_mappings primary key
+ (
+ id
+ );
+
+alter table ldap_oc_mappings add
+ constraint unq_ldap_oc_mappings unique
+ (
+ name
+ );
+
+create table ldap_attr_mappings (
+ id number not null,
+ oc_map_id number not null references ldap_oc_mappings(id),
+ name varchar2(255) not null,
+ sel_expr varchar2(255) not null,
+ sel_expr_u varchar2(255),
+ from_tbls varchar2(255) not null,
+ join_where varchar2(255),
+ add_proc varchar2(255),
+ delete_proc varchar2(255),
+ param_order number not null,
+ expect_return number not null
+);
+
+alter table ldap_attr_mappings add
+ constraint pk_ldap_attr_mappings primary key
+ (
+ id
+ );
+
+
+create table ldap_entries (
+ id number not null ,
+ dn varchar2(255) not null ,
+ dn_ru varchar2(255),
+ oc_map_id number not null references ldap_oc_mappings(id),
+ parent number not null ,
+ keyval number not null
+);
+
+alter table ldap_entries add
+ constraint PK_ldap_entries primary key
+ (
+ id
+ );
+
+alter table ldap_entries add
+ constraint unq1_ldap_entries unique
+ (
+ oc_map_id,
+ keyval
+ );
+
+alter table ldap_entries add
+ constraint unq2_ldap_entries unique
+ (
+ dn
+ );
+
+create sequence ldap_objclass_ids start with 1 increment by 1;
+
+create sequence ldap_attr_ids start with 1 increment by 1;
+
+create sequence ldap_entry_ids start with 1 increment by 1;
+
+create table ldap_referrals
+ (
+ entry_id number not null references ldap_entries(id),
+ url varchar(1023) not null
+);
+
+create table ldap_entry_objclasses
+ (
+ entry_id number not null references ldap_entries(id),
+ oc_name varchar(64)
+ );
+
+quit
diff --git a/servers/slapd/back-sql/rdbms_depend/oracle/backsql_drop.sql b/servers/slapd/back-sql/rdbms_depend/oracle/backsql_drop.sql
new file mode 100644
index 0000000..19bb8b6
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/oracle/backsql_drop.sql
@@ -0,0 +1,8 @@
+drop table ldap_attr_mappings;
+drop table ldap_entry_objclasses;
+drop table ldap_referrals;
+drop sequence ldap_entry_ids;
+drop sequence ldap_attr_ids;
+drop sequence ldap_objclass_ids;
+drop table ldap_entries;
+drop table ldap_oc_mappings;
diff --git a/servers/slapd/back-sql/rdbms_depend/oracle/slapd.conf b/servers/slapd/back-sql/rdbms_depend/oracle/slapd.conf
new file mode 100644
index 0000000..cc195d9
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/oracle/slapd.conf
@@ -0,0 +1,32 @@
+# $OpenLDAP$
+#
+# See slapd.conf(5) for details on configuration options.
+# This file should NOT be world readable.
+#
+include /usr/local/etc/openldap/schema/core.schema
+include /usr/local/etc/openldap/schema/cosine.schema
+include /usr/local/etc/openldap/schema/inetorgperson.schema
+
+# Define global ACLs to disable default read access.
+
+# Do not enable referrals until AFTER you have a working directory
+# service AND an understanding of referrals.
+#referral ldap://root.openldap.org
+
+pidfile /usr/local/var/slapd.pid
+argsfile /usr/local/var/slapd.args
+
+#######################################################################
+# sql database definitions
+#######################################################################
+
+database sql
+suffix "o=sql,c=RU"
+rootdn "cn=root,o=sql,c=RU"
+rootpw secret
+dbname ldap_ora8
+dbuser ldap
+dbpasswd ldap
+subtree_cond "UPPER(ldap_entries.dn) LIKE CONCAT('%',UPPER(?))"
+insentry_stmt "INSERT INTO ldap_entries (id,dn,oc_map_id,parent,keyval) VALUES (ldap_entry_ids.nextval,?,?,?,?)"
+upper_func UPPER
diff --git a/servers/slapd/back-sql/rdbms_depend/oracle/testdb_create.sql b/servers/slapd/back-sql/rdbms_depend/oracle/testdb_create.sql
new file mode 100644
index 0000000..710a5fa
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/oracle/testdb_create.sql
@@ -0,0 +1,68 @@
+CREATE TABLE persons (
+ id NUMBER NOT NULL,
+ name varchar2(255) NOT NULL,
+ surname varchar2(255) NOT NULL,
+ password varchar2(64) NOT NULL
+);
+
+CREATE TABLE institutes (
+ id NUMBER NOT NULL,
+ name varchar2(255)
+);
+
+CREATE TABLE documents (
+ id NUMBER NOT NULL,
+ title varchar2(255) NOT NULL,
+ abstract varchar2(255)
+);
+
+CREATE TABLE authors_docs (
+ pers_id NUMBER NOT NULL,
+ doc_id NUMBER NOT NULL
+);
+
+CREATE TABLE phones (
+ id NUMBER NOT NULL ,
+ phone varchar2(255) NOT NULL ,
+ pers_id NUMBER NOT NULL
+);
+
+
+ALTER TABLE authors_docs ADD
+ CONSTRAINT PK_authors_docs PRIMARY KEY
+ (
+ pers_id,
+ doc_id
+ );
+
+ALTER TABLE documents ADD
+ CONSTRAINT PK_documents PRIMARY KEY
+ (
+ id
+ );
+
+ALTER TABLE institutes ADD
+ CONSTRAINT PK_institutes PRIMARY KEY
+ (
+ id
+ );
+
+ALTER TABLE persons ADD
+ CONSTRAINT PK_persons PRIMARY KEY
+ (
+ id
+ );
+
+ALTER TABLE phones ADD
+ CONSTRAINT PK_phones PRIMARY KEY
+ (
+ id
+ );
+
+CREATE SEQUENCE person_ids START WITH 1 INCREMENT BY 1;
+
+CREATE SEQUENCE document_ids START WITH 1 INCREMENT BY 1;
+
+CREATE SEQUENCE institute_ids START WITH 1 INCREMENT BY 1;
+
+CREATE SEQUENCE phone_ids START WITH 1 INCREMENT BY 1;
diff --git a/servers/slapd/back-sql/rdbms_depend/oracle/testdb_data.sql b/servers/slapd/back-sql/rdbms_depend/oracle/testdb_data.sql
new file mode 100644
index 0000000..4fc1977
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/oracle/testdb_data.sql
@@ -0,0 +1,27 @@
+insert into institutes (id,name) values (institute_ids.nextval,'example');
+
+insert into persons (id,name,surname,password) values (person_ids.nextval,'Mitya','Kovalev','mit');
+
+insert into persons (id,name,surname) values (person_ids.nextval,'Torvlobnor','Puzdoy');
+
+insert into persons (id,name,surname) values (person_ids.nextval,'Akakiy','Zinberstein');
+
+
+insert into phones (id,phone,pers_id) values (phone_ids.nextval,'332-2334',1);
+
+insert into phones (id,phone,pers_id) values (phone_ids.nextval,'222-3234',1);
+
+insert into phones (id,phone,pers_id) values (phone_ids.nextval,'545-4563',2);
+
+
+insert into documents (id,abstract,title) values (document_ids.nextval,'abstract1','book1');
+
+insert into documents (id,abstract,title) values (document_ids.nextval,'abstract2','book2');
+
+
+insert into authors_docs (pers_id,doc_id) values (1,1);
+
+insert into authors_docs (pers_id,doc_id) values (1,2);
+
+insert into authors_docs (pers_id,doc_id) values (2,1);
+
diff --git a/servers/slapd/back-sql/rdbms_depend/oracle/testdb_drop.sql b/servers/slapd/back-sql/rdbms_depend/oracle/testdb_drop.sql
new file mode 100644
index 0000000..0cf4463
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/oracle/testdb_drop.sql
@@ -0,0 +1,25 @@
+DROP TABLE persons;
+DROP TABLE institutes;
+DROP TABLE documents;
+DROP TABLE authors_docs;
+DROP TABLE phones;
+DROP SEQUENCE person_ids;
+DROP SEQUENCE institute_ids;
+DROP SEQUENCE document_ids;
+DROP SEQUENCE phone_ids;
+DROP PROCEDURE create_person;
+DROP PROCEDURE delete_person;
+DROP PROCEDURE add_phone;
+DROP PROCEDURE delete_phone;
+DROP PROCEDURE set_person_name;
+DROP PROCEDURE set_org_name;
+DROP PROCEDURE set_doc_title;
+DROP PROCEDURE set_doc_abstract;
+DROP PROCEDURE create_document;
+DROP PROCEDURE create_org;
+DROP PROCEDURE delete_document;
+DROP PROCEDURE delete_org;
+DROP PROCEDURE make_doc_link;
+DROP PROCEDURE del_doc_link;
+DROP PROCEDURE make_author_link;
+DROP PROCEDURE del_author_link;
diff --git a/servers/slapd/back-sql/rdbms_depend/oracle/testdb_metadata.sql b/servers/slapd/back-sql/rdbms_depend/oracle/testdb_metadata.sql
new file mode 100644
index 0000000..354d7bd
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/oracle/testdb_metadata.sql
@@ -0,0 +1,252 @@
+-- mappings
+
+-- objectClass mappings: these may be viewed as structuralObjectClass, the ones that are used to decide how to build an entry
+-- id a unique number identifying the objectClass
+-- name the name of the objectClass; it MUST match the name of an objectClass that is loaded in slapd's schema
+-- keytbl the name of the table that is referenced for the primary key of an entry
+-- keycol the name of the column in "keytbl" that contains the primary key of an entry; the pair "keytbl.keycol" uniquely identifies an entry of objectClass "id"
+-- create_proc a procedure to create the entry
+-- delete_proc a procedure to delete the entry; it takes "keytbl.keycol" of the row to be deleted
+-- expect_return a bitmap that marks whether create_proc (1) and delete_proc (2) return a value or not
+insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,delete_proc,expect_return)
+values (1,'inetOrgPerson','persons','id','call create_person(?)','call delete_person(?)',0);
+
+insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,delete_proc,expect_return)
+values (2,'document','documents','id','call create_document(?)','call delete_document(?)',0);
+
+insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,delete_proc,expect_return)
+values (3,'organization','institutes','id','call create_org(?)','call delete_org(?)',0);
+
+-- attributeType mappings: describe how an attributeType for a certain objectClass maps to the SQL data.
+-- id a unique number identifying the attribute
+-- oc_map_id the value of "ldap_oc_mappings.id" that identifies the objectClass this attributeType is defined for
+-- name the name of the attributeType; it MUST match the name of an attributeType that is loaded in slapd's schema
+-- sel_expr the expression that is used to select this attribute (the "select <sel_expr> from ..." portion)
+-- from_tbls the expression that defines the table(s) this attribute is taken from (the "select ... from <from_tbls> where ..." portion)
+-- join_where the expression that defines the condition to select this attribute (the "select ... where <join_where> ..." portion)
+-- add_proc a procedure to insert the attribute; it takes the value of the attribute that is added, and the "keytbl.keycol" of the entry it is associated to
+-- delete_proc a procedure to delete the attribute; it takes the value of the attribute that is added, and the "keytbl.keycol" of the entry it is associated to
+-- param_order a mask that marks if the "keytbl.keycol" value comes before or after the value in add_proc (1) and delete_proc (2)
+-- expect_return a mask that marks whether add_proc (1) and delete_proc(2) are expected to return a value or not
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (1,1,'cn','persons.name||'' ''||persons.surname','persons',NULL,
+ NULL,NULL,0,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (2,1,'telephoneNumber','phones.phone','persons,phones',
+ 'phones.pers_id=persons.id','call add_phone(?,?)',
+ 'call delete_phone(?,?)',0,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (3,1,'givenName','persons.name','persons',NULL,'call set_person_name(?,?)',
+ NULL,0,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (4,1,'sn','persons.surname','persons',NULL,'call set_person_surname(?,?)',
+ NULL,0,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (5,1,'userPassword','persons.password','persons',
+ 'persons.password IS NOT NULL','call set_person_password(?,?)',
+ NULL,0,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (6,1,'seeAlso','seeAlso.dn','ldap_entries seeAlso,documents,authors_docs,persons',
+ 'seeAlso.keyval=documents.id AND seeAlso.oc_map_id=2 AND authors_docs.doc_id=documents.id AND authors_docs.pers_id=persons.id',
+ NULL,NULL,0,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (7,2,'description','documents.abstract','documents',NULL,'call set_doc_abstract(?,?)',
+ NULL,0,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (8,2,'documentTitle','documents.title','documents',NULL,'call set_doc_title(?,?)',NULL,0,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (9,2,'documentAuthor','documentAuthor.dn','ldap_entries documentAuthor,documents,authors_docs,persons',
+ 'documentAuthor.keyval=persons.id AND documentAuthor.oc_map_id=1 AND authors_docs.doc_id=documents.id AND authors_docs.pers_id=persons.id',
+ '?=call make_author_link(?,?)','?=call del_author_link(?,?)',0,3);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (10,2,'documentIdentifier','''document ''||text(documents.id)','documents',NULL,NULL,NULL,0,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (11,3,'o','institutes.name','institutes',NULL,'call set_org_name(?,?)',NULL,0,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (12,3,'dc','lower(institutes.name)','institutes,ldap_entries dcObject,ldap_entry_objclasses auxObjectClass',
+ 'institutes.id=dcObject.keyval AND dcObject.oc_map_id=3 AND dcObject.id=auxObjectClass.entry_id AND auxObjectClass.oc_name=''dcObject''',
+ NULL,NULL,0,0);
+
+-- entries mapping: each entry must appear in this table, with a unique DN rooted at the database naming context
+-- id a unique number > 0 identifying the entry
+-- dn the DN of the entry, in "pretty" form
+-- oc_map_id the "ldap_oc_mappings.id" of the main objectClass of this entry (view it as the structuralObjectClass)
+-- parent the "ldap_entries.id" of the parent of this objectClass; 0 if it is the "suffix" of the database
+-- keyval the value of the "keytbl.keycol" defined for this objectClass
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (ldap_entry_ids.nextval,'dc=example,dc=com',3,0,1);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (ldap_entry_ids.nextval,'cn=Mitya Kovalev,dc=example,dc=com',1,1,1);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (ldap_entry_ids.nextval,'cn=Torvlobnor Puzdoy,dc=example,dc=com',1,1,2);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (ldap_entry_ids.nextval,'cn=Akakiy Zinberstein,dc=example,dc=com',1,1,3);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (ldap_entry_ids.nextval,'documentTitle=book1,dc=example,dc=com',2,1,1);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (ldap_entry_ids.nextval,'documentTitle=book2,dc=example,dc=com',2,1,2);
+
+-- objectClass mapping: entries that have multiple objectClass instances are listed here with the objectClass name (view them as auxiliary objectClass)
+-- entry_id the "ldap_entries.id" of the entry this objectClass value must be added
+-- oc_name the name of the objectClass; it MUST match the name of an objectClass that is loaded in slapd's schema
+insert into ldap_entry_objclasses (entry_id,oc_name)
+values (1,'dcObject');
+
+insert into ldap_entry_objclasses (entry_id,oc_name)
+values (4,'referral');
+
+-- referrals mapping: entries that should be treated as referrals are stored here
+-- entry_id the "ldap_entries.id" of the entry that should be treated as a referral
+-- url the URI of the referral
+insert into ldap_referrals (entry_id,url)
+values (4,'ldap://localhost:9012/');
+
+
+-- procedures
+-- these procedures are specific for this RDBMS and are used in mapping objectClass and attributeType creation/modify/deletion
+CREATE OR REPLACE PROCEDURE create_person(keyval OUT NUMBER) AS
+BEGIN
+INSERT INTO persons (id,name) VALUES (person_ids.nextval,' ');
+SELECT person_ids.currval INTO keyval FROM DUAL;
+END;
+/
+
+CREATE OR REPLACE PROCEDURE delete_person(keyval IN NUMBER) AS
+BEGIN
+DELETE FROM phones WHERE pers_id=keyval;
+DELETE FROM authors_docs WHERE pers_id=keyval;
+DELETE FROM persons WHERE id=keyval;
+END;
+/
+
+CREATE OR REPLACE PROCEDURE create_org(keyval OUT NUMBER) AS
+BEGIN
+INSERT INTO institutes (id,name) VALUES (institute_ids.nextval,' ');
+SELECT institute_ids.currval INTO keyval FROM DUAL;
+END;
+/
+
+CREATE OR REPLACE PROCEDURE delete_org(keyval IN NUMBER) AS
+BEGIN
+DELETE FROM institutes WHERE id=keyval;
+END;
+/
+
+CREATE OR REPLACE PROCEDURE create_document(keyval OUT NUMBER) AS
+BEGIN
+INSERT INTO documents (id,title) VALUES (document_ids.nextval,' ');
+SELECT document_ids.currval INTO keyval FROM DUAL;
+END;
+/
+
+CREATE OR REPLACE PROCEDURE delete_document (keyval IN NUMBER) AS
+BEGIN
+DELETE FROM authors_docs WHERE doc_id=keyval;
+DELETE FROM documents WHERE id=keyval;
+END;
+/
+
+CREATE OR REPLACE PROCEDURE add_phone(pers_id IN NUMBER, phone IN varchar2) AS
+BEGIN
+INSERT INTO phones (id,pers_id,phone) VALUES (phone_ids.nextval,pers_id,phone);
+END;
+/
+
+CREATE OR REPLACE PROCEDURE delete_phone(keyval IN NUMBER, phone IN varchar2) AS
+BEGIN
+DELETE FROM phones WHERE pers_id=keyval AND phone=phone;
+END;
+/
+
+CREATE OR REPLACE PROCEDURE set_person_name(keyval IN NUMBER, new_name IN varchar2) AS
+BEGIN
+UPDATE persons SET name=new_name WHERE id=keyval;
+END;
+/
+
+CREATE OR REPLACE PROCEDURE set_org_name(keyval IN NUMBER, new_name IN varchar2) AS
+BEGIN
+UPDATE institutes SET name=new_name WHERE id=keyval;
+END;
+/
+
+CREATE OR REPLACE PROCEDURE set_doc_title (keyval IN NUMBER, new_title IN varchar2) AS
+BEGIN
+UPDATE documents SET title=new_title WHERE id=keyval;
+END;
+/
+
+CREATE OR REPLACE PROCEDURE set_doc_abstract (keyval IN NUMBER, new_abstract IN varchar2) AS
+BEGIN
+UPDATE documents SET abstract=new_abstract WHERE id=keyval;
+END;
+/
+
+CREATE OR REPLACE FUNCTION make_author_link (keyval IN NUMBER, author_dn IN varchar2) RETURN NUMBER AS
+per_id NUMBER;
+BEGIN
+SELECT keyval INTO per_id FROM ldap_entries
+ WHERE oc_map_id=1 AND dn=author_dn;
+IF NOT (per_id IS NULL) THEN
+ INSERT INTO authors_docs (doc_id,pers_id) VALUES (keyval,per_id);
+ RETURN 1;
+END IF;
+RETURN 0;
+END;
+/
+
+CREATE OR REPLACE FUNCTION make_doc_link (keyval IN NUMBER, doc_dn IN varchar2) RETURN NUMBER AS
+docid NUMBER;
+BEGIN
+SELECT keyval INTO docid FROM ldap_entries
+ WHERE oc_map_id=2 AND dn=doc_dn;
+IF NOT (docid IS NULL) THEN
+ INSERT INTO authors_docs (pers_id,doc_id) VALUES (keyval,docid);
+ RETURN 1;
+END IF;
+RETURN 0;
+END;
+/
+
+CREATE OR REPLACE FUNCTION del_doc_link (keyval IN NUMBER, doc_dn IN varchar2) RETURN NUMBER AS
+docid NUMBER;
+BEGIN
+SELECT keyval INTO docid FROM ldap_entries
+ WHERE oc_map_id=2 AND dn=doc_dn;
+IF NOT (docid IS NULL) THEN
+ DELETE FROM authors_docs WHERE pers_id=keyval AND doc_id=docid;
+ RETURN 1;
+END IF;
+RETURN 0;
+END;
+/
+
+CREATE OR REPLACE FUNCTION del_author_link (keyval IN NUMBER, author_dn IN varchar2) RETURN NUMBER AS
+per_id NUMBER;
+BEGIN
+SELECT keyval INTO per_id FROM ldap_entries
+ WHERE oc_map_id=1 AND dn=author_dn;
+
+IF NOT (per_id IS NULL) THEN
+ DELETE FROM authors_docs WHERE doc_id=keyval AND pers_id=per_id;
+ RETURN 1;
+END IF;
+ RETURN 0;
+END;
+/
diff --git a/servers/slapd/back-sql/rdbms_depend/pgsql/backsql_create.sql b/servers/slapd/back-sql/rdbms_depend/pgsql/backsql_create.sql
new file mode 100644
index 0000000..a4baa70
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/pgsql/backsql_create.sql
@@ -0,0 +1,50 @@
+drop table ldap_oc_mappings;
+drop sequence ldap_oc_mappings_id_seq;
+create table ldap_oc_mappings
+ (
+ id serial not null primary key,
+ name varchar(64) not null,
+ keytbl varchar(64) not null,
+ keycol varchar(64) not null,
+ create_proc varchar(255),
+ delete_proc varchar(255),
+ expect_return int not null
+);
+
+drop table ldap_attr_mappings;
+drop sequence ldap_attr_mappings_id_seq;
+create table ldap_attr_mappings
+ (
+ id serial not null primary key,
+ oc_map_id integer not null references ldap_oc_mappings(id),
+ name varchar(255) not null,
+ sel_expr varchar(255) not null,
+ sel_expr_u varchar(255),
+ from_tbls varchar(255) not null,
+ join_where varchar(255),
+ add_proc varchar(255),
+ delete_proc varchar(255),
+ param_order int not null,
+ expect_return int not null
+);
+
+drop table ldap_entries;
+drop sequence ldap_entries_id_seq;
+create table ldap_entries
+ (
+ id serial not null primary key,
+ dn varchar(255) not null,
+ oc_map_id integer not null references ldap_oc_mappings(id),
+ parent int NOT NULL,
+ keyval int NOT NULL,
+ UNIQUE ( oc_map_id, keyval ),
+ UNIQUE ( dn )
+);
+
+drop table ldap_entry_objclasses;
+create table ldap_entry_objclasses
+ (
+ entry_id integer not null references ldap_entries(id),
+ oc_name varchar(64)
+ );
+
diff --git a/servers/slapd/back-sql/rdbms_depend/pgsql/backsql_drop.sql b/servers/slapd/back-sql/rdbms_depend/pgsql/backsql_drop.sql
new file mode 100644
index 0000000..eff0a9e
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/pgsql/backsql_drop.sql
@@ -0,0 +1,4 @@
+DROP TABLE ldap_entry_objclasses;
+DROP TABLE ldap_attr_mappings;
+DROP TABLE ldap_entries;
+DROP TABLE ldap_oc_mappings;
diff --git a/servers/slapd/back-sql/rdbms_depend/pgsql/slapd.conf b/servers/slapd/back-sql/rdbms_depend/pgsql/slapd.conf
new file mode 100644
index 0000000..70a8dee
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/pgsql/slapd.conf
@@ -0,0 +1,35 @@
+# $OpenLDAP$
+#
+# See slapd.conf(5) for details on configuration options.
+# This file should NOT be world readable.
+#
+include /usr/local/etc/openldap/schema/core.schema
+include /usr/local/etc/openldap/schema/cosine.schema
+include /usr/local/etc/openldap/schema/inetorgperson.schema
+
+# Define global ACLs to disable default read access.
+
+# Do not enable referrals until AFTER you have a working directory
+# service AND an understanding of referrals.
+#referral ldap://root.openldap.org
+
+pidfile /usr/local/var/slapd.pid
+argsfile /usr/local/var/slapd.args
+
+#######################################################################
+# sql database definitions
+#######################################################################
+
+database sql
+suffix "o=sql,c=RU"
+rootdn "cn=root,o=sql,c=RU"
+rootpw secret
+dbname PostgreSQL
+dbuser postgres
+dbpasswd postgres
+insentry_stmt "insert into ldap_entries (id,dn,oc_map_id,parent,keyval) values ((select max(id)+1 from ldap_entries),?,?,?,?)"
+upper_func "upper"
+strcast_func "text"
+concat_pattern "?||?"
+has_ldapinfo_dn_ru no
+
diff --git a/servers/slapd/back-sql/rdbms_depend/pgsql/testdb_create.sql b/servers/slapd/back-sql/rdbms_depend/pgsql/testdb_create.sql
new file mode 100644
index 0000000..e1c57e7
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/pgsql/testdb_create.sql
@@ -0,0 +1,55 @@
+drop table persons;
+drop sequence persons_id_seq;
+create table persons (
+ id serial not null primary key,
+ name varchar(255) not null,
+ surname varchar(255) not null,
+ password varchar(64)
+);
+
+drop table institutes;
+drop sequence institutes_id_seq;
+create table institutes (
+ id serial not null primary key,
+ name varchar(255)
+);
+
+drop table documents;
+drop sequence documents_id_seq;
+create table documents (
+ id serial not null primary key,
+ title varchar(255) not null,
+ abstract varchar(255)
+);
+
+drop table authors_docs;
+create table authors_docs (
+ pers_id int not null,
+ doc_id int not null,
+ primary key ( pers_id, doc_id )
+);
+
+drop table phones;
+drop sequence phones_id_seq;
+create table phones (
+ id serial not null primary key,
+ phone varchar(255) not null ,
+ pers_id int not null
+);
+
+drop table certs;
+drop sequence certs_id_seq;
+CREATE TABLE certs (
+ id int not null primary key,
+ cert bytea not null,
+ pers_id int not null
+);
+
+drop table referrals;
+drop sequence referrals_id_seq;
+create table referrals (
+ id serial not null primary key,
+ name varchar(255) not null,
+ url varchar(255) not null
+);
+
diff --git a/servers/slapd/back-sql/rdbms_depend/pgsql/testdb_data.sql b/servers/slapd/back-sql/rdbms_depend/pgsql/testdb_data.sql
new file mode 100644
index 0000000..0e661d4
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/pgsql/testdb_data.sql
@@ -0,0 +1,21 @@
+insert into institutes (id,name) values (1,'Example');
+
+insert into persons (id,name,surname,password) values (1,'Mitya','Kovalev','mit');
+insert into persons (id,name,surname) values (2,'Torvlobnor','Puzdoy');
+insert into persons (id,name,surname) values (3,'Akakiy','Zinberstein');
+
+insert into phones (id,phone,pers_id) values (1,'332-2334',1);
+insert into phones (id,phone,pers_id) values (2,'222-3234',1);
+insert into phones (id,phone,pers_id) values (3,'545-4563',2);
+
+insert into documents (id,abstract,title) values (1,'abstract1','book1');
+insert into documents (id,abstract,title) values (2,'abstract2','book2');
+
+insert into authors_docs (pers_id,doc_id) values (1,1);
+insert into authors_docs (pers_id,doc_id) values (1,2);
+insert into authors_docs (pers_id,doc_id) values (2,1);
+
+insert into referrals (id,name,url) values (1,'Referral','ldap://localhost:9012/');
+
+insert into certs (id,cert,pers_id) values (1,decode('MIIDazCCAtSgAwIBAgIBAjANBgkqhkiG9w0BAQQFADB3MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEfMB0GA1UEChMWT3BlbkxEQVAgRXhhbXBsZSwgTHRkLjETMBEGA1UEAxMKRXhhbXBsZSBDQTEdMBsGCSqGSIb3DQEJARYOY2FAZXhhbXBsZS5jb20wHhcNMDMxMDE3MTYzMzE5WhcNMDQxMDE2MTYzMzE5WjB+MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEfMB0GA1UEChMWT3BlbkxEQVAgRXhhbXBsZSwgTHRkLjEYMBYGA1UEAxMPVXJzdWxhIEhhbXBzdGVyMR8wHQYJKoZIhvcNAQkBFhB1aGFtQGV4YW1wbGUuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDuxgp5ELV9LmhxWMpV7qc4028QQT3+zzFDXhruuXE7ji2n3S3ea8bOwDtJh+qnsDe561DhHHHlgIjMKCiDEizYMpxvJPYEXmvp0huRkMgpKZgmel95BSkt6TYmJ0erS3aoimOHLEFimmnTLolNRMiWqNBvqwobx940PGwUWEePKQIDAQABo4H/MIH8MAkGA1UdEwQCMAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBSjI94TbBmuDEeUUOiC37EK0Uf0XjCBoQYDVR0jBIGZMIGWgBRLbyEaNiTSkPlDsFNHLX3hwOaYI6F7pHkwdzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExHzAdBgNVBAoTFk9wZW5MREFQIEV4YW1wbGUsIEx0ZC4xEzARBgNVBAMTCkV4YW1wbGUgQ0ExHTAbBgkqhkiG9w0BCQEWDmNhQGV4YW1wbGUuY29tggEAMA0GCSqGSIb3DQEBBAUAA4GBAIgUcARb3OlWYNbmr1nmqESuxLn16uqI1Ot6WkcICvpkdQ+Bo+R9AP05xpoXocZtKdNvBu3FNxB/jFkiOcLU2lX7Px1Ijnsjh60qVRy9HOsHCungIKlGcnXLKHmKu0y//5jds/HnaJsGcHI5JRG7CBJbW+wrwge3trJ1xHJI8prN','base64'),3);
+
diff --git a/servers/slapd/back-sql/rdbms_depend/pgsql/testdb_drop.sql b/servers/slapd/back-sql/rdbms_depend/pgsql/testdb_drop.sql
new file mode 100644
index 0000000..c061ff8
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/pgsql/testdb_drop.sql
@@ -0,0 +1,13 @@
+DROP TABLE persons;
+DROP TABLE institutes;
+DROP TABLE documents;
+DROP TABLE authors_docs;
+DROP TABLE phones;
+DROP TABLE referrals;
+DROP FUNCTION create_person ();
+DROP FUNCTION update_person_cn (varchar, int);
+DROP FUNCTION add_phone (varchar, int);
+DROP FUNCTION create_doc ();
+DROP FUNCTION create_o ();
+DROP FUNCTION create_referral ();
+
diff --git a/servers/slapd/back-sql/rdbms_depend/pgsql/testdb_metadata.sql b/servers/slapd/back-sql/rdbms_depend/pgsql/testdb_metadata.sql
new file mode 100644
index 0000000..d645cf2
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/pgsql/testdb_metadata.sql
@@ -0,0 +1,146 @@
+-- mappings
+
+-- objectClass mappings: these may be viewed as structuralObjectClass, the ones that are used to decide how to build an entry
+-- id a unique number identifying the objectClass
+-- name the name of the objectClass; it MUST match the name of an objectClass that is loaded in slapd's schema
+-- keytbl the name of the table that is referenced for the primary key of an entry
+-- keycol the name of the column in "keytbl" that contains the primary key of an entry; the pair "keytbl.keycol" uniquely identifies an entry of objectClass "id"
+-- create_proc a procedure to create the entry
+-- delete_proc a procedure to delete the entry; it takes "keytbl.keycol" of the row to be deleted
+-- expect_return a bitmap that marks whether create_proc (1) and delete_proc (2) return a value or not
+insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,delete_proc,expect_return) values (1,'inetOrgPerson','persons','id','SELECT create_person()','DELETE FROM persons WHERE id=?',0);
+
+insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,delete_proc,expect_return) values (2,'document','documents','id','SELECT create_doc()','DELETE FROM documents WHERE id=?',0);
+
+insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,delete_proc,expect_return) values (3,'organization','institutes','id','SELECT create_o()','DELETE FROM institutes WHERE id=?',0);
+
+insert into ldap_oc_mappings (id,name,keytbl,keycol,create_proc,delete_proc,expect_return) values (4,'referral','referrals','id','SELECT create_referral()','DELETE FROM referrals WHERE id=?',0);
+
+-- attributeType mappings: describe how an attributeType for a certain objectClass maps to the SQL data.
+-- id a unique number identifying the attribute
+-- oc_map_id the value of "ldap_oc_mappings.id" that identifies the objectClass this attributeType is defined for
+-- name the name of the attributeType; it MUST match the name of an attributeType that is loaded in slapd's schema
+-- sel_expr the expression that is used to select this attribute (the "select <sel_expr> from ..." portion)
+-- from_tbls the expression that defines the table(s) this attribute is taken from (the "select ... from <from_tbls> where ..." portion)
+-- join_where the expression that defines the condition to select this attribute (the "select ... where <join_where> ..." portion)
+-- add_proc a procedure to insert the attribute; it takes the value of the attribute that is added, and the "keytbl.keycol" of the entry it is associated to
+-- delete_proc a procedure to delete the attribute; it takes the value of the attribute that is added, and the "keytbl.keycol" of the entry it is associated to
+-- param_order a mask that marks if the "keytbl.keycol" value comes before or after the value in add_proc (1) and delete_proc (2)
+-- expect_return a mask that marks whether add_proc (1) and delete_proc(2) are expected to return a value or not
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) values (1,1,'cn','text(persons.name||'' ''||persons.surname)','persons',NULL,'SELECT update_person_cn(?,?)','SELECT 1 FROM persons WHERE persons.name=? AND persons.id=? AND 1=0',3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) values (2,1,'telephoneNumber','phones.phone','persons,phones','phones.pers_id=persons.id','SELECT add_phone(?,?)','DELETE FROM phones WHERE phone=? AND pers_id=?',3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) values (3,1,'givenName','persons.name','persons',NULL,'UPDATE persons SET name=? WHERE id=?','UPDATE persons SET name='''' WHERE (name=? OR name='''') AND id=?',3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) values (4,1,'sn','persons.surname','persons',NULL,'UPDATE persons SET surname=? WHERE id=?','UPDATE persons SET surname='''' WHERE (surname=? OR surname='''') AND id=?',3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) values (5,1,'userPassword','persons.password','persons','persons.password IS NOT NULL','UPDATE persons SET password=? WHERE id=?','UPDATE persons SET password=NULL WHERE password=? AND id=?',3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) values (6,1,'seeAlso','seeAlso.dn','ldap_entries AS seeAlso,documents,authors_docs,persons','seeAlso.keyval=documents.id AND seeAlso.oc_map_id=2 AND authors_docs.doc_id=documents.id AND authors_docs.pers_id=persons.id',NULL,'DELETE from authors_docs WHERE authors_docs.doc_id=(SELECT documents.id FROM documents,ldap_entries AS seeAlso WHERE seeAlso.keyval=documents.id AND seeAlso.oc_map_id=2 AND seeAlso.dn=?) AND authors_docs.pers_id=?',3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) values (7,2,'description','documents.abstract','documents',NULL,'UPDATE documents SET abstract=? WHERE id=?','UPDATE documents SET abstract='''' WHERE abstract=? AND id=?',3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) values (8,2,'documentTitle','documents.title','documents',NULL,'UPDATE documents SET title=? WHERE id=?','UPDATE documents SET title='''' WHERE title=? AND id=?',3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) values (9,2,'documentAuthor','documentAuthor.dn','ldap_entries AS documentAuthor,documents,authors_docs,persons','documentAuthor.keyval=persons.id AND documentAuthor.oc_map_id=1 AND authors_docs.doc_id=documents.id AND authors_docs.pers_id=persons.id','INSERT INTO authors_docs (pers_id,doc_id) VALUES ((SELECT ldap_entries.keyval FROM ldap_entries WHERE upper(?)=upper(ldap_entries.dn)),?)','DELETE FROM authors_docs WHERE authors_docs.pers_id=(SELECT ldap_entries.keyval FROM ldap_entries WHERE upper(?)=upper(ldap_entries.dn)) AND authors_docs.doc_id=?',3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) values (10,2,'documentIdentifier','''document ''||text(documents.id)','documents',NULL,NULL,'SELECT 1 FROM documents WHERE title=? AND id=? AND 1=0',3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) values (11,3,'o','institutes.name','institutes',NULL,'UPDATE institutes SET name=? WHERE id=?','UPDATE institutes SET name='''' WHERE name=? AND id=?',3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) values (12,3,'dc','lower(institutes.name)','institutes,ldap_entries AS dcObject,ldap_entry_objclasses AS auxObjectClass','institutes.id=dcObject.keyval AND dcObject.oc_map_id=3 AND dcObject.id=auxObjectClass.entry_id AND auxObjectClass.oc_name=''dcObject''',NULL,'SELECT 1 FROM institutes WHERE lower(name)=? AND id=? and 1=0',3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) values (13,4,'ou','referrals.name','referrals',NULL,'UPDATE referrals SET name=? WHERE id=?','SELECT 1 FROM referrals WHERE name=? AND id=? and 1=0',3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) values (14,4,'ref','referrals.url','referrals',NULL,'UPDATE referrals SET url=? WHERE id=?','SELECT 1 FROM referrals WHERE url=? and id=? and 1=0',3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return) values (15,1,'userCertificate','certs.cert','persons,certs','certs.pers_id=persons.id',NULL,NULL,3,0);
+
+-- entries mapping: each entry must appear in this table, with a unique DN rooted at the database naming context
+-- id a unique number > 0 identifying the entry
+-- dn the DN of the entry, in "pretty" form
+-- oc_map_id the "ldap_oc_mappings.id" of the main objectClass of this entry (view it as the structuralObjectClass)
+-- parent the "ldap_entries.id" of the parent of this objectClass; 0 if it is the "suffix" of the database
+-- keyval the value of the "keytbl.keycol" defined for this objectClass
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval) values (1,'dc=example,dc=com',3,0,1);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval) values (2,'cn=Mitya Kovalev,dc=example,dc=com',1,1,1);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval) values (3,'cn=Torvlobnor Puzdoy,dc=example,dc=com',1,1,2);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval) values (4,'cn=Akakiy Zinberstein,dc=example,dc=com',1,1,3);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval) values (5,'documentTitle=book1,dc=example,dc=com',2,1,1);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval) values (6,'documentTitle=book2,dc=example,dc=com',2,1,2);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval) values (7,'ou=Referral,dc=example,dc=com',4,1,1);
+
+-- objectClass mapping: entries that have multiple objectClass instances are listed here with the objectClass name (view them as auxiliary objectClass)
+-- entry_id the "ldap_entries.id" of the entry this objectClass value must be added
+-- oc_name the name of the objectClass; it MUST match the name of an objectClass that is loaded in slapd's schema
+insert into ldap_entry_objclasses (entry_id,oc_name) values (1,'dcObject');
+
+insert into ldap_entry_objclasses (entry_id,oc_name) values (4,'pkiUser');
+
+insert into ldap_entry_objclasses (entry_id,oc_name) values (7,'extensibleObject');
+
+-- procedures
+-- these procedures are specific for this RDBMS and are used in mapping objectClass and attributeType creation/modify/deletion
+create function create_person () returns int
+as '
+ select setval (''persons_id_seq'', (select case when max(id) is null then 1 else max(id) end from persons));
+ insert into persons (id,name,surname)
+ values ((select case when max(id) is null then 1 else nextval(''persons_id_seq'') end from persons),'''','''');
+ select max(id) from persons
+' language 'sql';
+
+create function update_person_cn (varchar, int) returns int
+as '
+ update persons set name = (
+ select case
+ when position('' '' in $1) = 0 then $1
+ else substr($1, 1, position('' '' in $1) - 1)
+ end
+ ),surname = (
+ select case
+ when position('' '' in $1) = 0 then ''''
+ else substr($1, position('' '' in $1) + 1)
+ end
+ ) where id = $2;
+ select $2 as return
+' language 'sql';
+
+create function add_phone (varchar, int) returns int
+as '
+ select setval (''phones_id_seq'', (select case when max(id) is null then 1 else max(id) end from phones));
+ insert into phones (id,phone,pers_id)
+ values (nextval(''phones_id_seq''),$1,$2);
+ select max(id) from phones
+' language 'sql';
+
+create function create_doc () returns int
+as '
+ select setval (''documents_id_seq'', (select case when max(id) is null then 1 else max(id) end from documents));
+ insert into documents (id,title,abstract)
+ values ((select case when max(id) is null then 1 else nextval(''documents_id_seq'') end from documents),'''','''');
+ select max(id) from documents
+' language 'sql';
+
+create function create_o () returns int
+as '
+ select setval (''institutes_id_seq'', (select case when max(id) is null then 1 else max(id) end from institutes));
+ insert into institutes (id,name)
+ values ((select case when max(id) is null then 1 else nextval(''institutes_id_seq'') end from institutes),'''');
+ select max(id) from institutes
+' language 'sql';
+
+create function create_referral () returns int
+as '
+ select setval (''referrals_id_seq'', (select case when max(id) is null then 1 else max(id) end from referrals));
+ insert into referrals (id,name,url)
+ values ((select case when max(id) is null then 1 else nextval(''referrals_id_seq'') end from referrals),'''','''');
+ select max(id) from referrals
+' language 'sql';
+
diff --git a/servers/slapd/back-sql/rdbms_depend/timesten/backsql_create.sql b/servers/slapd/back-sql/rdbms_depend/timesten/backsql_create.sql
new file mode 100644
index 0000000..055e9df
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/timesten/backsql_create.sql
@@ -0,0 +1,66 @@
+
+create table ldap_oc_mappings
+ (
+ id integer not null primary key,
+ name varchar(64) not null,
+ keytbl varchar(64) not null,
+ keycol varchar(64) not null,
+ create_proc varchar(255),
+ delete_proc varchar(255),
+ expect_return tinyint not null
+);
+
+create table ldap_attr_mappings
+ (
+ id integer not null primary key,
+ oc_map_id integer not null,
+ name varchar(255) not null,
+ sel_expr varchar(255) not null,
+ sel_expr_u varchar(255),
+ from_tbls varchar(255) not null,
+ join_where varchar(255),
+ add_proc varchar(255),
+ delete_proc varchar(255),
+ param_order tinyint not null,
+ expect_return tinyint not null,
+ foreign key (oc_map_id) references ldap_oc_mappings(id)
+);
+
+create table ldap_entries
+ (
+ id integer not null primary key,
+ dn varchar(255) not null,
+ dn_ru varchar(255),
+ oc_map_id integer not null,
+ parent int NOT NULL ,
+ keyval int NOT NULL,
+ foreign key (oc_map_id) references ldap_oc_mappings(id)
+);
+
+create index ldap_entriesx1 on ldap_entries(dn_ru);
+
+create unique index unq1_ldap_entries on ldap_entries
+ (
+ oc_map_id,
+ keyval
+ );
+
+create unique index unq2_ldap_entries on ldap_entries
+ (
+ dn
+ );
+
+create table ldap_referrals
+ (
+ entry_id integer not null,
+ url varchar(4096) not null,
+ foreign key (entry_id) references ldap_entries(id)
+);
+
+create table ldap_entry_objclasses
+ (
+ entry_id integer not null,
+ oc_name varchar(64),
+ foreign key (entry_id) references ldap_entries(id)
+ );
+
diff --git a/servers/slapd/back-sql/rdbms_depend/timesten/backsql_drop.sql b/servers/slapd/back-sql/rdbms_depend/timesten/backsql_drop.sql
new file mode 100644
index 0000000..7aa0b83
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/timesten/backsql_drop.sql
@@ -0,0 +1,9 @@
+DROP TABLE ldap_referrals;
+
+DROP TABLE ldap_entry_objclasses;
+
+DROP TABLE ldap_attr_mappings;
+
+DROP TABLE ldap_entries;
+
+DROP TABLE ldap_oc_mappings;
diff --git a/servers/slapd/back-sql/rdbms_depend/timesten/create_schema.sh b/servers/slapd/back-sql/rdbms_depend/timesten/create_schema.sh
new file mode 100755
index 0000000..947db21
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/timesten/create_schema.sh
@@ -0,0 +1,4 @@
+ttIsql -connStr "DSN=ldap_tt;Overwrite=1" -f backsql_create.sql
+ttIsql -connStr "DSN=ldap_tt" -f testdb_create.sql
+ttIsql -connStr "DSN=ldap_tt" -f testdb_data.sql
+ttIsql -connStr "DSN=ldap_tt" -f testdb_metadata.sql
diff --git a/servers/slapd/back-sql/rdbms_depend/timesten/dnreverse/Makefile b/servers/slapd/back-sql/rdbms_depend/timesten/dnreverse/Makefile
new file mode 100644
index 0000000..1b0b1ee
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/timesten/dnreverse/Makefile
@@ -0,0 +1,48 @@
+## Copyright 1997-2022 The OpenLDAP Foundation, All Rights Reserved.
+## COPYING RESTRICTIONS APPLY, see COPYRIGHT file
+
+#
+# Build TimesTen ODBC Sample Programs for Solaris 2.5.1.
+# (c) Copyright 1996-1998, TimesTen Performance Software.
+# All rights reserved.
+## Note: This file was contributed by Sam Drake of TimesTen Performance
+## Software for use and redistribution as an integral part of
+## OpenLDAP Software. -Kdz
+
+CPLUSPLUS = CC
+TTCLASSES = ../../../../../../../../../cs/classes
+ODBC = /opt/TimesTen4.1/32
+CFLAGS = -g -I$(ODBC)/include -I. -I$(TTCLASSES) -DUNIX
+LDFLAGS = -g
+DIRLIBS = $(TTCLASSES)/ttclasses.a -L $(ODBC)/lib -R $(ODBC)/lib -ltten -lpthread -lm -lrt
+XLALIB = -L $(ODBC)/lib -lxla
+
+DIRPROGS= dnreverse
+
+DNREVERSE= dnreverse.o
+
+#
+# Top-level targets
+#
+
+all: $(DIRPROGS)
+
+direct: $(DIRPROGS)
+
+clean:
+ rm -rf $(DIRPROGS) *.o
+
+
+#
+# Direct-linked programs
+#
+
+dnreverse: $(DNREVERSE)
+ $(CPLUSPLUS) -o dnreverse $(LDFLAGS) $(DNREVERSE) $(DIRLIBS) $(XLALIB)
+
+#
+# .o files
+#
+
+dnreverse.o: dnreverse.cpp
+ $(CPLUSPLUS) $(CFLAGS) -c dnreverse.cpp
diff --git a/servers/slapd/back-sql/rdbms_depend/timesten/dnreverse/dnreverse.cpp b/servers/slapd/back-sql/rdbms_depend/timesten/dnreverse/dnreverse.cpp
new file mode 100644
index 0000000..7407b4e
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/timesten/dnreverse/dnreverse.cpp
@@ -0,0 +1,387 @@
+// Copyright 1997-2022 The OpenLDAP Foundation, All Rights Reserved.
+// COPYING RESTRICTIONS APPLY, see COPYRIGHT file
+
+// (c) Copyright 1999-2001 TimesTen Performance Software. All rights reserved.
+
+//// Note: This file was contributed by Sam Drake of TimesTen Performance
+//// Software for use and redistribution as an integral part of
+//// OpenLDAP Software. -Kdz
+
+#include <stdlib.h>
+
+#include <TTConnectionPool.h>
+#include <TTConnection.h>
+#include <TTCmd.h>
+#include <TTXla.h>
+
+#include <signal.h>
+
+TTConnectionPool pool;
+TTXlaConnection conn;
+TTConnection conn2;
+TTCmd assignDn_ru;
+TTCmd getNullDNs;
+
+//----------------------------------------------------------------------
+// This class contains all the logic to be implemented whenever
+// the SCOTT.MYDATA table is changed. This is the table that is
+// created by "sample.cpp", one of the other TTClasses demos.
+// That application should be executed before this one in order to
+// create and populate the table.
+//----------------------------------------------------------------------
+
+class LDAPEntriesHandler: public TTXlaTableHandler {
+private:
+ // Definition of the columns in the table
+ int Id;
+ int Dn;
+ int Oc_map_id;
+ int Parent;
+ int Keyval;
+ int Dn_ru;
+
+protected:
+
+public:
+ LDAPEntriesHandler(TTXlaConnection& conn, const char* ownerP, const char* nameP);
+ ~LDAPEntriesHandler();
+
+ virtual void HandleDelete(ttXlaUpdateDesc_t*);
+ virtual void HandleInsert(ttXlaUpdateDesc_t*);
+ virtual void HandleUpdate(ttXlaUpdateDesc_t*);
+
+ static void ReverseAndUpper(char* dnP, int id, bool commit=true);
+
+};
+
+LDAPEntriesHandler::LDAPEntriesHandler(TTXlaConnection& conn,
+ const char* ownerP, const char* nameP) :
+ TTXlaTableHandler(conn, ownerP, nameP)
+{
+ Id = Dn = Oc_map_id = Parent = Keyval = Dn_ru = -1;
+
+ // We are looking for several particular named columns. We need to get
+ // the ordinal position of the columns by name for later use.
+
+ Id = tbl.getColNumber("ID");
+ if (Id < 0) {
+ cerr << "target table has no 'ID' column" << endl;
+ exit(1);
+ }
+ Dn = tbl.getColNumber("DN");
+ if (Dn < 0) {
+ cerr << "target table has no 'DN' column" << endl;
+ exit(1);
+ }
+ Oc_map_id = tbl.getColNumber("OC_MAP_ID");
+ if (Oc_map_id < 0) {
+ cerr << "target table has no 'OC_MAP_ID' column" << endl;
+ exit(1);
+ }
+ Parent = tbl.getColNumber("PARENT");
+ if (Parent < 0) {
+ cerr << "target table has no 'PARENT' column" << endl;
+ exit(1);
+ }
+ Keyval = tbl.getColNumber("KEYVAL");
+ if (Keyval < 0) {
+ cerr << "target table has no 'KEYVAL' column" << endl;
+ exit(1);
+ }
+ Dn_ru = tbl.getColNumber("DN_RU");
+ if (Dn_ru < 0) {
+ cerr << "target table has no 'DN_RU' column" << endl;
+ exit(1);
+ }
+
+}
+
+LDAPEntriesHandler::~LDAPEntriesHandler()
+{
+
+}
+
+void LDAPEntriesHandler::ReverseAndUpper(char* dnP, int id, bool commit)
+{
+ TTStatus stat;
+ char dn_rn[512];
+ int i;
+ int j;
+
+ // Reverse and upper case the given DN
+
+ for ((j=0, i = strlen(dnP)-1); i > -1; (j++, i--)) {
+ dn_rn[j] = toupper(*(dnP+i));
+ }
+ dn_rn[j] = '\0';
+
+
+ // Update the database
+
+ try {
+ assignDn_ru.setParam(1, (char*) &dn_rn[0]);
+ assignDn_ru.setParam(2, id);
+ assignDn_ru.Execute(stat);
+ }
+ catch (TTStatus stat) {
+ cerr << "Error updating id " << id << " ('" << dnP << "' to '"
+ << dn_rn << "'): " << stat;
+ exit(1);
+ }
+
+ // Commit the transaction
+
+ if (commit) {
+ try {
+ conn2.Commit(stat);
+ }
+ catch (TTStatus stat) {
+ cerr << "Error committing update: " << stat;
+ exit(1);
+ }
+ }
+
+}
+
+
+
+void LDAPEntriesHandler::HandleInsert(ttXlaUpdateDesc_t* p)
+{
+ char* dnP;
+ int id;
+
+ row.Get(Dn, &dnP);
+ cerr << "DN '" << dnP << "': Inserted ";
+ row.Get(Id, &id);
+
+ ReverseAndUpper(dnP, id);
+
+}
+
+void LDAPEntriesHandler::HandleUpdate(ttXlaUpdateDesc_t* p)
+{
+ char* newDnP;
+ char* oldDnP;
+ char oDn[512];
+ int id;
+
+ // row is 'old'; row2 is 'new'
+ row.Get(Dn, &oldDnP);
+ strcpy(oDn, oldDnP);
+ row.Get(Id, &id);
+ row2.Get(Dn, &newDnP);
+
+ cerr << "old DN '" << oDn << "' / new DN '" << newDnP << "' : Updated ";
+
+ if (strcmp(oDn, newDnP) != 0) {
+ // The DN field changed, update it
+ cerr << "(new DN: '" << newDnP << "')";
+ ReverseAndUpper(newDnP, id);
+ }
+ else {
+ // The DN field did NOT change, leave it alone
+ }
+
+ cerr << endl;
+
+}
+
+void LDAPEntriesHandler::HandleDelete(ttXlaUpdateDesc_t* p)
+{
+ char* dnP;
+
+ row.Get(Dn, &dnP);
+ cerr << "DN '" << dnP << "': Deleted ";
+}
+
+
+
+
+//----------------------------------------------------------------------
+
+int pleaseStop = 0;
+
+extern "C" {
+ void
+ onintr(int sig)
+ {
+ pleaseStop = 1;
+ cerr << "Stopping...\n";
+ }
+};
+
+//----------------------------------------------------------------------
+
+int
+main(int argc, char* argv[])
+{
+
+ char* ownerP;
+
+ TTXlaTableList list(&conn); // List of tables to monitor
+
+ // Handlers, one for each table we want to monitor
+
+ LDAPEntriesHandler* sampP = NULL;
+
+ // Misc stuff
+
+ TTStatus stat;
+
+ ttXlaUpdateDesc_t ** arry;
+
+ int records;
+
+ SQLUBIGINT oldsize;
+ int j;
+
+ if (argc < 2) {
+ cerr << "syntax: " << argv[0] << " <username>" << endl;
+ exit(3);
+ }
+
+ ownerP = argv[1];
+
+ signal(SIGINT, onintr); /* signal for CTRL-C */
+#ifdef _WIN32
+ signal(SIGBREAK, onintr); /* signal for CTRL-BREAK */
+#endif
+
+ // Before we do anything related to XLA, first we connect
+ // to the database. This is the connection we will use
+ // to perform non-XLA operations on the tables.
+
+ try {
+ cerr << "Connecting..." << endl;
+
+ conn2.Connect("DSN=ldap_tt", stat);
+ }
+ catch (TTStatus stat) {
+ cerr << "Error connecting to TimesTen: " << stat;
+ exit(1);
+ }
+
+ try {
+ assignDn_ru.Prepare(&conn2,
+ "update ldap_entries set dn_ru=? where id=?",
+ "", stat);
+ getNullDNs.Prepare(&conn2,
+ "select dn, id from ldap_entries "
+ "where dn_ru is null "
+ "for update",
+ "", stat);
+ conn2.Commit(stat);
+ }
+ catch (TTStatus stat) {
+ cerr << "Error preparing update: " << stat;
+ exit(1);
+ }
+
+ // If there are any entries with a NULL reversed/upper cased DN,
+ // fix them now.
+
+ try {
+ cerr << "Fixing NULL reversed DNs" << endl;
+ getNullDNs.Execute(stat);
+ for (int k = 0;; k++) {
+ getNullDNs.FetchNext(stat);
+ if (stat.rc == SQL_NO_DATA_FOUND) break;
+ char* dnP;
+ int id;
+ getNullDNs.getColumn(1, &dnP);
+ getNullDNs.getColumn(2, &id);
+ // cerr << "Id " << id << ", Dn '" << dnP << "'" << endl;
+ LDAPEntriesHandler::ReverseAndUpper(dnP, id, false);
+ if (k % 1000 == 0)
+ cerr << ".";
+ }
+ getNullDNs.Close(stat);
+ conn2.Commit(stat);
+ }
+ catch (TTStatus stat) {
+ cerr << "Error updating NULL rows: " << stat;
+ exit(1);
+ }
+
+
+ // Go ahead and start up the change monitoring application
+
+ cerr << "Starting change monitoring..." << endl;
+ try {
+ conn.Connect("DSN=ldap_tt", stat);
+ }
+ catch (TTStatus stat) {
+ cerr << "Error connecting to TimesTen: " << stat;
+ exit(1);
+ }
+
+ /* set and configure size of buffer */
+ conn.setXlaBufferSize((SQLUBIGINT) 1000000, &oldsize, stat);
+ if (stat.rc) {
+ cerr << "Error setting buffer size " << stat << endl;
+ exit(1);
+ }
+
+ // Make a handler to process changes to the MYDATA table and
+ // add the handler to the list of all handlers
+
+ sampP = new LDAPEntriesHandler(conn, ownerP, "ldap_entries");
+ if (!sampP) {
+ cerr << "Could not create LDAPEntriesHandler" << endl;
+ exit(3);
+ }
+ list.add(sampP);
+
+ // Enable transaction logging for the table we're interested in
+
+ sampP->EnableTracking(stat);
+
+ // Get updates. Dispatch them to the appropriate handler.
+ // This loop will handle updates to all the tables.
+
+ while (pleaseStop == 0) {
+ conn.fetchUpdates(&arry, 1000, &records, stat);
+ if (stat.rc) {
+ cerr << "Error fetching updates" << stat << endl;
+ exit(1);
+ }
+
+ // Interpret the updates
+
+ for(j=0;j < records;j++){
+ ttXlaUpdateDesc_t *p;
+
+ p = arry[j];
+
+ list.HandleChange(p, stat);
+
+ } // end for each record fetched
+
+ if (records) {
+ cerr << "Processed " << records << " records\n";
+ }
+
+ if (records == 0) {
+#ifdef _WIN32
+ Sleep(250);
+#else
+ struct timeval t;
+ t.tv_sec = 0;
+ t.tv_usec = 250000; // .25 seconds
+ select(0, NULL, NULL, NULL, &t);
+#endif
+ }
+ } // end while pleasestop == 0
+
+
+ // When we get to here, the program is exiting.
+
+ list.del(sampP); // Take the table out of the list
+ delete sampP;
+
+ conn.setXlaBufferSize(oldsize, NULL, stat);
+
+ return 0;
+
+}
+
diff --git a/servers/slapd/back-sql/rdbms_depend/timesten/slapd.conf b/servers/slapd/back-sql/rdbms_depend/timesten/slapd.conf
new file mode 100644
index 0000000..f93de8b
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/timesten/slapd.conf
@@ -0,0 +1,31 @@
+# $OpenLDAP$
+#
+# See slapd.conf(5) for details on configuration options.
+# This file should NOT be world readable.
+#
+include /usr/local/etc/openldap/schema/core.schema
+include /usr/local/etc/openldap/schema/cosine.schema
+include /usr/local/etc/openldap/schema/inetorgperson.schema
+
+# Define global ACLs to disable default read access.
+
+# Do not enable referrals until AFTER you have a working directory
+# service AND an understanding of referrals.
+#referral ldap://root.openldap.org
+
+pidfile /usr/local/var/slapd.pid
+argsfile /usr/local/var/slapd.args
+
+#######################################################################
+# sql database definitions
+#######################################################################
+
+database sql
+suffix "o=sql,c=RU"
+rootdn "cn=root,o=sql,c=RU"
+rootpw secret
+dbname ldap_tt
+dbuser root
+dbpasswd
+subtree_cond "ldap_entries.dn LIKE ?"
+insentry_stmt "INSERT INTO ldap_entries (dn,oc_map_id,parent,keyval) VALUES (?,?,?,?)"
diff --git a/servers/slapd/back-sql/rdbms_depend/timesten/testdb_create.sql b/servers/slapd/back-sql/rdbms_depend/timesten/testdb_create.sql
new file mode 100644
index 0000000..768aec8
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/timesten/testdb_create.sql
@@ -0,0 +1,36 @@
+CREATE TABLE persons (
+ id int NOT NULL primary key,
+ name varchar(255) NOT NULL
+)
+unique hash on (id) pages=100;
+
+CREATE TABLE institutes (
+ id int NOT NULL primary key,
+ name varchar(255)
+)
+unique hash on (id) pages=100;
+
+CREATE TABLE documents (
+ id int NOT NULL primary key,
+ title varchar(255) NOT NULL,
+ abstract varchar(255)
+)
+unique hash on (id) pages=100;
+
+CREATE TABLE authors_docs (
+ pers_id int NOT NULL,
+ doc_id int NOT NULL,
+ PRIMARY KEY
+ (
+ pers_id,
+ doc_id
+ )
+) unique hash on (pers_id, doc_id) pages=100;
+
+CREATE TABLE phones (
+ id int NOT NULL primary key,
+ phone varchar(255) NOT NULL ,
+ pers_id int NOT NULL
+)
+unique hash on (id) pages=100;
+
diff --git a/servers/slapd/back-sql/rdbms_depend/timesten/testdb_data.sql b/servers/slapd/back-sql/rdbms_depend/timesten/testdb_data.sql
new file mode 100644
index 0000000..f141f41
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/timesten/testdb_data.sql
@@ -0,0 +1,16 @@
+insert into institutes (id,name) values (1,'sql');
+
+insert into persons (id,name) values (1,'Mitya Kovalev');
+insert into persons (id,name) values (2,'Torvlobnor Puzdoy');
+insert into persons (id,name) values (3,'Akakiy Zinberstein');
+
+insert into phones (id,phone,pers_id) values (1,'332-2334',1);
+insert into phones (id,phone,pers_id) values (2,'222-3234',1);
+insert into phones (id,phone,pers_id) values (3,'545-4563',2);
+
+insert into documents (id,abstract,title) values (1,'abstract1','book1');
+insert into documents (id,abstract,title) values (2,'abstract2','book2');
+
+insert into authors_docs (pers_id,doc_id) values (1,1);
+insert into authors_docs (pers_id,doc_id) values (1,2);
+insert into authors_docs (pers_id,doc_id) values (2,1);
diff --git a/servers/slapd/back-sql/rdbms_depend/timesten/testdb_drop.sql b/servers/slapd/back-sql/rdbms_depend/timesten/testdb_drop.sql
new file mode 100644
index 0000000..17b12af
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/timesten/testdb_drop.sql
@@ -0,0 +1,5 @@
+DROP TABLE persons;
+DROP TABLE institutes;
+DROP TABLE documents;
+DROP TABLE authors_docs;
+DROP TABLE phones;
diff --git a/servers/slapd/back-sql/rdbms_depend/timesten/testdb_metadata.sql b/servers/slapd/back-sql/rdbms_depend/timesten/testdb_metadata.sql
new file mode 100644
index 0000000..f9e3419
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/timesten/testdb_metadata.sql
@@ -0,0 +1,108 @@
+
+insert into ldap_oc_mappings
+(id,name, keytbl, keycol, create_proc,
+delete_proc,expect_return)
+values
+(1,'inetOrgPerson','persons','id', 'insert into persons (name) values ('');\n select last_insert_id();',
+NULL,0);
+
+insert into ldap_oc_mappings
+(id, name, keytbl, keycol,create_proc,delete_proc,expect_return)
+values
+(2, 'document','documents','id', NULL, NULL, 0);
+
+insert into ldap_oc_mappings
+(id,name, keytbl, keycol,create_proc,delete_proc,expect_return)
+values
+(3,'organization','institutes','id', NULL, NULL, 0);
+
+
+insert into ldap_attr_mappings
+(id, oc_map_id, name, sel_expr, from_tbls,join_where,add_proc,
+delete_proc,param_order,expect_return)
+values
+(1, 1, 'cn', 'persons.name', 'persons',NULL, NULL,
+NULL, 3, 0);
+
+insert into ldap_attr_mappings
+(id, oc_map_id,name, sel_expr, from_tbls,
+join_where, add_proc,delete_proc,param_order,expect_return)
+values
+(2, 1, 'telephoneNumber','phones.phone','persons,phones',
+'phones.pers_id=persons.id', NULL, NULL, 3, 0);
+
+insert into ldap_attr_mappings
+(id,oc_map_id, name, sel_expr, from_tbls, join_where,add_proc,
+delete_proc,param_order,expect_return)
+values
+(3, 1, 'sn', 'persons.name','persons', NULL, NULL,
+NULL, 3, 0);
+
+insert into ldap_attr_mappings
+(id, oc_map_id, name, sel_expr, from_tbls, join_where,
+add_proc,delete_proc,param_order,expect_return)
+values
+(4, 2, 'description', 'documents.abstract','documents', NULL,
+NULL, NULL, 3, 0);
+
+insert into ldap_attr_mappings
+(id, oc_map_id, name, sel_expr, from_tbls, join_where,
+add_proc,delete_proc,param_order,expect_return)
+values
+(5, 2, 'documentTitle','documents.title','documents',NULL,
+NULL, NULL, 3, 0);
+
+-- insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+-- values (6,2,'documentAuthor','persons.name','persons,documents,authors_docs',
+-- 'persons.id=authors_docs.pers_id AND documents.id=authors_docs.doc_id',
+-- NULL,NULL,3,0);
+
+insert into ldap_attr_mappings
+(id, oc_map_id, name, sel_expr, from_tbls, join_where,add_proc,
+delete_proc,param_order,expect_return)
+values
+(7, 3, 'o', 'institutes.name', 'institutes', NULL, NULL,
+NULL, 3, 0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (8,1,'documentDN','ldap_entries.dn','ldap_entries,documents,authors_docs,persons',
+ 'ldap_entries.keyval=documents.id AND ldap_entries.oc_map_id=2 AND authors_docs.doc_id=documents.id AND authors_docs.pers_id=persons.id',
+ NULL,NULL,3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (9,2,'documentAuthor','ldap_entries.dn','ldap_entries,documents,authors_docs,persons',
+ 'ldap_entries.keyval=persons.id AND ldap_entries.oc_map_id=1 AND authors_docs.doc_id=documents.id AND authors_docs.pers_id=persons.id',
+ NULL,NULL,3,0);
+
+-- entries
+
+insert into ldap_entries
+(id, dn, oc_map_id, parent, keyval)
+values
+(1, 'o=sql,c=RU', 3, 0, 1);
+
+insert into ldap_entries
+(id, dn, oc_map_id, parent, keyval)
+values
+(2, 'cn=Mitya Kovalev,o=sql,c=RU', 1, 1, 1);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (3,'cn=Torvlobnor Puzdoy,o=sql,c=RU',1,1,2);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (4,'cn=Akakiy Zinberstein,o=sql,c=RU',1,1,3);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (5,'documentTitle=book1,o=sql,c=RU',2,1,1);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (6,'documentTitle=book2,o=sql,c=RU',2,1,2);
+
+
+-- referrals
+
+insert into ldap_entry_objclasses (entry_id,oc_name)
+values (4,'referral');
+
+insert into ldap_referrals (entry_id,url)
+values (4,'ldap://localhost:9012');
diff --git a/servers/slapd/back-sql/rdbms_depend/timesten/ttcreate_schema.sh b/servers/slapd/back-sql/rdbms_depend/timesten/ttcreate_schema.sh
new file mode 100755
index 0000000..c4c5df2
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/timesten/ttcreate_schema.sh
@@ -0,0 +1,4 @@
+ttIsql -connStr "DSN=ldap_tt;Overwrite=1" -f backsql_create.sql
+ttIsql -connStr "DSN=ldap_tt" -f tttestdb_create.sql
+ttIsql -connStr "DSN=ldap_tt" -f tttestdb_data.sql
+ttIsql -connStr "DSN=ldap_tt" -f tttestdb_metadata.sql
diff --git a/servers/slapd/back-sql/rdbms_depend/timesten/tttestdb_create.sql b/servers/slapd/back-sql/rdbms_depend/timesten/tttestdb_create.sql
new file mode 100644
index 0000000..f5955d2
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/timesten/tttestdb_create.sql
@@ -0,0 +1,42 @@
+CREATE TABLE persons (
+ id int NOT NULL primary key,
+ name varchar(255) NOT NULL,
+ name_u varchar(255),
+ title varchar(255),
+ title_U varchar(255),
+ organization varchar(255)
+)
+unique hash on (id) pages=100;
+create index personsx1 on persons(title_U);
+create index personsx2 on persons(name_u);
+
+CREATE TABLE institutes (
+ id int NOT NULL primary key,
+ name varchar(255)
+)
+unique hash on (id) pages=100;
+
+CREATE TABLE documents (
+ id int NOT NULL primary key,
+ title varchar(255) NOT NULL,
+ abstract varchar(255)
+)
+unique hash on (id) pages=100;
+
+CREATE TABLE authors_docs (
+ pers_id int NOT NULL,
+ doc_id int NOT NULL,
+ PRIMARY KEY
+ (
+ pers_id,
+ doc_id
+ )
+) unique hash on (pers_id, doc_id) pages=100;
+
+CREATE TABLE phones (
+ id int NOT NULL primary key,
+ phone varchar(255) NOT NULL ,
+ pers_id int NOT NULL
+)
+unique hash on (id) pages=100;
+
diff --git a/servers/slapd/back-sql/rdbms_depend/timesten/tttestdb_data.sql b/servers/slapd/back-sql/rdbms_depend/timesten/tttestdb_data.sql
new file mode 100644
index 0000000..ca75339
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/timesten/tttestdb_data.sql
@@ -0,0 +1,20 @@
+insert into institutes (id,name) values (1,'sql');
+
+insert into persons (id,name, title, title_U, organization) values
+(1,'Mitya Kovalev', 'Engineer', 'ENGINEER', 'Development');
+insert into persons (id,name, title, title_U, organization) values
+(2,'Torvlobnor Puzdoy', 'Engineer', 'ENGINEER', 'Sales');
+insert into persons (id,name, title, title_U, organization) values
+(3,'Akakiy Zinberstein', 'Engineer', 'ENGINEER', 'Marketing');
+update persons set name_u = upper(name);
+
+insert into phones (id,phone,pers_id) values (1,'332-2334',1);
+insert into phones (id,phone,pers_id) values (2,'222-3234',1);
+insert into phones (id,phone,pers_id) values (3,'545-4563',2);
+
+insert into documents (id,abstract,title) values (1,'abstract1','book1');
+insert into documents (id,abstract,title) values (2,'abstract2','book2');
+
+insert into authors_docs (pers_id,doc_id) values (1,1);
+insert into authors_docs (pers_id,doc_id) values (1,2);
+insert into authors_docs (pers_id,doc_id) values (2,1);
diff --git a/servers/slapd/back-sql/rdbms_depend/timesten/tttestdb_drop.sql b/servers/slapd/back-sql/rdbms_depend/timesten/tttestdb_drop.sql
new file mode 100644
index 0000000..17b12af
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/timesten/tttestdb_drop.sql
@@ -0,0 +1,5 @@
+DROP TABLE persons;
+DROP TABLE institutes;
+DROP TABLE documents;
+DROP TABLE authors_docs;
+DROP TABLE phones;
diff --git a/servers/slapd/back-sql/rdbms_depend/timesten/tttestdb_metadata.sql b/servers/slapd/back-sql/rdbms_depend/timesten/tttestdb_metadata.sql
new file mode 100644
index 0000000..69bda8a
--- /dev/null
+++ b/servers/slapd/back-sql/rdbms_depend/timesten/tttestdb_metadata.sql
@@ -0,0 +1,122 @@
+
+insert into ldap_oc_mappings
+(id,name, keytbl, keycol, create_proc,
+delete_proc,expect_return)
+values
+(1,'inetOrgPerson','persons','id', 'insert into persons (name) values ('');\n select last_insert_id();',
+NULL,0);
+
+insert into ldap_oc_mappings
+(id, name, keytbl, keycol,create_proc,delete_proc,expect_return)
+values
+(2, 'document','documents','id', NULL, NULL, 0);
+
+insert into ldap_oc_mappings
+(id,name, keytbl, keycol,create_proc,delete_proc,expect_return)
+values
+(3,'organization','institutes','id', NULL, NULL, 0);
+
+
+insert into ldap_attr_mappings
+(id, oc_map_id, name, sel_expr, sel_expr_u, from_tbls,
+join_where,add_proc, delete_proc,param_order,expect_return)
+values
+(1, 1, 'cn', 'persons.name', 'persons.name_u','persons',
+NULL, NULL, NULL, 3, 0);
+
+insert into ldap_attr_mappings
+(id, oc_map_id, name, sel_expr, sel_expr_u, from_tbls,join_where,
+add_proc, delete_proc,param_order,expect_return)
+values
+(10, 1, 'title', 'persons.title', 'persons.title_u', 'persons',NULL, NULL,
+NULL, 3, 0);
+
+insert into ldap_attr_mappings
+(id, oc_map_id,name, sel_expr, from_tbls,
+join_where, add_proc,delete_proc,param_order,expect_return)
+values
+(2, 1, 'telephoneNumber','phones.phone','persons,phones',
+'phones.pers_id=persons.id', NULL, NULL, 3, 0);
+
+insert into ldap_attr_mappings
+(id,oc_map_id, name, sel_expr, from_tbls, join_where,add_proc,
+delete_proc,param_order,expect_return)
+values
+(3, 1, 'sn', 'persons.name','persons', NULL, NULL,
+NULL, 3, 0);
+
+insert into ldap_attr_mappings
+(id, oc_map_id, name, sel_expr, from_tbls, join_where,add_proc,
+delete_proc,param_order,expect_return)
+values
+(30, 1, 'ou', 'persons.organization','persons', NULL, NULL,
+NULL, 3, 0);
+
+insert into ldap_attr_mappings
+(id, oc_map_id, name, sel_expr, from_tbls, join_where,
+add_proc,delete_proc,param_order,expect_return)
+values
+(4, 2, 'description', 'documents.abstract','documents', NULL,
+NULL, NULL, 3, 0);
+
+insert into ldap_attr_mappings
+(id, oc_map_id, name, sel_expr, from_tbls, join_where,
+add_proc,delete_proc,param_order,expect_return)
+values
+(5, 2, 'documentTitle','documents.title','documents',NULL,
+NULL, NULL, 3, 0);
+
+-- insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+-- values (6,2,'documentAuthor','persons.name','persons,documents,authors_docs',
+-- 'persons.id=authors_docs.pers_id AND documents.id=authors_docs.doc_id',
+-- NULL,NULL,3,0);
+
+insert into ldap_attr_mappings
+(id, oc_map_id, name, sel_expr, from_tbls, join_where,add_proc,
+delete_proc,param_order,expect_return)
+values
+(7, 3, 'o', 'institutes.name', 'institutes', NULL, NULL,
+NULL, 3, 0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (8,1,'documentDN','ldap_entries.dn','ldap_entries,documents,authors_docs,persons',
+ 'ldap_entries.keyval=documents.id AND ldap_entries.oc_map_id=2 AND authors_docs.doc_id=documents.id AND authors_docs.pers_id=persons.id',
+ NULL,NULL,3,0);
+
+insert into ldap_attr_mappings (id,oc_map_id,name,sel_expr,from_tbls,join_where,add_proc,delete_proc,param_order,expect_return)
+values (9,2,'documentAuthor','ldap_entries.dn','ldap_entries,documents,authors_docs,persons',
+ 'ldap_entries.keyval=persons.id AND ldap_entries.oc_map_id=1 AND authors_docs.doc_id=documents.id AND authors_docs.pers_id=persons.id',
+ NULL,NULL,3,0);
+
+-- entries
+
+insert into ldap_entries
+(id, dn, oc_map_id, parent, keyval)
+values
+(1, 'o=sql,c=RU', 3, 0, 1);
+
+insert into ldap_entries
+(id, dn, oc_map_id, parent, keyval)
+values
+(2, 'cn=Mitya Kovalev,o=sql,c=RU', 1, 1, 1);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (3,'cn=Torvlobnor Puzdoy,o=sql,c=RU',1,1,2);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (4,'cn=Akakiy Zinberstein,o=sql,c=RU',1,1,3);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (5,'documentTitle=book1,o=sql,c=RU',2,1,1);
+
+insert into ldap_entries (id,dn,oc_map_id,parent,keyval)
+values (6,'documentTitle=book2,o=sql,c=RU',2,1,2);
+
+
+-- referrals
+
+insert into ldap_entry_objclasses (entry_id,oc_name)
+values (4,'referral');
+
+insert into ldap_referrals (entry_id,url)
+values (4,'http://localhost');
diff --git a/servers/slapd/back-sql/schema-map.c b/servers/slapd/back-sql/schema-map.c
new file mode 100644
index 0000000..f6294bb
--- /dev/null
+++ b/servers/slapd/back-sql/schema-map.c
@@ -0,0 +1,1012 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1999 Dmitry Kovalev.
+ * Portions Copyright 2002 Pierangelo Masarati.
+ * Portions Copyright 2004 Mark Adamson.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Dmitry Kovalev for inclusion
+ * by OpenLDAP Software. Additional significant contributors include
+ * Pierangelo Masarati and Mark Adamson.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <sys/types.h>
+#include "ac/string.h"
+
+#include "lutil.h"
+#include "slap.h"
+#include "proto-sql.h"
+
+#define BACKSQL_DUPLICATE (-1)
+
+/* NOTE: by default, cannot just compare pointers because
+ * objectClass/attributeType order would be machine-dependent
+ * (and tests would fail!); however, if you don't want to run
+ * tests, or see attributeTypes written in the same order
+ * they are defined, define */
+/* #undef BACKSQL_USE_PTR_CMP */
+
+/*
+ * Uses the pointer to the ObjectClass structure
+ */
+static int
+backsql_cmp_oc( const void *v_m1, const void *v_m2 )
+{
+ const backsql_oc_map_rec *m1 = v_m1,
+ *m2 = v_m2;
+
+#ifdef BACKSQL_USE_PTR_CMP
+ return SLAP_PTRCMP( m1->bom_oc, m2->bom_oc );
+#else /* ! BACKSQL_USE_PTR_CMP */
+ return ber_bvcmp( &m1->bom_oc->soc_cname, &m2->bom_oc->soc_cname );
+#endif /* ! BACKSQL_USE_PTR_CMP */
+}
+
+static int
+backsql_cmp_oc_id( const void *v_m1, const void *v_m2 )
+{
+ const backsql_oc_map_rec *m1 = v_m1,
+ *m2 = v_m2;
+
+ return ( m1->bom_id < m2->bom_id ? -1 : ( m1->bom_id > m2->bom_id ? 1 : 0 ) );
+}
+
+/*
+ * Uses the pointer to the AttributeDescription structure
+ */
+static int
+backsql_cmp_attr( const void *v_m1, const void *v_m2 )
+{
+ const backsql_at_map_rec *m1 = v_m1,
+ *m2 = v_m2;
+
+ if ( slap_ad_is_binary( m1->bam_ad ) || slap_ad_is_binary( m2->bam_ad ) ) {
+#ifdef BACKSQL_USE_PTR_CMP
+ return SLAP_PTRCMP( m1->bam_ad->ad_type, m2->bam_ad->ad_type );
+#else /* ! BACKSQL_USE_PTR_CMP */
+ return ber_bvcmp( &m1->bam_ad->ad_type->sat_cname, &m2->bam_ad->ad_type->sat_cname );
+#endif /* ! BACKSQL_USE_PTR_CMP */
+ }
+
+#ifdef BACKSQL_USE_PTR_CMP
+ return SLAP_PTRCMP( m1->bam_ad, m2->bam_ad );
+#else /* ! BACKSQL_USE_PTR_CMP */
+ return ber_bvcmp( &m1->bam_ad->ad_cname, &m2->bam_ad->ad_cname );
+#endif /* ! BACKSQL_USE_PTR_CMP */
+}
+
+int
+backsql_dup_attr( void *v_m1, void *v_m2 )
+{
+ backsql_at_map_rec *m1 = v_m1,
+ *m2 = v_m2;
+
+ if ( slap_ad_is_binary( m1->bam_ad ) || slap_ad_is_binary( m2->bam_ad ) ) {
+#ifdef BACKSQL_USE_PTR_CMP
+ assert( m1->bam_ad->ad_type == m2->bam_ad->ad_type );
+#else /* ! BACKSQL_USE_PTR_CMP */
+ assert( ber_bvcmp( &m1->bam_ad->ad_type->sat_cname, &m2->bam_ad->ad_type->sat_cname ) == 0 );
+#endif /* ! BACKSQL_USE_PTR_CMP */
+
+ } else {
+#ifdef BACKSQL_USE_PTR_CMP
+ assert( m1->bam_ad == m2->bam_ad );
+#else /* ! BACKSQL_USE_PTR_CMP */
+ assert( ber_bvcmp( &m1->bam_ad->ad_cname, &m2->bam_ad->ad_cname ) == 0 );
+#endif /* ! BACKSQL_USE_PTR_CMP */
+ }
+
+ /* duplicate definitions of attributeTypes are appended;
+ * this allows to define multiple rules for the same
+ * attributeType. Use with care! */
+ for ( ; m1->bam_next ; m1 = m1->bam_next );
+
+ m1->bam_next = m2;
+ m2->bam_next = NULL;
+
+ return BACKSQL_DUPLICATE;
+}
+
+static int
+backsql_make_attr_query(
+ backsql_info *bi,
+ backsql_oc_map_rec *oc_map,
+ backsql_at_map_rec *at_map )
+{
+ struct berbuf bb = BB_NULL;
+
+ backsql_strfcat_x( &bb, NULL, "lblbbbblblbcbl",
+ (ber_len_t)STRLENOF( "SELECT " ), "SELECT ",
+ &at_map->bam_sel_expr,
+ (ber_len_t)STRLENOF( " " ), " ",
+ &bi->sql_aliasing,
+ &bi->sql_aliasing_quote,
+ &at_map->bam_ad->ad_cname,
+ &bi->sql_aliasing_quote,
+ (ber_len_t)STRLENOF( " FROM " ), " FROM ",
+ &at_map->bam_from_tbls,
+ (ber_len_t)STRLENOF( " WHERE " ), " WHERE ",
+ &oc_map->bom_keytbl,
+ '.',
+ &oc_map->bom_keycol,
+ (ber_len_t)STRLENOF( "=?" ), "=?" );
+
+ if ( !BER_BVISNULL( &at_map->bam_join_where ) ) {
+ backsql_strfcat_x( &bb, NULL, "lb",
+ (ber_len_t)STRLENOF( " AND " ), " AND ",
+ &at_map->bam_join_where );
+ }
+
+ backsql_strfcat_x( &bb, NULL, "lbbb",
+ (ber_len_t)STRLENOF( " ORDER BY " ), " ORDER BY ",
+ &bi->sql_aliasing_quote,
+ &at_map->bam_ad->ad_cname,
+ &bi->sql_aliasing_quote );
+
+ at_map->bam_query = bb.bb_val.bv_val;
+
+#ifdef BACKSQL_COUNTQUERY
+ /* Query to count how many rows will be returned.
+
+ SELECT COUNT(*) FROM <from_tbls> WHERE <keytbl>.<keycol>=?
+ [ AND <join_where> ]
+
+ */
+ BER_BVZERO( &bb.bb_val );
+ bb.bb_len = 0;
+ backsql_strfcat_x( &bb, NULL, "lblbcbl",
+ (ber_len_t)STRLENOF( "SELECT COUNT(*) FROM " ),
+ "SELECT COUNT(*) FROM ",
+ &at_map->bam_from_tbls,
+ (ber_len_t)STRLENOF( " WHERE " ), " WHERE ",
+ &oc_map->bom_keytbl,
+ '.',
+ &oc_map->bom_keycol,
+ (ber_len_t)STRLENOF( "=?" ), "=?" );
+
+ if ( !BER_BVISNULL( &at_map->bam_join_where ) ) {
+ backsql_strfcat_x( &bb, NULL, "lb",
+ (ber_len_t)STRLENOF( " AND " ), " AND ",
+ &at_map->bam_join_where );
+ }
+
+ at_map->bam_countquery = bb.bb_val.bv_val;
+#endif /* BACKSQL_COUNTQUERY */
+
+ return 0;
+}
+
+static int
+backsql_add_sysmaps( backsql_info *bi, backsql_oc_map_rec *oc_map )
+{
+ backsql_at_map_rec *at_map;
+ char s[LDAP_PVT_INTTYPE_CHARS(long)];
+ struct berval sbv;
+ struct berbuf bb;
+
+ sbv.bv_val = s;
+ sbv.bv_len = snprintf( s, sizeof( s ), BACKSQL_IDNUMFMT, oc_map->bom_id );
+
+ /* extra objectClasses */
+ at_map = (backsql_at_map_rec *)ch_calloc(1,
+ sizeof( backsql_at_map_rec ) );
+ at_map->bam_ad = slap_schema.si_ad_objectClass;
+ at_map->bam_true_ad = slap_schema.si_ad_objectClass;
+ ber_str2bv( "ldap_entry_objclasses.oc_name", 0, 1,
+ &at_map->bam_sel_expr );
+ ber_str2bv( "ldap_entry_objclasses,ldap_entries", 0, 1,
+ &at_map->bam_from_tbls );
+
+ bb.bb_len = at_map->bam_from_tbls.bv_len + 1;
+ bb.bb_val = at_map->bam_from_tbls;
+ backsql_merge_from_clause( bi, &bb, &oc_map->bom_keytbl );
+ at_map->bam_from_tbls = bb.bb_val;
+
+ BER_BVZERO( &bb.bb_val );
+ bb.bb_len = 0;
+ backsql_strfcat_x( &bb, NULL, "lbcblb",
+ (ber_len_t)STRLENOF( "ldap_entries.id=ldap_entry_objclasses.entry_id AND ldap_entries.keyval=" ),
+ "ldap_entries.id=ldap_entry_objclasses.entry_id AND ldap_entries.keyval=",
+ &oc_map->bom_keytbl,
+ '.',
+ &oc_map->bom_keycol,
+ (ber_len_t)STRLENOF( " and ldap_entries.oc_map_id=" ),
+ " and ldap_entries.oc_map_id=",
+ &sbv );
+ at_map->bam_join_where = bb.bb_val;
+
+ at_map->bam_oc = oc_map->bom_oc;
+
+ at_map->bam_add_proc = NULL;
+ {
+ char tmp[STRLENOF("INSERT INTO ldap_entry_objclasses "
+ "(entry_id,oc_name) VALUES "
+ "((SELECT id FROM ldap_entries "
+ "WHERE oc_map_id= "
+ "AND keyval=?),?)") + LDAP_PVT_INTTYPE_CHARS(unsigned long)];
+ snprintf( tmp, sizeof(tmp),
+ "INSERT INTO ldap_entry_objclasses "
+ "(entry_id,oc_name) VALUES "
+ "((SELECT id FROM ldap_entries "
+ "WHERE oc_map_id=" BACKSQL_IDNUMFMT " "
+ "AND keyval=?),?)", oc_map->bom_id );
+ at_map->bam_add_proc = ch_strdup( tmp );
+ }
+
+ at_map->bam_delete_proc = NULL;
+ {
+ char tmp[STRLENOF("DELETE FROM ldap_entry_objclasses "
+ "WHERE entry_id=(SELECT id FROM ldap_entries "
+ "WHERE oc_map_id= "
+ "AND keyval=?) AND oc_name=?") + LDAP_PVT_INTTYPE_CHARS(unsigned long)];
+ snprintf( tmp, sizeof(tmp),
+ "DELETE FROM ldap_entry_objclasses "
+ "WHERE entry_id=(SELECT id FROM ldap_entries "
+ "WHERE oc_map_id=" BACKSQL_IDNUMFMT " "
+ "AND keyval=?) AND oc_name=?",
+ oc_map->bom_id );
+ at_map->bam_delete_proc = ch_strdup( tmp );
+ }
+
+ at_map->bam_param_order = 0;
+ at_map->bam_expect_return = 0;
+ at_map->bam_next = NULL;
+
+ backsql_make_attr_query( bi, oc_map, at_map );
+ if ( ldap_avl_insert( &oc_map->bom_attrs, at_map, backsql_cmp_attr, backsql_dup_attr ) == BACKSQL_DUPLICATE ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_add_sysmaps(): "
+ "duplicate attribute \"%s\" in objectClass \"%s\" map\n",
+ at_map->bam_ad->ad_cname.bv_val,
+ oc_map->bom_oc->soc_cname.bv_val );
+ }
+
+ /* FIXME: we need to correct the objectClass join_where
+ * after the attribute query is built */
+ ch_free( at_map->bam_join_where.bv_val );
+ BER_BVZERO( &bb.bb_val );
+ bb.bb_len = 0;
+ backsql_strfcat_x( &bb, NULL, "lbcblb",
+ (ber_len_t)STRLENOF( /* "ldap_entries.id=ldap_entry_objclasses.entry_id AND " */ "ldap_entries.keyval=" ),
+ /* "ldap_entries.id=ldap_entry_objclasses.entry_id AND " */ "ldap_entries.keyval=",
+ &oc_map->bom_keytbl,
+ '.',
+ &oc_map->bom_keycol,
+ (ber_len_t)STRLENOF( " AND ldap_entries.oc_map_id=" ),
+ " AND ldap_entries.oc_map_id=",
+ &sbv );
+ at_map->bam_join_where = bb.bb_val;
+
+ return 1;
+}
+
+struct backsql_attr_schema_info {
+ backsql_info *bas_bi;
+ SQLHDBC bas_dbh;
+ SQLHSTMT bas_sth;
+ backsql_key_t *bas_oc_id;
+ int bas_rc;
+};
+
+static int
+backsql_oc_get_attr_mapping( void *v_oc, void *v_bas )
+{
+ RETCODE rc;
+ BACKSQL_ROW_NTS at_row;
+ backsql_oc_map_rec *oc_map = (backsql_oc_map_rec *)v_oc;
+ backsql_at_map_rec *at_map;
+ struct backsql_attr_schema_info *bas = (struct backsql_attr_schema_info *)v_bas;
+
+ /* bas->bas_oc_id has been bound to bas->bas_sth */
+ *bas->bas_oc_id = oc_map->bom_id;
+
+ Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_attr_mapping(): "
+ "executing at_query\n"
+ " \"%s\"\n"
+ " for objectClass \"%s\"\n"
+ " with param oc_id=" BACKSQL_IDNUMFMT "\n",
+ bas->bas_bi->sql_at_query,
+ BACKSQL_OC_NAME( oc_map ),
+ *bas->bas_oc_id );
+
+ rc = SQLExecute( bas->bas_sth );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_attr_mapping(): "
+ "error executing at_query\n"
+ " \"%s\"\n"
+ " for objectClass \"%s\"\n"
+ " with param oc_id=" BACKSQL_IDNUMFMT "\n",
+ bas->bas_bi->sql_at_query,
+ BACKSQL_OC_NAME( oc_map ),
+ *bas->bas_oc_id );
+ backsql_PrintErrors( bas->bas_bi->sql_db_env,
+ bas->bas_dbh, bas->bas_sth, rc );
+ bas->bas_rc = LDAP_OTHER;
+ return BACKSQL_AVL_STOP;
+ }
+
+ backsql_BindRowAsStrings( bas->bas_sth, &at_row );
+ for ( ; rc = SQLFetch( bas->bas_sth ), BACKSQL_SUCCESS( rc ); ) {
+ const char *text = NULL;
+ struct berval bv;
+ struct berbuf bb = BB_NULL;
+ AttributeDescription *ad = NULL;
+
+ {
+ struct {
+ int idx;
+ char *name;
+ } required[] = {
+ { 0, "name" },
+ { 1, "sel_expr" },
+ { 2, "from" },
+ { -1, NULL },
+ };
+ int i;
+
+ for ( i = 0; required[ i ].name != NULL; i++ ) {
+ if ( at_row.value_len[ i ] <= 0 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "backsql_oc_get_attr_mapping(): "
+ "required column #%d \"%s\" is empty\n",
+ required[ i ].idx, required[ i ].name );
+ bas->bas_rc = LDAP_OTHER;
+ return BACKSQL_AVL_STOP;
+ }
+ }
+ }
+
+ Debug(LDAP_DEBUG_TRACE,
+ "attributeType: " "name=\"%s\" " "sel_expr=\"%s\" " "from=\"%s\" " "join_where=\"%s\" " "add_proc=\"%s\" " "delete_proc=\"%s\" " "sel_expr_u=\"%s\"\n",
+ at_row.cols[0], at_row.cols[1], at_row.cols[2],
+ at_row.cols[3] ? at_row.cols[3] : "",
+ at_row.cols[4] ? at_row.cols[4] : "",
+ at_row.cols[5] ? at_row.cols[5] : "",
+ at_row.cols[8] ? at_row.cols[8] : "" );
+
+ rc = slap_str2ad( at_row.cols[ 0 ], &ad, &text );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_attr_mapping(): "
+ "attribute \"%s\" for objectClass \"%s\" "
+ "is not defined in schema: %s\n",
+ at_row.cols[ 0 ],
+ BACKSQL_OC_NAME( oc_map ), text );
+ bas->bas_rc = LDAP_CONSTRAINT_VIOLATION;
+ return BACKSQL_AVL_STOP;
+ }
+ at_map = (backsql_at_map_rec *)ch_calloc( 1,
+ sizeof( backsql_at_map_rec ) );
+ at_map->bam_ad = ad;
+ at_map->bam_true_ad = ad;
+ if ( slap_syntax_is_binary( ad->ad_type->sat_syntax )
+ && !slap_ad_is_binary( ad ) )
+ {
+ char buf[ SLAP_TEXT_BUFLEN ];
+ struct berval bv;
+ const char *text = NULL;
+
+ bv.bv_val = buf;
+ bv.bv_len = snprintf( buf, sizeof( buf ), "%s;binary",
+ ad->ad_cname.bv_val );
+ at_map->bam_true_ad = NULL;
+ bas->bas_rc = slap_bv2ad( &bv, &at_map->bam_true_ad, &text );
+ if ( bas->bas_rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_attr_mapping(): "
+ "unable to fetch attribute \"%s\": %s (%d)\n",
+ buf, text, rc );
+ ch_free( at_map );
+ return BACKSQL_AVL_STOP;
+ }
+ }
+
+ ber_str2bv( at_row.cols[ 1 ], 0, 1, &at_map->bam_sel_expr );
+ if ( at_row.value_len[ 8 ] <= 0 ) {
+ BER_BVZERO( &at_map->bam_sel_expr_u );
+
+ } else {
+ ber_str2bv( at_row.cols[ 8 ], 0, 1,
+ &at_map->bam_sel_expr_u );
+ }
+
+ ber_str2bv( at_row.cols[ 2 ], 0, 0, &bv );
+ backsql_merge_from_clause( bas->bas_bi, &bb, &bv );
+ at_map->bam_from_tbls = bb.bb_val;
+ if ( at_row.value_len[ 3 ] <= 0 ) {
+ BER_BVZERO( &at_map->bam_join_where );
+
+ } else {
+ ber_str2bv( at_row.cols[ 3 ], 0, 1,
+ &at_map->bam_join_where );
+ }
+ at_map->bam_add_proc = NULL;
+ if ( at_row.value_len[ 4 ] > 0 ) {
+ at_map->bam_add_proc = ch_strdup( at_row.cols[ 4 ] );
+ }
+ at_map->bam_delete_proc = NULL;
+ if ( at_row.value_len[ 5 ] > 0 ) {
+ at_map->bam_delete_proc = ch_strdup( at_row.cols[ 5 ] );
+ }
+ if ( lutil_atoix( &at_map->bam_param_order, at_row.cols[ 6 ], 0 ) != 0 ) {
+ /* error */
+ }
+ if ( lutil_atoix( &at_map->bam_expect_return, at_row.cols[ 7 ], 0 ) != 0 ) {
+ /* error */
+ }
+ backsql_make_attr_query( bas->bas_bi, oc_map, at_map );
+ Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_attr_mapping(): "
+ "preconstructed query \"%s\"\n",
+ at_map->bam_query );
+ at_map->bam_next = NULL;
+ if ( ldap_avl_insert( &oc_map->bom_attrs, at_map, backsql_cmp_attr, backsql_dup_attr ) == BACKSQL_DUPLICATE ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_oc_get_attr_mapping(): "
+ "duplicate attribute \"%s\" "
+ "in objectClass \"%s\" map\n",
+ at_map->bam_ad->ad_cname.bv_val,
+ oc_map->bom_oc->soc_cname.bv_val );
+ ch_free( at_map );
+ }
+
+ if ( !BER_BVISNULL( &bas->bas_bi->sql_upper_func ) &&
+ BER_BVISNULL( &at_map->bam_sel_expr_u ) )
+ {
+ struct berbuf bb = BB_NULL;
+
+ backsql_strfcat_x( &bb, NULL, "bcbc",
+ &bas->bas_bi->sql_upper_func,
+ '(' /* ) */ ,
+ &at_map->bam_sel_expr,
+ /* ( */ ')' );
+ at_map->bam_sel_expr_u = bb.bb_val;
+ }
+ }
+ backsql_FreeRow( &at_row );
+ SQLFreeStmt( bas->bas_sth, SQL_CLOSE );
+
+ Debug( LDAP_DEBUG_TRACE, "backsql_load_schema_map(\"%s\"): "
+ "autoadding 'objectClass' and 'ref' mappings\n",
+ BACKSQL_OC_NAME( oc_map ) );
+
+ (void)backsql_add_sysmaps( bas->bas_bi, oc_map );
+
+ return BACKSQL_AVL_CONTINUE;
+}
+
+
+int
+backsql_load_schema_map( backsql_info *bi, SQLHDBC dbh )
+{
+ SQLHSTMT sth = SQL_NULL_HSTMT;
+ RETCODE rc;
+ BACKSQL_ROW_NTS oc_row;
+ backsql_key_t oc_id;
+ backsql_oc_map_rec *oc_map;
+ struct backsql_attr_schema_info bas;
+
+ int delete_proc_idx = 5;
+ int create_hint_idx = delete_proc_idx + 2;
+
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_load_schema_map()\n" );
+
+ /*
+ * TimesTen : See if the ldap_entries.dn_ru field exists in the schema
+ */
+ if ( !BACKSQL_DONTCHECK_LDAPINFO_DN_RU( bi ) ) {
+ rc = backsql_Prepare( dbh, &sth,
+ backsql_check_dn_ru_query, 0 );
+ if ( rc == SQL_SUCCESS ) {
+ /* Yes, the field exists */
+ bi->sql_flags |= BSQLF_HAS_LDAPINFO_DN_RU;
+ Debug( LDAP_DEBUG_TRACE, "ldapinfo.dn_ru field exists "
+ "in the schema\n" );
+ } else {
+ /* No such field exists */
+ bi->sql_flags &= ~BSQLF_HAS_LDAPINFO_DN_RU;
+ }
+
+ SQLFreeStmt( sth, SQL_DROP );
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "backsql_load_schema_map(): oc_query \"%s\"\n",
+ bi->sql_oc_query );
+
+ rc = backsql_Prepare( dbh, &sth, bi->sql_oc_query, 0 );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_load_schema_map(): "
+ "error preparing oc_query: \"%s\"\n",
+ bi->sql_oc_query );
+ backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc );
+ return LDAP_OTHER;
+ }
+
+ rc = SQLExecute( sth );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_load_schema_map(): "
+ "error executing oc_query: \n" );
+ backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc );
+ return LDAP_OTHER;
+ }
+
+ backsql_BindRowAsStrings( sth, &oc_row );
+ rc = SQLFetch( sth );
+
+ if ( BACKSQL_CREATE_NEEDS_SELECT( bi ) ) {
+ delete_proc_idx++;
+ create_hint_idx++;
+ }
+
+ for ( ; BACKSQL_SUCCESS( rc ); rc = SQLFetch( sth ) ) {
+ {
+ struct {
+ int idx;
+ char *name;
+ } required[] = {
+ { 0, "id" },
+ { 1, "name" },
+ { 2, "keytbl" },
+ { 3, "keycol" },
+ { -1, "expect_return" },
+ { -1, NULL },
+ };
+ int i;
+
+ required[4].idx = delete_proc_idx + 1;
+
+ for ( i = 0; required[ i ].name != NULL; i++ ) {
+ if ( oc_row.value_len[ required[ i ].idx ] <= 0 ) {
+ Debug( LDAP_DEBUG_ANY,
+ "backsql_load_schema_map(): "
+ "required column #%d \"%s\" is empty\n",
+ required[ i ].idx, required[ i ].name );
+ return LDAP_OTHER;
+ }
+ }
+ }
+
+ Debug(LDAP_DEBUG_TRACE,
+ "objectClass: " "id=\"%s\" " "name=\"%s\" " "keytbl=\"%s\" " "keycol=\"%s\" " "create_proc=\"%s\" " "create_keyval=\"%s\" " "delete_proc=\"%s\" " "expect_return=\"%s\"" "create_hint=\"%s\" \n",
+ oc_row.cols[0], oc_row.cols[1], oc_row.cols[2],
+ oc_row.cols[3],
+ oc_row.cols[4] ? oc_row.cols[4] : "",
+ (BACKSQL_CREATE_NEEDS_SELECT(bi) && oc_row.cols[5]) ? oc_row.cols[5] : "",
+ oc_row.cols[delete_proc_idx] ? oc_row.cols[delete_proc_idx] : "",
+ oc_row.cols[delete_proc_idx + 1],
+ ((oc_row.ncols > create_hint_idx) && oc_row.cols[create_hint_idx]) ? oc_row.cols[create_hint_idx] : "" );
+
+ oc_map = (backsql_oc_map_rec *)ch_calloc( 1,
+ sizeof( backsql_oc_map_rec ) );
+
+ if ( BACKSQL_STR2ID( &oc_map->bom_id, oc_row.cols[ 0 ], 0 ) != 0 ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_load_schema_map(): "
+ "unable to parse id=\"%s\"\n",
+ oc_row.cols[ 0 ] );
+ ch_free( oc_map );
+ return LDAP_OTHER;
+ }
+
+ oc_map->bom_oc = oc_find( oc_row.cols[ 1 ] );
+ if ( oc_map->bom_oc == NULL ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_load_schema_map(): "
+ "objectClass \"%s\" is not defined in schema\n",
+ oc_row.cols[ 1 ] );
+ ch_free( oc_map );
+ return LDAP_OTHER; /* undefined objectClass ? */
+ }
+
+ ber_str2bv( oc_row.cols[ 2 ], 0, 1, &oc_map->bom_keytbl );
+ ber_str2bv( oc_row.cols[ 3 ], 0, 1, &oc_map->bom_keycol );
+ oc_map->bom_create_proc = ( oc_row.value_len[ 4 ] <= 0 ) ? NULL
+ : ch_strdup( oc_row.cols[ 4 ] );
+
+ if ( BACKSQL_CREATE_NEEDS_SELECT( bi ) ) {
+ oc_map->bom_create_keyval = ( oc_row.value_len[ 5 ] <= 0 )
+ ? NULL : ch_strdup( oc_row.cols[ 5 ] );
+ }
+ oc_map->bom_delete_proc = ( oc_row.value_len[ delete_proc_idx ] <= 0 ) ? NULL
+ : ch_strdup( oc_row.cols[ delete_proc_idx ] );
+ if ( lutil_atoix( &oc_map->bom_expect_return, oc_row.cols[ delete_proc_idx + 1 ], 0 ) != 0 ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_load_schema_map(): "
+ "unable to parse expect_return=\"%s\" for objectClass \"%s\"\n",
+ oc_row.cols[ delete_proc_idx + 1 ], oc_row.cols[ 1 ] );
+ ch_free( oc_map );
+ return LDAP_OTHER;
+ }
+
+ if ( ( oc_row.ncols > create_hint_idx ) &&
+ ( oc_row.value_len[ create_hint_idx ] > 0 ) )
+ {
+ const char *text;
+
+ oc_map->bom_create_hint = NULL;
+ rc = slap_str2ad( oc_row.cols[ create_hint_idx ],
+ &oc_map->bom_create_hint, &text );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "load_schema_map(): "
+ "error matching "
+ "AttributeDescription %s "
+ "in create_hint: %s (%d)\n",
+ oc_row.cols[ create_hint_idx ],
+ text, rc );
+ backsql_PrintErrors( bi->sql_db_env, dbh,
+ sth, rc );
+ ch_free( oc_map );
+ return LDAP_OTHER;
+ }
+ }
+
+ /*
+ * FIXME: first attempt to check for offending
+ * instructions in {create|delete}_proc
+ */
+
+ oc_map->bom_attrs = NULL;
+ if ( ldap_avl_insert( &bi->sql_oc_by_oc, oc_map, backsql_cmp_oc, ldap_avl_dup_error ) == -1 ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_load_schema_map(): "
+ "duplicate objectClass \"%s\" in objectClass map\n",
+ oc_map->bom_oc->soc_cname.bv_val );
+ ch_free( oc_map );
+ return LDAP_OTHER;
+ }
+ if ( ldap_avl_insert( &bi->sql_oc_by_id, oc_map, backsql_cmp_oc_id, ldap_avl_dup_error ) == -1 ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_load_schema_map(): "
+ "duplicate objectClass \"%s\" in objectClass by ID map\n",
+ oc_map->bom_oc->soc_cname.bv_val );
+ return LDAP_OTHER;
+ }
+ oc_id = oc_map->bom_id;
+ Debug( LDAP_DEBUG_TRACE, "backsql_load_schema_map(): "
+ "objectClass \"%s\":\n keytbl=\"%s\" keycol=\"%s\"\n",
+ BACKSQL_OC_NAME( oc_map ),
+ oc_map->bom_keytbl.bv_val, oc_map->bom_keycol.bv_val );
+ if ( oc_map->bom_create_proc ) {
+ Debug( LDAP_DEBUG_TRACE, " create_proc=\"%s\"\n",
+ oc_map->bom_create_proc );
+ }
+ if ( oc_map->bom_create_keyval ) {
+ Debug( LDAP_DEBUG_TRACE, " create_keyval=\"%s\"\n",
+ oc_map->bom_create_keyval );
+ }
+ if ( oc_map->bom_create_hint ) {
+ Debug( LDAP_DEBUG_TRACE, " create_hint=\"%s\"\n",
+ oc_map->bom_create_hint->ad_cname.bv_val );
+ }
+ if ( oc_map->bom_delete_proc ) {
+ Debug( LDAP_DEBUG_TRACE, " delete_proc=\"%s\"\n",
+ oc_map->bom_delete_proc );
+ }
+ Debug( LDAP_DEBUG_TRACE, " expect_return: "
+ "add=%d, del=%d; attributes:\n",
+ BACKSQL_IS_ADD( oc_map->bom_expect_return ),
+ BACKSQL_IS_DEL( oc_map->bom_expect_return ) );
+ }
+
+ backsql_FreeRow( &oc_row );
+ SQLFreeStmt( sth, SQL_DROP );
+
+ /* prepare for attribute fetching */
+ Debug( LDAP_DEBUG_TRACE, "backsql_load_schema_map(): at_query \"%s\"\n",
+ bi->sql_at_query );
+
+ rc = backsql_Prepare( dbh, &sth, bi->sql_at_query, 0 );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_load_schema_map(): "
+ "error preparing at_query: \"%s\"\n",
+ bi->sql_at_query );
+ backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc );
+ return LDAP_OTHER;
+ }
+
+ rc = backsql_BindParamNumID( sth, 1, SQL_PARAM_INPUT, &oc_id );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_load_schema_map(): "
+ "error binding param \"oc_id\" for at_query\n" );
+ backsql_PrintErrors( bi->sql_db_env, dbh, sth, rc );
+ SQLFreeStmt( sth, SQL_DROP );
+ return LDAP_OTHER;
+ }
+
+ bas.bas_bi = bi;
+ bas.bas_dbh = dbh;
+ bas.bas_sth = sth;
+ bas.bas_oc_id = &oc_id;
+ bas.bas_rc = LDAP_SUCCESS;
+
+ (void)ldap_avl_apply( bi->sql_oc_by_oc, backsql_oc_get_attr_mapping,
+ &bas, BACKSQL_AVL_STOP, AVL_INORDER );
+
+ SQLFreeStmt( sth, SQL_DROP );
+
+ bi->sql_flags |= BSQLF_SCHEMA_LOADED;
+
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_load_schema_map()\n" );
+
+ return bas.bas_rc;
+}
+
+backsql_oc_map_rec *
+backsql_oc2oc( backsql_info *bi, ObjectClass *oc )
+{
+ backsql_oc_map_rec tmp, *res;
+
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_oc2oc(): "
+ "searching for objectclass with name=\"%s\"\n",
+ oc->soc_cname.bv_val );
+#endif /* BACKSQL_TRACE */
+
+ tmp.bom_oc = oc;
+ res = (backsql_oc_map_rec *)ldap_avl_find( bi->sql_oc_by_oc, &tmp, backsql_cmp_oc );
+#ifdef BACKSQL_TRACE
+ if ( res != NULL ) {
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_oc2oc(): "
+ "found name=\"%s\", id=%d\n",
+ BACKSQL_OC_NAME( res ), res->bom_id );
+ } else {
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_oc2oc(): "
+ "not found\n" );
+ }
+#endif /* BACKSQL_TRACE */
+
+ return res;
+}
+
+backsql_oc_map_rec *
+backsql_name2oc( backsql_info *bi, struct berval *oc_name )
+{
+ backsql_oc_map_rec tmp, *res;
+
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE, "==>oc_with_name(): "
+ "searching for objectclass with name=\"%s\"\n",
+ oc_name->bv_val );
+#endif /* BACKSQL_TRACE */
+
+ tmp.bom_oc = oc_bvfind( oc_name );
+ if ( tmp.bom_oc == NULL ) {
+ return NULL;
+ }
+
+ res = (backsql_oc_map_rec *)ldap_avl_find( bi->sql_oc_by_oc, &tmp, backsql_cmp_oc );
+#ifdef BACKSQL_TRACE
+ if ( res != NULL ) {
+ Debug( LDAP_DEBUG_TRACE, "<==oc_with_name(): "
+ "found name=\"%s\", id=%d\n",
+ BACKSQL_OC_NAME( res ), res->bom_id );
+ } else {
+ Debug( LDAP_DEBUG_TRACE, "<==oc_with_name(): "
+ "not found\n" );
+ }
+#endif /* BACKSQL_TRACE */
+
+ return res;
+}
+
+backsql_oc_map_rec *
+backsql_id2oc( backsql_info *bi, unsigned long id )
+{
+ backsql_oc_map_rec tmp, *res;
+
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE, "==>oc_with_id(): "
+ "searching for objectclass with id=%lu\n", id );
+#endif /* BACKSQL_TRACE */
+
+ tmp.bom_id = id;
+ res = (backsql_oc_map_rec *)ldap_avl_find( bi->sql_oc_by_id, &tmp,
+ backsql_cmp_oc_id );
+
+#ifdef BACKSQL_TRACE
+ if ( res != NULL ) {
+ Debug( LDAP_DEBUG_TRACE, "<==oc_with_id(): "
+ "found name=\"%s\", id=%lu\n",
+ BACKSQL_OC_NAME( res ), res->bom_id );
+ } else {
+ Debug( LDAP_DEBUG_TRACE, "<==oc_with_id(): "
+ "id=%lu not found\n", res->bom_id );
+ }
+#endif /* BACKSQL_TRACE */
+
+ return res;
+}
+
+backsql_at_map_rec *
+backsql_ad2at( backsql_oc_map_rec* objclass, AttributeDescription *ad )
+{
+ backsql_at_map_rec tmp = { 0 }, *res;
+
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_ad2at(): "
+ "searching for attribute \"%s\" for objectclass \"%s\"\n",
+ ad->ad_cname.bv_val, BACKSQL_OC_NAME( objclass ) );
+#endif /* BACKSQL_TRACE */
+
+ tmp.bam_ad = ad;
+ res = (backsql_at_map_rec *)ldap_avl_find( objclass->bom_attrs, &tmp,
+ backsql_cmp_attr );
+
+#ifdef BACKSQL_TRACE
+ if ( res != NULL ) {
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_ad2at(): "
+ "found name=\"%s\", sel_expr=\"%s\"\n",
+ res->bam_ad->ad_cname.bv_val,
+ res->bam_sel_expr.bv_val );
+ } else {
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_ad2at(): "
+ "not found\n" );
+ }
+#endif /* BACKSQL_TRACE */
+
+ return res;
+}
+
+/* attributeType inheritance */
+struct supad2at_t {
+ backsql_at_map_rec **ret;
+ AttributeDescription *ad;
+ unsigned n;
+};
+
+#define SUPAD2AT_STOP (-1)
+
+static int
+supad2at_f( void *v_at, void *v_arg )
+{
+ backsql_at_map_rec *at = (backsql_at_map_rec *)v_at;
+ struct supad2at_t *va = (struct supad2at_t *)v_arg;
+
+ if ( is_at_subtype( at->bam_ad->ad_type, va->ad->ad_type ) ) {
+ backsql_at_map_rec **ret = NULL;
+ unsigned i;
+
+ /* if already listed, holler! (should never happen) */
+ if ( va->ret ) {
+ for ( i = 0; i < va->n; i++ ) {
+ if ( va->ret[ i ]->bam_ad == at->bam_ad ) {
+ break;
+ }
+ }
+
+ if ( i < va->n ) {
+ return 0;
+ }
+ }
+
+ ret = ch_realloc( va->ret,
+ sizeof( backsql_at_map_rec * ) * ( va->n + 2 ) );
+ if ( ret == NULL ) {
+ ch_free( va->ret );
+ va->ret = NULL;
+ va->n = 0;
+ return SUPAD2AT_STOP;
+ }
+
+ ret[ va->n ] = at;
+ va->n++;
+ ret[ va->n ] = NULL;
+ va->ret = ret;
+ }
+
+ return 0;
+}
+
+/*
+ * stores in *pret a NULL terminated array of pointers
+ * to backsql_at_map_rec whose attributeType is supad->ad_type
+ * or derived from it
+ */
+int
+backsql_supad2at( backsql_oc_map_rec *objclass, AttributeDescription *supad,
+ backsql_at_map_rec ***pret )
+{
+ struct supad2at_t va = { 0 };
+ int rc;
+
+ assert( objclass != NULL );
+ assert( supad != NULL );
+ assert( pret != NULL );
+
+ *pret = NULL;
+
+ va.ad = supad;
+
+ rc = ldap_avl_apply( objclass->bom_attrs, supad2at_f, &va,
+ SUPAD2AT_STOP, AVL_INORDER );
+ if ( rc == SUPAD2AT_STOP ) {
+ return -1;
+ }
+
+ *pret = va.ret;
+
+ return 0;
+}
+
+static void
+backsql_free_attr( void *v_at )
+{
+ backsql_at_map_rec *at = v_at;
+
+ Debug( LDAP_DEBUG_TRACE, "==>free_attr(): \"%s\"\n",
+ at->bam_ad->ad_cname.bv_val );
+ ch_free( at->bam_sel_expr.bv_val );
+ if ( !BER_BVISNULL( &at->bam_from_tbls ) ) {
+ ch_free( at->bam_from_tbls.bv_val );
+ }
+ if ( !BER_BVISNULL( &at->bam_join_where ) ) {
+ ch_free( at->bam_join_where.bv_val );
+ }
+ if ( at->bam_add_proc != NULL ) {
+ ch_free( at->bam_add_proc );
+ }
+ if ( at->bam_delete_proc != NULL ) {
+ ch_free( at->bam_delete_proc );
+ }
+ if ( at->bam_query != NULL ) {
+ ch_free( at->bam_query );
+ }
+
+#ifdef BACKSQL_COUNTQUERY
+ if ( at->bam_countquery != NULL ) {
+ ch_free( at->bam_countquery );
+ }
+#endif /* BACKSQL_COUNTQUERY */
+
+ /* TimesTen */
+ if ( !BER_BVISNULL( &at->bam_sel_expr_u ) ) {
+ ch_free( at->bam_sel_expr_u.bv_val );
+ }
+
+ if ( at->bam_next ) {
+ backsql_free_attr( at->bam_next );
+ }
+
+ ch_free( at );
+
+ Debug( LDAP_DEBUG_TRACE, "<==free_attr()\n" );
+}
+
+static void
+backsql_free_oc( void *v_oc )
+{
+ backsql_oc_map_rec *oc = v_oc;
+
+ Debug( LDAP_DEBUG_TRACE, "==>free_oc(): \"%s\"\n",
+ BACKSQL_OC_NAME( oc ) );
+ ldap_avl_free( oc->bom_attrs, backsql_free_attr );
+ ch_free( oc->bom_keytbl.bv_val );
+ ch_free( oc->bom_keycol.bv_val );
+ if ( oc->bom_create_proc != NULL ) {
+ ch_free( oc->bom_create_proc );
+ }
+ if ( oc->bom_create_keyval != NULL ) {
+ ch_free( oc->bom_create_keyval );
+ }
+ if ( oc->bom_delete_proc != NULL ) {
+ ch_free( oc->bom_delete_proc );
+ }
+ ch_free( oc );
+
+ Debug( LDAP_DEBUG_TRACE, "<==free_oc()\n" );
+}
+
+int
+backsql_destroy_schema_map( backsql_info *bi )
+{
+ Debug( LDAP_DEBUG_TRACE, "==>destroy_schema_map()\n" );
+ ldap_avl_free( bi->sql_oc_by_oc, 0 );
+ ldap_avl_free( bi->sql_oc_by_id, backsql_free_oc );
+ Debug( LDAP_DEBUG_TRACE, "<==destroy_schema_map()\n" );
+ return 0;
+}
+
diff --git a/servers/slapd/back-sql/search.c b/servers/slapd/back-sql/search.c
new file mode 100644
index 0000000..d4177f6
--- /dev/null
+++ b/servers/slapd/back-sql/search.c
@@ -0,0 +1,2874 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1999 Dmitry Kovalev.
+ * Portions Copyright 2002 Pierangelo Masarati.
+ * Portions Copyright 2004 Mark Adamson.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Dmitry Kovalev for inclusion
+ * by OpenLDAP Software. Additional significant contributors include
+ * Pierangelo Masarati and Mark Adamson.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <sys/types.h>
+#include "ac/string.h"
+#include "ac/ctype.h"
+
+#include "lutil.h"
+#include "slap.h"
+#include "proto-sql.h"
+
+static int backsql_process_filter( backsql_srch_info *bsi, Filter *f );
+static int backsql_process_filter_eq( backsql_srch_info *bsi,
+ backsql_at_map_rec *at,
+ int casefold, struct berval *filter_value );
+static int backsql_process_filter_like( backsql_srch_info *bsi,
+ backsql_at_map_rec *at,
+ int casefold, struct berval *filter_value );
+static int backsql_process_filter_attr( backsql_srch_info *bsi, Filter *f,
+ backsql_at_map_rec *at );
+
+/* For LDAP_CONTROL_PAGEDRESULTS, a 32 bit cookie is available to keep track of
+ the state of paged results. The ldap_entries.id and oc_map_id values of the
+ last entry returned are used as the cookie, so 6 bits are used for the OC id
+ and the other 26 for ldap_entries ID number. If your max(oc_map_id) is more
+ than 63, you will need to steal more bits from ldap_entries ID number and
+ put them into the OC ID part of the cookie. */
+
+/* NOTE: not supported when BACKSQL_ARBITRARY_KEY is defined */
+#ifndef BACKSQL_ARBITRARY_KEY
+#define SQL_TO_PAGECOOKIE(id, oc) (((id) << 6 ) | ((oc) & 0x3F))
+#define PAGECOOKIE_TO_SQL_ID(pc) ((pc) >> 6)
+#define PAGECOOKIE_TO_SQL_OC(pc) ((pc) & 0x3F)
+
+static int parse_paged_cookie( Operation *op, SlapReply *rs );
+
+static void send_paged_response(
+ Operation *op,
+ SlapReply *rs,
+ ID *lastid );
+#endif /* ! BACKSQL_ARBITRARY_KEY */
+
+/* Look for chars that need to be escaped, return count of them.
+ * If out is non-NULL, copy escape'd val to it.
+ */
+static int
+backsql_val_escape( Operation *op, struct berval *in, struct berval *out )
+{
+ char *ptr, *end;
+ int q = 0;
+
+ ptr = in->bv_val;
+ end = ptr + in->bv_len;
+ while (ptr < end) {
+ if ( *ptr == '\'' )
+ q++;
+ ptr++;
+ }
+ if ( q && out ) {
+ char *dst;
+ out->bv_len = in->bv_len + q;
+ out->bv_val = op->o_tmpalloc( out->bv_len + 1, op->o_tmpmemctx );
+ ptr = in->bv_val;
+ dst = out->bv_val;
+ while (ptr < end ) {
+ if ( *ptr == '\'' )
+ *dst++ = '\'';
+ *dst++ = *ptr++;
+ }
+ *dst = '\0';
+ }
+ return q;
+}
+
+static int
+backsql_attrlist_add( backsql_srch_info *bsi, AttributeDescription *ad )
+{
+ int n_attrs = 0;
+ AttributeName *an = NULL;
+
+ if ( bsi->bsi_attrs == NULL ) {
+ return 1;
+ }
+
+ /*
+ * clear the list (retrieve all attrs)
+ */
+ if ( ad == NULL ) {
+ bsi->bsi_op->o_tmpfree( bsi->bsi_attrs, bsi->bsi_op->o_tmpmemctx );
+ bsi->bsi_attrs = NULL;
+ bsi->bsi_flags |= BSQL_SF_ALL_ATTRS;
+ return 1;
+ }
+
+ /* strip ';binary' */
+ if ( slap_ad_is_binary( ad ) ) {
+ ad = ad->ad_type->sat_ad;
+ }
+
+ for ( ; !BER_BVISNULL( &bsi->bsi_attrs[ n_attrs ].an_name ); n_attrs++ ) {
+ an = &bsi->bsi_attrs[ n_attrs ];
+
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_attrlist_add(): "
+ "attribute \"%s\" is in list\n",
+ an->an_name.bv_val );
+ /*
+ * We can live with strcmp because the attribute
+ * list has been normalized before calling be_search
+ */
+ if ( !BACKSQL_NCMP( &an->an_name, &ad->ad_cname ) ) {
+ return 1;
+ }
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_attrlist_add(): "
+ "adding \"%s\" to list\n", ad->ad_cname.bv_val );
+
+ an = (AttributeName *)bsi->bsi_op->o_tmprealloc( bsi->bsi_attrs,
+ sizeof( AttributeName ) * ( n_attrs + 2 ),
+ bsi->bsi_op->o_tmpmemctx );
+ if ( an == NULL ) {
+ return -1;
+ }
+
+ an[ n_attrs ].an_name = ad->ad_cname;
+ an[ n_attrs ].an_desc = ad;
+ BER_BVZERO( &an[ n_attrs + 1 ].an_name );
+
+ bsi->bsi_attrs = an;
+
+ return 1;
+}
+
+/*
+ * Initializes the search structure.
+ *
+ * If get_base_id != 0, the field bsi_base_id is filled
+ * with the entryID of bsi_base_ndn; it must be freed
+ * by backsql_free_entryID() when no longer required.
+ *
+ * NOTE: base must be normalized
+ */
+int
+backsql_init_search(
+ backsql_srch_info *bsi,
+ struct berval *nbase,
+ int scope,
+ time_t stoptime,
+ Filter *filter,
+ SQLHDBC dbh,
+ Operation *op,
+ SlapReply *rs,
+ AttributeName *attrs,
+ unsigned flags )
+{
+ backsql_info *bi = (backsql_info *)op->o_bd->be_private;
+ int rc = LDAP_SUCCESS;
+
+ bsi->bsi_base_ndn = nbase;
+ bsi->bsi_use_subtree_shortcut = 0;
+ BER_BVZERO( &bsi->bsi_base_id.eid_dn );
+ BER_BVZERO( &bsi->bsi_base_id.eid_ndn );
+ bsi->bsi_scope = scope;
+ bsi->bsi_filter = filter;
+ bsi->bsi_dbh = dbh;
+ bsi->bsi_op = op;
+ bsi->bsi_rs = rs;
+ bsi->bsi_flags = BSQL_SF_NONE;
+
+ bsi->bsi_attrs = NULL;
+
+ if ( BACKSQL_FETCH_ALL_ATTRS( bi ) ) {
+ /*
+ * if requested, simply try to fetch all attributes
+ */
+ bsi->bsi_flags |= BSQL_SF_ALL_ATTRS;
+
+ } else {
+ if ( BACKSQL_FETCH_ALL_USERATTRS( bi ) ) {
+ bsi->bsi_flags |= BSQL_SF_ALL_USER;
+
+ } else if ( BACKSQL_FETCH_ALL_OPATTRS( bi ) ) {
+ bsi->bsi_flags |= BSQL_SF_ALL_OPER;
+ }
+
+ if ( attrs == NULL ) {
+ /* NULL means all user attributes */
+ bsi->bsi_flags |= BSQL_SF_ALL_USER;
+
+ } else {
+ AttributeName *p;
+ int got_oc = 0;
+
+ bsi->bsi_attrs = (AttributeName *)bsi->bsi_op->o_tmpalloc(
+ sizeof( AttributeName ),
+ bsi->bsi_op->o_tmpmemctx );
+ BER_BVZERO( &bsi->bsi_attrs[ 0 ].an_name );
+
+ for ( p = attrs; !BER_BVISNULL( &p->an_name ); p++ ) {
+ if ( BACKSQL_NCMP( &p->an_name, slap_bv_all_user_attrs ) == 0 ) {
+ /* handle "*" */
+ bsi->bsi_flags |= BSQL_SF_ALL_USER;
+
+ /* if all attrs are requested, there's
+ * no need to continue */
+ if ( BSQL_ISF_ALL_ATTRS( bsi ) ) {
+ bsi->bsi_op->o_tmpfree( bsi->bsi_attrs,
+ bsi->bsi_op->o_tmpmemctx );
+ bsi->bsi_attrs = NULL;
+ break;
+ }
+ continue;
+
+ } else if ( BACKSQL_NCMP( &p->an_name, slap_bv_all_operational_attrs ) == 0 ) {
+ /* handle "+" */
+ bsi->bsi_flags |= BSQL_SF_ALL_OPER;
+
+ /* if all attrs are requested, there's
+ * no need to continue */
+ if ( BSQL_ISF_ALL_ATTRS( bsi ) ) {
+ bsi->bsi_op->o_tmpfree( bsi->bsi_attrs,
+ bsi->bsi_op->o_tmpmemctx );
+ bsi->bsi_attrs = NULL;
+ break;
+ }
+ continue;
+
+ } else if ( BACKSQL_NCMP( &p->an_name, slap_bv_no_attrs ) == 0 ) {
+ /* ignore "1.1" */
+ continue;
+
+ } else if ( p->an_desc == slap_schema.si_ad_objectClass ) {
+ got_oc = 1;
+ }
+
+ backsql_attrlist_add( bsi, p->an_desc );
+ }
+
+ if ( got_oc == 0 && !( bsi->bsi_flags & BSQL_SF_ALL_USER ) ) {
+ /* add objectClass if not present,
+ * because it is required to understand
+ * if an entry is a referral, an alias
+ * or so... */
+ backsql_attrlist_add( bsi, slap_schema.si_ad_objectClass );
+ }
+ }
+
+ if ( !BSQL_ISF_ALL_ATTRS( bsi ) && bi->sql_anlist ) {
+ AttributeName *p;
+
+ /* use hints if available */
+ for ( p = bi->sql_anlist; !BER_BVISNULL( &p->an_name ); p++ ) {
+ if ( BACKSQL_NCMP( &p->an_name, slap_bv_all_user_attrs ) == 0 ) {
+ /* handle "*" */
+ bsi->bsi_flags |= BSQL_SF_ALL_USER;
+
+ /* if all attrs are requested, there's
+ * no need to continue */
+ if ( BSQL_ISF_ALL_ATTRS( bsi ) ) {
+ bsi->bsi_op->o_tmpfree( bsi->bsi_attrs,
+ bsi->bsi_op->o_tmpmemctx );
+ bsi->bsi_attrs = NULL;
+ break;
+ }
+ continue;
+
+ } else if ( BACKSQL_NCMP( &p->an_name, slap_bv_all_operational_attrs ) == 0 ) {
+ /* handle "+" */
+ bsi->bsi_flags |= BSQL_SF_ALL_OPER;
+
+ /* if all attrs are requested, there's
+ * no need to continue */
+ if ( BSQL_ISF_ALL_ATTRS( bsi ) ) {
+ bsi->bsi_op->o_tmpfree( bsi->bsi_attrs,
+ bsi->bsi_op->o_tmpmemctx );
+ bsi->bsi_attrs = NULL;
+ break;
+ }
+ continue;
+ }
+
+ backsql_attrlist_add( bsi, p->an_desc );
+ }
+
+ }
+ }
+
+ bsi->bsi_id_list = NULL;
+ bsi->bsi_id_listtail = &bsi->bsi_id_list;
+ bsi->bsi_n_candidates = 0;
+ bsi->bsi_stoptime = stoptime;
+ BER_BVZERO( &bsi->bsi_sel.bb_val );
+ bsi->bsi_sel.bb_len = 0;
+ BER_BVZERO( &bsi->bsi_from.bb_val );
+ bsi->bsi_from.bb_len = 0;
+ BER_BVZERO( &bsi->bsi_join_where.bb_val );
+ bsi->bsi_join_where.bb_len = 0;
+ BER_BVZERO( &bsi->bsi_flt_where.bb_val );
+ bsi->bsi_flt_where.bb_len = 0;
+ bsi->bsi_filter_oc = NULL;
+
+ if ( BACKSQL_IS_GET_ID( flags ) ) {
+ int matched = BACKSQL_IS_MATCHED( flags );
+ int getentry = BACKSQL_IS_GET_ENTRY( flags );
+ int gotit = 0;
+
+ assert( op->o_bd->be_private != NULL );
+
+ rc = backsql_dn2id( op, rs, dbh, nbase, &bsi->bsi_base_id,
+ matched, 1 );
+
+ /* the entry is collected either if requested for by getentry
+ * or if get noSuchObject and requested to climb the tree,
+ * so that a matchedDN or a referral can be returned */
+ if ( ( rc == LDAP_NO_SUCH_OBJECT && matched ) || getentry ) {
+ if ( !BER_BVISNULL( &bsi->bsi_base_id.eid_ndn ) ) {
+ assert( bsi->bsi_e != NULL );
+
+ if ( dn_match( nbase, &bsi->bsi_base_id.eid_ndn ) )
+ {
+ gotit = 1;
+ }
+
+ /*
+ * let's see if it is a referral and, in case, get it
+ */
+ backsql_attrlist_add( bsi, slap_schema.si_ad_ref );
+ rc = backsql_id2entry( bsi, &bsi->bsi_base_id );
+ if ( rc == LDAP_SUCCESS ) {
+ if ( is_entry_referral( bsi->bsi_e ) )
+ {
+ BerVarray erefs = get_entry_referrals( op, bsi->bsi_e );
+ if ( erefs ) {
+ rc = rs->sr_err = LDAP_REFERRAL;
+ rs->sr_ref = referral_rewrite( erefs,
+ &bsi->bsi_e->e_nname,
+ &op->o_req_dn,
+ scope );
+ ber_bvarray_free( erefs );
+
+ } else {
+ rc = rs->sr_err = LDAP_OTHER;
+ rs->sr_text = "bad referral object";
+ }
+
+ } else if ( !gotit ) {
+ rc = rs->sr_err = LDAP_NO_SUCH_OBJECT;
+ }
+ }
+
+ } else {
+ rs->sr_err = rc;
+ }
+ }
+
+ if ( gotit && BACKSQL_IS_GET_OC( flags ) ) {
+ bsi->bsi_base_id.eid_oc = backsql_id2oc( bi,
+ bsi->bsi_base_id.eid_oc_id );
+ if ( bsi->bsi_base_id.eid_oc == NULL ) {
+ /* error? */
+ backsql_free_entryID( &bsi->bsi_base_id, 1,
+ op->o_tmpmemctx );
+ rc = rs->sr_err = LDAP_OTHER;
+ }
+ }
+ }
+
+ bsi->bsi_status = rc;
+
+ switch ( rc ) {
+ case LDAP_SUCCESS:
+ case LDAP_REFERRAL:
+ break;
+
+ default:
+ bsi->bsi_op->o_tmpfree( bsi->bsi_attrs,
+ bsi->bsi_op->o_tmpmemctx );
+ break;
+ }
+
+ return rc;
+}
+
+static int
+backsql_process_filter_list( backsql_srch_info *bsi, Filter *f, int op )
+{
+ int res;
+
+ if ( !f ) {
+ return 0;
+ }
+
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx, "c", '(' /* ) */ );
+
+ while ( 1 ) {
+ res = backsql_process_filter( bsi, f );
+ if ( res < 0 ) {
+ /*
+ * TimesTen : If the query has no answers,
+ * don't bother to run the query.
+ */
+ return -1;
+ }
+
+ f = f->f_next;
+ if ( f == NULL ) {
+ break;
+ }
+
+ switch ( op ) {
+ case LDAP_FILTER_AND:
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx, "l",
+ (ber_len_t)STRLENOF( " AND " ),
+ " AND " );
+ break;
+
+ case LDAP_FILTER_OR:
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx, "l",
+ (ber_len_t)STRLENOF( " OR " ),
+ " OR " );
+ break;
+ }
+ }
+
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx, "c", /* ( */ ')' );
+
+ return 1;
+}
+
+static int
+backsql_process_sub_filter( backsql_srch_info *bsi, Filter *f,
+ backsql_at_map_rec *at )
+{
+ backsql_info *bi = (backsql_info *)bsi->bsi_op->o_bd->be_private;
+ int i;
+ int casefold = 0;
+ int escaped = 0;
+ struct berval escval, *fvalue;
+
+ if ( !f ) {
+ return 0;
+ }
+
+ /* always uppercase strings by now */
+#ifdef BACKSQL_UPPERCASE_FILTER
+ if ( f->f_sub_desc->ad_type->sat_substr &&
+ SLAP_MR_ASSOCIATED( f->f_sub_desc->ad_type->sat_substr,
+ bi->sql_caseIgnoreMatch ) )
+#endif /* BACKSQL_UPPERCASE_FILTER */
+ {
+ casefold = 1;
+ }
+
+ if ( f->f_sub_desc->ad_type->sat_substr &&
+ SLAP_MR_ASSOCIATED( f->f_sub_desc->ad_type->sat_substr,
+ bi->sql_telephoneNumberMatch ) )
+ {
+
+ struct berval bv;
+ ber_len_t i, s, a;
+
+ /*
+ * to check for matching telephone numbers
+ * with intermixed chars, e.g. val='1234'
+ * use
+ *
+ * val LIKE '%1%2%3%4%'
+ */
+
+ BER_BVZERO( &bv );
+ if ( f->f_sub_initial.bv_val ) {
+ bv.bv_len += f->f_sub_initial.bv_len + backsql_val_escape( NULL, &f->f_sub_initial, NULL );
+ }
+ if ( f->f_sub_any != NULL ) {
+ for ( a = 0; f->f_sub_any[ a ].bv_val != NULL; a++ ) {
+ bv.bv_len += f->f_sub_any[ a ].bv_len + backsql_val_escape( NULL, &f->f_sub_any[ a ], NULL );
+ }
+ }
+ if ( f->f_sub_final.bv_val ) {
+ bv.bv_len += f->f_sub_final.bv_len + backsql_val_escape( NULL, &f->f_sub_final, NULL );
+ }
+ bv.bv_len = 2 * bv.bv_len - 1;
+ bv.bv_val = ch_malloc( bv.bv_len + 1 );
+
+ s = 0;
+ if ( !BER_BVISNULL( &f->f_sub_initial ) ) {
+ fvalue = &f->f_sub_initial;
+ escaped = backsql_val_escape( bsi->bsi_op, fvalue, &escval );
+ if ( escaped )
+ fvalue = &escval;
+ bv.bv_val[ s ] = fvalue->bv_val[ 0 ];
+ for ( i = 1; i < fvalue->bv_len; i++ ) {
+ bv.bv_val[ s + 2 * i - 1 ] = '%';
+ bv.bv_val[ s + 2 * i ] = fvalue->bv_val[ i ];
+ }
+ bv.bv_val[ s + 2 * i - 1 ] = '%';
+ s += 2 * i;
+ if ( escaped )
+ bsi->bsi_op->o_tmpfree( escval.bv_val, bsi->bsi_op->o_tmpmemctx );
+ }
+
+ if ( f->f_sub_any != NULL ) {
+ for ( a = 0; !BER_BVISNULL( &f->f_sub_any[ a ] ); a++ ) {
+ fvalue = &f->f_sub_any[ a ];
+ escaped = backsql_val_escape( bsi->bsi_op, fvalue, &escval );
+ if ( escaped )
+ fvalue = &escval;
+ bv.bv_val[ s ] = fvalue->bv_val[ 0 ];
+ for ( i = 1; i < fvalue->bv_len; i++ ) {
+ bv.bv_val[ s + 2 * i - 1 ] = '%';
+ bv.bv_val[ s + 2 * i ] = fvalue->bv_val[ i ];
+ }
+ bv.bv_val[ s + 2 * i - 1 ] = '%';
+ s += 2 * i;
+ if ( escaped )
+ bsi->bsi_op->o_tmpfree( escval.bv_val, bsi->bsi_op->o_tmpmemctx );
+ }
+ }
+
+ if ( !BER_BVISNULL( &f->f_sub_final ) ) {
+ fvalue = &f->f_sub_final;
+ escaped = backsql_val_escape( bsi->bsi_op, fvalue, &escval );
+ if ( escaped )
+ fvalue = &escval;
+ bv.bv_val[ s ] = fvalue->bv_val[ 0 ];
+ for ( i = 1; i < fvalue->bv_len; i++ ) {
+ bv.bv_val[ s + 2 * i - 1 ] = '%';
+ bv.bv_val[ s + 2 * i ] = fvalue->bv_val[ i ];
+ }
+ bv.bv_val[ s + 2 * i - 1 ] = '%';
+ s += 2 * i;
+ if ( escaped )
+ bsi->bsi_op->o_tmpfree( escval.bv_val, bsi->bsi_op->o_tmpmemctx );
+ }
+
+ bv.bv_val[ s - 1 ] = '\0';
+
+ (void)backsql_process_filter_like( bsi, at, casefold, &bv );
+ ch_free( bv.bv_val );
+
+ return 1;
+ }
+
+ /*
+ * When dealing with case-sensitive strings
+ * we may omit normalization; however, normalized
+ * SQL filters are more liberal.
+ */
+
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx, "c", '(' /* ) */ );
+
+ /* TimesTen */
+ Debug( LDAP_DEBUG_TRACE, "backsql_process_sub_filter(%s):\n",
+ at->bam_ad->ad_cname.bv_val );
+ Debug(LDAP_DEBUG_TRACE, " expr: '%s%s%s'\n", at->bam_sel_expr.bv_val,
+ at->bam_sel_expr_u.bv_val ? "' '" : "",
+ at->bam_sel_expr_u.bv_val ? at->bam_sel_expr_u.bv_val : "" );
+ if ( casefold && BACKSQL_AT_CANUPPERCASE( at ) ) {
+ /*
+ * If a pre-upper-cased version of the column
+ * or a precompiled upper function exists, use it
+ */
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "bl",
+ &at->bam_sel_expr_u,
+ (ber_len_t)STRLENOF( " LIKE '" ),
+ " LIKE '" );
+
+ } else {
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "bl",
+ &at->bam_sel_expr,
+ (ber_len_t)STRLENOF( " LIKE '" ), " LIKE '" );
+ }
+
+ if ( !BER_BVISNULL( &f->f_sub_initial ) ) {
+ ber_len_t start;
+
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE,
+ "==>backsql_process_sub_filter(%s): "
+ "sub_initial=\"%s\"\n", at->bam_ad->ad_cname.bv_val,
+ f->f_sub_initial.bv_val );
+#endif /* BACKSQL_TRACE */
+
+ fvalue = &f->f_sub_initial;
+ escaped = backsql_val_escape( bsi->bsi_op, fvalue, &escval );
+ if ( escaped )
+ fvalue = &escval;
+ start = bsi->bsi_flt_where.bb_val.bv_len;
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "b",
+ fvalue );
+ if ( escaped )
+ bsi->bsi_op->o_tmpfree( escval.bv_val, bsi->bsi_op->o_tmpmemctx );
+ if ( casefold && BACKSQL_AT_CANUPPERCASE( at ) ) {
+ ldap_pvt_str2upper( &bsi->bsi_flt_where.bb_val.bv_val[ start ] );
+ }
+ }
+
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "c", '%' );
+
+ if ( f->f_sub_any != NULL ) {
+ for ( i = 0; !BER_BVISNULL( &f->f_sub_any[ i ] ); i++ ) {
+ ber_len_t start;
+
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE,
+ "==>backsql_process_sub_filter(%s): "
+ "sub_any[%d]=\"%s\"\n", at->bam_ad->ad_cname.bv_val,
+ i, f->f_sub_any[ i ].bv_val );
+#endif /* BACKSQL_TRACE */
+
+ fvalue = &f->f_sub_any[ i ];
+ escaped = backsql_val_escape( bsi->bsi_op, fvalue, &escval );
+ if ( escaped )
+ fvalue = &escval;
+ start = bsi->bsi_flt_where.bb_val.bv_len;
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "bc",
+ fvalue,
+ '%' );
+ if ( escaped )
+ bsi->bsi_op->o_tmpfree( escval.bv_val, bsi->bsi_op->o_tmpmemctx );
+ if ( casefold && BACKSQL_AT_CANUPPERCASE( at ) ) {
+ /*
+ * Note: toupper('%') = '%'
+ */
+ ldap_pvt_str2upper( &bsi->bsi_flt_where.bb_val.bv_val[ start ] );
+ }
+ }
+ }
+
+ if ( !BER_BVISNULL( &f->f_sub_final ) ) {
+ ber_len_t start;
+
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE,
+ "==>backsql_process_sub_filter(%s): "
+ "sub_final=\"%s\"\n", at->bam_ad->ad_cname.bv_val,
+ f->f_sub_final.bv_val );
+#endif /* BACKSQL_TRACE */
+
+ fvalue = &f->f_sub_final;
+ escaped = backsql_val_escape( bsi->bsi_op, fvalue, &escval );
+ if ( escaped )
+ fvalue = &escval;
+ start = bsi->bsi_flt_where.bb_val.bv_len;
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "b",
+ fvalue );
+ if ( escaped )
+ bsi->bsi_op->o_tmpfree( escval.bv_val, bsi->bsi_op->o_tmpmemctx );
+ if ( casefold && BACKSQL_AT_CANUPPERCASE( at ) ) {
+ ldap_pvt_str2upper( &bsi->bsi_flt_where.bb_val.bv_val[ start ] );
+ }
+ }
+
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "l",
+ (ber_len_t)STRLENOF( /* (' */ "')" ), /* (' */ "')" );
+
+ return 1;
+}
+
+static int
+backsql_merge_from_tbls( backsql_srch_info *bsi, struct berval *from_tbls )
+{
+ if ( BER_BVISNULL( from_tbls ) ) {
+ return LDAP_SUCCESS;
+ }
+
+ if ( !BER_BVISNULL( &bsi->bsi_from.bb_val ) ) {
+ char *start, *end;
+ struct berval tmp;
+
+ ber_dupbv_x( &tmp, from_tbls, bsi->bsi_op->o_tmpmemctx );
+
+ for ( start = tmp.bv_val, end = strchr( start, ',' ); start; ) {
+ if ( end ) {
+ end[0] = '\0';
+ }
+
+ if ( strstr( bsi->bsi_from.bb_val.bv_val, start) == NULL )
+ {
+ backsql_strfcat_x( &bsi->bsi_from,
+ bsi->bsi_op->o_tmpmemctx,
+ "cs", ',', start );
+ }
+
+ if ( end ) {
+ /* in case there are spaces after the comma... */
+ for ( start = &end[1]; isspace( start[0] ); start++ );
+ if ( start[0] ) {
+ end = strchr( start, ',' );
+ } else {
+ start = NULL;
+ }
+ } else {
+ start = NULL;
+ }
+ }
+
+ bsi->bsi_op->o_tmpfree( tmp.bv_val, bsi->bsi_op->o_tmpmemctx );
+
+ } else {
+ backsql_strfcat_x( &bsi->bsi_from,
+ bsi->bsi_op->o_tmpmemctx,
+ "b", from_tbls );
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static int
+backsql_process_filter( backsql_srch_info *bsi, Filter *f )
+{
+ backsql_at_map_rec **vat = NULL;
+ AttributeDescription *ad = NULL;
+ unsigned i;
+ int done = 0;
+ int rc = 0;
+
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_process_filter()\n" );
+ if ( f->f_choice == SLAPD_FILTER_COMPUTED ) {
+ struct berval flt;
+ char *msg = NULL;
+
+ switch ( f->f_result ) {
+ case LDAP_COMPARE_TRUE:
+ BER_BVSTR( &flt, "10=10" );
+ msg = "TRUE";
+ break;
+
+ case LDAP_COMPARE_FALSE:
+ BER_BVSTR( &flt, "11=0" );
+ msg = "FALSE";
+ break;
+
+ case SLAPD_COMPARE_UNDEFINED:
+ BER_BVSTR( &flt, "12=0" );
+ msg = "UNDEFINED";
+ break;
+
+ default:
+ rc = -1;
+ goto done;
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "backsql_process_filter(): "
+ "filter computed (%s)\n", msg );
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx, "b", &flt );
+ rc = 1;
+ goto done;
+ }
+
+ if ( f->f_choice & SLAPD_FILTER_UNDEFINED ) {
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "l",
+ (ber_len_t)STRLENOF( "1=0" ), "1=0" );
+ done = 1;
+ rc = 1;
+ goto done;
+ }
+
+ switch( f->f_choice ) {
+ case LDAP_FILTER_OR:
+ rc = backsql_process_filter_list( bsi, f->f_or,
+ LDAP_FILTER_OR );
+ done = 1;
+ break;
+
+ case LDAP_FILTER_AND:
+ rc = backsql_process_filter_list( bsi, f->f_and,
+ LDAP_FILTER_AND );
+ done = 1;
+ break;
+
+ case LDAP_FILTER_NOT:
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "l",
+ (ber_len_t)STRLENOF( "NOT (" /* ) */ ),
+ "NOT (" /* ) */ );
+ rc = backsql_process_filter( bsi, f->f_not );
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "c", /* ( */ ')' );
+ done = 1;
+ break;
+
+ case LDAP_FILTER_PRESENT:
+ ad = f->f_desc;
+ break;
+
+ case LDAP_FILTER_EXT:
+ ad = f->f_mra->ma_desc;
+ if ( f->f_mr_dnattrs ) {
+ /*
+ * if dn attrs filtering is requested, better return
+ * success and let test_filter() deal with candidate
+ * selection; otherwise we'd need to set conditions
+ * on the contents of the DN, e.g. "SELECT ... FROM
+ * ldap_entries AS attributeName WHERE attributeName.dn
+ * like '%attributeName=value%'"
+ */
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "l",
+ (ber_len_t)STRLENOF( "1=1" ), "1=1" );
+ bsi->bsi_status = LDAP_SUCCESS;
+ rc = 1;
+ goto done;
+ }
+ break;
+
+ default:
+ ad = f->f_av_desc;
+ break;
+ }
+
+ if ( rc == -1 ) {
+ goto done;
+ }
+
+ if ( done ) {
+ rc = 1;
+ goto done;
+ }
+
+ /*
+ * Turn structuralObjectClass into objectClass
+ */
+ if ( ad == slap_schema.si_ad_objectClass
+ || ad == slap_schema.si_ad_structuralObjectClass )
+ {
+ /*
+ * If the filter is LDAP_FILTER_PRESENT, then it's done;
+ * otherwise, let's see if we are lucky: filtering
+ * for "structural" objectclass or ancestor...
+ */
+ switch ( f->f_choice ) {
+ case LDAP_FILTER_EQUALITY:
+ {
+ ObjectClass *oc = oc_bvfind( &f->f_av_value );
+
+ if ( oc == NULL ) {
+ Debug( LDAP_DEBUG_TRACE,
+ "backsql_process_filter(): "
+ "unknown objectClass \"%s\" "
+ "in filter\n",
+ f->f_av_value.bv_val );
+ bsi->bsi_status = LDAP_OTHER;
+ rc = -1;
+ goto done;
+ }
+
+ /*
+ * "structural" objectClass inheritance:
+ * - a search for "person" will also return
+ * "inetOrgPerson"
+ * - a search for "top" will return everything
+ */
+ if ( is_object_subclass( oc, bsi->bsi_oc->bom_oc ) ) {
+ static struct berval ldap_entry_objclasses = BER_BVC( "ldap_entry_objclasses" );
+
+ backsql_merge_from_tbls( bsi, &ldap_entry_objclasses );
+
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "lbl",
+ (ber_len_t)STRLENOF( "(2=2 OR (ldap_entries.id=ldap_entry_objclasses.entry_id AND ldap_entry_objclasses.oc_name='" /* ')) */ ),
+ "(2=2 OR (ldap_entries.id=ldap_entry_objclasses.entry_id AND ldap_entry_objclasses.oc_name='" /* ')) */,
+ &bsi->bsi_oc->bom_oc->soc_cname,
+ (ber_len_t)STRLENOF( /* ((' */ "'))" ),
+ /* ((' */ "'))" );
+ bsi->bsi_status = LDAP_SUCCESS;
+ rc = 1;
+ goto done;
+ }
+
+ break;
+ }
+
+ case LDAP_FILTER_PRESENT:
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "l",
+ (ber_len_t)STRLENOF( "3=3" ), "3=3" );
+ bsi->bsi_status = LDAP_SUCCESS;
+ rc = 1;
+ goto done;
+
+ /* FIXME: LDAP_FILTER_EXT? */
+
+ default:
+ Debug( LDAP_DEBUG_TRACE,
+ "backsql_process_filter(): "
+ "illegal/unhandled filter "
+ "on objectClass attribute" );
+ bsi->bsi_status = LDAP_OTHER;
+ rc = -1;
+ goto done;
+ }
+
+ } else if ( ad == slap_schema.si_ad_entryUUID ) {
+ unsigned long oc_id;
+#ifdef BACKSQL_ARBITRARY_KEY
+ struct berval keyval;
+#else /* ! BACKSQL_ARBITRARY_KEY */
+ unsigned long keyval;
+ char keyvalbuf[LDAP_PVT_INTTYPE_CHARS(unsigned long)];
+#endif /* ! BACKSQL_ARBITRARY_KEY */
+
+ switch ( f->f_choice ) {
+ case LDAP_FILTER_EQUALITY:
+ backsql_entryUUID_decode( &f->f_av_value, &oc_id, &keyval );
+
+ if ( oc_id != bsi->bsi_oc->bom_id ) {
+ bsi->bsi_status = LDAP_SUCCESS;
+ rc = -1;
+ goto done;
+ }
+
+#ifdef BACKSQL_ARBITRARY_KEY
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "bcblbc",
+ &bsi->bsi_oc->bom_keytbl, '.',
+ &bsi->bsi_oc->bom_keycol,
+ STRLENOF( " LIKE '" ), " LIKE '",
+ &keyval, '\'' );
+#else /* ! BACKSQL_ARBITRARY_KEY */
+ snprintf( keyvalbuf, sizeof( keyvalbuf ), "%lu", keyval );
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "bcbcs",
+ &bsi->bsi_oc->bom_keytbl, '.',
+ &bsi->bsi_oc->bom_keycol, '=', keyvalbuf );
+#endif /* ! BACKSQL_ARBITRARY_KEY */
+ break;
+
+ case LDAP_FILTER_PRESENT:
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "l",
+ (ber_len_t)STRLENOF( "4=4" ), "4=4" );
+ break;
+
+ default:
+ rc = -1;
+ goto done;
+ }
+
+ bsi->bsi_flags |= BSQL_SF_FILTER_ENTRYUUID;
+ rc = 1;
+ goto done;
+
+#ifdef BACKSQL_SYNCPROV
+ } else if ( ad == slap_schema.si_ad_entryCSN ) {
+ /*
+ * support for syncrepl as provider...
+ */
+#if 0
+ if ( !bsi->bsi_op->o_sync ) {
+ /* unsupported at present... */
+ bsi->bsi_status = LDAP_OTHER;
+ rc = -1;
+ goto done;
+ }
+#endif
+
+ bsi->bsi_flags |= ( BSQL_SF_FILTER_ENTRYCSN | BSQL_SF_RETURN_ENTRYUUID);
+
+ /* if doing a syncrepl, try to return as much as possible,
+ * and always match the filter */
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "l",
+ (ber_len_t)STRLENOF( "5=5" ), "5=5" );
+
+ /* save for later use in operational attributes */
+ /* FIXME: saves only the first occurrence, because
+ * the filter during updates is written as
+ * "(&(entryCSN<={contextCSN})(entryCSN>={oldContextCSN})({filter}))"
+ * so we want our fake entryCSN to match the greatest
+ * value
+ */
+ if ( bsi->bsi_op->o_private == NULL ) {
+ bsi->bsi_op->o_private = &f->f_av_value;
+ }
+ bsi->bsi_status = LDAP_SUCCESS;
+
+ rc = 1;
+ goto done;
+#endif /* BACKSQL_SYNCPROV */
+
+ } else if ( ad == slap_schema.si_ad_hasSubordinates || ad == NULL ) {
+ /*
+ * FIXME: this is not robust; e.g. a filter
+ * '(!(hasSubordinates=TRUE))' fails because
+ * in SQL it would read 'NOT (1=1)' instead
+ * of no condition.
+ * Note however that hasSubordinates is boolean,
+ * so a more appropriate filter would be
+ * '(hasSubordinates=FALSE)'
+ *
+ * A more robust search for hasSubordinates
+ * would * require joining the ldap_entries table
+ * selecting if there are descendants of the
+ * candidate.
+ */
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "l",
+ (ber_len_t)STRLENOF( "6=6" ), "6=6" );
+ if ( ad == slap_schema.si_ad_hasSubordinates ) {
+ /*
+ * instruct candidate selection algorithm
+ * and attribute list to try to detect
+ * if an entry has subordinates
+ */
+ bsi->bsi_flags |= BSQL_SF_FILTER_HASSUBORDINATE;
+
+ } else {
+ /*
+ * clear attributes to fetch, to require ALL
+ * and try extended match on all attributes
+ */
+ backsql_attrlist_add( bsi, NULL );
+ }
+ rc = 1;
+ goto done;
+ }
+
+ /*
+ * attribute inheritance:
+ */
+ if ( backsql_supad2at( bsi->bsi_oc, ad, &vat ) ) {
+ bsi->bsi_status = LDAP_OTHER;
+ rc = -1;
+ goto done;
+ }
+
+ if ( vat == NULL ) {
+ /* search anyway; other parts of the filter
+ * may succeed */
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "l",
+ (ber_len_t)STRLENOF( "7=7" ), "7=7" );
+ bsi->bsi_status = LDAP_SUCCESS;
+ rc = 1;
+ goto done;
+ }
+
+ /* if required, open extra level of parens */
+ done = 0;
+ if ( vat[0]->bam_next || vat[1] ) {
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "c", '(' );
+ done = 1;
+ }
+
+ i = 0;
+next:;
+ /* apply attr */
+ if ( backsql_process_filter_attr( bsi, f, vat[i] ) == -1 ) {
+ return -1;
+ }
+
+ /* if more definitions of the same attr, apply */
+ if ( vat[i]->bam_next ) {
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "l",
+ STRLENOF( " OR " ), " OR " );
+ vat[i] = vat[i]->bam_next;
+ goto next;
+ }
+
+ /* if more descendants of the same attr, apply */
+ i++;
+ if ( vat[i] ) {
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "l",
+ STRLENOF( " OR " ), " OR " );
+ goto next;
+ }
+
+ /* if needed, close extra level of parens */
+ if ( done ) {
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "c", ')' );
+ }
+
+ rc = 1;
+
+done:;
+ if ( vat ) {
+ ch_free( vat );
+ }
+
+ Debug( LDAP_DEBUG_TRACE,
+ "<==backsql_process_filter() %s\n",
+ rc == 1 ? "succeeded" : "failed" );
+
+ return rc;
+}
+
+static int
+backsql_process_filter_eq( backsql_srch_info *bsi, backsql_at_map_rec *at,
+ int casefold, struct berval *filter_value )
+{
+ /*
+ * maybe we should check type of at->sel_expr here somehow,
+ * to know whether upper_func is applicable, but for now
+ * upper_func stuff is made for Oracle, where UPPER is
+ * safely applicable to NUMBER etc.
+ */
+ if ( casefold && BACKSQL_AT_CANUPPERCASE( at ) ) {
+ ber_len_t start;
+
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "cbl",
+ '(', /* ) */
+ &at->bam_sel_expr_u,
+ (ber_len_t)STRLENOF( "='" ),
+ "='" );
+
+ start = bsi->bsi_flt_where.bb_val.bv_len;
+
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "bl",
+ filter_value,
+ (ber_len_t)STRLENOF( /* (' */ "')" ),
+ /* (' */ "')" );
+
+ ldap_pvt_str2upper( &bsi->bsi_flt_where.bb_val.bv_val[ start ] );
+
+ } else {
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "cblbl",
+ '(', /* ) */
+ &at->bam_sel_expr,
+ (ber_len_t)STRLENOF( "='" ), "='",
+ filter_value,
+ (ber_len_t)STRLENOF( /* (' */ "')" ),
+ /* (' */ "')" );
+ }
+
+ return 1;
+}
+
+static int
+backsql_process_filter_like( backsql_srch_info *bsi, backsql_at_map_rec *at,
+ int casefold, struct berval *filter_value )
+{
+ /*
+ * maybe we should check type of at->sel_expr here somehow,
+ * to know whether upper_func is applicable, but for now
+ * upper_func stuff is made for Oracle, where UPPER is
+ * safely applicable to NUMBER etc.
+ */
+ if ( casefold && BACKSQL_AT_CANUPPERCASE( at ) ) {
+ ber_len_t start;
+
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "cbl",
+ '(', /* ) */
+ &at->bam_sel_expr_u,
+ (ber_len_t)STRLENOF( " LIKE '%" ),
+ " LIKE '%" );
+
+ start = bsi->bsi_flt_where.bb_val.bv_len;
+
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "bl",
+ filter_value,
+ (ber_len_t)STRLENOF( /* (' */ "%')" ),
+ /* (' */ "%')" );
+
+ ldap_pvt_str2upper( &bsi->bsi_flt_where.bb_val.bv_val[ start ] );
+
+ } else {
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "cblbl",
+ '(', /* ) */
+ &at->bam_sel_expr,
+ (ber_len_t)STRLENOF( " LIKE '%" ),
+ " LIKE '%",
+ filter_value,
+ (ber_len_t)STRLENOF( /* (' */ "%')" ),
+ /* (' */ "%')" );
+ }
+
+ return 1;
+}
+
+static int
+backsql_process_filter_attr( backsql_srch_info *bsi, Filter *f, backsql_at_map_rec *at )
+{
+ backsql_info *bi = (backsql_info *)bsi->bsi_op->o_bd->be_private;
+ int casefold = 0;
+ struct berval *filter_value = NULL;
+ MatchingRule *matching_rule = NULL;
+ struct berval ordering = BER_BVC("<=");
+ struct berval escval;
+ int escaped = 0;
+
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_process_filter_attr(%s)\n",
+ at->bam_ad->ad_cname.bv_val );
+
+ /*
+ * need to add this attribute to list of attrs to load,
+ * so that we can do test_filter() later
+ */
+ backsql_attrlist_add( bsi, at->bam_ad );
+
+ backsql_merge_from_tbls( bsi, &at->bam_from_tbls );
+
+ if ( !BER_BVISNULL( &at->bam_join_where )
+ && strstr( bsi->bsi_join_where.bb_val.bv_val,
+ at->bam_join_where.bv_val ) == NULL )
+ {
+ backsql_strfcat_x( &bsi->bsi_join_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "lb",
+ (ber_len_t)STRLENOF( " AND " ), " AND ",
+ &at->bam_join_where );
+ }
+
+ if ( f->f_choice & SLAPD_FILTER_UNDEFINED ) {
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "l",
+ (ber_len_t)STRLENOF( "1=0" ), "1=0" );
+ return 1;
+ }
+
+ switch ( f->f_choice ) {
+ case LDAP_FILTER_EQUALITY:
+ filter_value = &f->f_av_value;
+ matching_rule = at->bam_ad->ad_type->sat_equality;
+
+ goto equality_match;
+
+ /* fail over into next case */
+
+ case LDAP_FILTER_EXT:
+ filter_value = &f->f_mra->ma_value;
+ matching_rule = f->f_mr_rule;
+
+equality_match:;
+ /* always uppercase strings by now */
+#ifdef BACKSQL_UPPERCASE_FILTER
+ if ( SLAP_MR_ASSOCIATED( matching_rule,
+ bi->sql_caseIgnoreMatch ) )
+#endif /* BACKSQL_UPPERCASE_FILTER */
+ {
+ casefold = 1;
+ }
+
+ escaped = backsql_val_escape( bsi->bsi_op, filter_value, &escval );
+ if ( escaped )
+ filter_value = &escval;
+
+ /* FIXME: directoryString filtering should use a similar
+ * approach to deal with non-prettified values like
+ * " A non prettified value ", by using a LIKE
+ * filter with all whitespaces collapsed to a single '%' */
+ if ( SLAP_MR_ASSOCIATED( matching_rule,
+ bi->sql_telephoneNumberMatch ) )
+ {
+ struct berval bv;
+ ber_len_t i;
+
+ /*
+ * to check for matching telephone numbers
+ * with intermized chars, e.g. val='1234'
+ * use
+ *
+ * val LIKE '%1%2%3%4%'
+ */
+
+ bv.bv_len = 2 * filter_value->bv_len - 1;
+ bv.bv_val = ch_malloc( bv.bv_len + 1 );
+
+ bv.bv_val[ 0 ] = filter_value->bv_val[ 0 ];
+ for ( i = 1; i < filter_value->bv_len; i++ ) {
+ bv.bv_val[ 2 * i - 1 ] = '%';
+ bv.bv_val[ 2 * i ] = filter_value->bv_val[ i ];
+ }
+ bv.bv_val[ 2 * i - 1 ] = '\0';
+
+ (void)backsql_process_filter_like( bsi, at, casefold, &bv );
+ ch_free( bv.bv_val );
+
+ break;
+ }
+
+ /* NOTE: this is required by objectClass inheritance
+ * and auxiliary objectClass use in filters for slightly
+ * more efficient candidate selection. */
+ /* FIXME: a bit too many specializations to deal with
+ * very specific cases... */
+ if ( at->bam_ad == slap_schema.si_ad_objectClass
+ || at->bam_ad == slap_schema.si_ad_structuralObjectClass )
+ {
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "lbl",
+ (ber_len_t)STRLENOF( "(ldap_entries.id=ldap_entry_objclasses.entry_id AND ldap_entry_objclasses.oc_name='" /* ') */ ),
+ "(ldap_entries.id=ldap_entry_objclasses.entry_id AND ldap_entry_objclasses.oc_name='" /* ') */,
+ filter_value,
+ (ber_len_t)STRLENOF( /* (' */ "')" ),
+ /* (' */ "')" );
+ break;
+ }
+
+ /*
+ * maybe we should check type of at->sel_expr here somehow,
+ * to know whether upper_func is applicable, but for now
+ * upper_func stuff is made for Oracle, where UPPER is
+ * safely applicable to NUMBER etc.
+ */
+ (void)backsql_process_filter_eq( bsi, at, casefold, filter_value );
+ break;
+
+ case LDAP_FILTER_GE:
+ ordering.bv_val = ">=";
+
+ /* fall thru to next case */
+
+ case LDAP_FILTER_LE:
+ filter_value = &f->f_av_value;
+
+ /* always uppercase strings by now */
+#ifdef BACKSQL_UPPERCASE_FILTER
+ if ( at->bam_ad->ad_type->sat_ordering &&
+ SLAP_MR_ASSOCIATED( at->bam_ad->ad_type->sat_ordering,
+ bi->sql_caseIgnoreMatch ) )
+#endif /* BACKSQL_UPPERCASE_FILTER */
+ {
+ casefold = 1;
+ }
+
+ escaped = backsql_val_escape( bsi->bsi_op, filter_value, &escval );
+ if ( escaped )
+ filter_value = &escval;
+
+ /*
+ * FIXME: should we uppercase the operands?
+ */
+ if ( casefold && BACKSQL_AT_CANUPPERCASE( at ) ) {
+ ber_len_t start;
+
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "cbbc",
+ '(', /* ) */
+ &at->bam_sel_expr_u,
+ &ordering,
+ '\'' );
+
+ start = bsi->bsi_flt_where.bb_val.bv_len;
+
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "bl",
+ filter_value,
+ (ber_len_t)STRLENOF( /* (' */ "')" ),
+ /* (' */ "')" );
+
+ ldap_pvt_str2upper( &bsi->bsi_flt_where.bb_val.bv_val[ start ] );
+
+ } else {
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "cbbcbl",
+ '(' /* ) */ ,
+ &at->bam_sel_expr,
+ &ordering,
+ '\'',
+ filter_value,
+ (ber_len_t)STRLENOF( /* (' */ "')" ),
+ /* ( */ "')" );
+ }
+ break;
+
+ case LDAP_FILTER_PRESENT:
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "lbl",
+ (ber_len_t)STRLENOF( "NOT (" /* ) */),
+ "NOT (", /* ) */
+ &at->bam_sel_expr,
+ (ber_len_t)STRLENOF( /* ( */ " IS NULL)" ),
+ /* ( */ " IS NULL)" );
+ break;
+
+ case LDAP_FILTER_SUBSTRINGS:
+ backsql_process_sub_filter( bsi, f, at );
+ break;
+
+ case LDAP_FILTER_APPROX:
+ /* we do our best */
+
+ filter_value = &f->f_av_value;
+ escaped = backsql_val_escape( bsi->bsi_op, filter_value, &escval );
+ if ( escaped )
+ filter_value = &escval;
+ /*
+ * maybe we should check type of at->sel_expr here somehow,
+ * to know whether upper_func is applicable, but for now
+ * upper_func stuff is made for Oracle, where UPPER is
+ * safely applicable to NUMBER etc.
+ */
+ (void)backsql_process_filter_like( bsi, at, 1, filter_value );
+ break;
+
+ default:
+ /* unhandled filter type; should not happen */
+ assert( 0 );
+ backsql_strfcat_x( &bsi->bsi_flt_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "l",
+ (ber_len_t)STRLENOF( "8=8" ), "8=8" );
+ break;
+
+ }
+
+ if ( escaped )
+ bsi->bsi_op->o_tmpfree( escval.bv_val, bsi->bsi_op->o_tmpmemctx );
+
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_process_filter_attr(%s)\n",
+ at->bam_ad->ad_cname.bv_val );
+
+ return 1;
+}
+
+static int
+backsql_srch_query( backsql_srch_info *bsi, struct berval *query )
+{
+ backsql_info *bi = (backsql_info *)bsi->bsi_op->o_bd->be_private;
+ int rc;
+
+ assert( query != NULL );
+ BER_BVZERO( query );
+
+ bsi->bsi_use_subtree_shortcut = 0;
+
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_srch_query()\n" );
+ BER_BVZERO( &bsi->bsi_sel.bb_val );
+ BER_BVZERO( &bsi->bsi_sel.bb_val );
+ bsi->bsi_sel.bb_len = 0;
+ BER_BVZERO( &bsi->bsi_from.bb_val );
+ bsi->bsi_from.bb_len = 0;
+ BER_BVZERO( &bsi->bsi_join_where.bb_val );
+ bsi->bsi_join_where.bb_len = 0;
+ BER_BVZERO( &bsi->bsi_flt_where.bb_val );
+ bsi->bsi_flt_where.bb_len = 0;
+
+ backsql_strfcat_x( &bsi->bsi_sel,
+ bsi->bsi_op->o_tmpmemctx,
+ "lbcbc",
+ (ber_len_t)STRLENOF( "SELECT DISTINCT ldap_entries.id," ),
+ "SELECT DISTINCT ldap_entries.id,",
+ &bsi->bsi_oc->bom_keytbl,
+ '.',
+ &bsi->bsi_oc->bom_keycol,
+ ',' );
+
+ if ( !BER_BVISNULL( &bi->sql_strcast_func ) ) {
+ backsql_strfcat_x( &bsi->bsi_sel,
+ bsi->bsi_op->o_tmpmemctx,
+ "blbl",
+ &bi->sql_strcast_func,
+ (ber_len_t)STRLENOF( "('" /* ') */ ),
+ "('" /* ') */ ,
+ &bsi->bsi_oc->bom_oc->soc_cname,
+ (ber_len_t)STRLENOF( /* (' */ "')" ),
+ /* (' */ "')" );
+ } else {
+ backsql_strfcat_x( &bsi->bsi_sel,
+ bsi->bsi_op->o_tmpmemctx,
+ "cbc",
+ '\'',
+ &bsi->bsi_oc->bom_oc->soc_cname,
+ '\'' );
+ }
+
+ backsql_strfcat_x( &bsi->bsi_sel,
+ bsi->bsi_op->o_tmpmemctx,
+ "b",
+ &bi->sql_dn_oc_aliasing );
+ backsql_strfcat_x( &bsi->bsi_from,
+ bsi->bsi_op->o_tmpmemctx,
+ "lb",
+ (ber_len_t)STRLENOF( " FROM ldap_entries," ),
+ " FROM ldap_entries,",
+ &bsi->bsi_oc->bom_keytbl );
+
+ backsql_strfcat_x( &bsi->bsi_join_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "lbcbl",
+ (ber_len_t)STRLENOF( " WHERE " ), " WHERE ",
+ &bsi->bsi_oc->bom_keytbl,
+ '.',
+ &bsi->bsi_oc->bom_keycol,
+ (ber_len_t)STRLENOF( "=ldap_entries.keyval AND ldap_entries.oc_map_id=? AND " ),
+ "=ldap_entries.keyval AND ldap_entries.oc_map_id=? AND " );
+
+ switch ( bsi->bsi_scope ) {
+ case LDAP_SCOPE_BASE:
+ if ( BACKSQL_CANUPPERCASE( bi ) ) {
+ backsql_strfcat_x( &bsi->bsi_join_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "bl",
+ &bi->sql_upper_func,
+ (ber_len_t)STRLENOF( "(ldap_entries.dn)=?" ),
+ "(ldap_entries.dn)=?" );
+ } else {
+ backsql_strfcat_x( &bsi->bsi_join_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "l",
+ (ber_len_t)STRLENOF( "ldap_entries.dn=?" ),
+ "ldap_entries.dn=?" );
+ }
+ break;
+
+ case BACKSQL_SCOPE_BASE_LIKE:
+ if ( BACKSQL_CANUPPERCASE( bi ) ) {
+ backsql_strfcat_x( &bsi->bsi_join_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "bl",
+ &bi->sql_upper_func,
+ (ber_len_t)STRLENOF( "(ldap_entries.dn) LIKE ?" ),
+ "(ldap_entries.dn) LIKE ?" );
+ } else {
+ backsql_strfcat_x( &bsi->bsi_join_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "l",
+ (ber_len_t)STRLENOF( "ldap_entries.dn LIKE ?" ),
+ "ldap_entries.dn LIKE ?" );
+ }
+ break;
+
+ case LDAP_SCOPE_ONELEVEL:
+ backsql_strfcat_x( &bsi->bsi_join_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "l",
+ (ber_len_t)STRLENOF( "ldap_entries.parent=?" ),
+ "ldap_entries.parent=?" );
+ break;
+
+ case LDAP_SCOPE_SUBORDINATE:
+ case LDAP_SCOPE_SUBTREE:
+ if ( BACKSQL_USE_SUBTREE_SHORTCUT( bi ) ) {
+ int i;
+ BackendDB *bd = bsi->bsi_op->o_bd;
+
+ assert( bd->be_nsuffix != NULL );
+
+ for ( i = 0; !BER_BVISNULL( &bd->be_nsuffix[ i ] ); i++ )
+ {
+ if ( dn_match( &bd->be_nsuffix[ i ],
+ bsi->bsi_base_ndn ) )
+ {
+ /* pass this to the candidate selection
+ * routine so that the DN is not bound
+ * to the select statement */
+ bsi->bsi_use_subtree_shortcut = 1;
+ break;
+ }
+ }
+ }
+
+ if ( bsi->bsi_use_subtree_shortcut ) {
+ /* Skip the base DN filter, as every entry will match it */
+ backsql_strfcat_x( &bsi->bsi_join_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "l",
+ (ber_len_t)STRLENOF( "9=9"), "9=9");
+
+ } else if ( !BER_BVISNULL( &bi->sql_subtree_cond ) ) {
+ /* This should always be true... */
+ backsql_strfcat_x( &bsi->bsi_join_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "b",
+ &bi->sql_subtree_cond );
+
+ } else if ( BACKSQL_CANUPPERCASE( bi ) ) {
+ backsql_strfcat_x( &bsi->bsi_join_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "bl",
+ &bi->sql_upper_func,
+ (ber_len_t)STRLENOF( "(ldap_entries.dn) LIKE ?" ),
+ "(ldap_entries.dn) LIKE ?" );
+
+ } else {
+ backsql_strfcat_x( &bsi->bsi_join_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "l",
+ (ber_len_t)STRLENOF( "ldap_entries.dn LIKE ?" ),
+ "ldap_entries.dn LIKE ?" );
+ }
+
+ break;
+
+ default:
+ assert( 0 );
+ }
+
+#ifndef BACKSQL_ARBITRARY_KEY
+ /* If paged results are in effect, ignore low ldap_entries.id numbers */
+ if ( get_pagedresults(bsi->bsi_op) > SLAP_CONTROL_IGNORED ) {
+ unsigned long lowid = 0;
+
+ /* Pick up the previous ldap_entries.id if the previous page ended in this objectClass */
+ if ( bsi->bsi_oc->bom_id == PAGECOOKIE_TO_SQL_OC( ((PagedResultsState *)bsi->bsi_op->o_pagedresults_state)->ps_cookie ) )
+ {
+ lowid = PAGECOOKIE_TO_SQL_ID( ((PagedResultsState *)bsi->bsi_op->o_pagedresults_state)->ps_cookie );
+ }
+
+ if ( lowid ) {
+ char lowidstring[48];
+ int lowidlen;
+
+ lowidlen = snprintf( lowidstring, sizeof( lowidstring ),
+ " AND ldap_entries.id>%lu", lowid );
+ backsql_strfcat_x( &bsi->bsi_join_where,
+ bsi->bsi_op->o_tmpmemctx,
+ "l",
+ (ber_len_t)lowidlen,
+ lowidstring );
+ }
+ }
+#endif /* ! BACKSQL_ARBITRARY_KEY */
+
+ rc = backsql_process_filter( bsi, bsi->bsi_filter );
+ if ( rc > 0 ) {
+ struct berbuf bb = BB_NULL;
+
+ backsql_strfcat_x( &bb,
+ bsi->bsi_op->o_tmpmemctx,
+ "bbblb",
+ &bsi->bsi_sel.bb_val,
+ &bsi->bsi_from.bb_val,
+ &bsi->bsi_join_where.bb_val,
+ (ber_len_t)STRLENOF( " AND " ), " AND ",
+ &bsi->bsi_flt_where.bb_val );
+
+ *query = bb.bb_val;
+
+ } else if ( rc < 0 ) {
+ /*
+ * Indicates that there's no possible way the filter matches
+ * anything. No need to issue the query
+ */
+ free( query->bv_val );
+ BER_BVZERO( query );
+ }
+
+ bsi->bsi_op->o_tmpfree( bsi->bsi_sel.bb_val.bv_val, bsi->bsi_op->o_tmpmemctx );
+ BER_BVZERO( &bsi->bsi_sel.bb_val );
+ bsi->bsi_sel.bb_len = 0;
+ bsi->bsi_op->o_tmpfree( bsi->bsi_from.bb_val.bv_val, bsi->bsi_op->o_tmpmemctx );
+ BER_BVZERO( &bsi->bsi_from.bb_val );
+ bsi->bsi_from.bb_len = 0;
+ bsi->bsi_op->o_tmpfree( bsi->bsi_join_where.bb_val.bv_val, bsi->bsi_op->o_tmpmemctx );
+ BER_BVZERO( &bsi->bsi_join_where.bb_val );
+ bsi->bsi_join_where.bb_len = 0;
+ bsi->bsi_op->o_tmpfree( bsi->bsi_flt_where.bb_val.bv_val, bsi->bsi_op->o_tmpmemctx );
+ BER_BVZERO( &bsi->bsi_flt_where.bb_val );
+ bsi->bsi_flt_where.bb_len = 0;
+
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_srch_query() returns %s\n",
+ query->bv_val ? query->bv_val : "NULL" );
+
+ return ( rc <= 0 ? 1 : 0 );
+}
+
+static int
+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 );
+}
+
+int
+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;
+}
+
+/* return LDAP_SUCCESS IFF we can retrieve the specified entry.
+ */
+int
+backsql_entry_get(
+ Operation *op,
+ struct berval *ndn,
+ ObjectClass *oc,
+ AttributeDescription *at,
+ int rw,
+ Entry **ent )
+{
+ backsql_srch_info bsi = { 0 };
+ SQLHDBC dbh = SQL_NULL_HDBC;
+ int rc;
+ SlapReply rs = { 0 };
+ AttributeName anlist[ 2 ];
+
+ *ent = NULL;
+
+ rc = backsql_get_db_conn( op, &dbh );
+ if ( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+
+ if ( at ) {
+ anlist[ 0 ].an_name = at->ad_cname;
+ anlist[ 0 ].an_desc = at;
+ BER_BVZERO( &anlist[ 1 ].an_name );
+ }
+
+ bsi.bsi_e = entry_alloc();
+ rc = backsql_init_search( &bsi,
+ ndn,
+ LDAP_SCOPE_BASE,
+ (time_t)(-1), NULL,
+ dbh, op, &rs, at ? anlist : NULL,
+ BACKSQL_ISF_GET_ENTRY );
+
+ if ( !BER_BVISNULL( &bsi.bsi_base_id.eid_ndn ) ) {
+ (void)backsql_free_entryID( &bsi.bsi_base_id, 0, op->o_tmpmemctx );
+ }
+
+ if ( rc == LDAP_SUCCESS ) {
+
+#if 0 /* not supported at present */
+ /* find attribute values */
+ if ( is_entry_alias( bsi.bsi_e ) ) {
+ Debug( LDAP_DEBUG_ACL,
+ "<= backsql_entry_get: entry is an alias\n" );
+ rc = LDAP_ALIAS_PROBLEM;
+ goto return_results;
+ }
+#endif
+
+ if ( is_entry_referral( bsi.bsi_e ) ) {
+ Debug( LDAP_DEBUG_ACL,
+ "<= backsql_entry_get: entry is a referral\n" );
+ rc = LDAP_REFERRAL;
+ goto return_results;
+ }
+
+ if ( oc && !is_entry_objectclass( bsi.bsi_e, oc, 0 ) ) {
+ Debug( LDAP_DEBUG_ACL,
+ "<= backsql_entry_get: "
+ "failed to find objectClass\n" );
+ rc = LDAP_NO_SUCH_ATTRIBUTE;
+ goto return_results;
+ }
+
+ *ent = bsi.bsi_e;
+ }
+
+return_results:;
+ if ( bsi.bsi_attrs != NULL ) {
+ op->o_tmpfree( bsi.bsi_attrs, op->o_tmpmemctx );
+ }
+
+ if ( rc != LDAP_SUCCESS ) {
+ if ( bsi.bsi_e ) {
+ entry_free( bsi.bsi_e );
+ }
+ }
+
+ return rc;
+}
+
+void
+backsql_entry_clean(
+ Operation *op,
+ Entry *e )
+{
+ void *ctx;
+
+ ctx = ldap_pvt_thread_pool_context();
+
+ if ( ctx == NULL || ctx != op->o_tmpmemctx ) {
+ if ( !BER_BVISNULL( &e->e_name ) ) {
+ op->o_tmpfree( e->e_name.bv_val, op->o_tmpmemctx );
+ BER_BVZERO( &e->e_name );
+ }
+
+ if ( !BER_BVISNULL( &e->e_nname ) ) {
+ op->o_tmpfree( e->e_nname.bv_val, op->o_tmpmemctx );
+ BER_BVZERO( &e->e_nname );
+ }
+ }
+
+ entry_clean( e );
+}
+
+int
+backsql_entry_release(
+ Operation *op,
+ Entry *e,
+ int rw )
+{
+ backsql_entry_clean( op, e );
+
+ entry_free( e );
+
+ return 0;
+}
+
+#ifndef BACKSQL_ARBITRARY_KEY
+/* This function is copied verbatim from back-bdb/search.c */
+static int
+parse_paged_cookie( Operation *op, SlapReply *rs )
+{
+ int rc = LDAP_SUCCESS;
+ PagedResultsState *ps = op->o_pagedresults_state;
+
+ /* this function must be invoked only if the pagedResults
+ * control has been detected, parsed and partially checked
+ * by the frontend */
+ assert( get_pagedresults( op ) > SLAP_CONTROL_IGNORED );
+
+ /* cookie decoding/checks deferred to backend... */
+ if ( ps->ps_cookieval.bv_len ) {
+ PagedResultsCookie reqcookie;
+ if( ps->ps_cookieval.bv_len != sizeof( reqcookie ) ) {
+ /* bad cookie */
+ rs->sr_text = "paged results cookie is invalid";
+ rc = LDAP_PROTOCOL_ERROR;
+ goto done;
+ }
+
+ AC_MEMCPY( &reqcookie, ps->ps_cookieval.bv_val, sizeof( reqcookie ));
+
+ if ( reqcookie > ps->ps_cookie ) {
+ /* bad cookie */
+ rs->sr_text = "paged results cookie is invalid";
+ rc = LDAP_PROTOCOL_ERROR;
+ goto done;
+
+ } else if ( reqcookie < ps->ps_cookie ) {
+ rs->sr_text = "paged results cookie is invalid or old";
+ rc = LDAP_UNWILLING_TO_PERFORM;
+ goto done;
+ }
+
+ } else {
+ /* Initial request. Initialize state. */
+ ps->ps_cookie = 0;
+ ps->ps_count = 0;
+ }
+
+done:;
+
+ return rc;
+}
+
+/* This function is copied nearly verbatim from back-bdb/search.c */
+static void
+send_paged_response(
+ Operation *op,
+ SlapReply *rs,
+ ID *lastid )
+{
+ LDAPControl ctrl, *ctrls[2];
+ BerElementBuffer berbuf;
+ BerElement *ber = (BerElement *)&berbuf;
+ PagedResultsCookie respcookie;
+ struct berval cookie;
+
+ Debug(LDAP_DEBUG_ARGS,
+ "send_paged_response: lastid=0x%08lx nentries=%d\n",
+ lastid ? *lastid : 0, rs->sr_nentries );
+
+ BER_BVZERO( &ctrl.ldctl_value );
+ ctrls[0] = &ctrl;
+ ctrls[1] = NULL;
+
+ ber_init2( ber, NULL, LBER_USE_DER );
+
+ if ( lastid ) {
+ respcookie = ( PagedResultsCookie )(*lastid);
+ cookie.bv_len = sizeof( respcookie );
+ cookie.bv_val = (char *)&respcookie;
+
+ } else {
+ respcookie = ( PagedResultsCookie )0;
+ BER_BVSTR( &cookie, "" );
+ }
+
+ op->o_conn->c_pagedresults_state.ps_cookie = respcookie;
+ op->o_conn->c_pagedresults_state.ps_count =
+ ((PagedResultsState *)op->o_pagedresults_state)->ps_count +
+ rs->sr_nentries;
+
+ /* return size of 0 -- no estimate */
+ ber_printf( ber, "{iO}", 0, &cookie );
+
+ if ( ber_flatten2( ber, &ctrls[0]->ldctl_value, 0 ) == -1 ) {
+ goto done;
+ }
+
+ ctrls[0]->ldctl_oid = LDAP_CONTROL_PAGEDRESULTS;
+ ctrls[0]->ldctl_iscritical = 0;
+
+ rs->sr_ctrls = ctrls;
+ rs->sr_err = LDAP_SUCCESS;
+ send_ldap_result( op, rs );
+ rs->sr_ctrls = NULL;
+
+done:
+ (void) ber_free_buf( ber );
+}
+#endif /* ! BACKSQL_ARBITRARY_KEY */
diff --git a/servers/slapd/back-sql/sql-wrap.c b/servers/slapd/back-sql/sql-wrap.c
new file mode 100644
index 0000000..289b590
--- /dev/null
+++ b/servers/slapd/back-sql/sql-wrap.c
@@ -0,0 +1,529 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1999 Dmitry Kovalev.
+ * Portions Copyright 2002 Pierangelo Masarati.
+ * Portions Copyright 2004 Mark Adamson.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Dmitry Kovalev for inclusion
+ * by OpenLDAP Software. Additional significant contributors include
+ * Pierangelo Masarati and Mark Adamson.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include "ac/string.h"
+#include <sys/types.h>
+
+#include "slap.h"
+#include "proto-sql.h"
+
+#define MAX_ATTR_LEN 16384
+
+void
+backsql_PrintErrors( SQLHENV henv, SQLHDBC hdbc, SQLHSTMT sth, int rc )
+{
+ SQLCHAR msg[SQL_MAX_MESSAGE_LENGTH]; /* msg. buffer */
+ SQLCHAR state[SQL_SQLSTATE_SIZE]; /* statement buf. */
+ SDWORD iSqlCode; /* return code */
+ SWORD len = SQL_MAX_MESSAGE_LENGTH - 1; /* return length */
+
+ Debug( LDAP_DEBUG_TRACE, "Return code: %d\n", rc );
+
+ for ( ; rc = SQLError( henv, hdbc, sth, state, &iSqlCode, msg,
+ SQL_MAX_MESSAGE_LENGTH - 1, &len ), BACKSQL_SUCCESS( rc ); )
+ {
+ Debug( LDAP_DEBUG_TRACE,
+ " nativeErrCode=%d SQLengineState=%s msg=\"%s\"\n",
+ (int)iSqlCode, state, msg );
+ }
+}
+
+RETCODE
+backsql_Prepare( SQLHDBC dbh, SQLHSTMT *sth, const char *query, int timeout )
+{
+ RETCODE rc;
+
+ rc = SQLAllocStmt( dbh, sth );
+ if ( rc != SQL_SUCCESS ) {
+ return rc;
+ }
+
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_Prepare()\n" );
+#endif /* BACKSQL_TRACE */
+
+#ifdef BACKSQL_MSSQL_WORKAROUND
+ {
+ char drv_name[ 30 ];
+ SWORD len;
+
+ SQLGetInfo( dbh, SQL_DRIVER_NAME, drv_name, sizeof( drv_name ), &len );
+
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE, "backsql_Prepare(): driver name=\"%s\"\n",
+ drv_name );
+#endif /* BACKSQL_TRACE */
+
+ ldap_pvt_str2upper( drv_name );
+ if ( !strncmp( drv_name, "SQLSRV32.DLL", STRLENOF( "SQLSRV32.DLL" ) ) ) {
+ /*
+ * stupid default result set in MS SQL Server
+ * does not support multiple active statements
+ * on the same connection -- so we are trying
+ * to make it not to use default result set...
+ */
+ Debug( LDAP_DEBUG_TRACE, "_SQLprepare(): "
+ "enabling MS SQL Server default result "
+ "set workaround\n" );
+ rc = SQLSetStmtOption( *sth, SQL_CONCURRENCY,
+ SQL_CONCUR_ROWVER );
+ if ( rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_Prepare(): "
+ "SQLSetStmtOption(SQL_CONCURRENCY,"
+ "SQL_CONCUR_ROWVER) failed:\n" );
+ backsql_PrintErrors( SQL_NULL_HENV, dbh, *sth, rc );
+ SQLFreeStmt( *sth, SQL_DROP );
+ return rc;
+ }
+ }
+ }
+#endif /* BACKSQL_MSSQL_WORKAROUND */
+
+ if ( timeout > 0 ) {
+ Debug( LDAP_DEBUG_TRACE, "_SQLprepare(): "
+ "setting query timeout to %d sec.\n",
+ timeout );
+ rc = SQLSetStmtOption( *sth, SQL_QUERY_TIMEOUT, timeout );
+ if ( rc != SQL_SUCCESS ) {
+ backsql_PrintErrors( SQL_NULL_HENV, dbh, *sth, rc );
+ SQLFreeStmt( *sth, SQL_DROP );
+ return rc;
+ }
+ }
+
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_Prepare() calling SQLPrepare()\n" );
+#endif /* BACKSQL_TRACE */
+
+ return SQLPrepare( *sth, (SQLCHAR *)query, SQL_NTS );
+}
+
+RETCODE
+backsql_BindRowAsStrings_x( SQLHSTMT sth, BACKSQL_ROW_NTS *row, void *ctx )
+{
+ RETCODE rc;
+
+ if ( row == NULL ) {
+ return SQL_ERROR;
+ }
+
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE, "==> backsql_BindRowAsStrings()\n" );
+#endif /* BACKSQL_TRACE */
+
+ rc = SQLNumResultCols( sth, &row->ncols );
+ if ( rc != SQL_SUCCESS ) {
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE, "backsql_BindRowAsStrings(): "
+ "SQLNumResultCols() failed:\n" );
+#endif /* BACKSQL_TRACE */
+
+ backsql_PrintErrors( SQL_NULL_HENV, SQL_NULL_HDBC, sth, rc );
+
+ } else {
+ SQLCHAR colname[ 64 ];
+ SQLSMALLINT name_len, col_type, col_scale, col_null;
+ SQLLEN col_prec;
+ int i;
+
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE, "backsql_BindRowAsStrings: "
+ "ncols=%d\n", (int)row->ncols );
+#endif /* BACKSQL_TRACE */
+
+ row->col_names = (BerVarray)ber_memcalloc_x( row->ncols + 1,
+ sizeof( struct berval ), ctx );
+ if ( row->col_names == NULL ) {
+ goto nomem;
+ }
+
+ row->col_prec = (UDWORD *)ber_memcalloc_x( row->ncols,
+ sizeof( UDWORD ), ctx );
+ if ( row->col_prec == NULL ) {
+ goto nomem;
+ }
+
+ row->col_type = (SQLSMALLINT *)ber_memcalloc_x( row->ncols,
+ sizeof( SQLSMALLINT ), ctx );
+ if ( row->col_type == NULL ) {
+ goto nomem;
+ }
+
+ row->cols = (char **)ber_memcalloc_x( row->ncols + 1,
+ sizeof( char * ), ctx );
+ if ( row->cols == NULL ) {
+ goto nomem;
+ }
+
+ row->value_len = (SQLLEN *)ber_memcalloc_x( row->ncols,
+ sizeof( SQLLEN ), ctx );
+ if ( row->value_len == NULL ) {
+ goto nomem;
+ }
+
+ if ( 0 ) {
+nomem:
+ ber_memfree_x( row->col_names, ctx );
+ row->col_names = NULL;
+ ber_memfree_x( row->col_prec, ctx );
+ row->col_prec = NULL;
+ ber_memfree_x( row->col_type, ctx );
+ row->col_type = NULL;
+ ber_memfree_x( row->cols, ctx );
+ row->cols = NULL;
+ ber_memfree_x( row->value_len, ctx );
+ row->value_len = NULL;
+
+ Debug( LDAP_DEBUG_ANY, "backsql_BindRowAsStrings: "
+ "out of memory\n" );
+
+ return LDAP_NO_MEMORY;
+ }
+
+ for ( i = 0; i < row->ncols; i++ ) {
+ SQLSMALLINT TargetType;
+
+ rc = SQLDescribeCol( sth, (SQLSMALLINT)(i + 1), &colname[ 0 ],
+ (SQLUINTEGER)( sizeof( colname ) - 1 ),
+ &name_len, &col_type,
+ &col_prec, &col_scale, &col_null );
+ /* FIXME: test rc? */
+
+ ber_str2bv_x( (char *)colname, 0, 1,
+ &row->col_names[ i ], ctx );
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE, "backsql_BindRowAsStrings: "
+ "col_name=%s, col_prec[%d]=%d\n",
+ colname, (int)(i + 1), (int)col_prec );
+#endif /* BACKSQL_TRACE */
+ if ( col_type != SQL_CHAR && col_type != SQL_VARCHAR )
+ {
+ col_prec = MAX_ATTR_LEN;
+ }
+
+ row->cols[ i ] = (char *)ber_memcalloc_x( col_prec + 1,
+ sizeof( char ), ctx );
+ row->col_prec[ i ] = col_prec;
+ row->col_type[ i ] = col_type;
+
+ /*
+ * ITS#3386, ITS#3113 - 20070308
+ * Note: there are many differences between various DPMS and ODBC
+ * Systems; some support SQL_C_BLOB, SQL_C_BLOB_LOCATOR. YMMV:
+ * This has only been tested on Linux/MySQL/UnixODBC
+ * For BINARY-type Fields (BLOB, etc), read the data as BINARY
+ */
+ if ( BACKSQL_IS_BINARY( col_type ) ) {
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE, "backsql_BindRowAsStrings: "
+ "col_name=%s, col_type[%d]=%d: reading binary data\n",
+ colname, (int)(i + 1), (int)col_type);
+#endif /* BACKSQL_TRACE */
+ TargetType = SQL_C_BINARY;
+
+ } else {
+ /* Otherwise read it as Character data */
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE, "backsql_BindRowAsStrings: "
+ "col_name=%s, col_type[%d]=%d: reading character data\n",
+ colname, (int)(i + 1), (int)col_type);
+#endif /* BACKSQL_TRACE */
+ TargetType = SQL_C_CHAR;
+ }
+
+ rc = SQLBindCol( sth, (SQLUSMALLINT)(i + 1),
+ TargetType,
+ (SQLPOINTER)row->cols[ i ],
+ col_prec + 1,
+ &row->value_len[ i ] );
+
+ /* FIXME: test rc? */
+ }
+
+ BER_BVZERO( &row->col_names[ i ] );
+ row->cols[ i ] = NULL;
+ }
+
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE, "<== backsql_BindRowAsStrings()\n" );
+#endif /* BACKSQL_TRACE */
+
+ return rc;
+}
+
+RETCODE
+backsql_BindRowAsStrings( SQLHSTMT sth, BACKSQL_ROW_NTS *row )
+{
+ return backsql_BindRowAsStrings_x( sth, row, NULL );
+}
+
+RETCODE
+backsql_FreeRow_x( BACKSQL_ROW_NTS *row, void *ctx )
+{
+ if ( row->cols == NULL ) {
+ return SQL_ERROR;
+ }
+
+ ber_bvarray_free_x( row->col_names, ctx );
+ ber_memfree_x( row->col_prec, ctx );
+ ber_memfree_x( row->col_type, ctx );
+ ber_memvfree_x( (void **)row->cols, ctx );
+ ber_memfree_x( row->value_len, ctx );
+
+ return SQL_SUCCESS;
+}
+
+
+RETCODE
+backsql_FreeRow( BACKSQL_ROW_NTS *row )
+{
+ return backsql_FreeRow_x( row, NULL );
+}
+
+static void
+backsql_close_db_handle( SQLHDBC dbh )
+{
+ if ( dbh == SQL_NULL_HDBC ) {
+ return;
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_close_db_handle(%p)\n",
+ (void *)dbh );
+
+ /*
+ * Default transact is SQL_ROLLBACK; commit is required only
+ * by write operations, and it is explicitly performed after
+ * each atomic operation succeeds.
+ */
+
+ /* TimesTen */
+ SQLTransact( SQL_NULL_HENV, dbh, SQL_ROLLBACK );
+ SQLDisconnect( dbh );
+ SQLFreeConnect( dbh );
+
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_close_db_handle(%p)\n",
+ (void *)dbh );
+}
+
+int
+backsql_conn_destroy(
+ backsql_info *bi )
+{
+ return 0;
+}
+
+int
+backsql_init_db_env( backsql_info *bi )
+{
+ RETCODE rc;
+ int ret = SQL_SUCCESS;
+
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_init_db_env()\n" );
+
+ rc = SQLAllocEnv( &bi->sql_db_env );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "init_db_env: SQLAllocEnv failed:\n" );
+ backsql_PrintErrors( SQL_NULL_HENV, SQL_NULL_HDBC,
+ SQL_NULL_HENV, rc );
+ ret = SQL_ERROR;
+ }
+
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_init_db_env()=%d\n", ret );
+
+ return ret;
+}
+
+int
+backsql_free_db_env( backsql_info *bi )
+{
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_free_db_env()\n" );
+
+ (void)SQLFreeEnv( bi->sql_db_env );
+ bi->sql_db_env = SQL_NULL_HENV;
+
+ /*
+ * stop, if frontend waits for all threads to shutdown
+ * before calling this -- then what are we going to delete??
+ * everything is already deleted...
+ */
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_free_db_env()\n" );
+
+ return SQL_SUCCESS;
+}
+
+static int
+backsql_open_db_handle(
+ backsql_info *bi,
+ SQLHDBC *dbhp )
+{
+ /* TimesTen */
+ char DBMSName[ 32 ];
+ int rc;
+
+ assert( dbhp != NULL );
+ *dbhp = SQL_NULL_HDBC;
+
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_open_db_handle()\n" );
+
+ rc = SQLAllocConnect( bi->sql_db_env, dbhp );
+ if ( !BACKSQL_SUCCESS( rc ) ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_open_db_handle(): "
+ "SQLAllocConnect() failed:\n" );
+ backsql_PrintErrors( bi->sql_db_env, SQL_NULL_HDBC,
+ SQL_NULL_HENV, rc );
+ return LDAP_UNAVAILABLE;
+ }
+
+ rc = SQLConnect( *dbhp,
+ (SQLCHAR*)bi->sql_dbname, SQL_NTS,
+ (SQLCHAR*)bi->sql_dbuser, SQL_NTS,
+ (SQLCHAR*)bi->sql_dbpasswd, SQL_NTS );
+ if ( rc != SQL_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_open_db_handle(): "
+ "SQLConnect() to database \"%s\" %s.\n",
+ bi->sql_dbname,
+ rc == SQL_SUCCESS_WITH_INFO ?
+ "succeeded with info" : "failed" );
+ backsql_PrintErrors( bi->sql_db_env, *dbhp, SQL_NULL_HENV, rc );
+ if ( rc != SQL_SUCCESS_WITH_INFO ) {
+ SQLFreeConnect( *dbhp );
+ return LDAP_UNAVAILABLE;
+ }
+ }
+
+ /*
+ * TimesTen : Turn off autocommit. We must explicitly
+ * commit any transactions.
+ */
+ SQLSetConnectOption( *dbhp, SQL_AUTOCOMMIT,
+ BACKSQL_AUTOCOMMIT_ON( bi ) ? SQL_AUTOCOMMIT_ON : SQL_AUTOCOMMIT_OFF );
+
+ /*
+ * See if this connection is to TimesTen. If it is,
+ * remember that fact for later use.
+ */
+ /* Assume until proven otherwise */
+ bi->sql_flags &= ~BSQLF_USE_REVERSE_DN;
+ DBMSName[ 0 ] = '\0';
+ rc = SQLGetInfo( *dbhp, SQL_DBMS_NAME, (PTR)&DBMSName,
+ sizeof( DBMSName ), NULL );
+ if ( rc == SQL_SUCCESS ) {
+ if ( strcmp( DBMSName, "TimesTen" ) == 0 ||
+ strcmp( DBMSName, "Front-Tier" ) == 0 )
+ {
+ Debug( LDAP_DEBUG_TRACE, "backsql_open_db_handle(): "
+ "TimesTen database!\n" );
+ bi->sql_flags |= BSQLF_USE_REVERSE_DN;
+ }
+
+ } else {
+ Debug( LDAP_DEBUG_TRACE, "backsql_open_db_handle(): "
+ "SQLGetInfo() failed.\n" );
+ backsql_PrintErrors( bi->sql_db_env, *dbhp, SQL_NULL_HENV, rc );
+ SQLDisconnect( *dbhp );
+ SQLFreeConnect( *dbhp );
+ return LDAP_UNAVAILABLE;
+ }
+ /* end TimesTen */
+
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_open_db_handle()\n" );
+
+ return LDAP_SUCCESS;
+}
+
+static void *backsql_db_conn_dummy;
+
+static void
+backsql_db_conn_keyfree(
+ void *key,
+ void *data )
+{
+ (void)backsql_close_db_handle( (SQLHDBC)data );
+}
+
+int
+backsql_free_db_conn( Operation *op, SQLHDBC dbh )
+{
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_free_db_conn()\n" );
+
+ (void)backsql_close_db_handle( dbh );
+ ldap_pvt_thread_pool_setkey( op->o_threadctx,
+ &backsql_db_conn_dummy, (void *)SQL_NULL_HDBC,
+ backsql_db_conn_keyfree, NULL, NULL );
+
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_free_db_conn()\n" );
+
+ return LDAP_SUCCESS;
+}
+
+int
+backsql_get_db_conn( Operation *op, SQLHDBC *dbhp )
+{
+ backsql_info *bi = (backsql_info *)op->o_bd->be_private;
+ int rc = LDAP_SUCCESS;
+ SQLHDBC dbh = SQL_NULL_HDBC;
+
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_get_db_conn()\n" );
+
+ assert( dbhp != NULL );
+ *dbhp = SQL_NULL_HDBC;
+
+ if ( op->o_threadctx ) {
+ void *data = NULL;
+
+ ldap_pvt_thread_pool_getkey( op->o_threadctx,
+ &backsql_db_conn_dummy, &data, NULL );
+ dbh = (SQLHDBC)data;
+
+ } else {
+ dbh = bi->sql_dbh;
+ }
+
+ if ( dbh == SQL_NULL_HDBC ) {
+ rc = backsql_open_db_handle( bi, &dbh );
+ if ( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+
+ if ( op->o_threadctx ) {
+ void *data = (void *)dbh;
+
+ ldap_pvt_thread_pool_setkey( op->o_threadctx,
+ &backsql_db_conn_dummy, data,
+ backsql_db_conn_keyfree, NULL, NULL );
+
+ } else {
+ bi->sql_dbh = dbh;
+ }
+ }
+
+ *dbhp = dbh;
+
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_get_db_conn()\n" );
+
+ return LDAP_SUCCESS;
+}
+
diff --git a/servers/slapd/back-sql/util.c b/servers/slapd/back-sql/util.c
new file mode 100644
index 0000000..94a00ca
--- /dev/null
+++ b/servers/slapd/back-sql/util.c
@@ -0,0 +1,572 @@
+/* $OpenLDAP$ */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 1999-2022 The OpenLDAP Foundation.
+ * Portions Copyright 1999 Dmitry Kovalev.
+ * Portions Copyright 2002 Pierangelo Masarati.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was initially developed by Dmitry Kovalev for inclusion
+ * by OpenLDAP Software. Additional significant contributors include
+ * Pierangelo Masarati.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <sys/types.h>
+#include "ac/string.h"
+#include "ac/ctype.h"
+#include "ac/stdarg.h"
+
+#include "slap.h"
+#include "proto-sql.h"
+#include "lutil.h"
+
+#define BACKSQL_MAX(a,b) ((a)>(b)?(a):(b))
+#define BACKSQL_MIN(a,b) ((a)<(b)?(a):(b))
+
+#define BACKSQL_STR_GROW 256
+
+const char backsql_def_oc_query[] =
+ "SELECT id,name,keytbl,keycol,create_proc,delete_proc,expect_return "
+ "FROM ldap_oc_mappings";
+const char backsql_def_needs_select_oc_query[] =
+ "SELECT id,name,keytbl,keycol,create_proc,create_keyval,delete_proc,"
+ "expect_return FROM ldap_oc_mappings";
+const char backsql_def_at_query[] =
+ "SELECT name,sel_expr,from_tbls,join_where,add_proc,delete_proc,"
+ "param_order,expect_return,sel_expr_u FROM ldap_attr_mappings "
+ "WHERE oc_map_id=?";
+const char backsql_def_delentry_stmt[] = "DELETE FROM ldap_entries WHERE id=?";
+const char backsql_def_renentry_stmt[] =
+ "UPDATE ldap_entries SET dn=?,parent=?,keyval=? WHERE id=?";
+const char backsql_def_insentry_stmt[] =
+ "INSERT INTO ldap_entries (dn,oc_map_id,parent,keyval) "
+ "VALUES (?,?,?,?)";
+const char backsql_def_delobjclasses_stmt[] = "DELETE FROM ldap_entry_objclasses "
+ "WHERE entry_id=?";
+const char backsql_def_subtree_cond[] = "ldap_entries.dn LIKE CONCAT('%',?)";
+const char backsql_def_upper_subtree_cond[] = "(ldap_entries.dn) LIKE CONCAT('%',?)";
+const char backsql_id_query[] = "SELECT id,keyval,oc_map_id,dn FROM ldap_entries WHERE ";
+/* better ?||? or cast(?||? as varchar) */
+const char backsql_def_concat_func[] = "CONCAT(?,?)";
+
+/* TimesTen */
+const char backsql_check_dn_ru_query[] = "SELECT dn_ru FROM ldap_entries";
+
+struct berbuf *
+backsql_strcat_x( struct berbuf *dest, void *memctx, ... )
+{
+ va_list strs;
+ ber_len_t cdlen, cslen, grow;
+ char *cstr;
+
+ assert( dest != NULL );
+ assert( dest->bb_val.bv_val == NULL
+ || dest->bb_val.bv_len == strlen( dest->bb_val.bv_val ) );
+
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_strcat()\n" );
+#endif /* BACKSQL_TRACE */
+
+ va_start( strs, memctx );
+ if ( dest->bb_val.bv_val == NULL || dest->bb_len == 0 ) {
+ dest->bb_val.bv_val = (char *)ber_memalloc_x( BACKSQL_STR_GROW * sizeof( char ), memctx );
+ dest->bb_val.bv_len = 0;
+ dest->bb_len = BACKSQL_STR_GROW;
+ }
+ cdlen = dest->bb_val.bv_len;
+ while ( ( cstr = va_arg( strs, char * ) ) != NULL ) {
+ cslen = strlen( cstr );
+ grow = BACKSQL_MAX( BACKSQL_STR_GROW, cslen );
+ if ( dest->bb_len - cdlen <= cslen ) {
+ char *tmp_dest;
+
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE, "backsql_strcat(): "
+ "buflen=%d, cdlen=%d, cslen=%d "
+ "-- reallocating dest\n",
+ dest->bb_len, cdlen + 1, cslen );
+#endif /* BACKSQL_TRACE */
+
+ tmp_dest = (char *)ber_memrealloc_x( dest->bb_val.bv_val,
+ dest->bb_len + grow * sizeof( char ), memctx );
+ if ( tmp_dest == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "backsql_strcat(): "
+ "could not reallocate string buffer.\n" );
+ va_end( strs );
+ return NULL;
+ }
+ dest->bb_val.bv_val = tmp_dest;
+ dest->bb_len += grow;
+
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE, "backsql_strcat(): "
+ "new buflen=%d, dest=%p\n",
+ dest->bb_len, dest );
+#endif /* BACKSQL_TRACE */
+ }
+ AC_MEMCPY( dest->bb_val.bv_val + cdlen, cstr, cslen + 1 );
+ cdlen += cslen;
+ }
+ va_end( strs );
+
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_strcat() (dest=\"%s\")\n",
+ dest->bb_val.bv_val );
+#endif /* BACKSQL_TRACE */
+
+ dest->bb_val.bv_len = cdlen;
+
+ return dest;
+}
+
+struct berbuf *
+backsql_strfcat_x( struct berbuf *dest, void *memctx, const char *fmt, ... )
+{
+ va_list strs;
+ ber_len_t cdlen;
+
+ assert( dest != NULL );
+ assert( fmt != NULL );
+ assert( dest->bb_len == 0 || dest->bb_len > dest->bb_val.bv_len );
+ assert( dest->bb_val.bv_val == NULL
+ || dest->bb_val.bv_len == strlen( dest->bb_val.bv_val ) );
+
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_strfcat()\n" );
+#endif /* BACKSQL_TRACE */
+
+ va_start( strs, fmt );
+ if ( dest->bb_val.bv_val == NULL || dest->bb_len == 0 ) {
+ dest->bb_val.bv_val = (char *)ber_memalloc_x( BACKSQL_STR_GROW * sizeof( char ), memctx );
+ dest->bb_val.bv_len = 0;
+ dest->bb_len = BACKSQL_STR_GROW;
+ }
+
+ cdlen = dest->bb_val.bv_len;
+ for ( ; fmt[0]; fmt++ ) {
+ ber_len_t cslen, grow;
+ char *cstr, cc[ 2 ] = { '\0', '\0' };
+ struct berval *cbv;
+
+ switch ( fmt[ 0 ] ) {
+
+ /* berval */
+ case 'b':
+ cbv = va_arg( strs, struct berval * );
+ cstr = cbv->bv_val;
+ cslen = cbv->bv_len;
+ break;
+
+ /* length + string */
+ case 'l':
+ cslen = va_arg( strs, ber_len_t );
+ cstr = va_arg( strs, char * );
+ break;
+
+ /* string */
+ case 's':
+ cstr = va_arg( strs, char * );
+ cslen = strlen( cstr );
+ break;
+
+ /* char */
+ case 'c':
+ /*
+ * `char' is promoted to `int' when passed through `...'
+ */
+ cc[0] = va_arg( strs, int );
+ cstr = cc;
+ cslen = 1;
+ break;
+
+ default:
+ assert( 0 );
+ }
+
+ grow = BACKSQL_MAX( BACKSQL_STR_GROW, cslen );
+ if ( dest->bb_len - cdlen <= cslen ) {
+ char *tmp_dest;
+
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE, "backsql_strfcat(): "
+ "buflen=%d, cdlen=%d, cslen=%d "
+ "-- reallocating dest\n",
+ dest->bb_len, cdlen + 1, cslen );
+#endif /* BACKSQL_TRACE */
+
+ tmp_dest = (char *)ber_memrealloc_x( dest->bb_val.bv_val,
+ ( dest->bb_len ) + grow * sizeof( char ), memctx );
+ if ( tmp_dest == NULL ) {
+ Debug( LDAP_DEBUG_ANY, "backsql_strfcat(): "
+ "could not reallocate string buffer.\n" );
+ va_end( strs );
+ return NULL;
+ }
+ dest->bb_val.bv_val = tmp_dest;
+ dest->bb_len += grow * sizeof( char );
+
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE, "backsql_strfcat(): "
+ "new buflen=%d, dest=%p\n", dest->bb_len, dest );
+#endif /* BACKSQL_TRACE */
+ }
+
+ assert( cstr != NULL );
+
+ AC_MEMCPY( dest->bb_val.bv_val + cdlen, cstr, cslen + 1 );
+ cdlen += cslen;
+ }
+
+ va_end( strs );
+
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_strfcat() (dest=\"%s\")\n",
+ dest->bb_val.bv_val );
+#endif /* BACKSQL_TRACE */
+
+ dest->bb_val.bv_len = cdlen;
+
+ return dest;
+}
+
+int
+backsql_entry_addattr(
+ Entry *e,
+ AttributeDescription *ad,
+ struct berval *val,
+ void *memctx )
+{
+ int rc;
+
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE, "backsql_entry_addattr(\"%s\"): %s=%s\n",
+ e->e_name.bv_val, ad->ad_cname.bv_val, val->bv_val );
+#endif /* BACKSQL_TRACE */
+
+ rc = attr_merge_normalize_one( e, ad, val, memctx );
+
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "backsql_entry_addattr(\"%s\"): "
+ "failed to merge value \"%s\" for attribute \"%s\"\n",
+ e->e_name.bv_val, val->bv_val, ad->ad_cname.bv_val );
+ return rc;
+ }
+
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_entry_addattr(\"%s\")\n",
+ e->e_name.bv_val );
+#endif /* BACKSQL_TRACE */
+
+ return LDAP_SUCCESS;
+}
+
+static char *
+backsql_get_table_spec( backsql_info *bi, char **p )
+{
+ char *s, *q;
+ struct berbuf res = BB_NULL;
+
+ assert( p != NULL );
+ assert( *p != NULL );
+
+ s = *p;
+ while ( **p && **p != ',' ) {
+ (*p)++;
+ }
+
+ if ( **p ) {
+ *(*p)++ = '\0';
+ }
+
+#define BACKSQL_NEXT_WORD { \
+ while ( *s && isspace( (unsigned char)*s ) ) s++; \
+ if ( !*s ) return res.bb_val.bv_val; \
+ q = s; \
+ while ( *q && !isspace( (unsigned char)*q ) ) q++; \
+ if ( *q ) *q++='\0'; \
+ }
+
+ BACKSQL_NEXT_WORD;
+ /* table name */
+ backsql_strcat_x( &res, NULL, s, NULL );
+ s = q;
+
+ BACKSQL_NEXT_WORD;
+ if ( strcasecmp( s, "AS" ) == 0 ) {
+ s = q;
+ BACKSQL_NEXT_WORD;
+ }
+
+ /* oracle doesn't understand "AS" :( and other RDBMSes don't need it */
+ backsql_strfcat_x( &res, NULL, "lbbsb",
+ STRLENOF( " " ), " ",
+ &bi->sql_aliasing,
+ &bi->sql_aliasing_quote,
+ s,
+ &bi->sql_aliasing_quote );
+
+ return res.bb_val.bv_val;
+}
+
+int
+backsql_merge_from_clause(
+ backsql_info *bi,
+ struct berbuf *dest_from,
+ struct berval *src_from )
+{
+ char *s, *p, *srcc, *pos, e;
+ struct berbuf res = BB_NULL;
+
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE, "==>backsql_merge_from_clause(): "
+ "dest_from=\"%s\",src_from=\"%s\"\n",
+ dest_from ? dest_from->bb_val.bv_val : "<NULL>",
+ src_from->bv_val );
+#endif /* BACKSQL_TRACE */
+
+ srcc = ch_strdup( src_from->bv_val );
+ p = srcc;
+
+ if ( dest_from != NULL ) {
+ res = *dest_from;
+ }
+
+ while ( *p ) {
+ s = backsql_get_table_spec( bi, &p );
+
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE, "backsql_merge_from_clause(): "
+ "p=\"%s\" s=\"%s\"\n", p, s );
+#endif /* BACKSQL_TRACE */
+
+ if ( BER_BVISNULL( &res.bb_val ) ) {
+ backsql_strcat_x( &res, NULL, s, NULL );
+
+ } else {
+ pos = strstr( res.bb_val.bv_val, s );
+ if ( pos == NULL || ( ( e = pos[ strlen( s ) ] ) != '\0' && e != ',' ) ) {
+ backsql_strfcat_x( &res, NULL, "cs", ',', s );
+ }
+ }
+
+ if ( s ) {
+ ch_free( s );
+ }
+ }
+
+#ifdef BACKSQL_TRACE
+ Debug( LDAP_DEBUG_TRACE, "<==backsql_merge_from_clause()\n" );
+#endif /* BACKSQL_TRACE */
+
+ free( srcc );
+ *dest_from = res;
+
+ return 1;
+}
+
+/*
+ * splits a pattern in components separated by '?'
+ * (double ?? are turned into single ? and left in the string)
+ * expected contains the number of expected occurrences of '?'
+ * (a negative value means parse as many as possible)
+ */
+
+int
+backsql_split_pattern(
+ const char *_pattern,
+ BerVarray *split_pattern,
+ int expected )
+{
+ char *pattern, *start, *end;
+ struct berval bv;
+ int rc = 0;
+
+#define SPLIT_CHAR '?'
+
+ assert( _pattern != NULL );
+ assert( split_pattern != NULL );
+
+ pattern = ch_strdup( _pattern );
+
+ start = pattern;
+ end = strchr( start, SPLIT_CHAR );
+ for ( ; start; expected-- ) {
+ char *real_end = end;
+ ber_len_t real_len;
+
+ if ( real_end == NULL ) {
+ real_end = start + strlen( start );
+
+ } else if ( real_end[ 1 ] == SPLIT_CHAR ) {
+ expected++;
+ AC_MEMCPY( real_end, real_end + 1, strlen( real_end ) );
+ end = strchr( real_end + 1, SPLIT_CHAR );
+ continue;
+ }
+
+ real_len = real_end - start;
+ if ( real_len == 0 ) {
+ ber_str2bv( "", 0, 1, &bv );
+ } else {
+ ber_str2bv( start, real_len, 1, &bv );
+ }
+
+ ber_bvarray_add( split_pattern, &bv );
+
+ if ( expected == 0 ) {
+ if ( end != NULL ) {
+ rc = -1;
+ goto done;
+ }
+ break;
+ }
+
+ if ( end != NULL ) {
+ start = end + 1;
+ end = strchr( start, SPLIT_CHAR );
+ }
+ }
+
+done:;
+
+ ch_free( pattern );
+
+ return rc;
+}
+
+int
+backsql_prepare_pattern(
+ BerVarray split_pattern,
+ BerVarray values,
+ struct berval *res )
+{
+ int i;
+ struct berbuf bb = BB_NULL;
+
+ assert( res != NULL );
+
+ for ( i = 0; values[i].bv_val; i++ ) {
+ if ( split_pattern[i].bv_val == NULL ) {
+ ch_free( bb.bb_val.bv_val );
+ return -1;
+ }
+ backsql_strfcat_x( &bb, NULL, "b", &split_pattern[ i ] );
+ backsql_strfcat_x( &bb, NULL, "b", &values[ i ] );
+ }
+
+ if ( split_pattern[ i ].bv_val == NULL ) {
+ ch_free( bb.bb_val.bv_val );
+ return -1;
+ }
+
+ backsql_strfcat_x( &bb, NULL, "b", &split_pattern[ i ] );
+
+ *res = bb.bb_val;
+
+ return 0;
+}
+
+int
+backsql_entryUUID(
+ backsql_info *bi,
+ backsql_entryID *id,
+ struct berval *entryUUID,
+ void *memctx )
+{
+ char uuidbuf[ LDAP_LUTIL_UUIDSTR_BUFSIZE ];
+ struct berval uuid;
+#ifdef BACKSQL_ARBITRARY_KEY
+ int i;
+ ber_len_t l, lmax;
+#endif /* BACKSQL_ARBITRARY_KEY */
+
+ /* entryUUID is generated as "%08x-%04x-%04x-0000-eaddrXXX"
+ * with eid_oc_id as %08x and hi and lo eid_id as %04x-%04x */
+ assert( bi != NULL );
+ assert( id != NULL );
+ assert( entryUUID != NULL );
+
+#ifdef BACKSQL_ARBITRARY_KEY
+ snprintf( uuidbuf, sizeof( uuidbuf ),
+ "%08x-0000-0000-0000-000000000000",
+ ( id->eid_oc_id & 0xFFFFFFFF ) );
+ lmax = id->eid_keyval.bv_len < 12 ? id->eid_keyval.bv_len : 12;
+ for ( l = 0, i = 9; l < lmax; l++, i += 2 ) {
+ switch ( i ) {
+ case STRLENOF( "00000000-0000" ):
+ case STRLENOF( "00000000-0000-0000" ):
+ case STRLENOF( "00000000-0000-0000-0000" ):
+ uuidbuf[ i++ ] = '-';
+ /* FALLTHRU */
+
+ default:
+ snprintf( &uuidbuf[ i ], 3, "%2x", id->eid_keyval.bv_val[ l ] );
+ break;
+ }
+ }
+#else /* ! BACKSQL_ARBITRARY_KEY */
+ /* note: works only with 32 bit architectures... */
+ snprintf( uuidbuf, sizeof( uuidbuf ),
+ "%08x-%04x-%04x-0000-000000000000",
+ ( (unsigned)id->eid_oc_id & 0xFFFFFFFF ),
+ ( ( (unsigned)id->eid_keyval & 0xFFFF0000 ) >> 020 /* 16 */ ),
+ ( (unsigned)id->eid_keyval & 0xFFFF ) );
+#endif /* ! BACKSQL_ARBITRARY_KEY */
+
+ uuid.bv_val = uuidbuf;
+ uuid.bv_len = strlen( uuidbuf );
+
+ ber_dupbv_x( entryUUID, &uuid, memctx );
+
+ return 0;
+}
+
+int
+backsql_entryUUID_decode(
+ struct berval *entryUUID,
+ unsigned long *oc_id,
+#ifdef BACKSQL_ARBITRARY_KEY
+ struct berval *keyval
+#else /* ! BACKSQL_ARBITRARY_KEY */
+ unsigned long *keyval
+#endif /* ! BACKSQL_ARBITRARY_KEY */
+ )
+{
+#if 0
+ fprintf( stderr, "==> backsql_entryUUID_decode()\n" );
+#endif
+
+ *oc_id = ( entryUUID->bv_val[0] << 030 /* 24 */ )
+ + ( entryUUID->bv_val[1] << 020 /* 16 */ )
+ + ( entryUUID->bv_val[2] << 010 /* 8 */ )
+ + entryUUID->bv_val[3];
+
+#ifdef BACKSQL_ARBITRARY_KEY
+ /* FIXME */
+#else /* ! BACKSQL_ARBITRARY_KEY */
+ *keyval = ( entryUUID->bv_val[4] << 030 /* 24 */ )
+ + ( entryUUID->bv_val[5] << 020 /* 16 */ )
+ + ( entryUUID->bv_val[6] << 010 /* 8 */ )
+ + entryUUID->bv_val[7];
+#endif /* ! BACKSQL_ARBITRARY_KEY */
+
+#if 0
+ fprintf( stderr, "<== backsql_entryUUID_decode(): oc=%lu id=%lu\n",
+ *oc_id, *keyval );
+#endif
+
+ return LDAP_SUCCESS;
+}
+