D.3. Ограничения XML и совместимость с SQL/XML

В SQL:2006 были внесены значительные изменения в посвящённой XML части ISO/IEC 9075-14 (SQL/XML). Реализация типа данных XML и связанных функций в PostgreSQL в большей степени соответствует более ранней редакции, SQL:2003, с некоторыми заимствованиями из последующих редакций. В частности:

  • Тогда как в текущем стандарте существует семейство типов данных XML, содержащих «документы» или «содержимое» в нетипизированном виде или с типами XML Schema, а также тип XML(SEQUENCE), содержащий произвольные части XML-документа, в PostgreSQL есть только один тип xml, который может содержать «документ» или «содержимое». Определённый в стандарте тип «последовательность» в PostgreSQL отсутствует.

  • PostgreSQL предоставляет две функции, появившиеся в SQL:2006, но вместо языка XML Query, как должно быть согласно стандарту, в них используется язык XPath 1.0.

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

D.3.1. Запросы ограничиваются XPath версии 1.0

Специфичные для PostgreSQL функции xpath() и xpath_exists() выполняют запросы к XML-документам на языке XPath. В PostgreSQL также имеются поддерживающие только XPath стандартные функции XMLEXISTS и XMLTABLE, хотя согласно стандарту они должны поддерживать XQuery. Все эти функции в PostgreSQL реализованы с использованием библиотеки libxml2, которая поддерживает только XPath 1.0.

Существует тесная связь между языком XQuery и XPath версии 2.0 и новее: любое выражение, синтаксически правильное и выполняющееся успешно, выдаёт в обоих языках одинаковые результаты (за незначительным исключением, связанным с числовым обозначением символов или использованием предопределённых сущностей — XQuery заменяет их соответствующими символами, а XPath оставляет в исходном виде). Но между XPath 1.0 и этими языками подобная связь отсутствует: он появился гораздо раньше и во многом отличается от них.

Заслуживают отдельного рассмотрения две категории ограничений: ограничение языка XQuery до XPath для функций, описанных в стандарте SQL, и ограничение XPath до версии 1.0 как для стандартизированных функций, так и для специфичных функций PostgreSQL.

D.3.1.1. Ограничение языка XQuery до XPath

В число отличий XQuery от XPath входят:

  • Выражения XQuery могут выдавать не только всевозможные значения XPath, но и конструировать новые XML-узлы. XPath может создавать и возвращать значения атомарных типов (числа, строки и так далее), но выдаваемые им XML-узлы должны уже присутствовать в документе, поступившем на вход выражения.

  • В XQuery есть управляющие конструкции для организации циклов, сортировки и группировки.

  • В XQuery поддерживается объявление и использование локальных функций.

В последних версиях XPath начинают появляться возможности, пересекающиеся с имеющимися в XQuery (например, конструкции for-each, sort, анонимные функции и функция parse-xml, создающая узел из строки), но до XPath 3.0 их не было.

D.3.1.2. Ограничения XPath до версии 1.0

Разработчикам, знакомым с XQuery и XPath 2.0 или новее, приходится иметь дело с рядом недостатков XPath версии 1.0:

  • Фундаментальный тип результатов XQuery/XPath, тип sequence, который может содержать XML-узлы, атомарные значения, и всё это вместе, в XPath 1.0 отсутствует. В 1.0 выражения могут выдавать только набор узлов (состоящих из нуля или нескольких узлов XML) или единственное атомарное значение.

  • В отличие от последовательностей XQuery/XPath, которые могут содержать произвольные элементы в любом требующемся порядке, во множестве узлов XPath 1.0 нет гарантированного порядка, и оно, как и любое другое множество, не может содержать несколько вхождений одного элемента.

    Примечание

    Библиотека libxml2 не всегда возвращает в PostgreSQL наборы узлов с внутренними членами в том порядке, в котором они идут во входном документе. В её документации не гарантируется корректное поведение, а выражение XPath 1.0 не может на это воздействовать.

  • Тогда как XQuery/XPath поддерживают все типы, определённые в стандарте XML Schema, а также множество операторов и функций, работающих с этими типами, XPath 1.0 поддерживает только множества узлов и три атомарных типа: boolean, double и string.

  • В XPath 1.0 отсутствует условный оператор. Выражение XQuery/XPath вида if ( hat ) then hat/@size else "no hat" не имеет эквивалента в XPath 1.0.

  • В XPath 1.0 нет оператора сравнения строк с упорядочиванием. Условия "cat" < "dog" и "cat" > "dog" оба являются ложными, так как они выполняются как числовые сравнения двух значений NaN. Условия же = и !=, напротив, сравнивают строки в виде строк.

  • XPath 1.0 размывает разницу между сравнением значений и общими сравнениями, которая имеется в XQuery/XPath. Сравнения sale/@hatsize = 7 и sale/@customer = "alice" по сути являются количественными сравнениями, и результатом их будет истина, если существует элемент sale с заданным значением атрибута, тогда как sale/@taxable = false() — сравнение всего набора узлов с фактическим булевским значением. Его результат будет истиной, только если у элемента sale вовсе не будет атрибута taxable.

  • В модели данных XQuery/XPath узел документа может иметь либо форму документа (то есть содержать в точности один элемент верхнего уровня, снаружи которого допускаются только комментарии и инструкции обработки), либо форму содержимого (с ослабленными ограничениями). В XPath 1.0 ему соответствует корневой узел, который может иметь только форму документа. Этим отчасти объясняется то, что значение типа xml, передаваемое в качестве элемента контекста любым функциям PostgreSQL на базе XPath, должно быть в форме документа.

Кроме отмеченных выше имеются и другие различия. В языках XQuery и XPath версии 2.0 и новее существует режим совместимости с XPath 1.0, а в документации W3C имеется перечень изменений функций и изменений в языке применительно к этому режиму. Этот перечень гораздо более полный, но тоже не исчерпывающий. Даже режим совместимости этих языков не обеспечивает их полную идентичность XPath 1.0.

D.3.1.3. Преобразование значений/типов данных между SQL и XML

В SQL:2006 и более поздних ревизиях чётко определены преобразования между стандартными типами SQL и типами стандарта XML Schema в обе стороны. Однако эти правила выражаются в типах и понятиях, определённых в XQuery/XPath, и не могут быть непосредственно применены к другой модели данных, присущей XPath 1.0.

Когда PostgreSQL сопоставляет значения данных SQL с XML (как в функции xmlelement), или XML с SQL (как в выходных столбцах xmltable), за исключением нескольких отдельно обрабатываемых случаев, PostgreSQL просто полагает, что строка XPath 1.0, содержащая данные типа XML, будет допустимой для ввода в текстовом виде в тип данных SQL, и наоборот. Это правило добродетельно своей простотой, и при этом преобразования для многих типов данных в итоге оказываются такими, какими и должны быть согласно стандарту. В текущем выпуске требуется явное преобразование, если выражение столбца xmltable выдаёт логическое значение или число с плавающей точкой; см. Подраздел D.3.2.

Там же, где это нужно для взаимодействия с другими системами, для некоторых типов данных можно явно использовать функции форматирования типов данных (например, описанные в Разделе 9.8) для получения преобразований, в точности соответствующих стандарту.

D.3.2. Непреднамеренные ограничения реализации

В этом разделе описываются дополнительные ограничения, присущие текущей реализации в PostgreSQL, но не самой библиотеке libxml2.

D.3.2.1. Необходимость приведения столбцов xmltable, имеющих логический или числовой тип

Выражение столбца xmltable, результатом которого должно быть логическое или числовое значение XPath, будет выдавать ошибку «unexpected XPath object type» (неожиданный тип объекта XPath). Чтобы обойти эту ошибку, это выражение надо поместить внутрь вызова функции string языка XPath; в этом случае PostgreSQL сможет успешно присвоить строковое значение выходному столбцу SQL, имеющему логический или числовой тип с плавающей точкой.

D.3.2.2. Результат вычисления пути или выходной столбец SQL типа XML

В текущей версии выражение столбца xmltable, выдающее набор XML-узлов, может быть присвоено выходному SQL-столбцу типа XML. В этом случае результатом будет конкатенация следующих данных: для большинства типов узлов в наборе узлов берётся текстовый узел со строковым значением узла в определении XPath 1.0, но для узлов—элементов — копия этого узла. Такой набор узлов может быть присвоен SQL-столбцу, имеющему не XML-тип, только если этот набор содержит один узел. При этом строковое значение узлов большинства типов заменяется пустой строкой, строковое значение узла-элемента заменяется конкатенацией только его непосредственных потомков — текстовых узлов (другие потомки исключаются из рассмотрения), а строковое значение текстового узла или атрибута выдаётся согласно определению XPath 1.0. Строковое выражение XPath, присваиваемое выходному столбцу типа XML, должно проходить разбор XML.

При разработке кода не рекомендуется полагаться на описанное особое поведение: во-первых, оно не вполне соответствует стандарту, а во-вторых, в PostgreSQL 12 оно изменено.

D.3.2.3. Передача параметров только по значению (BY VALUE)

В стандарте SQL определены два механизма передачи параметров, осуществляющих передачу XML-аргумента из SQL в XML-функцию или получение результата: BY REF, в котором конкретное значение в XML остаётся привязанным к своему узлу, и BY VALUE, в котором передаётся содержимое XML, но связь с узлом теряется. Выбрать механизм можно перед списком параметров, в качестве механизма по умолчанию для всех параметров, или после каждого отдельного параметра, переопределив тем самым выбор по умолчанию.

В качестве иллюстрации различия взгляните на следующие два запроса, которые в окружении SQL:2006 выдают true и false, если x является XML-значением:

SELECT XMLQUERY('$a is $b' PASSING BY REF x AS a, x AS b NULL ON EMPTY);
SELECT XMLQUERY('$a is $b' PASSING BY VALUE x AS a, x AS b NULL ON EMPTY);

В этом выпуске PostgreSQL принимает указание BY REF в конструкции XMLEXISTS или XMLTABLE, но игнорирует его. Тип xml содержит сериализованное представление данных в текстовом виде, поэтому сущность узла, которую нужно сохранять, отсутствует, и передача фактически производится по значению (BY VALUE).

D.3.2.4. Отсутствие именованных параметров запросов

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

D.3.2.5. Отсутствие типа XML(SEQUENCE)

Тип данных xml в PostgreSQL может содержать значение только в форме документа (DOCUMENT) или содержимого (CONTENT). Контекстный элемент выражения XQuery/XPath должен быть одиночным XML-узлом или атомарным значением, но в XPath 1.0 это может быть только XML-узел, и при этом нет типа узла, содержащего CONTENT. Как следствие, в PostgreSQL в качестве контекстного элемента XPath можно передать данные XML в единственном виде — в виде правильно оформленного документа (DOCUMENT).