Автоматический ввод-вывод XML в IDEF1-хранилище

Необходимы перепосты и переводы на другие языки. Газета "Компьютерные вести", kv.by/index2010343801.htm Издание 2-е, исправленное. Другие статьи (оглавление)


XML имеет смысл только как коммуникационных протокол, хранить его - большая ошибка (подробнее вы можете ознакомится с рассуждениями на debank.com). Однако писать код для ввода и вывода XML в реляционное хранилище - тоже лишняя работа, если оно само может принимать и размещать данные в таблицах в соответствии с некоторыми соглашениями. Автор хотел бы вынести на суд свои предложения относительно таких соглашений и заинтересован во мнении, комментариях и возможной реализации этих новшеств. Чертежи представлены в sql5.19.3.pdf , и ниже будут использоваться ссылки на страницы этого документа. Предложения являются дополнением к вопросу о протоколе, поднятым в разделе "Архитектура ОС" статьи "Ошибки и их исправление в эргономике API".

Первое, xml-элемент записывается в одноименную таблицу (т.е. имя тега совпадает с именем таблицы), xml-атрибут - в одноименное поле (имя атрибута совпадает с именем поля). Первичный ключ новой записи заполняется триггером.

Второе, во время создания двух записей, соответствующих родительскому и дочернему xml-элементам, первичный ключ одной копируется во внешний ключ другой. В случае неоднозначности из-за того, что одна таблица ссылается на другую несколькими внешними ключами или две таблицы ссылаются друг на друга одновременно, неоднозначность разрешается в имени открывающего xml-тега: после собственно имени тега ставится знак # и имя нужного ссылающегося поля. Выглядит как новое имя открывающего тега с символом # в середине имени (с.81-83). Закрывающий тег остается без изменений. Будем называть это детерминацией.

Третье, одноименные и не вложенные, а последовательно расположенные xml-элементы превращаются в список записей, т.е. первичный ключ одной записи также копируется во внешний ключ другой. Если таблица ссылается на саму себя несколькими внешними ключами, то неоднозначность также разрешается в имени открывающего xml-тега, только вместо знака # ставится $ (закрывающий тег также остается без изменений). И это также будем называть детерминацией (с.84-85). Примем, что детерминация должна быть указана в каждом из последовательно расположенных одноименных xml-элементов - чтобы пользователю меньше думать. Мы применяем разные знаки - # и $ - чтобы оба вида детерминации можно было использовать одновременно, например, tag#field1$field2

Обратная задача - вывод записей в xml-виде. Использование функций SQL/XML, XQuery, проприетарных веб-серверов (относительно последних подпробнее на lists.xml.org/archives/xml-dev/200802/msg00213.html) дают очень громоздкий код. В то же время как правило извлекаются записи, уже связанные внешним ключом, а значит работа происходит с деревом, уже сформированным в схеме хранилища. Предлагаем в таких случаях лаконичное 'SELECT * FROM a.b.c' для выбора данных из таблиц 'a', 'b', 'c'. Будем называть запись 'a.b.c' термином XTree - по аналогии с XPath.

Если одна таблица ссылается на другую несколькими внешними ключами или две таблицы ссылаются друг на друга одновременно, то после имени таблицы ставится знак # и имя нужного ссылающегося поля. Будем называть это рафинированием. Выглядит как имя таблицы с символом # в середине имени (с.12-14). Аналогично, если таблица, содержащая список, ссылается на саму себя несколькими внешними ключами, то после имени таблицы ставится знак $ и имя нужного ссылающегося поля. Это также будем называть рафинированием (с.15-16). Оба вида рафинирования можно использовать одновременно, например, table#field1$field2

Как записать детерминацию и рафинирование при нескольких ссылающихся полях? Перечислим ситуации.

Распорка (nog) - соединение двумя внешними ключами трех таблиц, причем ссылающиеся поля находятся в одной промежуточной таблице.

create table a (
  id   num      primary key,
  data float
);
create table b (
  id   num      primary key,
  ref1 num      references a(id),  -- нужно
  ref2 num      references a(id),  -- не нужно
  ref3 num      references c(id),  -- нужно
  ref4 num      references c(id),  -- не нужно
  data float
);
create table c (
  id   num      primary key,
  data float
);
И в детерминации, и в рафинировании ссылающиеся поля указываются через двоеточие; таблица-предок упоминается первой, потомок - второй (ссылающиеся поля могут быть одноименными). Таким образом ввод выглядит как
<a>
  <b#ref1:ref3>
    <c>
  </b>
</a>
вывод (XTree) как a.b#ref1:ref3.c , а маршрут по схеме к нужной таблице (XPath) как a/b#ref1:ref3/c

Стяжка (buckle) - также два ключа с тремя таблицами, но ссылающиеся поля ссылаются на промежуточную таблицу.
create table a (
  id   num      primary key,
  ref1 num      references b(id),  -- нужно
  ref2 num      references b(id),  -- не нужно
  data float
);
create table b (
  id   num      primary key,
  data float
);
create table c (
  id   num      primary key,
  ref1 num      references b(id),  -- нужно
  ref2 num      references b(id),  -- не нужно
  data float
);
Соответственно ввод
<a#ref1>
  <b>
    <c#ref3>
  </b>
</a>
вывод a#ref1.b.c#ref3 , маршрут a#ref1/b/c#ref3

Перекрёсток (crossroad) мы имеем, когда таблиц всего две.
create table a (
  id   num      primary key,
  ref  num      references b(id),
  data float
);
create table b (
  id   num      primary key,
  lnk  num      references a(id),
  data float
);
В этом случае мы можем полагать родительской таблицей как одну, так и другую. Поэтому ввести можно двумя способами
<a#ref>
  <b>
</a>
и
<a>
  <b#lnk>
</a>
XTree и XPath также две пары a#ref.b , a#ref/b и a.b#lnk , a/b#lnk

А вот ситуация из трех таблиц, две из которых - потомки третьей, имеет две разновидности в зависимости от того, родительская ссылается на дочерние или наоборот. Обе будем называть разветвлением (branching), но сначала рассмотрим ситуацию, когда ссылаются дочерние.
create table a (
  id   num      primary key,
  data float
);
create table b (
  id   num      primary key,
  ref1 num      references a(id),  -- нужно
  ref2 num      references a(id),  -- не нужно
  data float
);
create table c (
  id   num      primary key,
  ref1 num      references a(id),  -- нужно
  ref2 num      references a(id),  -- не нужно
  data float
);
Входящий XML выглядит как
<a>
  <b#ref1>
  <c#ref1>
</a>
а вот XTree и XPath имеют два варианта, ведь предикат может требовать дерево как со всеми ветвями, так и только с одной. Со всеми - это a.(b#ref1 c#ref1) и a/(b#ref1 c#ref1) А вот с одной существует только XTree, т.е. a.(b#ref1 | c#ref1) , т.к. в случае a/(b#ref1 | c#ref1) не понятно какой марштут брать. Теперь рассмотрим ситуацию, когда ссылается родительская таблица.
create table a (
  id   num      primary key,
  ref1 num      references b(id),  -- нужно
  ref2 num      references b(id),  -- не нужно
  ref3 num      references c(id),  -- нужно
  ref4 num      references c(id),  -- не нужно
  data float
);
create table b (
  id   num      primary key,
  data float
);
create table c (
  id   num      primary key,
  data float
);
Здесь ввод
<a#ref1+ref3>
  <b>
  <c>
</a>
вывод и маршрут с требованием записей во всех дочерних таблицах есть a#ref1+ref3.(b c) и a#ref1+ref3/(b c) Аналогично в случае, если достаточно только одной дочерней записи, существует XTree, т.е. a#ref1^ref3.(b | c) , но не существует XPath. Обратили внимание, что стоит #ref1^ref3 , а не #ref1+ref3 ? Этим мы избегаем когнитивного диссонанса между 'или', обозначаемом вертикальной чертой, и плюсиком.

Своеобразной инверсией разветвления является продолжение (continuation) - ситуация, в которой одна таблица одновременно является потомком двух других. Здесь ввода нет вообще! Потому что xml-элемент не может быть потомком двух других (специальные изголения для программистов оставим этим последним). Однако вывод существует, ведь можно одну запись извлечь дважды - как дочернюю двух разных родительских. У продолжения опять же две разновидности, и сначала рассмотрим ту, в которой ссылаются дочерние таблицы.
create table b (
  id   num      primary key,
  data float
);
create table c (
  id   num      primary key,
  data float
);
create table d (
  id   num      primary key,
  ref1 num      references b(id),  -- нужно
  ref2 num      references b(id),  -- не нужно
  ref3 num      references c(id),  -- нужно
  ref4 num      references c(id),  -- не нужно
  data float
);
Чтобы работа происходила, только если существуют все возможные xml-деревья, мы пишем .(b.d#ref1 c.d#ref3). и /(b/d#ref1 c/d#ref3)/ ; если существует хотя бы одно дерево /(b/d#ref1 | c/d#ref3)/ Теперь рассмотрим ситуацию, когда ссылается родительская таблица.
create table b (
  id   num      primary key,
  ref1 num      references d(id),  -- нужно
  ref2 num      references d(id),  -- не нужно
  data float
);
create table c (
  id   num      primary key,
  ref1 num      references d(id),  -- нужно
  ref2 num      references d(id),  -- не нужно
  data float
);
create table d (
  id   num      primary key,
  data float
);
Ну, в общем-то все то же самое - в смысле .(b#ref1 c#ref3).d. , /(b#ref1 c#ref3)/d/ и .(b#ref1 | c#ref3).d.

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

<a>
  <b#lnk1*lnk2>
</a>
в выводе и маршруте - как a.b#lnk1*lnk2 и a/b#lnk1*lnk2

Дмитрий Тюрин (DmitryTurin.narod.ru):
dima.turin@centrum.cz (все письма из домена .ru попадают в спам), dima.turin@gmail.com