--- /dev/null
+# $OpenLDAP$
+
+SRCS = init.c config.c search.c bind.c other.c \
+ entry-id.c schema-map.c sql-wrap.c modify.c util.c
+OBJS = init.lo config.lo search.lo bind.lo other.lo \
+ entry-id.lo schema-map.lo sql-wrap.lo modify.lo util.lo
+
+LDAP_INCDIR= ../../../include
+LDAP_LIBDIR= ../../../libraries
+
+BUILD_OPT = "--enable-sql"
+BUILD_MOD = @BUILD_SQL@
+LINKAGE = @BUILD_SQL_DYNAMIC@
+
+LIBBASE = back_sql
+
+XINCPATH = -I.. -I$(srcdir)/.. $(SLAPD_SQL_INCLUDES)
+XDEFS = $(MODULES_CPPFLAGS)
+XLDFLAGS = $(MODULES_LDFLAGS)
+
+all-local-lib: ../.backend
+
+../.backend: lib$(LIBBASE).a
+ @touch $@
+
--- /dev/null
+#ifndef __BACKSQL_H__
+#define __BACKSQL_H__
+
+/*
+ * Copyright 1999, Dmitry Kovalev (zmit@mail.ru), All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted only
+ * as authorized by the OpenLDAP Public License. A copy of this
+ * license is available at http://www.OpenLDAP.org/license.html or
+ * in file LICENSE in the top-level directory of the distribution.
+ */
+
+
+#include "external.h"
+#include "sql-types.h"
+#define BACKSQL_MAX_DN_LEN 255
+
+typedef struct
+{
+ char *dbhost;
+ int dbport;
+ char *dbuser;
+ char *dbpasswd;
+ char *dbname;
+ //SQL condition for subtree searches differs in syntax:
+ //"LIKE CONCAT('%',?)" or "LIKE '%'+?" or smth else
+ char *subtree_cond;
+ char *oc_query,*at_query;
+ char *insentry_query,*delentry_query;
+ Avlnode *db_conns;
+ Avlnode *oc_by_name;
+ Avlnode *oc_by_id;
+ int schema_loaded;
+ ldap_pvt_thread_mutex_t dbconn_mutex;
+ ldap_pvt_thread_mutex_t schema_mutex;
+ SQLHENV db_env;
+}backsql_info;
+
+#endif
\ No newline at end of file
--- /dev/null
+# Microsoft Developer Studio Project File - Name="backsql" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 5.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Static Library" 0x0104
+
+CFG=backsql - Win32 Single Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE
+!MESSAGE NMAKE /f "backsql.mak".
+!MESSAGE
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE
+!MESSAGE NMAKE /f "backsql.mak" CFG="backsql - Win32 Single Debug"
+!MESSAGE
+!MESSAGE Possible choices for configuration are:
+!MESSAGE
+!MESSAGE "backsql - Win32 Release" (based on "Win32 (x86) Static Library")
+!MESSAGE "backsql - Win32 Debug" (based on "Win32 (x86) Static Library")
+!MESSAGE "backsql - Win32 Single Debug" (based on "Win32 (x86) Static Library")
+!MESSAGE "backsql - Win32 Single Release" (based on\
+ "Win32 (x86) Static Library")
+!MESSAGE
+
+# Begin Project
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+
+!IF "$(CFG)" == "backsql - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "Release"
+# PROP BASE Intermediate_Dir "Release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "..\..\..\Release"
+# PROP Intermediate_Dir "..\..\..\Release\backsql"
+# PROP Target_Dir ""
+RSC=rc.exe
+# ADD BASE RSC /l 0x419
+# ADD RSC /l 0x419
+# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /FD /c
+# ADD CPP /nologo /MT /W3 /GX /O2 /I "..\\" /I "..\..\..\include" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /FD /c
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LIB32=link.exe -lib
+# ADD BASE LIB32 /nologo
+# ADD LIB32 /nologo
+
+!ELSEIF "$(CFG)" == "backsql - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "Debug"
+# PROP BASE Intermediate_Dir "Debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "..\..\..\Debug"
+# PROP Intermediate_Dir "..\..\..\Debug\backsql"
+# PROP Target_Dir ""
+RSC=rc.exe
+# ADD BASE RSC /l 0x419
+# ADD RSC /l 0x419
+# ADD BASE CPP /nologo /W3 /GX /Z7 /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /YX /FD /c
+# ADD CPP /nologo /MTd /W3 /GX /Z7 /Od /I "..\\" /I "..\..\..\include" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FR /YX /FD /c
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LIB32=link.exe -lib
+# ADD BASE LIB32 /nologo
+# ADD LIB32 /nologo
+
+!ELSEIF "$(CFG)" == "backsql - Win32 Single Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "backsql"
+# PROP BASE Intermediate_Dir "backsql"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "..\..\..\SDebug"
+# PROP Intermediate_Dir "..\..\..\SDebug\backsql"
+# PROP Target_Dir ""
+RSC=rc.exe
+# ADD BASE RSC /l 0x419
+# ADD RSC /l 0x419
+# ADD BASE CPP /nologo /MTd /W3 /GX /Z7 /Od /I "..\\" /I "..\..\..\include" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FR /YX /FD /c
+# ADD CPP /nologo /W3 /GX /Z7 /Od /I "..\\" /I "..\..\..\include" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FR /YX /FD /c
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LIB32=link.exe -lib
+# ADD BASE LIB32 /nologo
+# ADD LIB32 /nologo
+
+!ELSEIF "$(CFG)" == "backsql - Win32 Single Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "backldb0"
+# PROP BASE Intermediate_Dir "backldb0"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "..\..\..\SRelease"
+# PROP Intermediate_Dir "..\..\..\SRelease\backsql"
+# PROP Target_Dir ""
+RSC=rc.exe
+# ADD BASE RSC /l 0x419
+# ADD RSC /l 0x419
+# ADD BASE CPP /nologo /MT /W3 /GX /O2 /I "..\\" /I "..\..\..\include" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /FD /c
+# ADD CPP /nologo /W3 /GX /O2 /I "..\\" /I "..\..\..\include" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /FD /c
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LIB32=link.exe -lib
+# ADD BASE LIB32 /nologo
+# ADD LIB32 /nologo
+
+!ENDIF
+
+# Begin Target
+
+# Name "backsql - Win32 Release"
+# Name "backsql - Win32 Debug"
+# Name "backsql - Win32 Single Debug"
+# Name "backsql - Win32 Single Release"
+# Begin Source File
+
+SOURCE=".\back-sql.h"
+# End Source File
+# Begin Source File
+
+SOURCE=.\bind.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\config.c
+# End Source File
+# Begin Source File
+
+SOURCE=".\entry-id.c"
+# End Source File
+# Begin Source File
+
+SOURCE=".\entry-id.h"
+# End Source File
+# Begin Source File
+
+SOURCE=.\external.h
+# End Source File
+# Begin Source File
+
+SOURCE=.\init.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\modify.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\other.c
+# End Source File
+# Begin Source File
+
+SOURCE=".\schema-map.c"
+# End Source File
+# Begin Source File
+
+SOURCE=".\schema-map.h"
+# End Source File
+# Begin Source File
+
+SOURCE=.\search.c
+# End Source File
+# Begin Source File
+
+SOURCE=".\sql-types.h"
+# End Source File
+# Begin Source File
+
+SOURCE=".\sql-wrap.c"
+# End Source File
+# Begin Source File
+
+SOURCE=".\sql-wrap.h"
+# End Source File
+# Begin Source File
+
+SOURCE=.\util.c
+# End Source File
+# Begin Source File
+
+SOURCE=.\util.h
+# End Source File
+# End Target
+# End Project
--- /dev/null
+/*
+ * Copyright 1999, Dmitry Kovalev (zmit@mail.ru), All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted only
+ * as authorized by the OpenLDAP Public License. A copy of this
+ * license is available at http://www.OpenLDAP.org/license.html or
+ * in file LICENSE in the top-level directory of the distribution.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <sys/types.h>
+#include "slap.h"
+#include "back-sql.h"
+#include "sql-wrap.h"
+
+int backsql_bind(Backend *be,Connection *conn,Operation *op,
+ char *dn,char *ndn,int method,char *mech,struct berval *cred,char** edn)
+{
+ Debug(LDAP_DEBUG_TRACE,"==>backsql_bind()\n",0,0,0);
+ //for now, just return OK, allowing to test modify operations
+ send_ldap_result(conn,op,LDAP_SUCCESS,NULL,NULL,NULL,0);
+ Debug(LDAP_DEBUG_TRACE,"<==backsql_bind()\n",0,0,0);
+ return 0;
+}
+
+int backsql_unbind(Backend *be,Connection *conn,Operation *op)
+{
+ Debug(LDAP_DEBUG_TRACE,"==>backsql_unbind()\n",0,0,0);
+ backsql_free_db_conn(be,conn);
+ send_ldap_result(conn,op,LDAP_SUCCESS,NULL,NULL,NULL,0);
+ Debug(LDAP_DEBUG_TRACE,"<==backsql_unbind()\n",0,0,0);
+ return 0;
+}
--- /dev/null
+/*
+ * Copyright 1999, Dmitry Kovalev (zmit@mail.ru), All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted only
+ * as authorized by the OpenLDAP Public License. A copy of this
+ * license is available at http://www.OpenLDAP.org/license.html or
+ * in file LICENSE in the top-level directory of the distribution.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include "slap.h"
+#include "back-sql.h"
+#include "sql-wrap.h"
+
+int backsql_db_config(BackendDB *be,const char *fname,int lineno,int argc,char **argv)
+{
+ backsql_info *si=(backsql_info*) be->be_private;
+
+ Debug(LDAP_DEBUG_TRACE,"==>backsql_db_config()\n",0,0,0);
+ if (!si)
+ {
+ Debug(LDAP_DEBUG_TRACE,"backsql_db_config: be_private is NULL!!!\n",0,0,0);
+ exit(1);
+ }
+
+ if (!strcasecmp(argv[0],"dbhost"))
+ {
+ if (argc<2)
+ {
+ Debug(LDAP_DEBUG_TRACE,"<==backsql_db_config (%s line %d): missing hostname in dbhost directive\n",
+ fname,lineno,0);
+ }
+ else
+ {
+ si->dbhost=strdup(argv[1]);
+ Debug(LDAP_DEBUG_TRACE,"<==backsql_db_config(): hostname=%s\n",si->dbhost,0,0);
+ }
+ return(0);
+ }
+
+ if (!strcasecmp(argv[0],"dbuser"))
+ {
+ if (argc<2)
+ {
+ Debug(LDAP_DEBUG_TRACE,"<==backsql_db_config (%s line %d): missing username in dbuser directive\n",
+ fname,lineno,0);
+ }
+ else
+ {
+ si->dbuser=strdup(argv[1]);
+ Debug(LDAP_DEBUG_TRACE,"<==backsql_db_config(): dbuser=%s\n",argv[1],0,0);
+ }
+ return(0);
+ }
+
+ if (!strcasecmp(argv[0],"dbpasswd"))
+ {
+ if (argc<2)
+ {
+ Debug(LDAP_DEBUG_TRACE,"<==backsql_db_config (%s line %d): missing password in dbpasswd directive\n",
+ fname,lineno,0);
+ }
+ else
+ {
+ si->dbpasswd=strdup(argv[1]);
+ Debug(LDAP_DEBUG_TRACE,"<==backsql_db_config(): dbpasswd=%s\n",si->dbpasswd,0,0);
+ }
+ return(0);
+ }
+
+ if (!strcasecmp(argv[0],"dbname"))
+ {
+ if (argc<2)
+ {
+ Debug(LDAP_DEBUG_TRACE,"<==backsql_db_config (%s line %d): missing database name in dbname directive\n",
+ fname,lineno,0);
+ }
+ else
+ {
+ si->dbname=strdup(argv[1]);
+ Debug(LDAP_DEBUG_TRACE,"<==backsql_db_config(): dbname=%s\n",si->dbname,0,0);
+ }
+ return(0);
+ }
+
+ if (!strcasecmp(argv[0],"subtree_cond"))
+ {
+ if (argc<2)
+ {
+ Debug(LDAP_DEBUG_TRACE,"<==backsql_db_config (%s line %d): missing SQL condition in subtree_cond directive\n",
+ fname,lineno,0);
+ }
+ else
+ {
+ si->subtree_cond=strdup(argv[1]);
+ Debug(LDAP_DEBUG_TRACE,"<==backsql_db_config(): subtree_cond=%s\n",si->subtree_cond,0,0);
+ }
+ return(0);
+ }
+
+ if (!strcasecmp(argv[0],"oc_query"))
+ {
+ if (argc<2)
+ {
+ Debug(LDAP_DEBUG_TRACE,"<==backsql_db_config (%s line %d): missing SQL statement in oc_query directive\n",
+ fname,lineno,0);
+ }
+ else
+ {
+ si->oc_query=strdup(argv[1]);
+ Debug(LDAP_DEBUG_TRACE,"<==backsql_db_config(): oc_query=%s\n",si->oc_query,0,0);
+ }
+ return(0);
+ }
+
+ if (!strcasecmp(argv[0],"at_query"))
+ {
+ if (argc<2)
+ {
+ Debug(LDAP_DEBUG_TRACE,"<==backsql_db_config (%s line %d): missing SQL statement in at_query directive\n",
+ fname,lineno,0);
+ }
+ else
+ {
+ si->at_query=strdup(argv[1]);
+ Debug(LDAP_DEBUG_TRACE,"<==backsql_db_config(): at_query=%s\n",si->at_query,0,0);
+ }
+ return(0);
+ }
+
+ if (!strcasecmp(argv[0],"insentry_query"))
+ {
+ if (argc<2)
+ {
+ Debug(LDAP_DEBUG_TRACE,"<==backsql_db_config (%s line %d): missing SQL statement in insentry_query directive\n",
+ fname,lineno,0);
+ }
+ else
+ {
+ si->insentry_query=strdup(argv[1]);
+ Debug(LDAP_DEBUG_TRACE,"<==backsql_db_config(): insentry_query=%s\n",si->insentry_query,0,0);
+ }
+ return(0);
+ }
+
+ if (!strcasecmp(argv[0],"delentry_query"))
+ {
+ if (argc<2)
+ {
+ Debug(LDAP_DEBUG_TRACE,"<==backsql_db_config (%s line %d): missing SQL statement in delentry_query directive\n",
+ fname,lineno,0);
+ }
+ else
+ {
+ si->delentry_query=strdup(argv[1]);
+ Debug(LDAP_DEBUG_TRACE,"<==backsql_db_config(): delentry_query=%s\n",si->delentry_query,0,0);
+ }
+ return(0);
+ }
+
+ Debug(LDAP_DEBUG_TRACE,"<==backsql_db_config (%s line %d): unknown directive '%s' (ignored)\n",
+ fname,lineno,argv[0]);
+ return 0;
+}
--- /dev/null
+1) driver name comparison for MS SQL Server workaround is realy 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)...
+4) 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
+5) referral handling. this bug actually addresses all backends, as I can
+ understand. draft-ietf-ldapext-namedref-xx.txt says that referral should be
+ returned for ANY object containing "ref" attribute. And is_entry_referral
+ macro designed for "referral" objectclass only. This limits usability of
+ referrals too much. For instance, I could want to replicate some subtree on
+ another server, storing just "searchable" attributes + referral to full
+ object, and then use this subtree as kind of index for query routing.
+ If search returns referrals only for "referral" entries - I cannot do such
+ thing
+6) DO NOT EVER USE -O2 option (or any other optimization) under Un*x/gcc!!!
+ I have spent days trying to catch weird bugs, which went gone with optimization off
+7) The same thing that works on RedHat 6.0 (glibc 2.1.1), dumps core on
+ 6.1 (glibc 2.1.2) (the same code behaves differently when built on 6.0 and 6.1)
+ my problem was solved by upgrading iODBC to 3.0 beta - but it is kinda strange
+ that beta works better than release (and release still works fine on 6.0)
+8) Oracle does case-sensitive comparison of strings by default, so back-sql
+ becomes sensitive too when using Oracle. Later I'll add some option to slapd.conf
+ that would allow to set some function to process values before comparison
+ (something like "before_match UPPER", so that back-sql could generate
+ something like "select ... from ... where ... and UPPER(colname)=UPPER(?) or
+ UPPER(colname) LIKE UPPER(...)")
+9) ldapsearch sometimes refuses to show some attributes ("NOT PRINTABLE" diags)
+ on Win32 (on linux everything's fine -- at least with mySQL)
+
\ No newline at end of file
--- /dev/null
+CONTENT
+1. Purpose
+2. Metainformation used
+3. Typical back-sql operation
+4. Perspectives on back-sql as effective storage backend (not MAPPER)
+
+
+1. Purpose
+Primary purpose of this backend is to PRESENT information stored in some RDBMS
+as an LDAP subtree. It is being designed to be tunable to virtually any
+relational schema without having to change source. It is NOT designed as backend
+that uses RDBMS to store LDAP data (though you can use it for this purpose, it
+will definitely be not the most effective way).
+But THIS backend primarily targets the situation when you ALREADY HAVE some
+data in one or more RDBMSes of one or more different vendors on one or more
+different hosts and operating systems, having one or more different
+relational schemas. These could be data used by different software, which you
+want to integrate, or just local parts of bigger information project. Using
+LDAP standalone server with back-sql as middleware, you can integrate this
+heterogeneous information as a single distributed LDAP tree, and even organize
+data exchange between nodes, not having to worry about unique id's, different
+schemas etc (****see authordn attribute in samples, and dts_ldap utility).
+Or, you could simply want to export some information like ISP database to LDAP,
+to authenticate users, make email lookups or whatever...
+
+2. Metainformation used
+***
+Almost everything mentioned later is illustrated in example, which is located
+in backsql/RDBMS_DEPENDENT directory, and contains scripts for generating sample
+database for Oracle,MS SQL Server and mySQL.
+***
+First thing that one must arrange for himself is what set of objectclasses
+can present your RDBMS information. The easiest way is to create objectclass
+for each entity you had in ER-diagram when designing your relational schema.
+Or you could choose some other way...
+Nevertheless, when you think it out, we must define a way to translate LDAP
+operation requests to (series of) SQL queries. Let us deal with SEARCH
+operation.
+
+Example:
+Lets suppose that we store information about persons working in our
+organization in two tables:
+
+PERSONS PHONES
+---------- -------------
+id integer id integer
+first_name varchar pers_id integer references persons(id)
+last_name varchar phone
+middle_name varchar
+...
+
+(PHONES contains telephone numbers associated with persons). A person can have
+several numbers, then PHONES contains several records with corresponding
+pers_id, or no numbers (and no records in PHONES with such pers_id). LDAP
+objectclass to present such information could look like this:
+person
+-------
+MUST cn
+MAY telephoneNumber
+MAY firstName
+MAY lastName
+...
+
+To fetch all values for cn attribute given person ID, we construct the query:
+SELECT CONCAT(persons.first_name,' ',persons.last_name) as cn FROM persons WHERE persons.id=?
+
+for telephoneNumber we can use:
+SELECT phones.phone as telephoneNumber FROM persons,phones WHERE persons.id=phones.pers.id and persons.id=?
+
+if we wanted to service LDAP request with filter like (telephoneNumber=123*),
+we would construct something like:
+SELECT ... FROM persons,phones WHERE persons.id=phones.pers.id and persons.id=? and phones.phone like '123%'
+
+So, if we had information about what tables contain values for given each
+attribute, how to join this tables and arrange these values, we could try
+to automatically generate such statements, and translate search filters
+to SQL clauses
+
+To store such information, we add three more tables to our schema, so that
+ and fill it with data (see samples):
+
+ldap_objclasses
+---------------
+id=1
+name="person"
+keytbl="persons"
+keycol="id"
+create_proc="{call create_person(?)}"
+delete_proc="{call delete_person(?)}"
+
+ldap_attrs
+-----------
+id=1
+oc_id=1
+name="cn"
+sel_expr="CONCAT(persons.first_name,' ',persons.last_name)"
+from_tbls="persons"
+join_where=NULL
+add_proc=...
+delete_proc=...
+************
+id=<n>
+oc_id=1
+name="telephoneNumber"
+expr="phones.phone"
+from_tbls="persons,phones"
+join_where="phones.pers_id=persons.id"
+add_proc=...
+delete_proc=...
+
+
+ldap_entries
+------------
+id=1
+dn=<dn you choose>
+parent=<parent record id>
+keyval=<value of primary key>
+
+First two tables contain structured information about constructing queries like
+those we made in example. The latter (ldap_entries), contains information about
+structure of LDAP tree, referencing actual information by key value. Having
+objectclass id, we can determine table and column which contain primary keys,
+and load data for the entry attributes using our queries.
+
+3. Typical back-sql operation
+Having metainformation loaded, back-sql uses these tables to determine a set
+of primary keys of candidates (depending on search scope and filter). It tries
+to do it for each objectclass registered in ldap_objclasses.
+Exapmle:
+for our query with filter (telephoneNumber=123*) we would get following
+query (which loads candidate IDs)
+SELECT ldap_entries.id,persons.id, 'person' AS objectClass, ldap_entries.dn AS dn FROM ldap_entries,persons,phones WHERE persons.id=ldap_entries.keyval AND ldap_entries.objclass=? AND ldap_entries.parent=? AND phones.pers_id=persons.id AND (phones.phone LIKE '123%')
+(for ONELEVEL search)
+or "... AND dn=?" (for BASE search)
+or "... AND dn LIKE '%?'" (for SUBTREE)
+
+Then, for each candidate, we load attributes requested using per-attribute queries
+like
+
+SELECT phones.phone AS telephoneNumber FROM persons,phones WHERE persons.id=? AND phones.pers_id=persons.id
+
+Then, we use test_filter() to test entry for full LDAP search filter match (since
+we cannot effectively make sense of SYNTAX of corresponding LDAP schema attribute,
+we translate the filter into most relaxed SQL condition to filter candidates),
+and send it to user.
+
+ADD,DELETE,MODIFY operations also performed on per-attribute metainformation
+(add_proc etc.). In those fields one can specify an SQL statement or stored procedure
+call which can add, or delete given value of given attribute, using given entry
+keyval (see examples -- mostly ORACLE and MSSQL - since there're no stored procs in mySQL).
+
+
+4. Perspectives on back-sql as effective storage backend (not MAPPER)
+Though as I said, back-sql is intended for presenting existing databases to LDAP,
+and as such is not most effective in presenting LDAP data to RDBMS, I have a couple
+of ideas on this point, and going to implement this in back-sql using
+#ifdefs (one that wants to do RDBMS->LDAP, defines one flag, one that wants
+LDAP->RDBMS, defines another).
+These tasks have much in common (RDBMS access,connection handling etc), but
+latter does not need so much additional metainformation.
+For instance, it may have one table for each attribute type in LDAP schema,
+and use ldap_entries analog to present tree structure... Later this functionality
+will be described more closely...
+
--- /dev/null
+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). Then, run
+"configure <options you need> --enable-sql [--with-iodbc-includes=<path>] [--with-iodbc-libs=<path>]",
+this should build back-sql-enabled slapd.
+
+Under Win32/MSVC++, I modified the workspace so that back-sql is built into
+slapd automatically, since MS odbc32 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 later (when back-sql
+comes into some stable state).
+
+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), or using ODBC wizard in
+Control Panel (for odbc32). Next, you need to add appropriate "database"
+record to your slapd.conf. See
+sample provided in "back-sql/RDBMS_DEPENDENT/" subdirectory. The only thing
+worth noting about this is that "dbname" directive stands for ODBC datasource
+name, not the name of your database in RDBMS context.
+
+3. Creating and using back-sql metatables
+See SQL scripts and slapd.conf files in sample directory .
+Create db/user whatever for test, execute create.sql, create_testdb.sql,
+test_data.sql,test_metadata.sql from appropriate directory (use
+"mysql < xxx.sql" for mySQL, Query Analyzer+Open query file for MS SQL,
+sqlplus and "@xxx.sql" for Oracle)
+
+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
--- /dev/null
+Platforms and configurations it has been tested on (for now I included only
+ configurations I've tested personally):
+
+1) slapd on redhat linux 6.0/6.1 (glibc 2.1.1/2.1.2), built with egcs
+ (versions packaged with appropriate red hat):
+ iODBC 2.50.3 (on 6.0), 3.0beta (on 6.1),
+ mySQL (on same linux) 3.22.25,3.22.30 trough myODBC 2.50.23,
+ MSSQL (on WinNT 4/sp3) 7.0 through OpenLink driver suite 3 (broker on NT),
+ Personal Oracle (on WinNT4/sp3) 8.0.3 through OpenLink driver suite 3 (broker on NT),
+ Oracle (on linux 6.0) 8.0.5 through OpenLink driver suite 3 (broker on linux)
+
+2) slapd on WinNT4/sp3, Win98 second edition, Windows2000pre,
+ built with MSVC++ 5,6:
+ ODBC32.DLL shipped with appropriate system,
+ MSSQL (on WinNT4/sp3,Win98,Win2000) 7.0, through its native driver,
+ Personal Oracle (on WinNT4/sp3,Win98) 8.0.3, through its native driver,
+ Oracle 7 (on Solaris/Sparc 2.6) through its native driver
+
--- /dev/null
+/*
+ * Copyright 1999, Dmitry Kovalev (zmit@mail.ru), All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted only
+ * as authorized by the OpenLDAP Public License. A copy of this
+ * license is available at http://www.OpenLDAP.org/license.html or
+ * in file LICENSE in the top-level directory of the distribution.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <string.h>
+#include "slap.h"
+#include "back-sql.h"
+#include "sql-wrap.h"
+#include "schema-map.h"
+#include "entry-id.h"
+#include "util.h"
+
+backsql_entryID* backsql_free_entryID(backsql_entryID* id)
+{
+ backsql_entryID* next=id->next;
+ if (id->dn!=NULL)
+ free(id->dn);
+ free(id);
+ return next;
+}
+
+backsql_entryID* backsql_dn2id(backsql_entryID *id,SQLHDBC dbh,char *dn)
+{
+ static char id_query[]="SELECT id,keyval,objclass FROM ldap_entries WHERE dn=?";
+ SQLHSTMT sth;
+ BACKSQL_ROW_NTS row;
+ //SQLINTEGER nrows=0;
+ RETCODE rc;
+
+ Debug(LDAP_DEBUG_TRACE,"==>backsql_dn2id(): dn='%s'\n",dn,0,0);
+ backsql_Prepare(dbh,&sth,id_query,0);
+ if ((rc=backsql_BindParamStr(sth,1,dn,BACKSQL_MAX_DN_LEN)) != SQL_SUCCESS)
+ {
+ Debug(LDAP_DEBUG_TRACE,"backsql_dn2id(): error binding dn parameter:\n",0,0,0);
+ backsql_PrintErrors(SQL_NULL_HENV,dbh,sth,rc);
+ SQLFreeStmt(sth,SQL_DROP);
+ return NULL;
+ }
+
+ if ((rc=SQLExecute(sth)) != SQL_SUCCESS)
+ {
+ Debug(LDAP_DEBUG_TRACE,"backsql_dn2id(): error executing query:\n",0,0,0);
+ backsql_PrintErrors(SQL_NULL_HENV,dbh,sth,rc);
+ SQLFreeStmt(sth,SQL_DROP);
+ return NULL;
+ }
+
+ backsql_BindRowAsStrings(sth,&row);
+ if ((rc=SQLFetch(sth)) == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO)
+ {
+ if (id==NULL)
+ {
+ id=(backsql_entryID*)ch_calloc(1,sizeof(backsql_entryID));
+ }
+ id->id=atoi(row.cols[0]);
+ id->keyval=atoi(row.cols[1]);
+ id->oc_id=atoi(row.cols[2]);
+ id->dn=strdup(dn);
+ id->next=NULL;
+ }
+ else
+ id=NULL;
+ backsql_FreeRow(&row);
+
+ SQLFreeStmt(sth, SQL_DROP);
+ if (id!=NULL)
+ Debug(LDAP_DEBUG_TRACE,"<==backsql_dn2id(): id=%d\n",(int)id->id,0,0);
+ else
+ Debug(LDAP_DEBUG_TRACE,"<==backsql_dn2id(): no match\n",0,0,0);
+ return id;
+}
+
+
+int backsql_get_attr_vals(backsql_at_map_rec *at,backsql_srch_info *bsi)
+{
+ RETCODE rc;
+ SQLHSTMT sth;
+ BACKSQL_ROW_NTS row;
+ int i;
+
+ Debug(LDAP_DEBUG_TRACE,"==>backsql_get_attr_vals(): oc='%s' attr='%s' keyval=%d\n",
+ bsi->oc->name,at->name,bsi->c_eid->keyval);
+
+ if ((rc=backsql_Prepare(bsi->dbh,&sth,at->query,0)) != SQL_SUCCESS)
+ {
+ Debug(LDAP_DEBUG_TRACE,"backsql_get_attr_values(): error preparing query: %s\n",at->query,0,0);
+ backsql_PrintErrors(bsi->bi->db_env,bsi->dbh,sth,rc);
+ return 1;
+ }
+
+ if (backsql_BindParamID(sth,1,&(bsi->c_eid->keyval)) != SQL_SUCCESS)
+ {
+ Debug(LDAP_DEBUG_TRACE,"backsql_get_attr_values(): error binding key value parameter\n",0,0,0);
+ return 1;
+ }
+
+ if ((rc=SQLExecute(sth)) != SQL_SUCCESS && rc!= SQL_SUCCESS_WITH_INFO)
+ {
+ Debug(LDAP_DEBUG_TRACE,"backsql_get_attr_values(): error executing query\n",0,0,0);
+ backsql_PrintErrors(bsi->bi->db_env,bsi->dbh,sth,rc);
+ SQLFreeStmt(sth,SQL_DROP);
+ return 1;
+ }
+
+ backsql_BindRowAsStrings(sth,&row);
+ while ((rc=SQLFetch(sth)) == SQL_SUCCESS || rc==SQL_SUCCESS_WITH_INFO)
+ {
+ for (i=0;i<row.ncols;i++)
+ {
+ if (row.is_null[i]>0)
+ {
+ backsql_entry_addattr(bsi->e,row.col_names[i],row.cols[i],/*row.col_prec[i]*/
+ strlen(row.cols[i]));
+// Debug(LDAP_DEBUG_TRACE,"prec=%d\n",(int)row.col_prec[i],0,0);
+ }
+ // else
+ // Debug(LDAP_DEBUG_TRACE,"NULL value in this row for attribute '%s'\n",row.col_names[i],0,0);
+ }
+ }
+ backsql_FreeRow(&row);
+ SQLFreeStmt(sth,SQL_DROP);
+ Debug(LDAP_DEBUG_TRACE,"<==backsql_get_attr_vals()\n",0,0,0);
+ return 1;
+}
+
+
+Entry* backsql_id2entry(backsql_srch_info *bsi,Entry* e,backsql_entryID* eid)
+{
+ char **c_at_name;
+ backsql_at_map_rec *at;
+
+ Debug(LDAP_DEBUG_TRACE,"==>backsql_id2entry()\n",0,0,0);
+
+ bsi->oc=backsql_oc_with_id(bsi->bi,eid->oc_id);
+ bsi->e=e;
+ bsi->c_eid=eid;
+ e->e_attrs=NULL;
+ if (bsi->base_dn != NULL)
+ e->e_dn=strdup(bsi->c_eid->dn);
+
+ if (bsi->attrs!=NULL)
+ {
+ Debug(LDAP_DEBUG_TRACE,"backsql_id2entry(): custom attribute list\n",0,0,0);
+ for(c_at_name=bsi->attrs;*c_at_name!=NULL;c_at_name++)
+ {
+ if (!strcasecmp(*c_at_name,"objectclass") || !strcasecmp(*c_at_name,"0.10"))
+ {
+ //backsql_entry_addattr(bsi->e,"objectclass",bsi->oc->name,strlen(bsi->oc->name));
+ continue;
+ }
+ at=backsql_at_with_name(bsi->oc,*c_at_name);
+ if (at!=NULL)
+ backsql_get_attr_vals(at,bsi);
+ else
+ Debug(LDAP_DEBUG_TRACE,"backsql_id2entry(): attribute '%s' is not defined for objectlass '%s'\n",
+ *c_at_name,bsi->oc->name,0);
+
+ }
+ }
+ else
+ {
+ Debug(LDAP_DEBUG_TRACE,"backsql_id2entry(): retrieving all attributes\n",0,0,0);
+ avl_apply(bsi->oc->attrs,(AVL_APPLY)backsql_get_attr_vals,bsi,0,AVL_INORDER);
+ }
+ backsql_entry_addattr(bsi->e,"objectclass",bsi->oc->name,strlen(bsi->oc->name));
+
+ Debug(LDAP_DEBUG_TRACE,"<==backsql_id2entry()\n",0,0,0);
+ return e;
+}
--- /dev/null
+#ifndef __BACKSQL_ENTRYID_H__
+#define __BACKSQL_ENTRYID_H__
+
+typedef struct __backsql_entryID
+{
+ unsigned long id;
+ unsigned long keyval;
+ unsigned long oc_id;
+ char *dn;
+ struct __backsql_entryID *next;
+}backsql_entryID;
+
+backsql_entryID* backsql_dn2id(backsql_entryID* id,SQLHDBC dbh,char *dn);
+backsql_entryID* backsql_free_entryID(backsql_entryID* id);//returns next
+
+#endif
\ No newline at end of file
--- /dev/null
+/* $OpenLDAP$ */
+#ifndef _SQL_EXTERNAL_H
+#define _SQL_EXTERNAL_H
+
+LDAP_BEGIN_DECL
+
+extern int sql_back_initialize LDAP_P(( BackendInfo *bi ));
+extern int backsql_destroy LDAP_P(( BackendInfo *bi ));
+
+extern int backsql_db_init LDAP_P(( BackendDB *bd ));
+extern int backsql_db_open LDAP_P(( BackendDB *bd ));
+extern int backsql_db_close LDAP_P(( BackendDB *bd ));
+extern int backsql_db_destroy LDAP_P(( BackendDB *bd ));
+
+extern int backsql_db_config LDAP_P(( BackendDB *bd,
+ const char *fname, int lineno, int argc, char **argv ));
+
+extern int backsql_bind LDAP_P(( BackendDB *bd,
+ Connection *conn, Operation *op,
+ char *dn, char *ndn, int method, char* mech,
+ struct berval *cred, char** edn ));
+
+extern int backsql_unbind LDAP_P(( BackendDB *bd,
+ Connection *conn, Operation *op ));
+
+extern int backsql_search LDAP_P(( BackendDB *bd,
+ Connection *conn, Operation *op, char *base,
+ char *nbase, int scope, int deref, int sizelimit, int timelimit,
+ Filter *filter, char *filterstr, char **attrs, int attrsonly ));
+
+extern int backsql_compare LDAP_P((BackendDB *bd,
+ Connection *conn, Operation *op,
+ char *dn, char *ndn, Ava *ava ));
+
+extern int backsql_modify LDAP_P(( BackendDB *bd,
+ Connection *conn, Operation *op,
+ char *dn, char *ndn, LDAPModList *ml ));
+
+extern int backsql_modrdn LDAP_P(( BackendDB *bd,
+ Connection *conn, Operation *op,
+ char *dn, char *ndn, char*newrdn, int deleteoldrdn,
+ char *newSuperior ));
+
+extern int backsql_add LDAP_P(( BackendDB *bd,
+ Connection *conn, Operation *op, Entry *e ));
+
+extern int backsql_delete LDAP_P(( BackendDB *bd,
+ Connection *conn, Operation *op, char *dn, char *ndn ));
+
+extern int backsql_abandon LDAP_P(( BackendDB *bd,
+ Connection *conn, Operation *op, int msgid ));
+
+LDAP_END_DECL
+
+#endif /* _SQL_EXTERNAL_H */
+
--- /dev/null
+/*
+ * Copyright 1999, Dmitry Kovalev (zmit@mail.ru), All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted only
+ * as authorized by the OpenLDAP Public License. A copy of this
+ * license is available at http://www.OpenLDAP.org/license.html or
+ * in file LICENSE in the top-level directory of the distribution.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <sys/types.h>
+#include "slap.h"
+#include "back-sql.h"
+#include "sql-wrap.h"
+#include "schema-map.h"
+#include "util.h"
+
+#ifdef SLAPD_SQL_DYNAMIC
+
+int backsql_LTX_init_module(int argc, char *argv[]) {
+ BackendInfo bi;
+
+ memset( &bi, 0, sizeof(bi) );
+ bi.bi_type = "sql";
+ bi.bi_init = backbacksql_initialize;
+
+ backend_add(&bi);
+ return 0;
+}
+
+#endif /* SLAPD_SHELL_DYNAMIC */
+
+int sql_back_initialize(
+ BackendInfo *bi
+)
+{
+ Debug(LDAP_DEBUG_TRACE,"==>backsql_initialize()\n",0,0,0);
+ bi->bi_open = 0;
+ bi->bi_config = 0;
+ bi->bi_close = 0;
+ bi->bi_destroy = 0;
+
+ bi->bi_db_init = backsql_db_init;
+ bi->bi_db_config = backsql_db_config;
+ bi->bi_db_open = backsql_db_open;
+ bi->bi_db_close = backsql_db_close;
+ bi->bi_db_destroy = backsql_db_destroy;
+
+#ifdef BACKSQL_ALL_DONE
+ bi->bi_op_abandon = backsql_abandon;
+ bi->bi_op_compare = backsql_compare;
+#else
+ bi->bi_op_abandon = 0;
+ bi->bi_op_compare = 0;
+#endif
+ bi->bi_op_bind = backsql_bind;
+ bi->bi_op_unbind = backsql_unbind;
+ 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_acl_group = 0;
+
+ bi->bi_connection_init = 0;
+ bi->bi_connection_destroy = 0;
+
+ Debug(LDAP_DEBUG_TRACE,"<==backsql_initialize()\n",0,0,0);
+ return 0;
+}
+
+
+int backsql_destroy ( BackendInfo *bi )
+{
+ Debug(LDAP_DEBUG_TRACE,"==>backsql_destroy()\n",0,0,0);
+ Debug(LDAP_DEBUG_TRACE,"<==backsql_destroy()\n",0,0,0);
+ return 0;
+}
+
+int backsql_db_init(BackendDB *bd)
+{
+ backsql_info *si;
+
+ Debug(LDAP_DEBUG_TRACE,"==>backsql_db_init()\n",0,0,0);
+ si = (backsql_info *) ch_calloc( 1, sizeof(backsql_info) );
+ ldap_pvt_thread_mutex_init(&si->dbconn_mutex);
+ ldap_pvt_thread_mutex_init(&si->schema_mutex);
+ backsql_init_db_env(si);
+
+ bd->be_private=si;
+ Debug(LDAP_DEBUG_TRACE,"<==backsql_db_init()\n",0,0,0);
+ return 0;
+}
+
+int backsql_db_destroy(BackendDB *bd)
+{
+ backsql_info *si=(backsql_info*)bd->be_private;
+
+ Debug(LDAP_DEBUG_TRACE,"==>backsql_db_destroy()\n",0,0,0);
+ ldap_pvt_thread_mutex_lock(&si->dbconn_mutex);
+ backsql_free_db_env(si);
+ ldap_pvt_thread_mutex_unlock(&si->dbconn_mutex);
+ ldap_pvt_thread_mutex_lock(&si->schema_mutex);
+ backsql_destroy_schema_map(si);
+ ldap_pvt_thread_mutex_unlock(&si->schema_mutex);
+ ldap_pvt_thread_mutex_destroy(&si->schema_mutex);
+ ldap_pvt_thread_mutex_destroy(&si->dbconn_mutex);
+ free(si->dbname);
+ free(si->dbuser);
+ if (si->dbpasswd)
+ free(si->dbpasswd);
+ if (si->dbhost)
+ free(si->dbhost);
+ free(si->subtree_cond);
+ free(si->oc_query);
+ free(si->at_query);
+ free(si->insentry_query);
+ free(si->delentry_query);
+ free(si);
+ Debug(LDAP_DEBUG_TRACE,"<==backsql_db_destroy()\n",0,0,0);
+ return 0;
+}
+
+int backsql_db_open (BackendDB *bd)
+{
+ backsql_info *si=(backsql_info*)bd->be_private;
+ Connection tmp;
+ SQLHDBC dbh;
+
+ Debug(LDAP_DEBUG_TRACE,"==>backsql_db_open(): testing RDBMS connection\n",0,0,0);
+ if (si->dbname==NULL)
+ {
+ Debug(LDAP_DEBUG_TRACE,"backsql_db_open(): datasource name not specified (use dbname directive in slapd.conf)\n",0,0,0);
+ return 1;
+ }
+ if (si->dbuser==NULL)
+ {
+ Debug(LDAP_DEBUG_TRACE,"backsql_db_open(): user name not specified (use dbuser directive in slapd.conf)\n",0,0,0);
+ return 1;
+ }
+ if (si->subtree_cond==NULL)
+ {
+ Debug(LDAP_DEBUG_TRACE,"backsql_db_open(): subtree search SQL condition not specified (use subtree_cond directive in slapd.conf)\n",0,0,0);
+ Debug(LDAP_DEBUG_TRACE,"backsql_db_open(): setting '%s' as default\n",backsql_def_subtree_cond,0,0);
+ si->subtree_cond=strdup(backsql_def_subtree_cond);
+ }
+ if (si->oc_query==NULL)
+ {
+ Debug(LDAP_DEBUG_TRACE,"backsql_db_open(): objectclass mapping SQL statement not specified (use oc_query directive in slapd.conf)\n",0,0,0);
+ Debug(LDAP_DEBUG_TRACE,"backsql_db_open(): setting '%s' by default\n",backsql_def_oc_query,0,0);
+ si->oc_query=strdup(backsql_def_oc_query);
+ }
+ if (si->at_query==NULL)
+ {
+ Debug(LDAP_DEBUG_TRACE,"backsql_db_open(): attribute mapping SQL statement not specified (use at_query directive in slapd.conf)\n",0,0,0);
+ Debug(LDAP_DEBUG_TRACE,"backsql_db_open(): setting '%s' by default\n",backsql_def_at_query,0,0);
+ si->at_query=strdup(backsql_def_at_query);
+ }
+ if (si->insentry_query==NULL)
+ {
+ Debug(LDAP_DEBUG_TRACE,"backsql_db_open(): entry insertion SQL statement not specified (use insentry_query directive in slapd.conf)\n",0,0,0);
+ Debug(LDAP_DEBUG_TRACE,"backsql_db_open(): setting '%s' by default\n",backsql_def_insentry_query,0,0);
+ si->insentry_query=strdup(backsql_def_insentry_query);
+ }
+ if (si->delentry_query==NULL)
+ {
+ Debug(LDAP_DEBUG_TRACE,"backsql_db_open(): entry deletion SQL statement not specified (use delentry_query directive in slapd.conf)\n",0,0,0);
+ Debug(LDAP_DEBUG_TRACE,"backsql_db_open(): setting '%s' by default\n",backsql_def_delentry_query,0,0);
+ si->delentry_query=strdup(backsql_def_delentry_query);
+ }
+ tmp.c_connid=-1;
+ dbh=backsql_get_db_conn(bd,&tmp);
+ if (!dbh)
+ {
+ Debug(LDAP_DEBUG_TRACE,"backsql_db_open(): connection failed, exiting\n",0,0,0);
+ return 1;
+ }
+ backsql_free_db_conn(bd,&tmp);
+ Debug(LDAP_DEBUG_TRACE,"<==backsql_db_open(): test succeeded, schema map loaded\n",0,0,0);
+ return 0;
+}
+
+int backsql_db_close(BackendDB *bd)
+{
+ Debug(LDAP_DEBUG_TRACE,"==>backsql_db_close()\n",0,0,0);
+ Debug(LDAP_DEBUG_TRACE,"<==backsql_db_close()\n",0,0,0);
+ return 0;
+}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright 1999, Dmitry Kovalev (zmit@mail.ru), All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted only
+ * as authorized by the OpenLDAP Public License. A copy of this
+ * license is available at http://www.OpenLDAP.org/license.html or
+ * in file LICENSE in the top-level directory of the distribution.
+ */
+
+#include "portable.h"
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <string.h>
+#include "slap.h"
+#include "back-sql.h"
+#include "sql-wrap.h"
+#include "schema-map.h"
+#include "entry-id.h"
+#include "util.h"
+
+int backsql_modify(BackendDB *be,Connection *conn,Operation *op,
+ char *dn,char *ndn,LDAPModList *modlist)
+{
+ backsql_info *bi=(backsql_info*)be->be_private;
+ SQLHDBC dbh;
+ SQLHSTMT sth;
+ RETCODE rc;
+ backsql_oc_map_rec *oc=NULL;
+ backsql_entryID e_id,*res;
+ LDAPModList *c_mod;
+ backsql_at_map_rec *at=NULL;
+ struct berval *at_val;
+ int i;
+
+ dn=dn_validate(dn);
+ Debug(LDAP_DEBUG_TRACE,"==>backsql_modify(): changing entry '%s'\n",dn,0,0);
+ dbh=backsql_get_db_conn(be,conn);
+ if (!dbh)
+ {
+ Debug(LDAP_DEBUG_TRACE,"backsql_modify(): could not get connection handle - exiting\n",0,0,0);
+ send_ldap_result(conn,op,LDAP_OTHER,"","SQL-backend error",NULL,NULL);
+ return 1;
+ }
+ res=backsql_dn2id(&e_id,dbh,dn);
+ if (res==NULL)
+ {
+ Debug(LDAP_DEBUG_TRACE,"backsql_modify(): could not lookup entry id\n",0,0,0);
+ send_ldap_result(conn,op,LDAP_NO_SUCH_OBJECT,"",NULL,NULL,NULL);
+ return 1;
+ }
+
+ oc=backsql_oc_with_id(bi,e_id.oc_id);
+ if (oc==NULL)
+ {
+ Debug(LDAP_DEBUG_TRACE,"backsql_modify(): cannot determine objectclass of entry -- aborting\n",0,0,0);
+ send_ldap_result(conn,op,LDAP_OTHER,"","SQL-backend error",NULL,NULL);
+ return 1;
+ }
+
+ SQLAllocStmt(dbh, &sth);
+
+ Debug(LDAP_DEBUG_TRACE,"backsql_modify(): traversing modifications list\n",0,0,0);
+ for(c_mod=modlist;c_mod!=NULL;c_mod=c_mod->ml_next)
+ {
+ Debug(LDAP_DEBUG_TRACE,"backsql_modify(): attribute '%s'\n",c_mod->ml_type,0,0);
+ at=backsql_at_with_name(oc,c_mod->ml_type);
+ if (at==NULL)
+ {
+ Debug(LDAP_DEBUG_TRACE,"backsql_add(): attribute provided is not registered in this objectclass ('%s')\n",c_mod->ml_type,0,0);
+ continue;
+ }
+ SQLBindParameter(sth,1,SQL_PARAM_INPUT,SQL_C_ULONG,SQL_INTEGER,0,0,&e_id.keyval,0,0);
+ switch(c_mod->ml_op)
+ {
+ case LDAP_MOD_REPLACE:
+ {
+ char *query;
+ int qlen;
+ SQLHSTMT asth;
+ BACKSQL_ROW_NTS row;
+
+ Debug(LDAP_DEBUG_TRACE,"backsql_modify(): replacing values for attribute '%s'\n",at->name,0,0);
+ if (at->add_proc==NULL)
+ {
+ Debug(LDAP_DEBUG_TRACE,"backsql_modify(): add procedure is not defined for this attribute ('%s') - unable to perform replacements\n",at->name,0,0);
+ break;
+ }
+del_all:
+ query=NULL;
+ qlen=0;
+ query=backsql_strcat(query,&qlen,"SELECT ",at->sel_expr," AS ",at->name,
+ " FROM ",at->from_tbls,
+ " WHERE ",oc->keytbl,".",oc->keycol,"=?",NULL);
+ if (at->join_where!=NULL && at->join_where[0]!='\0')
+ query=backsql_strcat(query,&qlen," AND ",at->join_where,NULL);
+
+ Debug(LDAP_DEBUG_TRACE,"backsql_modify() constructed query to get all existing values: %s\n",query,0,0);
+ if ((rc=backsql_Prepare(dbh,&asth,query,0)) != SQL_SUCCESS)
+ {
+ Debug(LDAP_DEBUG_TRACE,"backsql_get_attr_values(): error preparing query\n",0,0,0);
+ backsql_PrintErrors(bi->db_env,dbh,asth,rc);
+ free(query);
+ break;
+ }
+ free(query);
+
+ if (backsql_BindParamID(asth,1,&e_id.keyval) != SQL_SUCCESS)
+ {
+ Debug(LDAP_DEBUG_TRACE,"backsql_get_attr_values(): error binding key value parameter\n",0,0,0);
+ backsql_PrintErrors(bi->db_env,dbh,asth,rc);
+ SQLFreeStmt(asth,SQL_DROP);
+ break;
+ }
+
+ if ((rc=SQLExecute(asth)) != SQL_SUCCESS && rc!= SQL_SUCCESS_WITH_INFO)
+ {
+ Debug(LDAP_DEBUG_TRACE,"backsql_get_attr_values(): error executing attribute query\n",0,0,0);
+ backsql_PrintErrors(bi->db_env,dbh,asth,rc);
+ SQLFreeStmt(asth,SQL_DROP);
+ break;
+ }
+
+ backsql_BindRowAsStrings(asth,&row);
+ while ((rc=SQLFetch(asth)) == SQL_SUCCESS || rc==SQL_SUCCESS_WITH_INFO)
+ {
+ for (i=0;i<row.ncols;i++)
+ {
+ SQLBindParameter(sth,2,SQL_PARAM_INPUT,SQL_C_CHAR,SQL_CHAR,0,0,row.cols[i],strlen(row.cols[i]),0);
+ Debug(LDAP_DEBUG_TRACE,"backsql_modify(): executing '%s'\n",at->delete_proc,0,0);
+ rc=SQLExecDirect(sth,at->delete_proc,SQL_NTS);
+ if (rc!=SQL_SUCCESS)
+ {
+ Debug(LDAP_DEBUG_TRACE,"backsql_modify(): delete_proc execution failed\n",0,0,0);
+ backsql_PrintErrors(bi->db_env,dbh,sth,rc);
+ }
+ }
+ }
+ backsql_FreeRow(&row);
+ SQLFreeStmt(asth,SQL_DROP);
+ }
+ //PASSTHROUGH - to add new attributes -- do NOT add break
+ case LDAP_MOD_ADD:
+ if (at->add_proc==NULL)
+ {
+ Debug(LDAP_DEBUG_TRACE,"backsql_modify(): add procedure is not defined for this attribute ('%s')\n",at->name,0,0);
+ break;
+ }
+ if (c_mod->ml_bvalues==NULL)
+ {
+ Debug(LDAP_DEBUG_TRACE,"backsql_modify(): no values given to add for attribute '%s'\n",at->name,0,0);
+ break;
+ }
+ Debug(LDAP_DEBUG_TRACE,"backsql_modify(): adding new values for attribute '%s'\n",at->name,0,0);
+ for(i=0,at_val=c_mod->ml_bvalues[0];at_val!=NULL;i++,at_val=c_mod->ml_bvalues[i])
+ {
+ //check for syntax here - maybe need binary bind?
+ SQLBindParameter(sth,2,SQL_PARAM_INPUT,SQL_C_CHAR,SQL_CHAR,0,0,at_val->bv_val,at_val->bv_len,0);
+ Debug(LDAP_DEBUG_TRACE,"backsql_modify(): executing '%s'\n",at->add_proc,0,0);
+ rc=SQLExecDirect(sth,at->add_proc,SQL_NTS);
+ if (rc!=SQL_SUCCESS)
+ {
+ Debug(LDAP_DEBUG_TRACE,"backsql_modify(): add_proc execution failed\n",0,0,0);
+ backsql_PrintErrors(bi->db_env,dbh,sth,rc);
+ }
+ }
+ break;
+ case LDAP_MOD_DELETE:
+ if (at->delete_proc==NULL)
+ {
+ Debug(LDAP_DEBUG_TRACE,"backsql_modify(): delete procedure is not defined for this attribute ('%s')\n",at->name,0,0);
+ break;
+ }
+ if (c_mod->ml_bvalues==NULL)
+ {
+ Debug(LDAP_DEBUG_TRACE,"backsql_modify(): no values given to delete for attribute '%s' -- deleting all values\n",at->name,0,0);
+ goto del_all;
+ }
+ Debug(LDAP_DEBUG_TRACE,"backsql_modify(): deleting values for attribute '%s'\n",at->name,0,0);
+ for(i=0,at_val=c_mod->ml_bvalues[0];at_val!=NULL;i++,at_val=c_mod->ml_bvalues[i])
+ {
+ //check for syntax here - maybe need binary bind?
+ SQLBindParameter(sth,2,SQL_PARAM_INPUT,SQL_C_CHAR,SQL_CHAR,0,0,at_val->bv_val,at_val->bv_len,0);
+ Debug(LDAP_DEBUG_TRACE,"backsql_modify(): executing '%s'\n",at->delete_proc,0,0);
+ rc=SQLExecDirect(sth,at->delete_proc,SQL_NTS);
+ if (rc!=SQL_SUCCESS)
+ {
+ Debug(LDAP_DEBUG_TRACE,"backsql_modify(): delete_proc execution failed\n",0,0,0);
+ backsql_PrintErrors(bi->db_env,dbh,sth,rc);
+ }
+ }
+ break;
+ }
+ SQLFreeStmt(sth,SQL_RESET_PARAMS);
+ }
+
+ SQLFreeStmt(sth,SQL_DROP);
+ send_ldap_result(conn,op,LDAP_SUCCESS,"",NULL,NULL,NULL);
+ Debug(LDAP_DEBUG_TRACE,"<==backsql_modify()\n",0,0,0);
+ return 0;
+}
+
+int backsql_modrdn(BackendDB *be,Connection *conn,Operation *op,
+ char *dn,char *ndn,char *newrdn,int deleteoldrdn,char *newSuperior)
+{
+ Debug(LDAP_DEBUG_TRACE,"==>backsql_modrdn()\n",0,0,0);
+ return 0;
+}
+
+int backsql_add(BackendDB *be,Connection *conn,Operation *op,Entry *e)
+{
+ backsql_info *bi=(backsql_info*)be->be_private;
+ SQLHDBC dbh;
+ SQLHSTMT sth;
+ unsigned long new_keyval;
+ long i;
+ RETCODE rc;
+ backsql_oc_map_rec *oc=NULL;
+ backsql_at_map_rec *at_rec=NULL;
+ backsql_entryID parent_id,*res;
+ Attribute *at;
+ struct berval *at_val;
+ char *pdn;
+
+ Debug(LDAP_DEBUG_TRACE,"==>backsql_add(): adding entry '%s'\n",e->e_dn,0,0);
+ if (dn_validate(e->e_dn)==NULL)
+ {
+ Debug(LDAP_DEBUG_TRACE,"==>backsql_add(): invalid dn '%s' -- aborting\n",e->e_dn,0,0);
+ }
+ for(at=e->e_attrs;at!=NULL;at=at->a_next)
+ {
+ //Debug(LDAP_DEBUG_TRACE,"backsql_add(): scanning entry -- %s\n",at->a_type,0,0);
+ if (!strcasecmp(at->a_type,"objectclass"))
+ {
+ oc=backsql_oc_with_name(bi,at->a_vals[0]->bv_val);
+ break;
+ }
+ }
+
+ if (oc==NULL)
+ {
+ Debug(LDAP_DEBUG_TRACE,"backsql_add(): cannot determine objectclass of entry -- aborting\n",0,0,0);
+ send_ldap_result(conn,op,LDAP_OTHER,"","SQL-backend error",NULL,NULL);
+ return 1;
+ }
+ if (oc->create_proc == NULL)
+ {
+ Debug(LDAP_DEBUG_TRACE,"backsql_add(): create procedure is not defined for this objectclass - aborting\n",0,0,0);
+ send_ldap_result(conn,op,LDAP_OTHER,"","SQL-backend error",NULL,NULL);
+ return 1;
+ }
+
+ dbh=backsql_get_db_conn(be,conn);
+ if (!dbh)
+ {
+ Debug(LDAP_DEBUG_TRACE,"backsql_add(): could not get connection handle - exiting\n",0,0,0);
+ send_ldap_result(conn,op,LDAP_OTHER,"","SQL-backend error",NULL,NULL);
+ return 1;
+ }
+
+ SQLAllocStmt(dbh, &sth);
+ SQLBindParameter(sth,1,SQL_PARAM_OUTPUT,SQL_C_ULONG,SQL_INTEGER,0,0,&new_keyval,0,0);
+ //SQLBindParameter(sth,2,SQL_PARAM_OUTPUT,SQL_C_SLONG,SQL_INTEGER,0,0,&retcode,0,0);
+
+ Debug(LDAP_DEBUG_TRACE,"backsql_add(): executing '%s'\n",oc->create_proc,0,0);
+ rc=SQLExecDirect(sth,oc->create_proc,SQL_NTS);
+ if (rc != SQL_SUCCESS)
+ {
+ Debug(LDAP_DEBUG_TRACE,"backsql_add(): create_proc execution failed\n",0,0,0);
+ backsql_PrintErrors(bi->db_env,dbh,sth,rc);
+ SQLFreeStmt(sth,SQL_DROP);
+ send_ldap_result(conn,op,LDAP_OTHER,"","SQL-backend error",NULL,NULL);
+ return 1;
+ }
+ SQLFreeStmt(sth,SQL_RESET_PARAMS);
+ Debug(LDAP_DEBUG_TRACE,"backsql_add(): create_proc returned keyval=%d\n",new_keyval,0,0);
+
+ for(at=e->e_attrs;at!=NULL;at=at->a_next)
+ {
+ at_rec=backsql_at_with_name(oc,at->a_type);
+ if (at_rec==NULL)
+ {
+ Debug(LDAP_DEBUG_TRACE,"backsql_add(): attribute provided is not registered in this objectclass ('%s')\n",at->a_type,0,0);
+ continue;
+ }
+ if (at_rec->add_proc==NULL)
+ {
+ Debug(LDAP_DEBUG_TRACE,"backsql_add(): add procedure is not defined for this attribute ('%s')\n",at->a_type,0,0);
+ continue;
+ }
+ SQLBindParameter(sth,1,SQL_PARAM_INPUT,SQL_C_LONG,SQL_INTEGER,0,0,&new_keyval,0,0);
+ for(i=0,at_val=at->a_vals[0];at_val!=NULL;i++,at_val=at->a_vals[i])
+ {
+ //if (at->a_syntax==SYNTAX_BIN)
+ // SQLBindParameter(sth,2,SQL_PARAM_INPUT,SQL_C_CHAR,SQL_BINARY,0,0,at_val->bv_val,0,0);
+ //else
+ SQLBindParameter(sth,2,SQL_PARAM_INPUT,SQL_C_CHAR,SQL_CHAR,0,0,at_val->bv_val,at_val->bv_len,0);
+ Debug(LDAP_DEBUG_TRACE,"backsql_add(): executing '%s'\n",at_rec->add_proc,0,0);
+ rc=SQLExecDirect(sth,at_rec->add_proc,SQL_NTS);
+ if (rc!=SQL_SUCCESS)
+ {
+ Debug(LDAP_DEBUG_TRACE,"backsql_add(): add_proc execution failed\n",0,0,0);
+ backsql_PrintErrors(bi->db_env,dbh,sth,rc);
+ }
+ }
+ }
+ SQLFreeStmt(sth,SQL_RESET_PARAMS);
+ pdn=dn_parent(be,e->e_dn);
+ res=backsql_dn2id(&parent_id,dbh,pdn);
+ if (res==NULL)
+ {
+ Debug(LDAP_DEBUG_TRACE,"backsql_add(): could not lookup parent entry for new record ('%s')\n",
+ pdn,0,0);
+ send_ldap_result(conn,op,LDAP_OTHER,"","SQL-backend error",NULL,NULL);
+ return 1;
+ }
+ free(pdn);
+ backsql_BindParamStr(sth,1,e->e_dn,BACKSQL_MAX_DN_LEN);
+ SQLBindParameter(sth,2,SQL_PARAM_INPUT,SQL_C_LONG,SQL_INTEGER,0,0,&oc->id,0,0);
+ SQLBindParameter(sth,3,SQL_PARAM_INPUT,SQL_C_LONG,SQL_INTEGER,0,0,&parent_id.id,0,0);
+ SQLBindParameter(sth,4,SQL_PARAM_INPUT,SQL_C_LONG,SQL_INTEGER,0,0,&new_keyval,0,0);
+ rc=SQLExecDirect(sth,bi->insentry_query,SQL_NTS);
+ if (rc != SQL_SUCCESS)
+ {
+ Debug(LDAP_DEBUG_TRACE,"backsql_add(): could not insert ldap_entries record\n",0,0,0);
+ backsql_PrintErrors(bi->db_env,dbh,sth,rc);
+ //execute delete_proc to delete data added !!!
+ SQLFreeStmt(sth,SQL_DROP);
+ send_ldap_result(conn,op,LDAP_OTHER,"","SQL-backend error",NULL,NULL);
+ return 1;
+ }
+ SQLFreeStmt(sth,SQL_DROP);
+ send_ldap_result(conn,op,LDAP_SUCCESS,"",NULL,NULL,NULL);
+ return 0;
+}
+
+int backsql_delete(BackendDB *be,Connection *conn,Operation *op,
+ char *dn,char *ndn)
+{
+ backsql_info *bi=(backsql_info*)be->be_private;
+ SQLHDBC dbh;
+ SQLHSTMT sth;
+ RETCODE rc;
+ backsql_oc_map_rec *oc=NULL;
+ backsql_entryID e_id,*res;
+
+ dn=dn_validate(dn);
+ Debug(LDAP_DEBUG_TRACE,"==>backsql_delete(): deleting entry '%s'\n",dn,0,0);
+ dbh=backsql_get_db_conn(be,conn);
+ if (!dbh)
+ {
+ Debug(LDAP_DEBUG_TRACE,"backsql_delete(): could not get connection handle - exiting\n",0,0,0);
+ send_ldap_result(conn,op,LDAP_OTHER,"","SQL-backend error",NULL,NULL);
+ return 1;
+ }
+ res=backsql_dn2id(&e_id,dbh,dn);
+ if (res==NULL)
+ {
+ Debug(LDAP_DEBUG_TRACE,"backsql_delete(): could not lookup entry id\n",0,0,0);
+ send_ldap_result(conn,op,LDAP_NO_SUCH_OBJECT,"",NULL,NULL,NULL);
+ return 1;
+ }
+
+ oc=backsql_oc_with_id(bi,e_id.oc_id);
+ if (oc==NULL)
+ {
+ Debug(LDAP_DEBUG_TRACE,"backsql_delete(): cannot determine objectclass of entry -- aborting\n",0,0,0);
+ send_ldap_result(conn,op,LDAP_OTHER,"","SQL-backend error",NULL,NULL);
+ return 1;
+ }
+ if (oc->delete_proc == NULL)
+ {
+ Debug(LDAP_DEBUG_TRACE,"backsql_delete(): delete procedure is not defined for this objectclass - aborting\n",0,0,0);
+ send_ldap_result(conn,op,LDAP_OTHER,"","SQL-backend error",NULL,NULL);
+ return 1;
+ }
+
+ SQLAllocStmt(dbh, &sth);
+ SQLBindParameter(sth,1,SQL_PARAM_INPUT,SQL_C_ULONG,SQL_INTEGER,0,0,&e_id.keyval,0,0);
+ //SQLBindParameter(sth,2,SQL_PARAM_OUTPUT,SQL_C_SLONG,SQL_INTEGER,0,0,&retcode,0,0);
+
+ Debug(LDAP_DEBUG_TRACE,"backsql_delete(): executing '%s'\n",oc->delete_proc,0,0);
+ rc=SQLExecDirect(sth,oc->delete_proc,SQL_NTS);
+ if (rc != SQL_SUCCESS)
+ {
+ Debug(LDAP_DEBUG_TRACE,"backsql_delete(): delete_proc execution failed\n",0,0,0);
+ backsql_PrintErrors(bi->db_env,dbh,sth,rc);
+ SQLFreeStmt(sth,SQL_DROP);
+ send_ldap_result(conn,op,LDAP_OTHER,"","SQL-backend error",NULL,NULL);
+ return 1;
+ }
+ SQLFreeStmt(sth,SQL_RESET_PARAMS);
+
+ SQLBindParameter(sth,1,SQL_PARAM_INPUT,SQL_C_ULONG,SQL_INTEGER,0,0,&e_id.id,0,0);
+ rc=SQLExecDirect(sth,bi->delentry_query,SQL_NTS);
+ if (rc != SQL_SUCCESS)
+ {
+ Debug(LDAP_DEBUG_TRACE,"backsql_delete(): failed to delete record from ldap_entries\n",0,0,0);
+ backsql_PrintErrors(bi->db_env,dbh,sth,rc);
+ SQLFreeStmt(sth,SQL_DROP);
+ send_ldap_result(conn,op,LDAP_OTHER,"","SQL-backend error",NULL,NULL);
+ return 1;
+ }
+ SQLFreeStmt(sth,SQL_DROP);
+
+ send_ldap_result(conn,op,LDAP_SUCCESS,"",NULL,NULL,NULL);
+ Debug(LDAP_DEBUG_TRACE,"<==backsql_delete()\n",0,0,0);
+ return 0;
+}
--- /dev/null
+#ifndef __BACKSQL_SCHEMA_MAP_H__
+#define __BACKSQL_SCHEMA_MAP_H__
+
+typedef struct
+{
+ char *name;
+ char *keytbl;
+ char *keycol;
+ char *create_proc;//expected to return keyval of newly created entry
+ char *delete_proc;//supposed to expect keyval as parameter and delete all the attributes as well
+ unsigned long id;
+ Avlnode *attrs;
+}backsql_oc_map_rec;
+
+typedef struct
+{
+ char *name;//literal name of corresponding LDAP attribute type
+ char *from_tbls;
+ char *join_where;
+ char *sel_expr;
+ char *add_proc; //supposed to expect 2 binded values: entry keyval and attr. value to add, like "add_name(?,?)"
+ char *modify_proc; //supposed to expect two binded values: entry keyval and old and new values of attr
+ char *delete_proc; //supposed to expect 2 binded values: entry keyval and attr. value to delete
+ char *query; //for optimization purposes attribute load query is preconstructed from parts on schemamap load time
+}backsql_at_map_rec;
+
+int backsql_load_schema_map(backsql_info *si,SQLHDBC dbh);
+backsql_oc_map_rec* backsql_oc_with_name(backsql_info *si,char* objclass);
+backsql_oc_map_rec* backsql_oc_with_id(backsql_info *si,unsigned long id);
+backsql_at_map_rec* backsql_at_with_name(backsql_oc_map_rec* objclass,char* attr);
+int backsql_destroy_schema_map(backsql_info *si);
+
+#endif
\ No newline at end of file
--- /dev/null
+#ifndef __BACKSQL_SQL_TYPES_H__
+#define __BACKSQL_SQL_TYPES_H__
+
+/*
+ * Copyright 1999, Dmitry Kovalev (zmit@mail.ru), All rights reserved.
+ *
+ * Redistribution and use in source and binary forms are permitted only
+ * as authorized by the OpenLDAP Public License. A copy of this
+ * license is available at http://www.OpenLDAP.org/license.html or
+ * in file LICENSE in the top-level directory of the distribution.
+ */
+
+#include <sql.h>
+#include <sqlext.h>
+
+typedef struct
+{
+ SWORD ncols;
+ char** col_names;
+ UDWORD *col_prec;
+ char** cols;
+ SQLINTEGER* is_null;
+}BACKSQL_ROW_NTS;
+
+#endif
\ No newline at end of file