mirror of
https://git.openldap.org/openldap/openldap.git
synced 2024-12-21 03:10:25 +08:00
165 lines
6.6 KiB
Plaintext
165 lines
6.6 KiB
Plaintext
|
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...
|
||
|
|