Pull to refresh

Использование XPath для указания ссылок на объекты

Reading time6 min
Views2.3K
Данный топик рассказывает о возможности использования XPath для выбора объектов из базы данных в случаях, когда использование SQL нежелательно.

Постановка задачи


Во многих системах для тех или иных целей часто требуется выбирать объекты из базы данных в качестве значений тех или иных полей. Например, выбрать поставщика товара из списка. Это является частью пользовательского интерфейса системы.

Однако кроме пользователей у системы есть программисты, инженеры-настройщики, дизайнеры и пр., которым также иногда нужно указывать системе, какие объекты нужно выбрать. Например, возможно, что после выбора города поставщика нужно ограничивать список поставщиков. Для этого обычно программист пишет соответствующий SQL, например, «SELECT id, name FROM agents WHERE city=?». То есть SQL используется для того, чтобы указать системе, какие объекты нужно выбрать из базы данных.

Чаще всего такой выбор является частью исходного кода системы. Скрытый в PHP или в Java-коде (или, лучше, в SQL-bundles или в хранимых процедурах) подобный код смотрится привычно. Но иногда фильтр становится не частью исходного кода, а частью настройки системы, которую производит системный инженер или верстальщик XSLT/HTML-кода.

Пример 1: Система управления предприятием. Есть список объектов «Сотрудник» и список объектов «Офис». Нужно к объекту «Сотрудник» добавить поле выбора офиса, при этом показав список офисов в той же стране, где зарегистрирован сотрудник. Обычным способом является запись соответствующего SQL-фильтра в свойства атрибута «Офис сотрудника» класса «Сотрудник». Этот SQL принимает, например, на вход ID сотрудника и вычисляет соответствующий список офисов.

Пример 2: Система управления сайтом, основанная на XSLT. Из базы данных выбирается объект (например, статья), строится XML, после чего с помощью XSLT преобразования получается HTML и он отдаётся клиенту. Однако часто кроме самого текста статьи к XML нужно прикрепить дополнительные элементы — например, список остальных статей в разделе для быстрой навигации. Или даже список разделов текущего сайта, если дизайн позволяет менять его через систему управления. Для выбора соответствующих объектов можно было бы использовать тот же SQL, принимая в качестве аргументов ID текущего объекта (статьи).

Использование SQL в подобных примерах обладает следующими недостатками:
1) Системный инженер, не знакомый с Java/PHP/.NET должен всё-таки знать SQL
2) Инженер должен знать внутреннее устройство таблиц системы
3) Использование SQL для подобных настроек приложения «замораживает» структуру таблиц, делая её частью public API
4) Данный SQL имеет право на чтение как минимум всех данных из соответствующих таблиц. Ограничить права приложением без использования row-level security нереально.

Поэтому в Arp.Site для выбора объектов из текстов шаблонов используется другой способ указания. Так как для шаблонов в основном используется XSLT, то логичнее всего было использовать ту технологию, которая ближе всего к XML. А именно XPath.

XPath позволяет осуществить выборку объектов из дерева (точнее, из ациклического ориентированного графа) с учётом ограничений-предикатов. Запись выражений на XPath более компактная и более понятная, чем в SQL.

Пример 1: /офисы/офис[@страна=$сотрудник/@страна]
Пример 2: //folder[@active='true']/article — для выбора статей из того же раздела. //site[@active='true']/folder — для выбора разделов текущего сайта.

Техническая реализация


Для реализации возможно использования двух подходов: интерпретация и компиляция.

Интерпретация — это построение реального (для очень маленьких сайтов) или виртуального (для больших систем) DOM-дерева объектов и «скармливание» некоторому XPath-движку самого выражения XPath и корня (или текущего объекта) DOM-дерева. Результатом выполнения является указатель на другой объект (или NodeList) DOM-дерева, который и преобразуется в набор ID-ников системы.

Преимущества:
— самый быстрый способ реализации «с нуля»
— самый понятный, если нет оптимизаций, направленных на ускорение

Недостатки
— самый тормозной способ, если каждая операция перехода по дереву будет требовать обращения к базе данных
— очень сложно делать оптимизации выполнения.

Что такое оптимизации и зачем они нужны? Пример:
— частью XPath выражения является, например, //article[@name='123']. Любой XPath-процессор будет проходить по всему дереву объектов, и для каждого сравнивать его тип (article), а также наличие свойства name и его равенство значению '123'. Наличие оптимизаций позволяет заменить все подобные операции, на, например, одну операцию выборки всех статей, имена которых равны '123'.
— XPath выражение //*[@active='true'] должно выбрать список «текущих» объектов — сайт, раздел, подраздел, статья (для построения navigation path). Однако перебор всех объектов по дереву нежелателен, поэтому данные выражения также нужно оптимизировать.

Для интерпретации в Arp.Site используется библиотека Apache Commons JXPath. Данная библиотека принимает на вход любой объект (бин, DOM node, JDOM node, etc), либо любой произвольный, если вы объясните библиотеке, как получить у этого объекта свойства и дочерние объекты. Также данная библиотека позволяет подменить некоторые стандартные обработчики своими. Например, заменить обработчик Step для Axis=DESCENDANT_OR_SELF (операция "//" в XPath) своим оптимизированным.

На вход библиотеки подаётся объект, связанный с объектом информационного дерева. При необходимости из базы данных подгружается информация о его потомках.

Компиляция в SQL

Вторым подходом стало предварительная компиляция XPath в SQL, после чего он кешировался на будущее и исполнялся.

Преимущества
— значительное ускорение за счёт единственности SQL запроса к базе данных

Недостатки
— значительно сложнее писать код, нужно отслеживать все использованные alias'ы таблиц, возвращаемые значения, корректно обрабатывать различные предикаты.
— Работа над построением SQL сводится к работе над строками
— требуется значительное количество оптимизаций для упрощения и ускорения SQL

Пример оптимизации. Пусть у нас есть XPATH //site[@active='true']/folder. Без оптимизации SQL мог бы выглядеть следующим образом:
SELECT f.id FROM objects f, objects s WHERE s.id=f.parent AND s.class='site' AND f.class='folder' AND s.id IN (1,4,73,423) (где 1,4,73,423 — список «текущих» объектов).
Однако, система может использовать следующие hints:
— site в дереве объектов не может быть потомком site (т.е. он только один в списке текущих объектов)
— site точно есть в базе данных
— его id равен «1»
то SQL, очевидно, можно переписать как
SELECT f.id FROM objects f WHERE f.parent=1 f.class='folder'

Подобных оптимизаций можно придумать очень много, хотя большинство из них system-specific.

Примеры компиляций в SQL:
  • //site[@ active='true']/*[@active='true']/*[@state='published']
    SELECT t7.id FROM struct_cells t7, registry_objects t8 WHERE t7.obj=t8.id AND t7.erased=0 AND t8.erased=0 AND t8.state=2 AND t7.state=3 AND t7.parent=360965
  • //*[@current='true']/*[@state='published'] | //*[@current='true']/*[@state='published']/file[@state='published' or @state='archived']
    (SELECT t4.id FROM struct_cells t4, registry_objects t5 WHERE t4.obj=t5.id AND t4.erased=0 AND t5.erased=0 AND t5.state=2 AND t4.state=3 AND t4.parent=1) UNION (SELECT t13.id FROM struct_cells t13, registry_objects t14, struct_cells t10, registry_objects t11 WHERE t13.obj=t14.id AND t13.erased=0 AND t14.erased=0 AND t14.class=10 AND (t14.state=2 AND t13.state IN (3,4)) AND t10.obj=t11.id AND t10.erased=0 AND t11.erased=0 AND t11.state=2 AND t10.state=3 AND t10.parent=1 AND t13.parent=t10.id))


Компиляция в JPAQL

Третий подход, разработанный после появления JPA 2.0, включивший в себя CriteriaQuery API, состоит в построении запроса с использованием соответствующего API без прямых операций со строками запроса.

Преимущества
— значительное упрощение кода построения запросов
— избавление от ошибок-опечаток
— код будет работать на любой базе данных, поддерживаемой JPA

Недостатки
— требуется значительное количество оптимизаций для упрощения и ускорения EJBQL
— незначительное замедление из-за возможной неоптимизированности кода транслятора EJBQL -> SQL
— значительное замедление из-за необходимости эмулирования функций вроде UNION, отсутствующих в EJBQL.
— отсутствие поддержки некоторых операций, например, limit/row_number в CriteriaQuery делает невозможным поддержку position-предикатов, например, //site/folder[2].

Примеры компиляций в JPAQL:
  • //site[@active='true']/*[@active='true']/*[@state='published']
    SELECT t0.id
    FROM ru.arptek.arpsite.content.Cell as t1, ru.arptek.arpsite.content.Cell as t0
    INNER JOIN t0.webObject as t2
    WHERE ( t1.parent.id=360930 ) and ( t1.erased=0 ) and ( t1.id in (360965, 360930, 417026, 124316, 63316, 1) ) and ( t1.id=t0.parent.id ) and ( t0.erased=0 ) and ( t2.erased=0 ) and ( ( t2.stateId=2 ) and ( t0.stateId=3 ) )
  • //*[@current='true']/*[@state='published'] | //*[@current='true']/*[@state='published']/file[@state='published' or @state='archived']
    SELECT t0.id
    FROM ru.arptek.arpsite.content.Cell as t0
    WHERE ( exists (
    SELECT 1
    FROM ru.arptek.arpsite.content.Cell as t1
    INNER JOIN t1.webObject as t2
    WHERE ( t1.parent.id=1 ) and ( t1.erased=0 ) and ( t2.erased=0 ) and ( ( t2.stateId=2 ) and ( t1.stateId=3 ) ) and ( t0=t1 )) ) or ( exists (
    SELECT 1
    FROM ru.arptek.arpsite.content.Cell as t3, ru.arptek.arpsite.content.Cell as t4
    INNER JOIN t3.webObject as t5
    INNER JOIN t4.webObject as t6
    WHERE ( t4.parent.id=1 ) and ( t4.erased=0 ) and ( t6.erased=0 ) and ( ( t6.stateId=2 ) and ( t4.stateId=3 ) ) and ( t4.id=t3.parent.id ) and ( t3.erased=0 ) and ( t5.erased=0 ) and ( t5.objectClassId=10 ) and ( ( t5.stateId=2 ) and ( t3.stateId in (3, 4) ) ) and ( t0=t3 )) )


Legal notice


Данная идея использовалась в Arp.Site с 2002 года и претерпевала только технологические изменения (интерпретация, компиляция в SQL, компиляция в JPAQL). Я не имею прямого отношения к реализации данной идеи у других своих работодателей (хотя и помогал советами), поэтому содержание данного топика не попадает под соответствующие NDA. В связи с наличие prior art, данная технология не может быть защищена патентами, датированными после 2002 года. Наличие более ранних патентов не исследовалось.

Автором изначальной реализации является Тимофей Сысоев, внедрение технологии компиляции под SQL и под JPAQL — ваш покорный слуга.
Tags:
Hubs:
+9
Comments11

Articles