Например, у вас (некоего поставщика услуг Интернет) в реляционной СУБД имеется информация об учётных записях пользователей, и вы хотите использовать современные решения, ожидающие, что такая информация будет доступна через LDAP (для аутентификации пользователей, поиска адресов электронной почты и т.п.). Либо вы хотите синхронизировать или распространять информацию между различными сайтами/приложениями, использующими реляционные СУБД и/или LDAP. Или что-то ещё...
Этот механизм НЕ проектировался как механизм манипуляции данными общего назначения, использующий реляционную СУБД вместо BerkeleyDB (которую использует стандартный механизм BDB), хотя с некоторыми ограничениями он может использоваться в таком качестве. Дополнительную информацию по этому поводу можно найти по адресу http://www.openldap.org/faq/index.cgi?file=378 (OpenLDAP FAQ-O-Matic/General LDAP FAQ/Directories vs. conventional databases).
Идея (подробно раскрываемая ниже) заключается в использовании некоторых метаданных для трансляции запросов LDAP в SQL-запросы, при этом реляционная схема данных остаётся без изменений и старые приложения могут продолжать использовать её без каких-либо модификаций. Это позволяет SQL-приложениям и LDAP-приложениям взаимодействовать без репликации и обмениваться данными по мере необходимости.
Механизм SQL спроектирован так, чтобы его можно было настроить на взаимодействие с практически любой реляционной схемой данных без необходимости внесения изменений в исходный код (с помощью упомянутых выше метаданных). Кроме того, он использует ODBC для соединения с реляционными СУБД, имеет богатые возможности настройки под SQL-диалекты различных СУБД, то есть он может быть использован для интеграции и обмена данными с различными СУБД, операционными системами, хостами и т.п., другими словами, для работы в высоко гетерогенном окружении.
Этот механизм манипуляции данными является экспериментальным.
dbhost <имя хоста>
dbpasswd <пароль>
dbuser <имя пользователя>
fetch_attrs <список_атрибутов>
fetch_all_attrs { NO | yes }
Почти всё, о чём говорится далее, проиллюстрировано на примерах в директории servers/slapd/back-sql/rdbms_depend/ дерева исходных кодов OpenLDAP. В этой же директории находятся скрипты генерации баз данных примеров для СУБД Oracle, MS SQL Server, mySQL и других (в том числе PostgreSQL и IBM db2).
Первое, что необходимо продумать, - это набор объектных классов LDAP, с помощью которых может быть представлена информация из вашей реляционной СУБД.
Самый простой путь - это создать объектный класс для каждой сущности, которая была в вашей ER-диаграмме, когда вы проектировали вашу реляционную схему. Любая реляционная схема, независимо от того, насколько она нормализована, проектировалась уже после того, как была разработана некоторая модель домена вашего приложения (например, учётные записи, сервисы и т.п. в модели поставщика услуг Интернет), и используется с учётом сущностей этой модели, а не только таблиц нормализованной схемы. Это означает, что для каждого атрибута каждого экземпляра такой сущности существует эффективный SQL-запрос для получения его значения.
Кроме того, вы, возможно, хотите, чтобы ваши объектные классы соответствовали каким-то объектным классам из стандартных схем данных, таким, например, как InetOrgPerson и ему подобные.
Так или иначе, уже на этапе обдумывания объектных классов необходимо определить способ трансляции запросов операций LDAP в SQL-запрос (или серию SQL-запросов). Давайте займёмся операцией поиска SEARCH.
Пример: Предположим, что мы храним информацию о людях, работающих в нашей организации, в двух таблицах:
PERSONS PHONES ---------- ------------- id integer id integer first_name varchar pers_id integer ссылается на persons(id) last_name varchar phone middle_name varchar ...
(в таблице PHONES хранятся номера телефонов, ассоциируемые с людьми). У человека может быть несколько номеров телефонов, тогда в таблице PHONES будет содержаться несколько записей с соответствующим pers_id, или вообще не быть номеров телефонов (и записей в таблице PHONES с таким pers_id). Объектный класс LDAP для представления такой информации может выглядеть так:
person ------- MUST cn MAY telephoneNumber $ firstName $ lastName ...
Для получения всех значений атрибутов cn человека с заданным ID, мы строим запрос:
SELECT CONCAT(persons.first_name,' ',persons.last_name) AS cn FROM persons WHERE persons.id=?
Для получения значений атрибута telephoneNumber мы можем использовать:
SELECT phones.phone AS telephoneNumber FROM persons,phones WHERE persons.id=phones.pers_id AND persons.id=?
Если мы хотим обслуживать LDAP-запросы с фильтрами типа (telephoneNumber=123*), мы можем построить что-то вроде:
SELECT ... FROM persons,phones WHERE persons.id=phones.pers_id AND persons.id=? AND phones.phone like '%1%2%3%'
(обратите внимание, что в шаблоне для поиска совпадения с telephoneNumber присутствует несколько символов подстановки для учёта возможных вспомогательных символов, таких как пробелы, тире и т.п.; это зависит от проекта, поскольку синтаксис атрибута telephoneNumber определяется отдельно). Итак, если у нас есть сведения о том, какие таблицы содержат значения для каждого атрибута, как объединять эти таблицы и как представить эти значения, мы можем попытаться автоматически генерировать подобные выражения и транслировать поисковые фильтры в SQL-условия WHERE.
Для хранения таких сведений мы добавляем три новые таблицы в нашу схему базы данных и заносим в них данные (смотрите примеры):
ldap_oc_mappings (для упрощения примера некоторые столбцы не указаны) --------------- id=1 name="person" keytbl="persons" keycol="id"
В этой таблице определяется отображение между объектным классом (имя которого находится в столбце "name") и таблицей, содержащей первичный ключ для соответствующих записей. Так, в нашем примере информация об объекте, описывающем человека, которую мы пытаемся представить в виде объектного класса "person", располагается в двух таблицах (persons и phones), и идентифицируется содержимым столбца persons.id (которое мы называем первичным ключом для этого объекта). В столбцах таблицы ldap_oc_mappings keytbl и keycol будут храниться, соответственно, "persons" (имя таблицы) и "id" (имя столбца).
ldap_attr_mappings (для упрощения примера некоторые столбцы не указаны) ----------- id=1 oc_map_id=1 name="cn" sel_expr="CONCAT(persons.first_name,' ',persons.last_name)" from_tbls="persons" join_where=NULL ************ id=<n> oc_map_id=1 name="telephoneNumber" sel_expr="phones.phone" from_tbls="persons,phones" join_where="phones.pers_id=persons.id"
В этой таблице определяется отображение между атрибутами LDAP и SQL-запросами для загрузки значений этих атрибутов. Обратите внимание, что в отличие от схемы данных LDAP здесь отсутствуют типы атрибутов, то есть значения атрибута "cn" для объектного класса "person" могут находиться в таблицах, отличающихся от таблиц, где хранятся значения атрибута "cn" для каких-либо других объектных классов. Таким образом, отображения атрибутов зависят от отображений объектных классов (в схеме данных LDAP типы атрибутов не зависят от объектных классов). Поэтому у нас имеется столбец oc_map_id со ссылкой на идентификатор из таблицы ldap_oc_mappings.
Мы делим SQL-запрос, загружающий данные для заданного атрибута, на три фрагмента. Первый указывается в столбце sel_expr. Это выражение, которое помещается между ключевыми словами SELECT и FROM SQL-запроса, и определяет ЧТО следует загружать. Во втором фрагменте (from_tbls) указывается список таблиц - текст между ключевыми словами FROM и WHERE. Для удобства в нём могут содержаться псевдонимы (смотрите примеры). Последний фрагмент (join_where) представляет собой часть условия WHERE SQL-запроса, в котором (при его наличии) описывается условие соединения таблицы, содержащей данные для значения атрибута, с таблицей, содержащей первичный ключ (соединение по внешним ключам или им подобным). Если данные для значения атрибута хранятся в той же таблице, что и первичные ключи, в этом столбце остаётся значение NULL (как в приведённом выше случае с атрибутом "cn").
Поскольку данная информация имеется у нас в виде трёх фрагментов, мы можем не только конструировать запросы загрузки значений атрибутов по идентификатору записи (для этого мы могли бы хранить SQL-запрос целиком), но также конструировать запросы, которые получают идентификаторы объектов, соответствующих заданному поисковому фильтру (или, как минимум, части такого фильтра). Примеры смотрите ниже.
ldap_entries ------------ id=1 dn=<выбранный вами dn> oc_map_id=... parent=<идентификатор родительской записи> keyval=<значение первичного ключа>
В этой таблице определяется отображение между DN записей в вашем дереве LDAP и значениями первичных ключей для соответствующих реляционных данных. У данных в этой таблице рекурсивная структура (столбец parent ссылается на столбец id той же таблицы), что позволяет представить произвольную древовидную структуру с использованием плоских реляционных данных. Имея идентификатор отображения объектного класса (oc_map_id), мы можем определить таблицу и столбец, в которых хранится первичный ключ, а значение этого ключа хранится в столбце keyval. Таким образом определяется точное соответствие между записью LDAP и указанным DN.
Имейте ввиду, что такой дизайн таблицы (смотрите точный SQL-запрос создания таблицы) подразумевает одно важное ограничение - значением первичного ключа должно быть целое число. Как показывает практика, обычно это не вызывает проблем. Если же кому-то требуется поддержка других типов первичных ключей, можно написать свой патч и отправить его в OpenLDAP ITS для включения в исходный код.
Кроме того, некоторые пользователи жалуются, что на самом деле им не нужны очень структурированные деревья и они не хотят обновлять ещё одну таблицу всякий раз, когда они добавляют или удаляют данные в реляционную схему. Такие люди могут использовать вместо реальной таблицы ldap_entries представление (view), наподобие такого (предложено Robin Elfrink):
CREATE VIEW ldap_entries (id, dn, oc_map_id, parent, keyval) AS SELECT 0, UPPER('o=MyCompany,c=NL'), 3, 0, 'baseObject' FROM unixusers WHERE userid='root' UNION SELECT (1000000000+userid), UPPER(CONCAT(CONCAT('cn=',gecos),',o=MyCompany,c=NL')), 1, 0, userid FROM unixusers UNION SELECT (2000000000+groupnummer), UPPER(CONCAT(CONCAT('cn=',groupnaam),',o=MyCompany,c=NL')), 2, 0, groupnummer FROM groups;
Если ваша реляционная СУБД не поддерживает объединения (UNION) в представлениях, то только один объектный класс может быть отображён в ldap_entries, и нельзя будет создать baseObject; возможные обходные пути для этой ситуации можно найти в описании директивы baseObject.
Пример: для нашего поискового запроса с фильтром (telephoneNumber=123*) мы получим следующий сгенерированный запрос (загружающий идентификаторы записей-кандидатов):
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 '%1%2%3%')
(для поиска с диапазоном ONELEVEL), либо "... AND dn=?" (для поиска с диапазоном BASE), либо "... AND dn LIKE '%?'" (для поиска с диапазоном SUBTREE).
Затем для каждой записи-кандидата мы загружаем запрашиваемые атрибуты с использованием отдельного запроса для каждого атрибута, наподобие такого:
SELECT phones.phone AS telephoneNumber FROM persons,phones WHERE persons.id=? AND phones.pers_id=persons.id
Затем мы используем вызов test_filter() из API механизма frontend для тестирования записи на совпадение с полным поисковым фильтром LDAP (поскольку мы не можем эффективно проверить, удовлетворяют ли наши значения синтаксису (SYNTAX) соответствующего атрибута из схемы данных LDAP, мы транслируем этот фильтр в наиболее лояльное SQL-условие для фильтрации записей-кандидатов), и отправляем сформированные данные пользователю.
Операции ADD, DELETE, MODIFY и MODRDN также выполняются с использованием метаданных для каждого отдельного атрибута (столбец add_proc и другие). В этих полях можно указать выражение SQL или вызов хранимой процедуры, с помощью которых можно добавить или удалить заданные значения заданного атрибута в записях с указанным keyval (смотрите примеры, главным образом по PostgreSQL, ORACLE и MSSQL, поскольку на момент написания этой man-страницы в MySQL не было поддержки хранимых процедур).
Мы просто добавили дополнительные столбцы в таблицы ldap_oc_mappings и ldap_attr_mappings, в которые помещаются выражения для выполнения (такие, как create_proc, add_proc, del_proc и другие), а также флаги, регулирующие порядок параметров, передаваемых в эти выражения. Чтобы разобраться в том, что такое передаваемые параметры, и получить больше информации по этому вопросу, обратитесь к примерам - для тех, кто знаком с обсуждаемыми выше концепциями, они говорят сами за себя.
В эту таблицу помещается любое количество имён объектных классов, которые участвуют в формировании соответствующих записей, в дополнение к тому классу, который упомянут в таблице отображений. Механизм SQL автоматически добавляет отображения атрибута "objectclass" для каждого значения объектного класса из данной таблицы. Так например, имея отображение для объектного класса "inetOrgPerson" (в основной таблице отображений), вы можете использовать для запросов той же записи фильтры, в которых фигурирует объектный класс "person" (из дополнительной таблицы).
Раньше отсылки были реализованы отдельно, методом добавления дополнительной таблицы, позволявшей помещать атрибут "ref" в любую запись с одновременным добавлением сведений о дополнительном объектном классе "referral" в таблицу ldap_entry_objclasses. В текущей реализации отсылки рассматриваются по аналогии с любой другой определённой пользователем схемой данных, в связи с тем, что класс "referral" является структурным объектным классом. Предлагаемая практика - определять запись "referral" в таблице ldap_oc_mappings. В связанных с нею записях будут определяться атрибуты именования, такие как "ou" или "cn", а также атрибут "ref", содержащий URL. В случае, если требуется иметь несколько отсылок в одной записи, можно создать отдельную таблицу для URL, в которой URL будут отображаться на соответствующие записи. Использование атрибутов именования обычно требует занесения сведений об объектном классе "extensibleObject" в таблицу ldap_entry_objclasses.
Механизм back-sql поддерживает операционный атрибут hasSubordintes в результатах операций поиска Search и сравнения Compare; также он частично поддерживается в фильтрах. Ввиду конструктивных ограничений фильтр "вынос мозга" (!(hasSubordinates=TRUE)) вместо того, чтобы вернуть все листовые записи, вообще не даст результатов, поскольку будет раскрыт в SQL-условие ... AND NOT (1=1). Если вам требуется найти все листовые записи, используйте фильтр (hasSubordinates=FALSE).
Значение с синтаксисом directoryString в форме "__First___Last_" (на месте символов нижнего подчёркивания подразумеваются пробелы, ASCII-символ 0x20) соответствует своему "правильному" аналогу "First_Last". В настоящий момент механизм back-sql не поддерживает такое соответствие, если не приведённые к "правильному" виду данные были записаны средствами СУБД. Если же происходит попытка записи не приведённых к "правильному" виду данных через back-sql, то в базу фактически будут помещены "правильные" данные.
Закладки на сайте Проследить за страницей |
Created 1996-2024 by Maxim Chirkov Добавить, Поддержать, Вебмастеру |