Элементарные данные - деревья
Маска
Элементарным объектом, обрабатываемым языком TML,
является не запись, а дерево.
Деревья состоят из множества записей, размещенных в разных таблицах.
В запросе на получение дерева запись упоминается указанием таблицы, в которой она хранится
(такое упоминание будем называть звеном).
Первой указывается верхняя таблица, затем остальные по порядку вложенности.
Каждая обрамляющая таблица отделяется от вложенной знаком "точка" (".") без пробелов.
Каждый запрос оканчивается знаком "точка с запятой" (";").
tablename1.tablename2.tablename3.tablename4;
Может быть запрошено любое под-дерево.
tablename2.tablename3.tablename4;
Звенья могут быть одноименными.
tablename1.tablename2.tablename3.tablename2;
Интересно сравнить назначение слов, перечисляемых через слэш в указании пути к файлу,
и слов, перечисляемых через точку в TML.
В первом случае мы имеем перечисление значений одного и того же поля
разных записей одной таблицы ("dir1/dir2/dir3"),
во втором - перечисление имен ("tab1.tab2.tab3").
Для обозначения нескольких уровней иерархии могут быть использованы
спец-символы (*, +, ?) и натуральные числа -
в этом качестве будем называть их повторителями.
Нужно помнить, что имя звена не может быть числом.
повторитель | количество обозначаемых промежуточных уровней иерархии |
* | 0-∞ |
+ | 1-∞ |
? | 0-1 |
5 | 5 |
3~5 | 3-5 |
Вот несколько примеров.
tablename.*.tabname;
tablename.+.tabname;
tablename.?.tabname;
tablename.5.tabname;
tablename.3~5.tabname;
tablename.0~5.tabname;
Правило для вложенных таблиц: не извлекается то, чего не просили.
Если в примере выше "tabname" имела вложенные таблицы,
то последние из базы данных извлечены не будут.
Интересно сравнить назначение специальных символов в "shell"-е и повторителей в TML-е.
В первом неизвестно название файла, во втором - глубина вложенности.
В первом, например, звездочка означает произвольное количество букв в слове,
во втором - произвольное количество звеньев.
Будем называть пайщиком (sharer)
несколько любых подряд идущих звеньев запроса или
комбинацию пайщиков, перечисленных в круглых скобках.
Пайщик повторителя
Можно отобрать в запросе не все вложенные таблицы,
а только со строго определенными именами.
Для этого эти имена нужно поставить перед повторителем.
Если в примере выше нужно отобрать повторителями не все таблицы,
а только имеющие название "tname" или "tcap", то выражения должны выглядеть так
(запятая между именами в скобках означает логическую операцию "or")
tablename.(tname,tcap)*.tabname;
tablename.(tname,tcap)+.tabname;
tablename.(tname,tcap)?.tabname;
tablename.(tname,tcap)5.tabname;
tablename.(tname,tcap)3~5.tabname;
Повторитель может действовать не на одно звено, а на несколько звеньев.
tablename.(tname1.tname2.tname3)*.tabname;
tablename.(tname1.tname2.tname3)+.tabname;
tablename.(tname1.tname2.tname3)?.tabname;
tablename.(tname1.tname2.tname3)5.tabname;
tablename.(tname1.tname2.tname3)3~5.tabname;
Например, первое выражение из примера выше означает повтор
"tname1.tname2.tname3" любое количество раз, т.е.
tablename.tname1.tname2.tname3.tabname
tablename.tname1.tname2.tname3.tname1.tname2.tname3.tabname
tablename.tname1.tname2.tname3.tname1.tname2.tname3.tname1.tname2.tname3.tabname
Часть запроса, стоящую под знаком повторителя,
будем называть пайщиком повторителя
(отсутствие какого-либо повторителя после закрывающей круглой скобки
означает число один).
Часть результата запроса, полученную действием повторителя
(выделенна зеленым) будем называть нунтяку (nuntaku).
tablename.(tname).tabname;
Нунтяку всегда состоит из максимального количества пайщиков,
это количество ограничено только требованием существования звеньев после нунтяку
(в т.ч. требованием существования последующих нунтяку!).
Таким образом если в базе данных хранится следующее дерево
tablename.t1.t2.t1.t2.t1.t2.t1.t2
то в ниже приведенном запросе нунтяку состоит только из трех пайщиков.
tablename.(t1.t2)*.t1.t2;
Если применяются повторители "*", "+",
то после повторителя в квадратных скобках можно указать условие,
удовлетворение полей пайщика которому
прекращает дальнейшее увеличение числа пайщиков в нунтяку.
Такие квадратные скобки будем называть завершающими скобками,
а пайщика, удовлетворившего условию, будем называть последним пайщиком.
Запрос выдаст дерево вместе с ним.
Если ни один пайщик не удовлетворяет выражению в завершающих скобках,
то тем не менее запрос прошел успешно и вернет дерево вместе с нунтяку.
tablename.*[fld=0].tabname;
tablename.tname*[fld=0].tabname;
tablename.(tname1.tname2)*[tname1.fld=0 tname2.fld=0].tabname;
Присваивания в завершающих скобках выполняются только
в последнем пайщике и только после присваиваний в скобках поля.
В завершающих скобках может находиться все то, что может находиться в скобках поля,
а порядок обработки в завершающих скобках аналогичен
порядку обработки в скобках поля.
t.(t1[a11].t2[a22])*[t1.fld=0 t2.fld=0 t1.fld10 t2.fld10].t3;
Пайщики под-дерева
Если в некоторую обрамляющую таблицу вложены две и более вложенных таблиц
(несколько под-деревьев),
то для обращения к таблице вместе с под-деревьями
надо нужные под-деревья указать в круглых скобках через пробел -
такое перечисление под-деревьев будем называть сборным пайщиком (joint sharer).
Чтобы попасть в результат запроса, дереву необходимо иметь все
указанные под-деревья целиком.
tablename1.(tab21.tab31.tab41 tab22.tab32.tab42);
Перечисление через запятую означает,
что достаточно иметь любое из под-деревьев, но целиком -.
такое перечисление под-деревьев будем называть альтернативным пайщиком
(alternative sharer).
tablename1.(tab21.tab31.tab41, tab22.tab32.tab42);
Везде в TML знак "пробел" (один или несколько знаков "пробел" или "табуляция")
по смыслу означает операцию "and", а знак запятая (",") - операцию "or".
Везде в TML операция "and" имеет приоритет над операцией "or".
После перечисления под-деревьев через запятую (но не через пробел) может стоять
любой повторитель -
это означает, что перечисленные под-деревья равнозначны в процессе применения повторителя
(в частности могут перемежаться).
tablename1.(tab21.tab31.tab41, tab22.tab32.tab42, tab23.tab33.tab43)*.tablename5;
Круглые скобки могут быть вложенными, например
tablename1.(tab21.(tab31, tab32) tab22);
Если после круглых скобок через точку перечислены какие-либо звенья,
то последние также являются частью запроса и продолжает иерархию для всех под-деревьев,
указанных в круглых скобках.
Следующие два выражения эквивалентны.
tablename1.(tab21.tab31.tab41 tab22.tab32).tablename5;
tablename1.(tab21.tab31.tab41.tablename5 tab22.tab32.tablename5);
Обезглавливание
Если запрос должен вернуть не все дерево "tab1.tab2.tab3.tab4",
а только под-дерево "tab3.tab4", то выражение нужно разделить на части словом "->"
(которое в дальнейшем будем называть секач или axe).
tab1.tab2 -> tab2.tab3.tab4;
Совокупность звеньев слева от секача будем называть
деревом адреса (address tree),
справа - деревом данных (primary tree).
Последнее звено внешнего дерева является полем, будем называть его
полем секача (field of axe).
Название поля секача может не совпадать названием верхней таблицы дерева данных.
tabname1.tabname2.field -> tablename1.tablename2.tablename3;
Дерево адреса может содержать пайщиков.
tabname1.(tabname2 tabname20).field -> tablename1.tablename2.tablename3;
tabname1.(tabname2)*.field -> tablename1.tablename2.tablename3;
Индексация списка
Некоторые звенья возможно являются списками,
например, звено "tablename2" в примере ниже.
Если нет никаких специальных указаний, списки извлекаются целиком.
tablename1.tablename2.tablename3;
Но можно ограничить количество извлекаемых элементов,
указан необходимые индексы в фигурных скобках
- далее такие индексы будем называть индексами списка.
Совокупность указаных элементов также будет списком,
порядок следования элементов сохраняется
(в примере выше таблица "tablename3" продолжает иерархию
каждого выбранного элемента списка "tablename2").
tablename1.tablename2{1,4,5}.tablename3;
Несколько значений индекса перечисляются через запятую (например, "1,3,5"),
диапазон индекса задается указанием границ диапазона через знак тильда (например, "10~15"),
триплет (диапазон индекса с шагом, отличным от единицы) задается
указанием нижней границы, шага и верхний границы через знаки тильда (например, "30~33~90"),
возможна комбинация всех способов (например, "1,3,5,10~15,30~33~90").
В индексах списка могут находиться:
- порядковые номера элементов списка, отсчитываемые от 1.
Выход за границу списка не является ошибкой -
можно указать номер больший, чем существует в списке.
tablename1.tablename2{5}.tablename3;
tablename1.tablename2{3,15,27}.tablename3;
tablename1.tablename2{3~27}.tablename3;
Порядковые номера могут находится в колонке некоторой таблицы.
tablename1.tablename2{tab.fld}.tablename3;
tablename1.tablename2{tab1.tab2.fld}.tablename3;
- знак "$", обозначающий количество элементов в списке.
Запись в поле "$+1"-го элемента означает создание нового элемента
tablename1.tablename2{$-3}.tablename3;
tablename1.tablename2{$+1}[fld1].tablename3;
tablename1.tablename2{$+2}[fld1].tablename3;
- знак "!", обозначающий номер уровня иерархии текущего звена.
Уровни иерархии отсчитываются от 1, отсчет начинается с первого звена запроса.
Все элементы списка находятся на одном и том же уровне иерархии.
tablename1.tablename2{!-3}.tablename3;
tablename1.tablename2{!+3}[fld1].tablename3;
tablename1[tablename2{!+3}[fld1].tablename2;
Возможно использование всего этого одновременно.
tablename1.tablename2{ 3,15,27,40~52,$-5, tab.fld,tab1.tab2.fld }.tablename3;
tablename1.tablename2{(3,15,27,40~52,$-5)(tab.fld,tab1.tab2.fld)}.tablename3;
Отсутствие какого-либо знака между круглыми скобками внутри индекса означает операцию "and",
а знак запятая (",") - операцию "or".
Деревянные и списочные операции
Деревянные операции
Деревянные операции записываются в скобках поля -
так, как будто вложенные записи (под-деревья) являются полями текущей записи.
Во вложенной таблице возможно создание новой записи
(с одновременным занесением одинаковых значений в первичный и внешний ключи),
удаление существующей записи и очищение ссылки на существующую запись.
Создание новой записи
(второй пример использует детерминацию):
tablename1[ tablename2<+>[field110 field120] ].tablename2;
tablename1[ tablename2/ref<+>[field110 field120] ].tablename2/ref;
Возможна вставка записей
tablename1[ tablename2<+>tab.lnk->tablename2 ].tablename2;
При удалении записи необходимо указать, сколько уровней иерархии должно быть удалено:
в первом примере ниже удаляется 3 уровня иерархии,
во втором - столько, сколько указано в поле "fld"
(если у удаленных записей самого нижнего уровня иерархии (у "нижайших" записей)
были вложенные записи,
то они сохраняются в базе данных),
в третьем - все уровни:
знак означает,
что должны быть каскадно удалены все вложенные записи.
tablename1[ tablename2<->3 ];
tablename1[ tablename2<->tab.fld ];
tablename1[ tablename2<-> ];
Возможно удалять только записи, обладающие определенными значениями полей
tablename1[ tablename2[field1<10 field2>20]<->3 ];
tablename1[ tablename2[field1<10 field2>20]<->tab.fld ];
tablename1[ tablename2[field1<10 field2>20]<-> ];
Выбрасывание - это присвоение ссылающемуся полю значения "null"
без удаления записи (это используется при обработке графа).
tablename1[ tablename2[field1<10 field2>20]<~>3 ];
tablename1[ tablename2[field1<10 field2>20]<~>tab.fld ];
tablename1[ tablename2[field1<10 field2>20]<~> ];
Списочные операции
Создание и вставка нового элемента списка выглядит так
(вставляем три новых элемента перед 3,5-м и 7-м элементами
в нумерации до создания новых элементов)
tablename1.tablename2{3,5<+>[field11 field12]}{7<+>[field110 field120]}.tablename3;
Аналогично вставка записей в список
осуществляется перед указанным элементом и
означает сдвиг последующих элементов на соответствующее количество позиций,
и вырезание осуществляется начиная с указанного элемента и
означает сдвиг в обратном направлении.
Сдвиг элементов списка выполняется путем изменения значений ссылающихся полей
(в примере ниже в список вставляются все записи, полученные в результате под-запроса).
tablename1.tablename2{5<+>tab.lnk->tablename2}.tablename3;
Вырезание элементов списка есть изменение значений ссылающихся полей и
удаление соответствующие записей,
если у удаляемых записей есть под-деревья, то они также удаляются
(в первом примере ниже удаляется 3 элемента списка,
во втором - столько, сколько указано в поле "fld",
в третьем удаляется остаток списка).
tablename1.tablename2{5<->3 }.tablename3;
tablename1.tablename2{5<->tab.fld}.tablename3;
tablename1.tablename2{5<-> }.tablename3;
Выбрасывание элеменов списка - это то же самое, что вырезание,
но элементы списка (записи) из базы данных не удаляются
(это используется для обработки графа).
Соответствующим ссылающимся полям выброшенных элементов присваивается значение "null",
т.е. выброшенные элементы не соединены друг с другом.
tablename1.tablename2{5<~>3 }.tablename3;
tablename1.tablename2{5<~>tab.fld}.tablename3;
tablename1.tablename2{5<~> }.tablename3;
Выбрасывание всех элементов списка есть разбиение его на изолированные элементы
(следующие два выражения эквивалентны)
tablename1.tablename2{1<~> }.tablename3;
tablename1.tablename2{ref_fieldnull}.tablename3;
К поля можно обратиться
- в скобках поля
(сразу после названия звена (или после индексов списка - если они есть)
могут находиться квадратные скобки, называемые скобками поля)
table[subtable.field5];
-
как к соответствующему месту в уровне иерархии
table.subtable.field5;
В скобках поля полям звена и полям под-дерева можно присваивать и
сравнивать их с числом, текстом (в двойных кавычках),
полем любой таблицы
(в дальнейшем будем называть его правым полем,
предоставляющий его запрос - правым запросом,
а запись, в которой содержится правое поле - правой записью или
правым списком, если она является списком.
Обычные запросы, в т.ч. те, в которых содержаться правые запросы,
будем называть левыми запросами).
Поля всегда стоят слева от оператора сравнения или присваивания,
а числа, текст и правые поля всегда справа.
Обычно данные для правого поля поступают от GUI,
поэтому существует только одна правая запись.
В случае правого списка используется только его первый элемент.
Сравнение полей является условием,
которому звено (а значит - и все дерево) должно удовлетворять,
чтобы попасть в результат запроса.
tablename1.tablename2[a1=1 a2>2, a1≠null a2≤tab.fld, a3="text"].tablename3;
Внутри скобок поля могут применятся круглые скобки для группирования требований,
предъявляемых к полям.
tablename1.tablename2[a1=1 (a2>2, a2<0)].tablename3;
Знак "пробел" (один или несколько знаков "пробел" или "табуляция")
между сравнениями внутри одной и той же скобки поля означает операцию "and",
а знак запятая (",") - операцию "or".
Любое присваивание в скобке поля выполняется после всех сравнений
в той же скобке поля, и только при их успешности.
Присваивания должны быть записаны после всех сравнений.
tab1.tab2[a1=1 (a2>2, a2<0) a11 a2table.fld a3"text"].tab3;
Присваивания можно сгруппировать со сравнениями с помощью круглых скобок,
тогда присваивание в круглых скобках выполняется после всех сравнений
в тех же круглых скобках и при успешности только в них.
Присваивания в круглых скобках должны быть записаны после всех сравнений
в тех же круглых скобках.
Круглые скобки, перечисленные через пробел, выполняются в порядке следования:
каждая последующая круглая скобка выполняется после всех присваиваний предыдущих скобок.
Если сравнение в круглой скобке неуспешно,
то присваивания в этой и оставшихся круглых скобках не выполняются,
а присваивания в предыдущих круглых скобках не отменяются.
tab1.tab2[(a1=1 a12) (a2>2, a2<0 a2table.fld) a3"text"].tab3;
Круглые скобки, перечисленные через запятую, выполняются независимо друг от друга.
Присваивание после них выполняется при успешности в любой из них.
tab1.tab2[(a1=1 a12), (a2>2, a2<0 a2table.fld) a3"text"].tab3;
Во вложенной скобке присваивания выполняются,
только если успешна операция сравнения в той же скобке
(успешность операции сравнения в над-скобке значения не имеет).
В над-скобке присваивания выполняются,
только если успешны операции сравнения во всех вложенных скобках.
tab1.tab2[(a1=1 a12 (a2>2 a210) (a3<0 a3 -10) )].tab3;
Обработка скобок поля разных звеньев происходит в порядке следования звеньев.
Если сравнение в скобке поля неуспешно,
то присваивания в этой и оставшихся скобках,
а также в продолжающей функции не выполняются
(но выполняется в завершающей),
а присваивания в предыдущих скобках не отменяются.
tabname1.tabname2[a1=1 a21].tabname3[b1=1 b21].tabname4[c1=1 c21].tabname5;
Между скобками поля разных звеньев выполняется операция "and".
tablename1.tablename2[a1<100].tablename3[a2<100].tablename4;
Специальным присваиваемым значением является "null".
tablename1.tablename2[a1null].tablename3;
Если звено, являющееся списком, имеет скобки поля (звено "tablename2" в примере ниже),
то в результат запроса попадут только те элементы списка, которые удовлетворяют скобкам поля.
При копировании в другой список или выводе
порядок следования выбранных элементов списка не нарушается.
tablename1.tablename2[a1=1 (a2>2, a2<0)].tablename3;
Если звено, являющееся списком, имеет и индексы списка, и скобки поля,
то индексы списка всегда записываются перед скобками поля,
а скобки поля применяются только к тем элементам списка,
которые указаны в индексах списка.
tablename1.tablename2{3~7}[a1=1 (a2>2, a2<0)].tablename3;
Перестановка
При сравнении левого поля с правым полем вместо знака равенства может использоваться
специальный оператор сравнения под названием перестановка "<~".
Если правый список употребляется только один раз,
то левое поле может быть равно любому значению в правом списке.
tab1.tab2[a1<~table1.field1 a2<~table2.field2].tab3;
Если одинаковый правый список "пермутируется" с несколькими левыми запросами
одного или нескольких звеньев,
то левые поля могут быть равны любому из значений правого списка,
но так, чтобы не быть при этом равными друг другу.
tab1.tab2[a1<~table.field a2<~table.field].tab3;
В качестве левого запроса может быть также
продолжающая и завершающая функции.
tab1.tab2[a1<~table.field a2<~table.field].tab3 | a3<~table.field || a4<~table.field;
Количество употреблений правого запроса в левых запросах и
в продолжающей и завершающей функциях
не должно превышать количества элементов в правом списке.
Если оператор перестановки используется в нунтяку,
то количество пайщиков в результате запроса будет ограничено
количеством элементов правого списка.
tab1.tab2[a<~table.fld]*[a=10].tab3;
Существует несколько псевдо-полей, в которые записывать нельзя:
- "$" обозначает количество элементов в списке и во множестве
tablename1.tablename2{3}[fld$].tablename3;
- "!" обозначает номер уровня иерархии текущего звена.
Уровни иерархии отсчитываются от 1,
отсчет начинается с первого звена запроса.
Записи-элементы списка находятся на одном и том же уровне иерархии.
tablename1.tablename2[a1!]*.tablename3;
tablename1.tablename2*[!=5].tablename3;
-
"^" обозначает значение первичного ключа предыдущего звена
(если предыдущее звено - список,
то обозначает значение первичного ключа того элемента списка,
который является родителем данного звена)
tablename1.tablename2[a1= ^]*.tablename3;
tablename1.tablename2[a1^]*.tablename3;
и само предыдущее звено - когда предваряет название поля
(т.е. в скобках поля можно использовать предикат, относящийся к предшествующему звену -
поля предшествующего звена нужно предварять символом "^")
tablename1.tablename2[^.a1= 10]*.tablename3;
tablename1.tablename2[^.a1 10]*.tablename3;
tablename1.tablename2[sum < ^.sum + ^.weight]*.tablename3;
tablename1.tablename2[sum ^.sum + ^.weight]*.tablename3;
-
"^^" обозначает значение первичного ключа пред-предыдущего звена и само пред-предыдущее звено,
"^^^" - значение первичного ключа пред-пред-предыдущего звена и т.д.
Под-деревья
Индексы списка под-дерева могут содержать знак
,
который означает, что звено ("tab" в примере ниже),
имеющее скобки поля, удовлетворяет запросу,
если хотя бы один элемент множества или списка ("x" в примере ниже)
удовлетворяет требованию.
tab[ x{}.tab2=^^ ];
Наличие под-дерева без указания его полей означает,
звено (а значит и все дерево) удовлетворяет запросу только в том случае,
если у него есть указанное под-дерево
(причем само под-дерево в результат запроса не выводится).
tab1.tab2[t1.t2.t3.t4.t5 table1.table2.table3].tab3;
Если в одном выражении надо наложить условие на несколько полей разных звеньев,
то после записи дерева нужно поставить знак "вертикальная черта" ("|")
и записать сравнение полей разных звеньев,
выражение после вертикальной черты будем называть продолжающей функцией.
tab1.tab2[a2<100].tab3[a3<100].tab4 | a2+a3>125;
Если поля разных звеньев имеют одинаковые имена, но сами звенья разноименные,
то поля в продолжающей функции различают тем,
что перед ними указывают название одного непосредственно предшествующего звена
через знак "точка".
tab1.tab2[a1<100].tab3[a1<100].tab4 | tab2.a1+tab3.a1>125;
Запрещено использовать продолжающую функцию для одноименных звеньев.
tab1.tab2[a2<100].tab3.tab2.tab3 | a2+a3>125;
tab1.tab2[a2<100]:10.tab3[a3<100] | a2+a3>125;
В продолжающей функции может находиться все то, что может находиться в скобке поля,
а порядок обработки в продолжающей функции аналогичен
порядку обработки в скобках поля.
Продолжающая функция выполняется после
завершающей скобки (в т.ч. после всех присваиваний).
tab1.tab2[a1100].tab3[a1100].tab4 | tab2.a1+tab3.a1>125;
Между индексами списка, скобками поля и продолжающей функцией выполняется операция "and".
Если логические сомножители до завершающей функции уже дали значение 'not',
то завершающая функция выполняться не будет.
После продолжающей функции через "||" (без пробела между знаками "вертикальная черта")
может находится завершающая функция
(продолжающая функция перед ней может отсутствовать -
в этом случае завершающая функция оказывается записанной сразу после дерева).
Завершающая функция полностью аналогична продолжающей,
единственное отличие состоит в том,
что завершающая функция всегда использует поля последнего пайщика или звена.
tab1[a1≠0].tab2[a2<100].tab3.tab2.tab3.tab4[a4≠0] | a1+a4=0 || a2+a3>125;
tab1[a1≠0].tab2[a2<100]:10.tab3[a3<100].tab4[a4≠0] | a1+a4=0 || a2+a3>125;
Специальные операции
Удаление всех записей некоторой таблицы выполняется оператором "~~"
(но на вложенные записи удаление рекурсивно, каскадно не распространяется).
Оператор условного выполнения применяется к запросам, его формат
(первая строка - полная форма, вторая и третья строки - сокращенные формы):
одинарные фигурные скобки выполняются, если "request1" возвращает одно или несколько деревьев;
двойные фигурные скобки выполняются в противном случае.
request1 ? { request2 } {{ request3 }};
request1 ? { request2 };
request1 ? {{ request2 }};
Вы можете построит конструкцию типа
request1 ? { }
request2 ? { }
request3 ? { }
request4 ? { }
request5 ? { }
{{ }};
Комментарий - это все, что расположено до конца строки после двух идущих подряд знаков минус,
и все, что заключено в между знаками /* */
-- comment
/*
comment
comment
comment
*/
Тюрин Дмитрий
Сайт управляется системой
uCoz