From: Dmitry Kovalev Date: Thu, 16 Mar 2000 19:08:22 +0000 (+0000) Subject: added back-sql files X-Git-Tag: LDBM_PRE_GIANT_RWLOCK~3143 X-Git-Url: https://git.sur5r.net/?a=commitdiff_plain;h=d63b3dc2fd267235dc837f4e16514df2f866f95e;p=openldap added back-sql files --- diff --git a/servers/slapd/back-sql/Makefile.in b/servers/slapd/back-sql/Makefile.in new file mode 100644 index 0000000000..562edbc89d --- /dev/null +++ b/servers/slapd/back-sql/Makefile.in @@ -0,0 +1,25 @@ +# $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 $@ + diff --git a/servers/slapd/back-sql/back-sql.h b/servers/slapd/back-sql/back-sql.h new file mode 100644 index 0000000000..ef18a01dab --- /dev/null +++ b/servers/slapd/back-sql/back-sql.h @@ -0,0 +1,39 @@ +#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 diff --git a/servers/slapd/back-sql/backsql.dsp b/servers/slapd/back-sql/backsql.dsp new file mode 100644 index 0000000000..8a5189e935 --- /dev/null +++ b/servers/slapd/back-sql/backsql.dsp @@ -0,0 +1,205 @@ +# 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 diff --git a/servers/slapd/back-sql/bind.c b/servers/slapd/back-sql/bind.c new file mode 100644 index 0000000000..1919dea11e --- /dev/null +++ b/servers/slapd/back-sql/bind.c @@ -0,0 +1,35 @@ +/* + * 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 +#include +#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; +} diff --git a/servers/slapd/back-sql/config.c b/servers/slapd/back-sql/config.c new file mode 100644 index 0000000000..626021567c --- /dev/null +++ b/servers/slapd/back-sql/config.c @@ -0,0 +1,168 @@ +/* + * 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 +#include +#include +#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; +} diff --git a/servers/slapd/back-sql/docs/bugs b/servers/slapd/back-sql/docs/bugs new file mode 100644 index 0000000000..6c503e6445 --- /dev/null +++ b/servers/slapd/back-sql/docs/bugs @@ -0,0 +1,36 @@ +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 diff --git a/servers/slapd/back-sql/docs/concept b/servers/slapd/back-sql/docs/concept new file mode 100644 index 0000000000..357c0f88e5 --- /dev/null +++ b/servers/slapd/back-sql/docs/concept @@ -0,0 +1,164 @@ +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= +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= +parent= +keyval= + +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... + diff --git a/servers/slapd/back-sql/docs/install b/servers/slapd/back-sql/docs/install new file mode 100644 index 0000000000..afa3f2dbed --- /dev/null +++ b/servers/slapd/back-sql/docs/install @@ -0,0 +1,38 @@ +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 --enable-sql [--with-iodbc-includes=] [--with-iodbc-libs=]", +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 diff --git a/servers/slapd/back-sql/docs/platforms b/servers/slapd/back-sql/docs/platforms new file mode 100644 index 0000000000..3c90ae5af1 --- /dev/null +++ b/servers/slapd/back-sql/docs/platforms @@ -0,0 +1,18 @@ +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 + diff --git a/servers/slapd/back-sql/entry-id.c b/servers/slapd/back-sql/entry-id.c new file mode 100644 index 0000000000..ae9e17eb26 --- /dev/null +++ b/servers/slapd/back-sql/entry-id.c @@ -0,0 +1,178 @@ +/* + * 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 +#include +#include +#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;i0) + { + 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; +} diff --git a/servers/slapd/back-sql/entry-id.h b/servers/slapd/back-sql/entry-id.h new file mode 100644 index 0000000000..0411eaa63f --- /dev/null +++ b/servers/slapd/back-sql/entry-id.h @@ -0,0 +1,16 @@ +#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 diff --git a/servers/slapd/back-sql/external.h b/servers/slapd/back-sql/external.h new file mode 100644 index 0000000000..9a49533b65 --- /dev/null +++ b/servers/slapd/back-sql/external.h @@ -0,0 +1,56 @@ +/* $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 */ + diff --git a/servers/slapd/back-sql/init.c b/servers/slapd/back-sql/init.c new file mode 100644 index 0000000000..d14af1190a --- /dev/null +++ b/servers/slapd/back-sql/init.c @@ -0,0 +1,191 @@ +/* + * 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 +#include +#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 diff --git a/servers/slapd/back-sql/modify.c b/servers/slapd/back-sql/modify.c new file mode 100644 index 0000000000..e05a9bdc81 --- /dev/null +++ b/servers/slapd/back-sql/modify.c @@ -0,0 +1,409 @@ +/* + * 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 +#include +#include +#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;idelete_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; +} diff --git a/servers/slapd/back-sql/schema-map.h b/servers/slapd/back-sql/schema-map.h new file mode 100644 index 0000000000..2d79258732 --- /dev/null +++ b/servers/slapd/back-sql/schema-map.h @@ -0,0 +1,33 @@ +#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 diff --git a/servers/slapd/back-sql/sql-types.h b/servers/slapd/back-sql/sql-types.h new file mode 100644 index 0000000000..45af97e827 --- /dev/null +++ b/servers/slapd/back-sql/sql-types.h @@ -0,0 +1,25 @@ +#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 +#include + +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