9.14. XML-функции

Функции и подобные им выражения, описанные в этом разделе, работают со значениями типа xml. Информацию о типе xml вы можете найти в Разделе 8.13. Выражения xmlparse и xmlserialize, преобразующие значения xml в текст и обратно, здесь повторно не рассматриваются. Для использования большинства этих функций дистрибутив должен быть собран с ключом configure --with-libxml.

9.14.1. Создание XML-контента

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

9.14.1.1. xmlcomment

xmlcomment(текст)

Функция xmlcomment создаёт XML-значение, содержащее XML-комментарий с заданным текстом. Этот текст не должен содержать «--» или заканчиваться знаком «-», чтобы результирующая конструкция была допустимой в XML. Если аргумент этой функции NULL, результатом её тоже будет NULL.

Пример:

SELECT xmlcomment('hello');

  xmlcomment
--------------
 <!--hello-->

9.14.1.2. xmlconcat

xmlconcat(xml[, ...])

Функция xmlconcat объединяет несколько XML-значений и выдаёт в результате один фрагмент XML-контента. Значения NULL отбрасываются, так что результат будет равен NULL, только если все аргументы равны NULL.

Пример:

SELECT xmlconcat('<abc/>', '<bar>foo</bar>');

      xmlconcat
----------------------
 <abc/><bar>foo</bar>

XML-объявления, если они присутствуют, обрабатываются следующим образом. Если во всех аргументах содержатся объявления одной версии XML, эта версия будет выдана в результате; в противном случае версии не будет. Если во всех аргументах определён атрибут standalone со значением «yes», это же значение будет выдано в результате. Если во всех аргументах есть объявление standalone, но минимум в одном со значением «no», в результате будет это значение. В противном случае в результате не будет объявления standalone. Если же окажется, что в результате должно присутствовать объявление standalone, а версия не определена, тогда в результате будет выведена версия 1.0, так как XML-объявление не будет допустимым без указания версии. Указания кодировки игнорируются и будут удалены в любых случаях.

Пример:

SELECT xmlconcat('<?xml version="1.1"?><foo/>', '<?xml version="1.1" standalone="no"?><bar/>');

             xmlconcat
-----------------------------------
 <?xml version="1.1"?><foo/><bar/>

9.14.1.3. xmlelement

xmlelement(name имя [, xmlattributes(значение [AS атрибут] [, ...])]
  [, содержимое, ...])

Выражение xmlelement создаёт XML-элемент с заданным именем, атрибутами и содержимым.

Примеры:

SELECT xmlelement(name foo);

 xmlelement
------------
 <foo/>

SELECT xmlelement(name foo, xmlattributes('xyz' as bar));

    xmlelement
------------------
 <foo bar="xyz"/>

SELECT xmlelement(name foo, xmlattributes(current_date as bar), 'cont', 'ent');

             xmlelement
-------------------------------------
 <foo bar="2007-01-26">content</foo>

Если имена элементов и атрибутов содержат символы, недопустимые в XML, эти символы заменяются последовательностями _xHHHH_, где HHHH — шестнадцатеричный код символа в Unicode. Например:

SELECT xmlelement(name "foo$bar", xmlattributes('xyz' as "a&b"));

            xmlelement
----------------------------------
 <foo_x0024_bar a_x0026_b="xyz"/>

Если в качестве значения атрибута используется столбец таблицы, имя атрибута можно не указывать явно, этим именем станет имя столбца. Во всех остальных случаях имя атрибута должно быть определено явно. Таким образом, это выражение допустимо:

CREATE TABLE test (a xml, b xml);
SELECT xmlelement(name test, xmlattributes(a, b)) FROM test;

А следующие варианты — нет:

SELECT xmlelement(name test, xmlattributes('constant'), a, b) FROM test;
SELECT xmlelement(name test, xmlattributes(func(a, b))) FROM test;

Содержимое элемента, если оно задано, будет форматировано согласно его типу данных. Когда оно само имеет тип xml, из него можно конструировать сложные XML-документы. Например:

SELECT xmlelement(name foo, xmlattributes('xyz' as bar),
                            xmlelement(name abc),
                            xmlcomment('test'),
                            xmlelement(name xyz));

                  xmlelement
----------------------------------------------
 <foo bar="xyz"><abc/><!--test--><xyz/></foo>

Содержимое других типов будет оформлено в виде блока символьных данных XML. Это, в частности, означает, что символы <, > и & будут преобразованы в сущности XML. Двоичные данные (данные типа bytea) представляются в кодировке base64 или в шестнадцатеричном виде, в зависимости от значения параметра xmlbinary. Следует ожидать, что конкретные представления отдельных типов данных могут быть изменены для приведения типов SQL и Postgres Pro в соответствие со стандартом XML Schema, когда появится его более полное описание.

9.14.1.4. xmlforest

xmlforest(содержимое [AS имя] [, ...])

Выражение xmlforest создаёт последовательность XML-элементов с заданными именами и содержимым.

Примеры:

SELECT xmlforest('abc' AS foo, 123 AS bar);

          xmlforest
------------------------------
 <foo>abc</foo><bar>123</bar>


SELECT xmlforest(table_name, column_name)
FROM information_schema.columns
WHERE table_schema = 'pg_catalog';

                                         xmlforest
-------------------------------------------------------------------------------------------
 <table_name>pg_authid</table_name><column_name>rolname</column_name>
 <table_name>pg_authid</table_name><column_name>rolsuper</column_name>
 ...

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

Имена элементов с символами, недопустимыми для XML, преобразуются так же, как и для xmlelement. Данные содержимого тоже приводятся к виду, допустимому для XML (кроме данных, которые уже имеют тип xml).

Заметьте, что такие XML-последовательности не являются допустимыми XML-документами, если они содержат больше одного элемента на верхнем уровне, поэтому может иметь смысл вложить выражения xmlforest в xmlelement.

9.14.1.5. xmlpi

xmlpi(name цель [, содержимое])

Выражение xmlpi создаёт инструкцию обработки XML. Содержимое, если оно задано, не должно содержать последовательность символов ?>.

Пример:

SELECT xmlpi(name php, 'echo "hello world";');

            xmlpi
-----------------------------
 <?php echo "hello world";?>

9.14.1.6. xmlroot

xmlroot(xml, version текст | нет значения [, standalone yes|no|нет значения])

Выражение xmlroot изменяет свойства корневого узла XML-значения. Если в нём указывается версия, она заменяет значение в объявлении версии корневого узла; также в корневой узел переносится значение свойства standalone.

SELECT xmlroot(xmlparse(document '<?xml version="1.1"?><content>abc</content>'),
               version '1.0', standalone yes);

                xmlroot
----------------------------------------
 <?xml version="1.0" standalone="yes"?>
 <content>abc</content>

9.14.1.7. xmlagg

xmlagg(xml)

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

Пример:

CREATE TABLE test (y int, x xml);
INSERT INTO test VALUES (1, '<foo>abc</foo>');
INSERT INTO test VALUES (2, '<bar/>');
SELECT xmlagg(x) FROM test;
        xmlagg
----------------------
 <foo>abc</foo><bar/>

Чтобы задать порядок сложения элементов, в агрегатный вызов можно добавить предложение ORDER BY, описанное в Подраздел 4.2.7. Например:

SELECT xmlagg(x ORDER BY y DESC) FROM test;
        xmlagg
----------------------
 <bar/><foo>abc</foo>

Следующий нестандартный подход рекомендовался в предыдущих версиях и может быть по-прежнему полезен в некоторых случаях:

SELECT xmlagg(x) FROM (SELECT * FROM test ORDER BY y DESC) AS tab;
        xmlagg
----------------------
 <bar/><foo>abc</foo>

9.14.2. Условия с XML

Описанные в этом разделе выражения проверяют свойства значений xml.

9.14.2.1. IS DOCUMENT

xml IS DOCUMENT

Выражение IS DOCUMENT возвращает true, если аргумент представляет собой правильный XML-документ, false в противном случае (т. е. если это фрагмент содержимого) и NULL, если его аргумент также NULL. Чем документы отличаются от фрагментов содержимого, вы можете узнать в Разделе 8.13.

9.14.2.2. IS NOT DOCUMENT

xml IS NOT DOCUMENT

Выражение IS NOT DOCUMENT возвращает false, если аргумент представляет собой правильный XML-документ, true в противном случае (т. е. если это фрагмент содержимого) и NULL, если его аргумент — NULL.

9.14.2.3. XMLEXISTS

XMLEXISTS(текст
 PASSING [BY REF] xml [BY REF])

Функция xmlexists возвращает true, если выражение XPath в первом аргументе возвращает какие либо узлы, и false — в противном случае. (Если один из аргументов равен NULL, результатом также будет NULL.)

Пример:

SELECT xmlexists('//town[text() = ''Toronto'']' PASSING BY REF '<towns><town>Toronto</town><town>Ottawa</town></towns>');

 xmlexists
------------
 t
(1 row)

Указания BY REF не несут смысловой нагрузки в Postgres Pro, но могут присутствовать для соответствия стандарту SQL и совместимости с другими реализациями. По стандарту SQL первое указание BY REF является обязательным, а второе — нет. Также заметьте, что, согласно стандарту SQL, конструкция xmlexists должна принимать в первом аргументе выражение XQuery, но Postgres Pro в настоящее время поддерживает только XPath, подмножество XQuery.

9.14.2.4. xml_is_well_formed

xml_is_well_formed(текст)
xml_is_well_formed_document(текст)
xml_is_well_formed_content(текст)

Эти функции проверяют, является ли текст правильно оформленным XML, и возвращают соответствующее логическое значение. Функция xml_is_well_formed_document проверяет аргумент как правильно оформленный документ, а xml_is_well_formed_content — правильно оформленное содержание. Функция xml_is_well_formed может делать первое или второе, в зависимости от значения параметра конфигурации xmloption (DOCUMENT или CONTENT, соответственно). Это значит, что xml_is_well_formed помогает понять, будет ли успешным простое приведение к типу xml, тогда как две другие функции проверяют, будут ли успешны соответствующие варианты XMLPARSE.

Примеры:

SET xmloption TO DOCUMENT;
SELECT xml_is_well_formed('<>');
 xml_is_well_formed 
--------------------
 f
(1 row)

SELECT xml_is_well_formed('<abc/>');
 xml_is_well_formed 
--------------------
 t
(1 row)

SET xmloption TO CONTENT;
SELECT xml_is_well_formed('abc');
 xml_is_well_formed 
--------------------
 t
(1 row)

SELECT xml_is_well_formed_document('<pg:foo xmlns:pg="http://postgresql.org/stuff">bar</pg:foo>');
 xml_is_well_formed_document 
-----------------------------
 t
(1 row)

SELECT xml_is_well_formed_document('<pg:foo xmlns:pg="http://postgresql.org/stuff">bar</my:foo>');
 xml_is_well_formed_document 
-----------------------------
 f
(1 row)

Последний пример показывает, что при проверке также учитываются сопоставления пространств имён.

9.14.3. Обработка XML

Для обработки значений типа xml с помощью выражений XPath 1.0 в Postgres Pro представлены функции xpath и xpath_exists.

xpath(xpath, xml [, nsarray])

Функция xpath вычисляет выражение XPath (аргумент xpath типа text) для заданного xml. Она возвращает массив XML-значений с набором узлов, полученных в результате выражения XPath. Если выражение XPath выдаёт не набор узлов, а скалярное значение, возвращается массив из одного элемента.

Вторым аргументом должен быть правильно оформленный XML-документ. В частности, в нём должен быть единственный корневой элемент.

В необязательном третьем аргументе функции передаются сопоставления пространств имён. Эти сопоставления должны определяться в двумерном массиве типа text, во второй размерности которого 2 элемента (т. е. это должен быть массив массивов, состоящих из 2 элементов). В первом элементе каждого массива определяется псевдоним (префикс) пространства имён, а во втором — его URI. Псевдонимы, определённые в этом массиве, не обязательно должны совпадать с префиксами пространств имён в самом XML-документе (другими словами, для XML-документа и функции xpath псевдонимы имеют локальный характер).

Пример:

SELECT xpath('/my:a/text()', '<my:a xmlns:my="http://example.com">test</my:a>',
             ARRAY[ARRAY['my', 'http://example.com']]);

 xpath  
--------
 {test}
(1 row)

Для пространства имён по умолчанию (анонимного) это выражение можно записать так:

SELECT xpath('//mydefns:b/text()', '<a xmlns="http://example.com"><b>test</b></a>',
             ARRAY[ARRAY['mydefns', 'http://example.com']]);

 xpath
--------
 {test}
(1 row)
xpath_exists(xpath, xml [, nsarray])

Функция xpath_exists представляет собой специализированную форму функции xpath. Она возвращает не весь набор XML-узлов, удовлетворяющих выражению XPath, а только одно логическое значение, показывающее, есть ли такие узлы. Эта функция равнозначна стандартному условию XMLEXISTS, за исключением того, что она также поддерживает сопоставления пространств имён.

Пример:

SELECT xpath_exists('/my:a/text()', '<my:a xmlns:my="http://example.com">test</my:a>',
                     ARRAY[ARRAY['my', 'http://example.com']]);

 xpath_exists  
--------------
 t
(1 row)

9.14.4. Отображение таблиц в XML

Следующие функции отображают содержимое реляционных таблиц в значения XML. Их можно рассматривать как средства экспорта в XML:

table_to_xml(tbl regclass, nulls boolean, tableforest boolean, targetns text)
query_to_xml(query text, nulls boolean, tableforest boolean, targetns text)
cursor_to_xml(cursor refcursor, count int, nulls boolean,
              tableforest boolean, targetns text)

Результат всех этих функций имеет тип xml.

table_to_xml отображает в xml содержимое таблицы, имя которой задаётся в параметре tbl. Тип regclass принимает идентификаторы строк в обычной записи, которые могут содержать указание схемы и кавычки. Функция query_to_xml выполняет запрос, текст которого передаётся в параметре query, и отображает в xml результирующий набор. Последняя функция, cursor_to_xml выбирает указанное число строк из курсора, переданного в параметре cursor. Этот вариант рекомендуется использовать с большими таблицами, так как все эти функции создают результирующий xml в памяти.

Если параметр tableforest имеет значение false, результирующий XML-документ выглядит так:

<имя_таблицы>
  <row>
    <имя_столбца1> данные </имя_столбца1>
    <имя_столбца2> данные </имя_столбца2>
  </row>

  <row>
    ...
  </row>

  ...
</имя_таблицы>

А если tableforest равен true, в результате будет выведен следующий фрагмент XML:

<имя_таблицы>
  <имя_столбца1> данные </имя_столбца1>
  <имя_столбца2> данные </имя_столбца2>
</имя_таблицы>

<имя_таблицы>
  ...
</имя_таблицы>

...

Если имя таблицы неизвестно, например, при отображении результатов запроса или курсора, вместо него в первом случае вставляется table, а во втором — row.

Выбор между этими форматами остаётся за пользователем. Первый вариант позволяет создать готовый XML-документ, что может быть полезно для многих приложений, а второй удобно применять с функцией cursor_to_xml, если её результаты будут собираться в документ позже. Полученный результат можно изменить по вкусу с помощью рассмотренных выше функций создания XML-содержимого, в частности xmlelement.

Значения данных эти функции отображают так же, как и ранее описанная функция xmlelement.

Параметр nulls определяет, нужно ли включать в результат значения NULL. Если он установлен, значения NULL в столбцах представляются так:

<имя_столбца xsi:nil="true"/>

Здесь xsi — префикс пространства имён XML Schema Instance. При этом в результирующий XML будет добавлено соответствующее объявление пространства имён. Если же данный параметр равен false, столбцы со значениями NULL просто не будут выводиться.

Параметр targetns определяет целевое пространство имён для результирующего XML. Если пространство имён не нужно, значением этого параметра должна быть пустая строка.

Следующие функции выдают документы XML Schema, которые содержат схемы отображений, выполняемых соответствующими ранее рассмотренными функциями:

table_to_xmlschema(tbl regclass, nulls boolean, tableforest boolean,
  targetns text)
query_to_xmlschema(query text, nulls boolean, tableforest boolean,
  targetns text)
cursor_to_xmlschema(cursor refcursor, nulls boolean, tableforest boolean,
  targetns text)

Чтобы результаты отображения данных в XML соответствовали XML-схемам, важно, чтобы паре функций передавались одинаковые параметры.

Следующие функции выдают отображение данных в XML и соответствующую XML-схему в одном документе (или фрагменте), объединяя их вместе. Это может быть полезно там, где желательно получить самодостаточные результаты с описанием:

table_to_xml_and_xmlschema(tbl regclass, nulls boolean, tableforest boolean,
  targetns text)
query_to_xml_and_xmlschema(query text, nulls boolean, tableforest boolean,
  targetns text)

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

schema_to_xml(schema name, nulls boolean, tableforest boolean,
  targetns text)
schema_to_xmlschema(schema name, nulls boolean, tableforest boolean,
  targetns text)
schema_to_xml_and_xmlschema(schema name, nulls boolean, tableforest boolean,
  targetns text)

database_to_xml(nulls boolean, tableforest boolean, targetns text)
database_to_xmlschema(nulls boolean, tableforest boolean, targetns text)
database_to_xml_and_xmlschema(nulls boolean, tableforest boolean,
  targetns text)

Заметьте, что объём таких данных может быть очень большим, а XML будет создаваться в памяти. Поэтому, вместо того, чтобы пытаться отобразить в XML сразу всё содержимое больших схем или баз данных, лучше делать это по таблицам, возможно даже используя курсор.

Результат отображения содержимого схемы будет выглядеть так:

<имя_схемы>

отображение-таблицы1

отображение-таблицы2

...

</имя_схемы>

Формат отображения таблицы определяется параметром tableforest, описанным выше.

Результат отображения содержимого базы данных будет таким:

<имя_схемы>

<имя_схемы1>
  ...
</имя_схемы1>

<имя_схемы2>
  ...
</имя_схемы2>

...

</имя_схемы>

Здесь отображение схемы имеет вид, показанный выше.

В качестве примера, иллюстрирующего использование результата этих функций, на Рисунке 9.1 показано XSLT-преобразование, которое переводит результат функции table_to_xml_and_xmlschema в HTML-документ, содержащий таблицу с данными. Подобным образом результаты этих функций можно преобразовать и в другие форматы на базе XML.

Рисунок 9.1. XSLT-преобразование, переводящее результат SQL/XML в формат HTML

<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns="http://www.w3.org/1999/xhtml"
>

  <xsl:output method="xml"
      doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
      doctype-public="-//W3C/DTD XHTML 1.0 Strict//EN"
      indent="yes"/>

  <xsl:template match="/*">
    <xsl:variable name="schema" select="//xsd:schema"/>
    <xsl:variable name="tabletypename"
                  select="$schema/xsd:element[@name=name(current())]/@type"/>
    <xsl:variable name="rowtypename"
                  select="$schema/xsd:complexType[@name=$tabletypename]/xsd:sequence/xsd:element[@name='row']/@type"/>

    <html>
      <head>
        <title><xsl:value-of select="name(current())"/></title>
      </head>
      <body>
        <table>
          <tr>
            <xsl:for-each select="$schema/xsd:complexType[@name=$rowtypename]/xsd:sequence/xsd:element/@name">
              <th><xsl:value-of select="."/></th>
            </xsl:for-each>
          </tr>

          <xsl:for-each select="row">
            <tr>
              <xsl:for-each select="*">
                <td><xsl:value-of select="."/></td>
              </xsl:for-each>
            </tr>
          </xsl:for-each>
        </table>
      </body>
    </html>
  </xsl:template>

</xsl:stylesheet>