Loading [MathJax]/extensions/TeX/AMSmath.js

пятница, 15 июня 2012 г.

Расставить идентификаторы при помощи xquery

Возникла как-то задача расставить идентификаторы для определённого набора объектов в xml БД. Судя по выдаче гугла по этому вопросу или эта задача возникла только у меня, или она на столько тривиальна, что не стоит внимания.

Конкретнее про задачу: есть набор объектов, необходимо добавить к каждому уникальный целый атрибут 'id'. Казалось бы, простая задача (она действительно простая), но сходу решить её не удалось.

Единственное решение, которое мне удалось накопать только 1 раз (потом его нигде не находил) было в подсчёте всех узлов с атрибутом id и добавлению следующему узлу без этого атрибута id со значением count(от всех узлов с атрибутом) + 1. Как можно заметить - решение не самое быстрое и красивое.

Немного покурив xquery, я всё же придумал более оптимальное решение:

declare namespace t = 'none';

declare updating function t:setId($arg, $idNum)
{
  let $x := head($arg)
  return
    if( $x ) then
      insert node (attribute { 'id2' } {$idNum} ) into $x
    else
      ()
    ,if( tail($arg) ) then
      t:setId(tail($arg), $idNum + 1)
    else
      ()
};

let $i := collection('a_SUITE')/module//object
return t:setId($i, 1)


Что это такое и почему оно такое страшное? Последние 2 строки очевидны - создаём список нужных узлов и запускаем функцию проставления в ней id'шников.

Теперь про саму функцию. Во-первых, если функция вносит изменения в БД, она должна быть помечена как updating. Во-вторых, функция может возвращать несколько значений. Т.е. делать return 0, 1 как в питоне. Т.о., конструкция

return
    if( $x ) then
      insert node (attribute { 'id2' } {$idNum} ) into $x
    else
      ()
    ,if( tail($arg) ) then
      t:setId(tail($arg), $idNum + 1)
    else
      ()


возвращает 2 значения. Либо это

 insert node (attribute { 'id2' } {$idNum} ) into $x, t:setId(tail($arg), $idNum + 1)

либо

(), ()

Последнее - пустые значения, т.к. если на вход подать пустой объект, произойдёт ошибка исполнения, а сделать 2 return внутри 1 функции нельзя. Поэтому на пустом tail() просто ничего не запускаем. Ещё 2 случая на практике никогда не выполнятся.

Интересней всего то, что такой огород пришлось городить по 2 причинам. Первая - отсутствие возможности просто выполнить функцию - любое действие должно выполняться именно внутри return. Вторая - отсутствие mutable переменных. Самое смешное, что по отдельности ни первая ни вторая причина проблемы бы не вызывали.