Вы находитесь на странице: 1из 7

XQuery, libferris и виртуальные файловые системы

Бен Мартин, 27 июля 2007г.

Введение

Объединив движок XQuery и виртуальную файловую систему, вы можете пользоваться привычным


языком запросов для доступа к реляционным базам данных, базам данных Беркли db4, файловым
системам, сетевым ресурсам и к файлам XML. libferris представляет собой виртуальную файловую
систему, позволяющую вам видеть различные источники данных через единый интерфейс файловой
системы. Что же дает этот интерфейс? Это и привычные конструкции file://, http://, ftp://, и
более интересные возможности, такие как работа с базами данных, XML-файлами и даже с
приложениями emacs, Evolution, XWindow, и Firefox.

Например, сделав небольшие изменения в выражении XPath и указав для поиска клиентов файл db4
вместо XML-файла, мы можем значительно увеличить эффективность запроса. Поиск в db4
происходит быстрее, так как этот файл проиндексирован, и нужный блок информации будет найден
всего лишь за несколько обращений к диску. Использование же XML-файла для поиска в таблице
клиентов потребует чтения всего файла и его обработки парсером; но так оперативная память не
индексирована, поиск в такой большой структуре DOM также займет определенное время.

Для понимания этой статьи читателю было бы полезно иметь общее представление об XQuery. Здесь
приводятся достаточно простые примеры запросов XQueries, чтобы показать скорее не сам инструмент
XQuery, а возможности его совместной работы с libferris.

В этой статье с помощью инструментов типа ferrisls из пакета libferris будут показаны прямые
взаимодействия с файловой системой. Это аналогично просмотру исходного XML-файла при отладке
конкретного XQuery-запроса или разборе выражения XPath. В самом начале экспериментов
использование ferrisls для непосредственного просмотра содержимого источника данных (файловой
системы) поможет вам разобраться, почему запрос XQuery возвращает, возможно, не тот результат,
который вы ожидали увидеть.

libferris доступен для POSIX-платформ. Уже существует репозиторий бинарных инсталляционных


пакетов для Fedora Core 6 и вскоре появится версия для Fedora Core 7. В противоположность старому
линуксовому стилю монтировать все файловые системы вручную, libferris в большинстве случаев сам
занимается монтированием файловых систем для вас. Например, если вы пытаетесь читать XML-файл
как каталог, то libferris автоматически подмонтирует его и позволит вам просматривать внутреннее
содержимое этого файла.

XQuery и db4

libferris может примонтировать и XML-файл, и файл db4 как файловые системы. Файлы db4 имеют
достаточно гибкую модель данных, и из-за этого форматы db4 и XML могут выглядеть очень
похожими при использовании libferris. Основное различие между ними – это внутренне устройство
хранилища данных; например, внести небольшие изменения в файл db4 вам удастся намного быстрее,
чем в файл XML (при работе с db4 необходимо сохранить только измененные данные, а при
обновлении файла XML обычно происходит запись всей структуры DOM на жесткий диск).

Предположим, имеется таблица для поиска клиентов в XML-файле, фрагмент которого показан ниже.

<customers>
<customer id="3" givenname="Foo" familyname="Bar"/>
<customer id="15" givenname="Bobby" familyname="McGee"/>
<customer id="131" givenname="Ziggy" familyname="Stardust" />
</customers>

Сейчас мы рассмотрим команды, с помощью которых можно создать файл db4, содержащий такую же
информацию. А другой метод синхронизации файла db4 с исходным XML-файлом будет показан в
конце статьи.

Мы имеем дело со смешением подходов XML и db4, поэтому имеются некоторые различия в терминах,
обозначающих очень похожие вещи. В libferris XML-атрибут принято называть расширенным
атрибутом (EA = extended attribute), как это принято в терминологии файловой системы. Таким
образом, аргумент командной строки --show-ea показывает ferrisls, какие (расширенные)
атрибуты мы хотим увидеть. А опция --preserve-list для команды ferriscp явно указывает,
какие атрибуты необходимо скопировать из источника в результирующий файл.

Следующие две команды ferrisls показывают, что одна и та же информация будет доступна из
XML-файла и из файла db4. Различие состоит лишь в расширении имени файла с данными о клиентах.

$ fcreate --create-type=db4 --rdn=customers.db .


$ ferriscp -av --preserve-list=id,givenname,familyname customers.xml/customers
customers.db
$ ferrisls --show-ea=givenname,id customers.xml/customers
Foo 3
Bobby 15
Ziggy 131
$ ferrisls --show-ea=givenname,id customers.db/customers
Foo 3
Bobby 15
Ziggy 131

Далее показан простой запрос XQuery для файла customers.xml. URL-адрес файла с данными и
идентификатор (ID) клиента объявлены здесь как переменные, а для того, чтобы открыть файл
customers.xml, используется стандартная функция doc().

declare variable $docurl := "file:///.../customers.xml";


declare variable $customerid := "131";
<resultdata>
{
for $c in doc( $docurl )/customers/*[@id=$customerid]
return
<person cid="{ $c/@id }" surname="{ $c/@familyname }" />
}
</resultdata>

Рассмотренный выше запрос может выполняться как командой xqilla из пакета XQilla, так и
командой feffis-xqilla из библиотеки libferris. Однако последняя команда делает некоторые
дополнительные функции доступными для XQuery.

$ ferris-xqilla customers.xq
<resultdata>
<person cid="131" surname="Stardust"/>
</resultdata>
Совсем немного нам придется изменить в коде, чтобы вместо файла customers.xml использовать
customers.db. Изменится значение имени файла в переменной docurl и вместо стандартной функции
doc() мы воспользуемся специальной функцией ferris-doc(). Эти две функции работают
одинаково, но вторая позволяет libferris получать дополнительную информацию о данных и запросах.
Запрос, который оперирует непосредственно файлом customers.xml, также может использовать
дополнительную функцию ferris-doc(), но это не дает никаких преимуществ.

declare variable $docurl := "file:///.../customers.db";


declare variable $customerid := "131";
<resultdata>
{
for $c in ferris-doc( $docurl )/customers/*[@id=$customerid]
return
<person cid="{ $c/@id }" surname="{ $c/@familyname }" />
}
</resultdata>

Так как в нашем запросе участвует специальная функция ferris-doc(), нужно вызывать команду
ferris-qilla для просмотра результатов работы запроса. Они идентичны тем, что были получены из
файла customers.xml.

$ ferris-xqilla customers-db4.xq
<resultdata>
<person cid="131" surname="Stardust"/>
</resultdata>

XQuery и PostgreSQL

libferris может примонтировать PostgreSQL как файловую систему. Результат будет доступен как по
URL-адресу вида postgresql://, так и по сокращенной схеме pg://. На верхнем уровне полученной из
PostgreSQL файловой системы будут находиться названия серверов, ниже – баз данных, и затем
таблиц. Например, на локальной машине таблица foo из базы данных bar будет доступна по адресу
pg://localhost/bar/foo.

Реляционные таблицы libferris представляет так: для каждого кортежа создает отдельный файл, а для
отображения столбцов таблицы использует (расширенные) атрибуты. Так например, таблица,
содержащая два столбца (name varchar, id int) и две строки будет выглядеть как директория с двумя
файлами, каждый из которых будет иметь два доступных (расширенных) атрибута – name и id.

Функции работы с базами данных также доступны для таблиц в представлении libferris. Они могут
быть вызваны для создания виртуальной директории с результатами. Например, если в базе данных
xmldotcom2007 имеется таблица customers и функция customerlookup(int, int), которая выдает
группу клиентов по и их идентификаторам (ID), то и таблица, и эта функция будут доступны
непосредственно из каталога базы данных – pg://localhost/xmldotcom2007. В этом примере для
иллюстрации мы рассмотрим функцию postgresql с простейшей логикой.

База данных xmldotcom2007 может быть создана и наполнена следующим образом.

bash$ psql
create database xmldotcom2007;
\c xmldotcom2007
create table customers ( id serial primary key, givenname varchar(100), familyname
varchar(100) );
insert into customers values ( 3, 'Foo', 'Bar' );
insert into customers values ( 15, 'Bobby', 'McGee' );
insert into customers values ( 131, 'Ziggy', 'Stardust' );

CREATE TYPE customerlookup_result


AS (f1 int, fname varchar(100), lname varchar(100));
CREATE FUNCTION customerlookup( int, int )
returns setof customerlookup_result
AS
$BODY$
DECLARE
rec customerlookup_result;
BEGIN
for rec in
select id,givenname,familyname from customers
where id >= $1 and id <= $2
LOOP
return next rec;
END LOOP;
return;
END;
$BODY$
LANGUAGE 'plpgsql' ;
\q
bash$

Командная строка обеспечивает прямое взаимодействие с этой базой данных. Опция -0 для ferrisls
означает то же самое, что и –l в ls(1), кроме тех случаев, когда файловая система получает запрос о
том, какие атрибуты она рекомендует показывать пользователю, а какие - нет. В случае таблицы из
реляционной базы данных все столбцы таблицы считаются представляющими интерес для
пользователя. Чтобы вызвать функцию postgresql, нужно заключить URL-адрес в одинарные
кавычки, чтобы bash не попытался неверно проинтерпретировать круглые скобки. Учтите, что
(расширенные) атрибуты будут называтся f1, fname и lname, так как тип результата функции
customerlookup() содержит поля с такими именами.

$ ferrisls pg://localhost/xmldotcom2007
$ ferrisls -0 pg://localhost/xmldotcom2007/customers
131 Ziggy Stardust 131 id
15 Bobby McGee 15 id
3 Foo Bar 3 id
$ ferrisls --xml pg://localhost/xmldotcom2007/customers
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<ferrisls>

<ferrisls familyname="" givenname="" id=""


name="customers" primary-key="id"
url="pg:///localhost/xmldotcom2007/customers">
<context familyname="Stardust" givenname="Ziggy"
id="131" name="131" primary-key="id"/>
...

$ ferrisls --xml 'pg://localhost/xmldotcom2007/customerlookup(3,3)'


<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<ferrisls>
<ferrisls ...>
<context f1="3" fname="Foo" lname="Bar" name="3-Foo-Bar"
primary-key="f1-fname-lname"/>
</ferrisls>
</ferrisls>

Следующие команды производят вызов функции postgresql из запроса XQuery и вывод результатов.
Обратите внимание, что здесь мы используем @f1, @lname в качестве атрибутов, потому что функция
postgresql выдает результаты с такими названиями в типе customerlookup_result. XPath-выражение
просто повторяется в цикле для всех полученных кортежей в предположении, что вся логика
реализована внутри самой функции postgresql.

declare variable $docurl := "pg://localhost/xmldotcom2007/customerlookup(";


declare variable $mincustomerid := "3";
declare variable $maxcustomerid := "15";
<resultdata>
{
for $c in ferris-doc(
concat( $docurl, $mincustomerid, ",", $maxcustomerid, ")" ))/*
return
<person cid="{ $c/@f1 }"
surname="{ $c/@lname }" fn="{ $c/@fname }" />
}
</resultdata>

Применение XQuery для локального и сетевого поиска

XQuery может использовать индекс файловой системы и поиск по ней, как описано в этой статье Linux
Journal. Это позволяет создать собственное решение для поиска в Intranet-сети, объединив
информацию из индекса файлового сервера, RDF и из других источников с помощью запросов
XQuieries. Другое приложение может, например, производить поиск документов, к которым вы хотели
бы применить запрос XQuery, во внешнем цикле с использованием ferris-
doc("fulltextquery://...") и затем применять к ним сам запрос во внутреннем цикле.

Далее мы рассмотрим запрос XQuery, который ищет слова “alice” (Алиса) и “wonderland” (Страна
Чудес) с помощью бинарного полнотекстового поиска в индексе вашей файловой системы и
представляет результат в виде очень простого XML-файла. Возможность комбинировать несколько
вызовов функции ferris-doc() в одном XQuery-запросе позволяет создать удобный пользовательский
интерфейс для поиска по файловому серверу, используя лишь libferris и XQuery.

declare variable $qtype := "boolean";


declare variable $person := "alice";
declare variable $location := "wonderland";
<data>
{
for $idx in ferris-doc( concat("fulltextquery://", $qtype, "/",
$person, " ", $location))
for $res in $idx/*
return
<match
name="{ $res/@name }" url="{ $res/@url }"
modification-time="{ $res/@mtime-display }"
>
</match>
}
</data>

$ ferris-xqilla xquery-index.xq
<?xml version="1.0"?>
<data>
<match modification-time="99 Jul 27 12:53"
name="file:///.../doc/CommandLine/command.txt ...>
<match modification-time="00 Mar 11 06:58"
name="file:///.../doc/Gimp/Grokking-the-GIMP-v1.0/node8.html
...>
...</data>

Индексы файловой системы могут быть объединены с запросами по местоположению. Например,


далее рассмотрен запрос XQuery, выполняющий поиск всех файлов, для которых отмечено
географическое положение во Флоренции (Италия). Дополнительную информацию о географических
отметках и разрешении конфликтов в названиях местностей можно найти в этой статье Linux.com.

declare variable $placename := "eiffel-tower";


<data>
{
for $idx in ferris-doc( concat("eaq://(emblem:has-", $placename, "==1)"))
for $res in $idx/*
return
<match
name="{ $res/@name }" url="{ $res/@url }"
modification-time="{ $res/@mtime-display }"
>
</match>
}
</data>

Актуальный db4-кэш с помощью rsync

И XML-, и db4-файлы libferris представляет в виде файловых систем, поэтому вы можете


синхронизировать файл db4 с XML-файлом, используя стандартный инструмент rsync(1). Для этого
нужно указать виртуальную файловую систему libferris источником (source) и пунктом назначения
(destination) в Userspace (FUSE). Инструмент для работы с libferris из FUSE называется ferrisfs.

Атрибуты XML могут быть синхронизированы с соответствующими им в кэше db4-файла, так как
rsync поддерживает для (расширенных) атрибутов аргумент командной строки –X. Для атрибутов, в
которых пользователям разрешается хранить свою информацию, в файловых системах предусмотрен
префикс “user.”. Это дает достаточно простое разделение пространств имен (расширенных) атрибутов,
в котором пользователи имеют ограничения на доступ к атрибутам с префиксом “system.”.

О деталях внутренней реализации файловой системы заботится опция --prepend-user-dot-prefix-


to-ea-regex команды ferrisfs. Так как XML-файл не имеет ограничений на пространство имен
“user.”, параметр --prepend-user-dot-prefix-to-ea-regex укажет ferrisfs, что необходимо
собрать имена расширенных атрибутов, объединив префикс “user.” и названия атрибутов. Например,
XML-атрибут id будет представляться как user.id во входной файловой системе. А на выходе префикс
“user.” будет автоматически удален. Расширенные атрибуты вида “user.x” видит только rsync, и все
довольны.

Параметр --show-ea-regex используется, чтобы сообщить libferris, какие (расширенные) атрибуты


будут доступны для rsync. Это значит, что все остальные атрибуты XML-файла, не подходящие под
шаблону этого регулярного выражения, не будут синхронизированы и перенесены в файл db4.

fcreate --create-type=db4 --rdn=customers.db .


mkdir -p customers
mkdir -p input
ferrisfs --prepend-user-dot-prefix-to-ea-regex='.*' -u `pwd`/customers.db customers
ferrisfs --prepend-user-dot-prefix-to-ea-regex='.* \
--show-ea-regex='(id|givenname|familyname)' -u `pwd`/customers.xml/customers input
rsync -avzX --delete-after input/ customers/
db_dump -p customers.db
...
fusermount -u input
fusermount -u customers

Итоги

Комбинация XQilla и libferris позволяет вам из одного запроса XQuery работать со всеми файловыми
системами, которые поддерживаются libferris. В случае db4 и XML вы можете выбирать любой из этих
форматов для увеличения производительности, и это потребует лишь небольших изменений в запросе
XQuery.

Libferris может работать и с другими интересными источниками данных, такими как rdf DB (например,
созданными с помощью библиотеки redland) или прямые запросы данных, которые показаны в Firefox.
Но эти примеры будут темой для совсем другой статьи.

Дополнительные материалы

• Сайт проекта libferris


• Статьи об XQuery на XML.com: Начинаем работу с XQuery и Начинаем работу с XQuery,
часть 2
• Сайт rsync
• Статьи Linux Journal об XML: Весь мир – это файловая системы libferris и xsltfs://, описанная в
Виртуальные файловые системы – это виртуальные офисные документы
• Географические отметки файлов с использованием libferris и Google Earth at Linux.com
• Сайт XQilla