пятница, 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 переменных. Самое смешное, что по отдельности ни первая ни вторая причина проблемы бы не вызывали.

5 комментариев:

  1. А зачем понадобилось расставлять id?
    По-моему к узлу или классу узлов всегда можно обратиться через xpath.

    ОтветитьУдалить
    Ответы
    1. 1. У тебя могут быть 2 разных узла с одинаковым набором атрибутов и их надо как-то различать. Можно конечно отслеживать путь, по которому они лежат, но это неудобно.
      2. Отображение связи 2 узлов. В моём конкретном случае - связь между реализацией и и вызовом функции.
      3. Ускорение поиска. Например, если в реализации функции указан id="10", то ты можешь быстро запросить все реализации с таким id из индекса. По имени не получится либо по причине 1, либо потому что runtime оптимизатор запросов может тупить.

      Удалить
  2. 1) -> :first, :second, :nth(1), :last
    2)
    3) в браузерах запрос по id крайне медленный (почему, я пока не понимаю) + в рамках одного xml id обязан быть уникальным (или у тебя xml не один), но мысль понял.

    Если не лениво, расскажи, в каком контексте ты это все используешь и как оно работает.

    ОтветитьУдалить
    Ответы
    1. Ну, для моих целей оно, видимо, не особо подходит. Предполагалось, что я буду представлять AST программы в xml формате и обрабатывать при помощи native-xml БД.

      Один из примеров использования id'шника: идёшь по ходу выполнения программы, у тебя встречается вызов функции. Без id придётся каждый раз выполнять поисковый запрос, а это может быть весьма долго.

      > в браузерах запрос по id крайне медленный
      Насчёт скорости - может быть много причин. Нужно смотреть запрос + быть уверенным, что было включено индексирование + быть уверенным, что оно используется. Кстати, а почему именно по id? Оно чем-то отличается от обычного атрибута?

      Вообще, в xquery есть функция generate-id(), но она возвращает не целое значение, а строку и она не везде реализована.

      Удалить
  3. Тем что он обязан быть уникальным, и то что к нему возможна абсолютная адресация через css\js. Еще абсолютная адресация есть для class. Но для id один к одному для элемента, для класса это многие ко многим. И то и другое грубо говоря семантическое имя элемента.
    [ul]
    [li class="even" id="fst"]
    [li class="odd veryimportant"]
    [li class="even"]
    [li class="odd" id="lst"]
    [/ul]

    ОтветитьУдалить