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

    Данный топик рассказывает о возможности использования 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 — ваш покорный слуга.

    Комментарии 11

      0
        0
        Это для указания ссылки на часть объекта, который сам по себе является XML-документом. Подразумевает, что:
        а) у нас есть URI
        б) этот URI является XML

        Тут же рассматривается другой случай, когда нам нужно получить, грубо говоря, набор URI из чего-то, что напоминает DOM-дерево (информационная система)

        Хотя в основе — www.w3.org/TR/xptr-xpointer/ — тот же XPath.
          0
          вот именно. это расширение xpath. с поддержкой диапазонов, фоллбэков, пространств имён и пр…
            0
            Пространства имён пока не были нужны, функционала основного XPath хватало :)
        0
        Штука, конечно, интересная. Но у меня есть некоторые сомнения, что ею так уж удобно пользоваться. Часто всякие надстройки приводят к тому, что приходится изучать собственный язык шаблонизатора (в данном случае XSLT), потом еще основной язык, к которому в конечном счете приводится шаблон (в данном случае SQL), да плюс еще особенности преобразований шаблонов к исходному коду. Не думаю что системный инженер, знакомый с Java/PHP/.NET не способен изучить несколько SQL команд (для удобства можно использовать специальные view для пользователей, чтобы не замораживать структуру таблиц).

        Мне это напоминает стиль поборника абстракции: «Любая бизнес-логика приложения спрятана в каком-то конфигурационном xml-файле где-то в репозитории».
          0
          1) Язык шаблонизатора (XSLT) является основным языком CMS Arp.Site. С точки зрения верстальщика он является конечным языком и не сводится ни к SQL, ни к исходному коду — стандарт XSLT реализован полностью и не расширен с помощью функций. То есть изучить надо только XSLT (ну и HTML), а XPath является его частью.

          2) Если же мы говорим о системе управления предприятием, то инженер-настройщик часто в начале не знает ни Java, ни SQL, ни структуры таблиц. Он знает только бизнес-модель, и описать её в терминах XPath'а ему чаще проще, чем в SQL.
            0
            У меня какое-то смешанное чувство. С одной стороны XPath дает уникальную гибкость приложению. Тем более что Arp.Site реализован на Java и настройщику будет сложно «чуть допилить». С другой стороны…

            В первом случае верстальщик занимается программированием, но на более привычном ему языке. Во втором случае бизнес-логика оказывается внутри XSLT-шаблона представления. Наверняка дело не ограничится получением и представлением данных, потребуется некоторая обработка. В итоге модель, контроллер и представление (MVC) окажутся неотделимы друг от друга.

            Первая же картинка по запросу «Arp.Site»:
            КПЗ Arp.Site
              0
              Бизнес-кода в XSLT нет, поэтому никакой дополнительной обработки в XSLT, кроме преобразования в HTML/PDF/PNG/SVG не происходит.

              Вся бизнес-логика реализована на Java. И это жёстко — XSLT-преобразование просто не может ничего сделать в системе, для этого нет ни API, ни функций расширения.

              P.S.: Пользуйтесь Google, он красивее картинки ищет :)
                0
                P.P.S: Возможно, стоит сделать топик о том, как сделана система Arp.Site в большом масштабе, где там разделение Data/Business/View
                  0
                  Цитирую: «Например, возможно, что после выбора города поставщика нужно ограничивать список поставщиков». Зависимости поставщиков от города — это бизнес-уровень. В данном случае он оказывается в шаблоне представления.

                  Расскажите про Arp.Site. Мне интересна архитектура.

                    0
                    1) Нет, в данном случае зависимость описывается на уровне модели. То есть где-то описывается модель класса «Сотрудник», у которого есть атрибуты «Страна», «Офис». Но атрибуты и их свойства задаются, например, через Web-интерфейс (или GUI) системным инженером, не имеющим отношения к программированию. И связь эта обычно задаётся с помощью SQL.

                    2) Ок, в отдельном топике.

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

          Самое читаемое