XML становится все более и более важным для предприятий. Часто пользователям нужно создавать XML-документы из содержимого реляционной базы данных. Это можно сделать разными способами, но функции расширяемости IBM® Informix Dynamic Server® version 9.x (IDS 9.x) предоставляют возможность создавать свои собственные функции для генерирования XML из вашей базы данных.
В данной статье рассматривается простой подход к генерированию XML, который может соответствовать многим требованиям и предоставить стандартный блок для более сложных решений. Пример, сопровождающий эту статью, использует демонстрационную базу данных stores7, поставляемую вместе с продуктом IDS 9.x.
Нам может понадобиться сгенерировать данные в XML-формате, используя SQL-команды различного типа. Данные могут быть размещены в одной таблице, либо понадобится соединение нескольких таблиц. Что у этих SQL-команд общего – они возвращают набор строк данных. Поэтому решение одинаково для работы с одной или несколькими таблицами.
IDS 9.x предоставляет поддержку для нового типа с названием rows. Это означает, что результаты целой строки могут быть переданы в определенную пользователем функцию (user-defined function - UDF) как один аргумент. Строка определяется столбцами, имеющими имя и тип. Что остается сделать – передать дополнительный аргумент, идентифицирующий имя строки для инкапсулирования данных строки в XML-представлении. Рассмотрим функцию, имеющую следующее определение:
CREATE FUNCTION genxml(varchar(30), ROW) RETURNING lvarchar . . . |
Первый аргумент – это имя, которое мы хотим дать строке, а второй аргумент – это сама строка. Строка содержит свое определение, то есть, функция genxml может определить количество столбцов в строке, их имена и т.д. Следующие команды генерируют XML-представление для каждой строки таблицы customer:
SELECT genxml("customer", customer)
FROM customer; |
Второй аргумент – это имя таблицы. Это означает, что в качестве второго аргумента передается целая строка. Первая возвращаемая строка выглядит примерно так (форматирование изменено для данной статьи):
<customer> <customer_num>101<//customer_num> <fname>Ludwig </fname> <lname>Pauli </lname> <company>All Sports Supplies </company> <address1>213 Erstwild Court </address1> <city>Sunnyvale </city> <state>CA</state> <zipcode>94086</zipcode> <phone>408-789-8075 </phone> </customer> |
Имена столбцов были извлечены из определения строки функцией genxml и использованы как теги для соответствующих столбцов. Но что, если мы хотим не весь список столбцов? Мы можем динамически создать строку с желаемыми столбцами как второй аргумент функции genxml следующим образом:
SELECT genxml("customer", ROW(customer_num, fname, lname))
FROM customer; |
Получаемая XML-строка аналогична предыдущей, но с меньшим числом полей. Поскольку можно создавать строку динамически, мы можем соединять несколько таблиц и создавать результат, основанный на всех таблицах, как показано в следующем примере:
SELECT genxml("customer_calls",
ROW(A.customer_num, fname, lname, call_dtime, call_code,
call_descr, res_dtime, res_dtime, res_descr)
)
FROM customer a, cust_calls b
WHERE a.customer_num = b.customer_num; |
Эти примеры иллюстрируют множество требований по генерированию строк в XML-формате. Также может существовать требование сгенерировать XML-файл на основе результатов SQL-команды, содержащей агрегатное выражение. Агрегатные выражения можно передавать в функцию genxml, как показано ниже:
SELECT genxml("stats", ROW(customer_num, COUNT(*)) )
FROM cust_calls
GROUP BY customer_num; |
Теперь с этим основным инструментом мы готовы завершить создание XML-документа, добавив заголовок и сноску.
Если столбец содержит значение NULL, функция genxml и все другие функции, описанные в данной статье, не будут обрабатывать столбец. И это имеет смысл, так как единственными элементами, которые могли бы появляться в результате, были бы открывающий и закрывающий тег для столбца.
Таблица может содержать столбец, имеющий тип row. В этом случае переданная в функцию genxml строка будет содержать именованный или неименованный тип row. Например, мы могли бы иметь следующее определение:
CREATE ROW TYPE address_t ( name varchar(20), address1 varchar(20), address2 varchar(20), city varchar(15), state char(2), zipcode char(5) ); CREATE TABLE employee ( name varchar(30), address address_t, phone varchar(18) ); |
Функция будет обрабатывать тип row, именованный или неименованный, так же как и другие базовые типы. Выполнение и результат запроса по таблице employee выглядел бы так:
SELECT genxml("employee", employee) FROM employee;
<employee>
<name>Roy</name>
<address>
<address1>123 first street</address1>
<city>Denver</city>
<state>CO</state>
<zipcode>80111</zipcode>
</address>
<phone>303-555-1212</phone>
</employee> |
Как вы можете заметить, теги столбца address окружают определение строки, а имена столбцов строки используются для идентификации значений также как и на более высоком уровне. Обратите внимание, что столбец address2 содержит значение NULL и, следовательно, не появляется в результате.
Есть еще одна ситуация, которую мы должны рассмотреть. IDS 9.x поддерживает определение иерархий таблиц, основанных на именованных типах row. На рисунке 1 показан пример иерархии.
Рисунок 1. Пример иерархии
Одной из причин наличия такой иерархии является возможность выполнять вычисления рисков, отличающихся в зависимости от типов ссуд. Создав несколько функций risk, работающих с конкретными типами row, мы можем выполнить следующий запрос:
SELECT loan_number, risk(loans) FROM loans WHERE branch_id = 127; |
Этот запрос перебирает все строки с branch_id равным 127 и вызывает соответствующую функцию risk в зависимости от типа возвращаемой строки. Если мы хотим сгенерировать XML-представление для каждого типа loan для конкретной отрасли (branch) с использованием существующей функции genxml, мы должны выполнить один SQL-запрос для каждой подчиненной таблицы. Мы должны сделать это для того, чтобы дать для каждой строки нужное имя типа row.
Поскольку мы работаем с именованными типами row, функция genxml могла бы извлекать эту информацию и использовать ее неявно. Обратите внимание, что IDS различает функции в зависимости от их названий и типов аргументов. Следовательно, мы можем создать новую, дополнительную функцию genxml, принимающую в качестве аргумента только строку и извлекающую имя из определения строки. Затем мы можем сгенерировать XML-представление таблицы loans для конкретной отрасли при помощи следующей SQL-команды:
SELECT genxml(loans) FROM loans WHERE branch_id = 127; |
Если бы вы использовали эту функцию с неименованным типом row, заголовком было бы полное определение строки. Для таблицы customer вы получили бы следующий заголовок:
<ROW(customer_num integer, fname char(15), lname char(15),
company char(20), address1 char(20), address2 char(20),
city char(15),state char(2), zipcode char(5),
phone char(18)
)> |
Аналогичное имя строки использовалось бы и для закрывающего тега строки.
XML используется для описания данных. Он может включать в себя также информацию, указывающую тип документа и определения корректных документов этого типа. Такие файлы называются декларациями типа документа (document type declaration -DTD). DTD сами являются XML-документами.
XML описывает данные, но не способ их представления. Представление информации может быть описано на расширяемом языке таблиц стилей (extensible stylesheet language - XSL).
Наконец, вы должны указать, что документ является XML-документом и соответствует конкретному XML-стандарту. Это значит, что вы должны окружить сгенерированные строки, по крайней мере, указанием, что это XML-документ, и тегом для завершения документа. Результат может быть примерно таким:
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE customer_set SYSTEM "/home/dtd/customer_set.dtd">
<?xml-stylesheet type="text/xsl"
href="/home/xsl/customer_set.xsl" ?>
<customer_set>
. . . (XML-formatted rows)
</customer_set> |
CREATE PROCEDURE xmlcustomerset()
RETURNING LVARCHAR
DEFINE result LVARCHAR;
DEFINE ressql LVARCHAR;
LET result = '<?xml version="1.0" encoding="ISO-8859-1" ?>';
LET result = result ||
'<!DOCTYPE customer_set SYSTEM "/home/dtd/customer_set.dtd">';
LET result = result ||
'<?xml-stylesheet type="text/xsl" href="/home/xsl/customer_set.xsl" ?>';
LET result = result || '<customer_set>';
FOREACH SELECT genxml('customer', customer) INTO ressql FROM customer
LET result = result || ressql;
END FOREACH;
LET result = result || '</customer_set>';
RETURN result;
END PROCEDURE;
EXECUTE PROCEDURE xmlcustomerset(); |
Процедура очень простая. Она просто инициализирует переменную result заголовочной информацией. Затем выполняет SQL-команды, возвращающие несколько строк в XML-формате, и добавляет результат к переменной result. Наконец, она завершает документ, добавляя закрывающий тег customer_set, и возвращает результат. Последняя строка показывает, как выполняется процедура.
Отслеживание заголовочной информации
Процесс создания XML-документа из хранимой процедуры всегда один и тот же. Хорошо бы было обобщить процесс вместо создания множества хранимых процедур. Первым шагом для этого является поиск способа отслеживания типа документа и таблицы стилей, которые будут использоваться. Мы можем использовать таблицу для хранения этой информации:
CREATE TABLE genxmlinfo ( name varchar(30) PRIMARY KEY, dtypepath lvarchar, xslpath lvarchar ); |
Столбец name – это тип используемого документа. В приведенном выше примере это customer_set. Столбец dtypepath содержит путь к DTD, а xslpath содержит путь к XSL, используемой для данного типа. Если значение столбца равно NULL, строка заголовка не появляется в документе. Это дает нам гибкость в определении того, что должно включаться в документ.
Обобщение создания XML-документов
Если бы мы использовали хранимые процедуры для генерирования XML-документов, мы должны были бы иметь одну процедуру для каждой SQL-команды, которую хотели бы использовать. Это необходимо из-за того, что язык хранимых процедур поддерживает только статические SQL-команды.
Теперь, когда у нас есть способ извлекать информацию о заголовке, мы можем создать дополнительные пользовательские функции, которые будут выполнять SQL-команды динамически и добавлять информацию о заголовке. Мы можем решить эту задачу двумя способами. Либо мы просто работаем с результатом функции genxml(), либо мы принимаем общую SQL-команду и форматируем строки, возвращаемые командой. Сначала рассмотрим последний вариант.
Мы должны разработать функцию, принимающую в качестве аргументов тип документа и SQL-команду, и возвращающую полный XML-документ:
CREATE FUNCTION genxmlhdr(LVARCHAR, LVARCHAR) RETURNING LVARCHAR . . . |
Затем мы можем сгенерировать XML-документ customer_set, выполнив следующую команду:
EXECUTE FUNCTION genxmlhdr("customer_set",
"SELECT * FROM customer"); |
При этом сгенерируется XML-документ, который немного отличается от XML-документа, сгенерированного процедурой xmlcustomerset(): у нас нет способа предоставить имя для возвращаемых строк. Мы должны дать им общее имя. В данном случае - row. Это не является проблемой. Мы просто должны принимать это во внимание при сопоставлении DTD и XSL-документов, относящихся к нашим типам документов.
Мы можем обеспечить немного большую гибкость, добавив функцию, аналогичную genxml(lvarchar, lvarchar), предполагающую, что результатом SQL-команды будут строки в XML-формате. Команда для создания документа customer_set станет такой:
EXECUTE FUNCTION addxmlhdr("customer_set",
"SELECT genxml('customer', customer) FROM customer"); |
Для завершения нашего набора функций мы должны добавить еще одну функцию, принимающую SQL-команду и возвращающую все строки XML-документа без информации о заголовке. С этой функцией теперь возможно выполнять динамические SQL-команды в хранимой процедуре. Поскольку сигнатура этой функции отличается от сигнатуры genxml(lvarchar, row), мы можем использовать это же имя:
CREATE FUNCTION genxml(LVARCHAR, LVARCHAR) RETURNING LVARCHAR . . . |
Первый аргумент представляет имя строки, а второй – SQL-команду. Команда для генерирования документа customer_set без заголовка следующая:
EXECUTE FUNCTION genxml("customer_set", "SELECT * FROM customer"); |
Пользовательские агрегатные функции
IDS 9.x предоставляет возможность создавать собственные агрегатные функции. Поскольку создание XML-документа включает в себя обработку набора строк для возврата одного результата, пользовательские агрегатные функции (user-defined aggregate - UDA) хорошо подходят для XML-обработки.
По определению реляционная база данных не гарантирует какой-либо сортировки строк. Единственным способом сортировки строк является обработка их после SQL-команды, включающей предложение ORDER BY. Вы можете решить эту проблему при помощи временной таблицы для хранения результата, который вы хотите сгенерировать в XML-формате. Предполагается, что следующие версии IDS разрешат выборку данных из результата SQL-команды.
Если порядок строк не важен, вы могли бы сгенерировать XML-документ, используя UDA. Этот UDA должен использоваться следующим способом:
SELECT aggrxml(customer, "customer_set") FROM customer; |
Первый аргумент UDA – строка, которую мы хотим обработать. Второй аргумент – значение, представляющее название типа генерируемого документа. Это значит, что результат будет аналогичен результату, генерируемому функциями genxml(lvarchar, lvarchar), которые рассматривались выше. Такое применение более естественно, чем использование команды EXECUTE FUNCTION. Поскольку это агрегатная функция, вы можете воспользоваться преимуществами SQL-предложения GROUP BY. Эта возможность полезна при генерировании нескольких XML-документов. Например, она может уменьшить количество SQL-команд, выполняемых в хранимой процедуре, путем удаления встроенного цикла FOR EACH.
Все рассмотренные выше функции были реализованы на языке C и доступны в примере кода. Вот список функций, включая типы их аргументов:
| addxmlhdr(lvarchar, lvarchar) | aggrxml(ROW, lvarchar) | genxml(lvarchar, ROW) |
| genxml(ROW) | genxml(lvarchar, lvarchar) | genxmlhdr(lvarchar, lvarchar) |
Обратите внимание на то, что функции addxmlhdr(lvarchar, lvarchar), genxml(lvarchar, lvarchar) и genxmlhdr(lvarchar, lvarchar) могут быть выполнены только в команде EXECUTE FUNCTION.
Исходный код для этих функций занимает около 300 строк текста на C. Код был протестирован с IDS 9.30.TC2 в Microsoft® Windows® 2000 и IDS 9.30.Uc2 в Linux®.
Реализация также включает функцию, полезную при желании изменить код: set_tracing. Эта функция позволяет вам включить трассировку выполнения функции. Необходимо удалить строку "-DMITRACE_OFF=1" из файлов make и добавить код трассировки в исходный код. Более подробную информацию по трассировке можно найти в книге "Серверное программирование на языке C", ссылка на которую приведена в разделе Ресурсы.
Установка кода примера состоит из трех этапов:
-
Загрузка кода: Все файлы примера должны быть загружены в подкаталог
genxmlкаталога$INFORMIXDIR/extend. -
Компилирование кода: Вы должны скомпилировать исходный код для вашей платформы. Код примера содержит два файла make:
winnt.makиmakefile.Winnt.makиспользуется для компиляции в Microsoft Windows 2000. Предполагается использование Microsoft Visual C++. Файлmakefileиспользуется для компиляции кода на платформах UNIX#x422. Вы должны изменить операторы include в makefile (строка 2) для соответствия платформе. -
Регистрация функций: Перед использованием рассмотренных выше функций они должны быть зарегистрированы в базах данных, в которых эти функции нужны. Сценарий
genxml.sqlрегистрирует все необходимые функции и создает таблицуgenxmlinfo, использующуюся для отслеживания информации о типах документов.
Для удаления функций вы можете выполнить сценарий genxml_d.sql в соответствующей базе данных. Он удалит все функции и таблицу genxmlinfo. Если вы хотите продолжать использовать таблицу, измените сценарии установки и удаления и закомментируйте строки, содержащие таблицу.
Простейшим способом зарегистрировать и удалить регистрацию функций является выполнение сценариев из командной строки:
dbaccess -e jroy genxml <errlog 2<&1 |
Этот пример использует имя базы данных jroy и перенаправляет выводимую программой dbaccess информацию в файл errlog. При возникновении ошибки сценарий продолжает выполнение следующей команды. Параметр -e указывает программе dbaccess выводить команды на стандартное устройство вывода, для того чтобы увидеть результат каждой команды.
Предоставляемый с данной статьей код содержит два главных ограничения:
- Строка XML-файла не может превышать 2048 символов.
- Размер создаваемого XML-документа ограничен 32KB.
Эти ограничения могут быть сняты путем добавления кода, управляющего этими ограничениями. Пример с типом idn_mrlvarchar можно найти на Web-сайте IBM, ссылка на который приведена в разделе Ресурсы. Код, описание типа и способы его использования вы можете также найти в книге "Компоненты с открытым исходным кодом", ссылка на которую тоже приведена в разделе Ресурсы. При использовании реализации, аналогичной idn_mrlvarchar, размер вашего XML-документа может достигать 4TB.
Данная реализация поддерживает большинство типов данных IDS, за исключением типов коллекций (LIST, MULTISET, SET) и типов больших объектов (BLOB, BYTE, CLOB и TEXT).
Способность генерировать XML-документы прямо из сервера базы данных может значительно упростить реализацию решения. Упрощается взаимодействие между приложением и базой данных. Это приложение может выполняться на сервере приложений, на web-сервере, быть CGI-программой или автономным приложением. Поскольку код genxml выполняется на сервере базы данных, он легко доступен для любого приложения. Эта возможность способствует повторному использованию кода.
Код genxml демонстрирует мощь объектно-реляционой технологии. Также он демонстрирует по крайней мере еще две вещи:
- Вы не всегда имеете возможность ждать, пока производитель базы данных предоставит функциональность для решения ваших бизнес-задач.
- В сервер базы данных можно добавить различные функции с минимальными усилиями и значительно усовершенствовать решение бизнес-задач.
| Описание | Имя | Размер | Метод загрузки |
|---|---|---|---|
| genxml sample code | genxml.zip | 128 KB | FTP |
- Оригинал статьи Generating XML from IDS 9.x.
- "Informix Dynamic Server.2000: Серверное программирование на языке C", Jacques Roy, ISBN 0-13-013709-X, Prentice-Hall (Informix Press), 2000.
- "Объектно-реляционные базы данных: Руководство техника", Paul Brown, ISBN 0-13-019460-3, Prentice-Hall (Informix Press), 2001.
- "Компоненты с открытым исходным кодом для Informix Dynamic Server 9.x", Jacques Roy, William W. White, Jean T. Anderson, Paul G. Brown, ISBN 0-13-042827-2, Prentice-Hall (Informix Press), 2002.

Жак Рой (Jacques Roy) Жак Рой является сотрудником всемирной организации поддержки продаж продуктов фирмы IBM. Он имеет более чем двадцатилетний стаж работы и уже более пяти лет занимается расширяемостью баз данных. Он является автором книги "Informix Dynamic Server 2000: Серверное программирование на языке C" и соавтором книги "Компоненты с открытым исходным кодом для Informix Dynamic Server 9.x".