Слово «Живые», в названии статьи, означает, что механизмы, код и данные, из этих статей, используются в рабочем проекте.
Возможно, вам будет интересно посмотреть на некоторые варианты решений разработки БД (структур, механизмов).
На картинке изображён кусок кода, описывающего глобал правил справочника.
CRUD методы, в процессе своей работы, постоянно обращаются к этим правилам чтобы узнать, какие именно действия необходимо выполнить.
Ранее, мы остановились на том, что у нас есть следующие глобалы:
- ^Dictionary — элементы справочников и их свойства;
- ^NameDictionaryElement — названия элементов справочников на различных языках;
- ^IndexDictionary — основной индекс справочников;
- ^RefsDictionary — глобал обратных ссылок на индекс (его назначение будет описано далее).
^Dictionary("Vehicle","TransmissionType",1,0,"UpdateTime")="62086,66625"
^Dictionary("Vehicle","TransmissionType",1,0,"uid")=888
^Dictionary("Vehicle","TransmissionType",2,0,"UpdateTime")="62086,66625"
^Dictionary("Vehicle","TransmissionType",2,0,"uid")=888
^NameDictionaryElement(1,"partUri",0)="akp"
^NameDictionaryElement(1,"partUri",0,"UpdateTime")="62086,66625"
^NameDictionaryElement(1,"ru",0)="АКП"
^NameDictionaryElement(1,"ru",0,"UpdateTime")="62086,66625"
^NameDictionaryElement(2,"partUri",0)="meh"
^NameDictionaryElement(2,"partUri",0,"UpdateTime")="62086,66625"
^NameDictionaryElement(2,"ru",0)="МЕХ"
^NameDictionaryElement(2,"ru",0,"UpdateTime")="62086,66625"
^IndexDictionary("Vehicle","TransmissionType","name","partUri","akp",1)=1
^IndexDictionary("Vehicle","TransmissionType","name","partUri","meh",2)=1
^IndexDictionary("Vehicle","TransmissionType","name","ru","акп",1)=1
^IndexDictionary("Vehicle","TransmissionType","name","ru","мех",2)=1
^IndexDictionary("Vehicle","TransmissionType","uid",888,1)=1
^IndexDictionary("Vehicle","TransmissionType","uid",888,2)=1
^RefsDictionary(1,"^|""MONTOLOGY""|IndexDictionary(""Vehicle"",""TransmissionType"",""name"",""partUri"",""akp"",1)")=1
^RefsDictionary(1,"^|""MONTOLOGY""|IndexDictionary(""Vehicle"",""TransmissionType"",""name"",""ru"",""акп"",1)")=1
^RefsDictionary(1,"^|""MONTOLOGY""|IndexDictionary(""Vehicle"",""TransmissionType"",""uid"",888,1)")=1
^RefsDictionary(2,"^|""MONTOLOGY""|IndexDictionary(""Vehicle"",""TransmissionType"",""name"",""partUri"",""meh"",2)")=1
^RefsDictionary(2,"^|""MONTOLOGY""|IndexDictionary(""Vehicle"",""TransmissionType"",""name"",""ru"",""мех"",2)")=1
^RefsDictionary(2,"^|""MONTOLOGY""|IndexDictionary(""Vehicle"",""TransmissionType"",""uid"",888,2)")=1
set ^Dictionary("Vehicle","TransmissionType",1,0,"UpdateTime")="62086,66625"
set ^Dictionary("Vehicle","TransmissionType",1,0,"uid")=888
set ^Dictionary("Vehicle","TransmissionType",2,0,"UpdateTime")="62086,66625"
set ^Dictionary("Vehicle","TransmissionType",2,0,"uid")=888
set ^NameDictionaryElement(1,"partUri",0)="akp"
set ^NameDictionaryElement(1,"partUri",0,"UpdateTime")="62086,66625"
set ^NameDictionaryElement(1,"ru",0)="АКП"
set ^NameDictionaryElement(1,"ru",0,"UpdateTime")="62086,66625"
set ^NameDictionaryElement(2,"partUri",0)="meh"
set ^NameDictionaryElement(2,"partUri",0,"UpdateTime")="62086,66625"
set ^NameDictionaryElement(2,"ru",0)="МЕХ"
set ^NameDictionaryElement(2,"ru",0,"UpdateTime")="62086,66625"
set ^IndexDictionary("Vehicle","TransmissionType","name","partUri","akp",1)=1
set ^IndexDictionary("Vehicle","TransmissionType","name","partUri","meh",2)=1
set ^IndexDictionary("Vehicle","TransmissionType","name","ru","акп",1)=1
set ^IndexDictionary("Vehicle","TransmissionType","name","ru","мех",2)=1
set ^IndexDictionary("Vehicle","TransmissionType","uid",888,1)=1
set ^IndexDictionary("Vehicle","TransmissionType","uid",888,2)=1
set ^RefsDictionary(1,"^|""MONTOLOGY""|IndexDictionary(""Vehicle"",""TransmissionType"",""name"",""partUri"",""akp"",1)")=1
set ^RefsDictionary(1,"^|""MONTOLOGY""|IndexDictionary(""Vehicle"",""TransmissionType"",""name"",""ru"",""акп"",1)")=1
set ^RefsDictionary(1,"^|""MONTOLOGY""|IndexDictionary(""Vehicle"",""TransmissionType"",""uid"",888,1)")=1
set ^RefsDictionary(2,"^|""MONTOLOGY""|IndexDictionary(""Vehicle"",""TransmissionType"",""name"",""partUri"",""meh"",2)")=1
set ^RefsDictionary(2,"^|""MONTOLOGY""|IndexDictionary(""Vehicle"",""TransmissionType"",""name"",""ru"",""мех"",2)")=1
set ^RefsDictionary(2,"^|""MONTOLOGY""|IndexDictionary(""Vehicle"",""TransmissionType"",""uid"",888,2)")=1
Глобал правил справочников
В этом глобале хранятся описания всех онтологий и типов справочников. Указываются специфические свойства, характеристики, различные виды индексов. Прописываются вызовы функций проверки данных. Выстраивается иерархия вложенных структур и прочее. Я буду рассказывать постепенно и объяснять на примерах. Со временем, глобал правил будет разрастаться. То же самое происходит в процессе жизни проекта: появляются новые типы справочников; добавляются новые свойства; меняются служебные функции обработки данных; программируется новая функциональность. Основное назначение этого глобала — хранить в себе всю специфику обработки справочных данных.
Рассмотрим как устроен глобал правил ^RuleDictionary — пусть его индексы означают следующее:
- онтология
- тип справочника
- название одного из CRUD методов
- указание на тип выполняемого действия
- очерёдность выполняемого действия
Обращаю ваше внимание, что индексы глобала правил, начиная с третьего, в других случаях, могут означать что-то другое (когда мы столкнёмся с этим на примере — я опишу это подробно).
Распечатаем все правила для онтологии SimpleOntology выполним комманду:
zwrite ^RuleDictionary(«SimpleOntology»)
Напоминаю что «MONTOLOGY» — это название пространства имён.
MONTOLOGY>zwrite ^RuleDictionary("SimpleOntology")
^RuleDictionary("SimpleOntology","SimpleType","create","check",10)="do clearPunctuationAndControlCharAllLang()"
^RuleDictionary("SimpleOntology","SimpleType","create","check",20)="do checkUniqueNameElementAllLang()"
^RuleDictionary("SimpleOntology","SimpleType","update","check",10)="do clearPunctuationAndControlCharAllLang()"
^RuleDictionary("SimpleOntology","SimpleType","update","check",20)="do checkUniqueNameElementAllLang()"
MONTOLOGY>
set ^RuleDictionary("SimpleOntology","SimpleType","create","check",10)="do clearPunctuationAndControlCharAllLang()"
set ^RuleDictionary("SimpleOntology","SimpleType","create","check",20)="do checkUniqueNameElementAllLang()"
set ^RuleDictionary("SimpleOntology","SimpleType","update","check",10)="do clearPunctuationAndControlCharAllLang()"
set ^RuleDictionary("SimpleOntology","SimpleType","update","check",20)="do checkUniqueNameElementAllLang()"
Давайте внимательно посмотрим на то что мы вывели. Как видно, в онтологии SimpleOntology существует один тип справочника SimpleType. В этом справочнике нет никаких дополнительных параметров (кроме свойств по умолчанию). Для метода create определено две функции предварительной обработки и проверки данных. Те же самые функции, определены для метода update. Они хранятся в значениях глобала. Очерёдность действий (10, 20) может быть не обязательно целым числом. Последовательность выполнения идёт от меньшего к большему. Правила для SimpleOntology и SimpleType будут использоваться для всех простых справочников по умолчанию, в которых существуют только стандартные свойства: названия, дата обновления и uid пользователя.
Create
Чтобы понять, как это работает — рассмотрим метод create. Для простоты, опустим блокировки, обработку транзакций и различные виды ошибок. Потом мы ещё к этому вернёмся.
#; --------------------------------------------------------------------------------------------------
#; Создать элемент справочника.
#; При создании можно задать название элемента на разных языках.
#; --------------------------------------------------------------------------------------------------
create()
#; очищаем карту выходных параметров
kill map("out")
#; объявляем новую локальную переменну t
#; в которой будет хранится вся временная информация метода create
new t
#; выполняем функцию проверки входных данных
set t("err")=$$check("create")
#; выходим, если есть ошибка
if t("err")<0 { quit t("err") }
#; устанавливаем свойство "UpdateTime"
do setProperty(t("ontology"),t("type"),"UpdateTime",$horolog,t("id"),"false")
#; устанавливаем свойство "uid"
do setProperty(t("ontology"),t("type"),"uid",t("uid"),t("id"),"true")
set t("lang")=""
for {
#; получаем следующий язык (на котором задано название элемента справочника)
set t("lang")=$order(t("nameList",t("lang")))
#; выходим из цикла, если следующего языка нет
quit:t("lang")=""
#; получаем название для текущего языка
set t("name")=$get(t("nameList",t("lang")))
#; устанавливаем название для элемента справочника
do setName(t("ontology"),t("type"),t("lang"),t("name"),t("id"))
}
#; сохраняем онтологию и тип элемента справочника в индексном глобале
do saveOntoAndTypeID
#; сохраняем путь к элементу справочника в индексном глобале
do saveElementPath
#; записываем в карту выходных параметров идентификатор созданного элемента
set map("out","id")=t("id")
#; очищаем карту входных параметров
kill map("in")
#; возвращаем идентификатор созданного элемента
quit t("id")
#; --------------------------------------------------------------------------------------------------
Мы видим, что в метод create() явно не передаются никакие параметры. Предполагается, что они записаны в локальную переменную map, которая должна быть определена до вызова метода. В переменной map, нами будет использоваться две основные ветки: map(«in») и map(«out»). Локальная переменная от глобальной отличается только тем, что она не сохраняется на диске и имеет ограничение на максимальный размер. В остальном — она такая же индексированная, как и глобал. Её можно передавать как параметр функциям: $order(), $qsubscript(), $qlength(), $name() и другим. Чтобы не плодить множество переменных, и не следить за областями видимости: достаточно объявить одну переменную new t. Переменная t будет доступна в любом месте метода create, а также во всех других программах и подпрограммах которые из него вызываются. То же самое можно сказать и про переменную map. Если же внутри какой-то функции, вызываемой в процессе выполнения create, встретится new t — то эта функция, не будет иметь доступ к переменной t, объявленной внутри метода create. Она будет работать со своей переменной t, находящейся внутри своей области видимости (в другом месте стека переменных). Возможно, принципы области видимости переменных в Caché я объяснил не достаточно понятно — задавайте вопросы в комментариях.
check
Далее в t(«err») мы записываем результат функции $$check(«create»). Если все проверки прошли успешно — функция вернёт 0, в противном случае — результат будет отрицательным. Посмотрим как устроена функция $$check() и вызываемые из неё методы. Замечу, что в данных статьях: метод, функция, подпрограмма — слова синонимы. Служебное слово private — означает что метод можно вызвать только внтури программы Dictionary.
#; --------------------------------------------------------------------------------------------------
#; Функция проверки.
#; Использует глобал правил ^RuleDictionary для определения вызова функций.
#; --------------------------------------------------------------------------------------------------
check(action)private
#; объявляем новую локальную переменную check
new check
#; вызываем обязательную функцию проверки для входного действия action
set check=$case(action,"create":$$checkCreate(),"update":$$checkUpdate(),"delete":$$checkDelete(),:-1)
#; возвращаем ошибку
quit:check<0 check
#; получаем "uid" пользователя из входной карты параметров
set t("uid")=$get(map("in","uid"),888)
#; проверяем, существуют ли ветка глобала правил "check", для данной онтологии типа справочника и действия
if $data(^RuleDictionary(t("ontology"),t("type"),action,"check"))
{
#; в t("map") записываем каноническое имя нужной ветки глобала правил
set t("map")=$name(^RuleDictionary(t("ontology"),t("type"),action,"check"))
}
else {
#; в t("map") записываем каноническое имя ветки глобала правил для онтологии и типа справочника по умолчанию
set t("map")=$name(^RuleDictionary("SimpleOntology","SimpleType",action,"check"))
}
set check("i")=""
for {
#; получаем следующий номер действия
set check("i")=$order(@t("map")@(check("i")))
#; выходим из цикла, если следующего номера нет
quit:check("i")=""
#; выполняем текущую функцию проверки
xecute $get(@t("map")@(check("i")))
#; выходим из цикла, если проверка не пройдена
quit:check<0
}
#; возвращаем результат проверок
quit check
#; --------------------------------------------------------------------------------------------------
#; Функция проверки элемента справочника при создании.
#; Получает id, ontology, type, и другие параметры.
#; --------------------------------------------------------------------------------------------------
checkCreate()private
#; получаем онтологию из входной карты параметров
s t("ontology")=$get(map("in","ontology"),"")
#; возвращаем ошибку если онтология не задана
quit:t("ontology")="" -1
#; получаем тип справочника из входной карты параметров
set t("type")=$get(map("in","type"),"")
#; возвращаем ошибку, если тип справочника не задан
quit:t("type")="" -1
#; выполняем функцию проверки имён
set t("check")=$$checkNames()
#; возвращаем ошибку, если проверка не пройдена
quit:t("check")<0 t("check")
#; получаем идентификатор для создаваемого элемента справочника
set t("id")=$increment(^Dictionary("MaxID"))
#; получаем путь к ветке глобала ^Dictionary
set t("path")=$name(^Dictionary(t("ontology"),t("type"),t("id")))
#; возвращаем успешный результат проверки
quit 0
#; --------------------------------------------------------------------------------------------------
checkUpdate() quit 0
#; --------------------------------------------------------------------------------------------------
checkDelete() quit 0
#; --------------------------------------------------------------------------------------------------
#; Проверка имён на различных языках.
#; --------------------------------------------------------------------------------------------------
checkNames()private
#; получаем имена на всех языках из входной карты параметров
merge t("nameList")=map("in","nameList")
#; устанавливаем в t("check") ошибку
#; для случая когда ни один язык не задан
set t("check")=-1
set t("lang")=""
for {
#; получаем следующий язык
set t("lang")=$order(t("nameList",t("lang")))
#; выходим из цикла, если следующего языка нет
quit:t("lang")=""
#; получаем название для текущего языка
set t("name")=$get(t("nameList",t("lang")),"")
#; если имя пустое
if t("name")="" {
#; устанавливаем в t("check") ошибку
set t("check")=-1
#; выходим из цикла
quit
}
else {
#; в t("check") записываем признак того, что всё в порядке
set t("check")=0
}
}
#; возвращаем результат проверки имён
quit t("check")
#; --------------------------------------------------------------------------------------------------
В самом начале метода check() мы выполняем обязательную функцию проверки для текущего действия. В нашем случае это функция checkCreate(). Обратите внимание, что заглушки функций checkUpdate() и checkDelete() — также присутствуют в коде — это необходимо тем, кто скопирует программу Dictionary себе и откомпилирует её.
Подпрограмма checkCreate() проверяет наличие во входной карте параметров онтологии и типа справочника. Внутри checkNames() — проверяется наличие имён для каждого заданного языка, с обязательным условием наличия хотя бы одного языка (в нашей системе нельзя создать элемент справочника без названия). В этом же методе происходит генерация идентификатора создаваемого элемента справочника безопасной функцией $increment(). Текущий максимальный идентификатор элемента справочника (для любой онтологии и типа) хранится в ^Dictionary(«MaxID»).
все идентификаторы уникальны, даже в пределах различных справочников и онтологий
В конце метода checkCreate() в переменную t(«path») мы записываем каноническое имя ветки глобала ^Dictionary, в которой будет хранится создаваемый элемент справочника.
Обратим внимание на сроку merge t(«nameList»)=map(«in»,«nameList») метода checkNames().
Команда merge обеспечивает копирование целых поддеревьев индексированных переменных друг в друга, не важно определены ли они как локальные или как глобальные.
Определение взято из книги: Вольфганг Кирстен. Михаэль Ирингер. Матиас Кюн. Бернхард Рериг. Постреляционная СУБД Caché 5. Объектно-ориентированная разработка приложений. Второе издание. Москва. Издательство БИНОМ. 2005. Перевод А.Маслова, К. Аристова. Страница 116.
Итак, после выполнения метода checkCreate(), в локальной переменной t будут содержаться следующие важные данные:
- t(«ontology») — онтология элемента справочника
- t(«type») — тип справочнкиа
- t(«nameList») — названия на всех заданных языках
- t(«id») — идентификатор
- t(«path») — каноническое имя ветки глобала ^Dictionary, где будет хранится информация об элементе
Продолжим выполнение функции check(). После того как выполнился checkCreate(), мы получаем uid пользователя (888 — по умолчанию). Далее в переменную t(«map») — мы записываем каноническое имя ветки глобала правил, содержащей необходимые функции проверки для нашей онтологии типа справочника и действия. Так как сейчас, в глобале ^RuleDictionary, определены правила только для онтологии и типа по умолчанию, то в t(«map») будет записан канонический вид ветки ^RuleDictionary(«SimpleOntology»,«SimpleType»,«create»,«check»).
Разберём подробно строку:
set check("i")=$order(@t("map")@(check("i")))
Напоминаю что @ — это оператор косвенности. Начальное значение check(«i») = "". Значит в начале цикла строка эквивалентна следующей:
set check("i")=$order(^RuleDictionary("SimpleOntology","SimpleType","create","check",""))
То есть check(«i») будет равно 10. Если синтаксис оператора косвенности вызывает вопросы — спрашивайте в комментариях.
Теперь разберём:
xecute $get(@t("map")@(check("i")))
Команда xecute обеспечивает выполнение строки символов в виде однострочной подпрограммы.
Страница 115, та же книга.
То есть в начале цикла мы выполним то, что записано в ^RuleDictionary(«SimpleOntology»,«SimpleType»,«create»,«check»,10). А это:
do clearPunctuationAndControlCharAllLang()
После выполнения этой подпрограммы, из названий на всех заданных языках будет выброшена пунктуация и служебные символы.
На следующей итерации цикла мы выполним значение ^RuleDictionary(«SimpleOntology»,«SimpleType»,«create»,«check»,20). А это:
do checkUniqueNameElementAllLang()
Эта подпрограмма проверяет уникальность имени для всех заданных языков. Если хотя бы одно имя не уникально, в переменную check, определённую ещё в методе check(), запишется ошибка.
#; --------------------------------------------------------------------------------------------------
clearPunctuationAndControlCharAllLang()
set t("lang")=""
for {
#; цикл по всем языкам
set t("lang")=$order(t("nameList",t("lang")))
quit:t("lang")=""
#; очищаем пунктуацию и служебные символы из названия на текущем языке
set t("nameList",t("lang"))=$$clearPunctuationAndControlChar($get(t("nameList",t("lang")),""))
}
quit
#; --------------------------------------------------------------------------------------------------
checkUniqueNameElementAllLang()
set t("lang")=""
for {
#; цикл по всем языкам
set t("lang")=$order(t("nameList",t("lang")))
quit:t("lang")=""
#; получаем текущее название
set t("name")=$get(t("nameList",t("lang")),"")
#; в переменную check записываем статус уникальности имени (0 - значит уникально)
set check=$$checkUniqueNameElement()
#; выходим из цикла если какое-то имя не уникально
quit:check<0
}
quit
#; --------------------------------------------------------------------------------------------------
#; Функция удаляет из строки всю пунктуацию и служебные символы.
#; --------------------------------------------------------------------------------------------------
clearPunctuationAndControlChar(str)
new t
#; в t("str") будет хранится результирующая строка
set t("str")=""
#; цикл по всем символам строки
for t("i")=1:1:$length(str) {
#; получаем следующий символ
set t("ch")=$extract(str,t("i"))
if '((t("ch")?1P)||(t("ch")?1C)) {
#; добавляем к результитрующей строке текущий символ
set t("str")=t("str")_t("ch")
}
}
#; возвращаем полученную строку
quit t("str")
#; --------------------------------------------------------------------------------------------------
#; --------------------------------------------------------------------------------------------------
#; Функция проверяет на уникальность имя элемента, для указанного языка, типа и онтологии.
#; При проверке, регистр переводится вниз.
#; --------------------------------------------------------------------------------------------------
checkUniqueNameElement()
#; устанавливаем успешный результат
set t("q")=0
set t("uniqueId")=""
for {
#; проверяем, есть ли в индексном глобале идентификатор элемента справочника
#; для данной онтологии, типа,языка и названия (в нижнем регистре)
set t("uniqueId")=$order(^IndexDictionary(t("ontology"),t("type"),"name",t("lang"),$zconvert(t("name"),"l"),t("uniqueId")))
#; выходим если идентификатора нет
quit:t("uniqueId")=""
#; проверяем, равенство найденного идентификатора текущему элементу
#; это необходимо для метода update
if (t("uniqueId")'=t("id")) {
#; устанавливаем ошибку, если найденный идентификатор отличается от текущего
set t("q")=-1
quit
}
}
#; возвращаем результат
quit t("q")
#; --------------------------------------------------------------------------------------------------
В подпрограммах используется новые, для нас, функции: $zconvert(), $length(), $extract(); а также оператор проверки по шаблону — символ "?". Проверка наличия в строке пунктуации и служебных символов, может быть выполнена и другим способом, с помощью встроенных кашевских функций, однако у меня в проекте используется метод посимвольного анализа.
Итак, наш метод create(), успешно выполнил строку: set t(«err»)=$$check(«create»), а в переменной t — хранится важная служебная информация.
setProperty
Далее мы устанавливаем свойства «UpdateTime» и «uid» с помощью функции setProperty().
#; --------------------------------------------------------------------------------------------------
#; Установка значения, для определённого свойства элемента справочника.
#; По умолчанию создаётся индекс и обратная ссылка на него.
#; --------------------------------------------------------------------------------------------------
setProperty(ontology,type,property,value,id,index="true")private
#; устанавливаем значение свойства в нулевую (текущую) версию элемента справочника
set ^Dictionary(ontology,type,id,0,property)=value
#; если свойство индексированное
if index="true" {
#; создаём индекс
set ^IndexDictionary(ontology,type,property,value,id)=1
#; сохраняем обратную ссылку на индекс
set ^RefsDictionary(id,$name(^|"MONTOLOGY"|IndexDictionary(ontology,type,property,value,id)))=1
}
quit 0
#; --------------------------------------------------------------------------------------------------
$horolog — это текущая дата и время в формате Caché. Обратите внимание, что свойство «UpdateTime» — мы не индексируем (крайний параметр index=«false»). В этом же методе впервые появляется глобал ^RefsDictionary. Структура его индексов проста:
- идентификатор элемента
- каноническое имя ветки глобала индекса
То есть при необходимости (например при удалении элемента) — мы быстро получим все различные наборы индексов, которые содержат в себе упоминание данного элемента.
setName
Далее мы устанавливаем значения названий с помощью функции setName().
#; --------------------------------------------------------------------------------------------------
#; Установка значения имени, для определённого языка элемента справочника.
#; Создаётся индекс(в нижнем регистре) и обратная ссылка.
#; --------------------------------------------------------------------------------------------------
setName(ontology,type,lang,value,id)private
#; устанавливаем значение имени для текущей версии и языка
set ^NameDictionaryElement(id,lang,0)=value
#; сохраняем дату создания/обновления
set ^NameDictionaryElement(id,lang,0,"UpdateTime")=$horolog
#; создаём индекс по названию
set ^IndexDictionary(ontology,type,"name",lang,$zconvert(value,"l"),id)=1
#; сохраняем обратную ссылку
set ^RefsDictionary(id,$name(^|"MONTOLOGY"|IndexDictionary(ontology,type,"name",lang,$zconvert(value,"l"),id)))=1
quit 0
#; --------------------------------------------------------------------------------------------------
После этого мы вызываем функцию saveOntoAndTypeID, которая запишет в ветку глобала ^IndexDictionary(«ID»,t(«id»)) онтологию и тип элемента. Это необходимо на случай, если в будущем, нам нужно будет узнать по элементу справочника, к какому типу и онтологии он принадлежит. Далее функция saveElementPath в ту же ветку, запишет путь к элементу справочника.
#; --------------------------------------------------------------------------------------------------
saveOntoAndTypeID
set ^IndexDictionary("ID",t("id"),"ontology")=t("ontology")
set ^RefsDictionary(t("id"),$name(^|"MONTOLOGY"|IndexDictionary("ID",t("id"),"ontology")))=1
set ^IndexDictionary("ID",t("id"),"type")=t("type")
set ^RefsDictionary(t("id"),$name(^|"MONTOLOGY"|IndexDictionary("ID",t("id"),"type")))=1
quit
#; --------------------------------------------------------------------------------------------------
saveElementPath
set ^IndexDictionary("ID",t("id"),"path")=t("path")
set ^RefsDictionary(t("id"),$name(^|"MONTOLOGY"|IndexDictionary("ID",t("id"),"path")))=1
quit
#; --------------------------------------------------------------------------------------------------
Обратите внимание, что все записи в глобал ^RefsDictionary содержат в себе явное название пространства имён |«MONTOLOGY»|. Если в будущем, мы будем работать с этим глобалом из другого пространства имён, то у нас всегда будет корректный полный путь. Конечно, даже внутри самой программы Dictionary, необходимо прописывать полные пути (на случай вызова методов из других пространств имён). У меня в проекте, это реализовано посредством макросов, однако для простоты, я пока что привожу упрощённый код.
#; --------------------------------------------------------------------------------------------------
#; Имя программы
#; --------------------------------------------------------------------------------------------------
Dictionary
#; --------------------------------------------------------------------------------------------------
#; Получить элемент справочника.
#; --------------------------------------------------------------------------------------------------
retrieve(id,lang="ru",version=0)
quit $get(^NameDictionaryElement(id,lang,version),"")
#; --------------------------------------------------------------------------------------------------
#; Получить список элементов справочника по индексу.
#; --------------------------------------------------------------------------------------------------
retrieveListByIndex(ontology,type,index,value,str="",lang="ru")
#;принудительно переводим в нижний регистр строку, с которой должны начинатся названия элементов
set str=$zconvert(str,"L")
set id=""
for {
#;получаем следующий идентификатор(индекс) на текущем уровне
set id=$order(^IndexDictionary(ontology,type,index,value,id))
#;выходим из цикла, если на текущем уровне данных больше нет
quit:id=""
#;получаем название элемента
set name=$$retrieve(id,lang)
#;проверяем начинается ли название элемента строкой str
if $extract($zconvert(name,"L"),1,$length(str))=str {
#;выводим результат (идентификатор и название)
write id_" "_name,!
}
}
quit
#; --------------------------------------------------------------------------------------------------
#; Создать элемент справочника.
#; При создании можно задать название элемента на разных языках.
#; --------------------------------------------------------------------------------------------------
create()
#; очищаем карту выходных параметров
kill map("out")
#; объявляем новую локальную переменну t
#; в которой будет хранится вся временная информация метода create
new t
#; выполняем функцию проверки входных данных
set t("err")=$$check("create")
#; выходим, если есть ошибка
if t("err")<0 { quit t("err") }
#; устанавливаем свойство "UpdateTime"
do setProperty(t("ontology"),t("type"),"UpdateTime",$horolog,t("id"),"false")
#; устанавливаем свойство "uid"
do setProperty(t("ontology"),t("type"),"uid",t("uid"),t("id"),"true")
set t("lang")=""
for {
#; получаем следующий язык (на котором задано название элемента справочника)
set t("lang")=$order(t("nameList",t("lang")))
#; выходим из цикла, если следующего языка нет
quit:t("lang")=""
#; получаем название для текущего языка
set t("name")=$get(t("nameList",t("lang")))
#; устанавливаем название для элемента справочника
do setName(t("ontology"),t("type"),t("lang"),t("name"),t("id"))
}
#; сохраняем онтологию и тип элемента справочника в индексном глобале
do saveOntoAndTypeID
#; сохраняем путь к элементу справочника в индексном глобале
do saveElementPath
#; записываем в карту выходных параметров идентификатор созданного элемента
set map("out","id")=t("id")
#; очищаем карту входных параметров
kill map("in")
#; возвращаем идентификатор созданного элемента
quit t("id")
#; --------------------------------------------------------------------------------------------------
#; Функция проверки.
#; Использует глобал правил ^RuleDictionary для определения вызова функций.
#; --------------------------------------------------------------------------------------------------
check(action)private
#; объявляем новую локальную переменную check
new check
#; вызываем обязательную функцию проверки для входного действия action
set check=$case(action,"create":$$checkCreate(),"update":$$checkUpdate(),"delete":$$checkDelete(),:-1)
#; возвращаем ошибку
quit:check<0 check
#; получаем "uid" пользователя из входной карты параметров
set t("uid")=$get(map("in","uid"),888)
#; проверяем, существуют ли ветка глобала правил "check", для данной онтологии типа справочника и действия
if $data(^RuleDictionary(t("ontology"),t("type"),action,"check"))
{
#; в t("map") записываем каноническое имя нужной ветки глобала правил
set t("map")=$name(^RuleDictionary(t("ontology"),t("type"),action,"check"))
}
else {
#; в t("map") записываем каноническое имя ветки глобала правил для онтологии и типа справочника по умолчанию
set t("map")=$name(^RuleDictionary("SimpleOntology","SimpleType",action,"check"))
}
set check("i")=""
for {
#; получаем следующий номер действия
set check("i")=$order(@t("map")@(check("i")))
#; выходим из цикла, если следующего номера нет
quit:check("i")=""
#; выполняем текущую функцию проверки
xecute $get(@t("map")@(check("i")))
#; выходим из цикла, если проверка не пройдена
quit:check<0
}
#; возвращаем результат проверок
quit check
#; --------------------------------------------------------------------------------------------------
#; Функция проверки элемента справочника при создании.
#; Получает id, ontology, type, и другие параметры.
#; --------------------------------------------------------------------------------------------------
checkCreate()private
#; получаем онтологию из входной карты параметров
s t("ontology")=$get(map("in","ontology"),"")
#; возвращаем ошибку если онтология не задана
quit:t("ontology")="" -1
#; получаем тип справочника из входной карты параметров
set t("type")=$get(map("in","type"),"")
#; возвращаем ошибку, если тип справочника не задан
quit:t("type")="" -1
#; выполняем функцию проверки имён
set t("check")=$$checkNames()
#; возвращаем ошибку, если проверка не пройдена
quit:t("check")<0 t("check")
#; получаем идентификатор для создаваемого элемента справочника
set t("id")=$increment(^Dictionary("MaxID"))
#; получаем путь к ветке глобала ^Dictionary
set t("path")=$name(^Dictionary(t("ontology"),t("type"),t("id")))
#; возвращаем успешный результат проверки
quit 0
#; --------------------------------------------------------------------------------------------------
checkUpdate() quit 0
#; --------------------------------------------------------------------------------------------------
checkDelete() quit 0
#; --------------------------------------------------------------------------------------------------
#; Проверка имён на различных языках.
#; --------------------------------------------------------------------------------------------------
checkNames()private
#; получаем имена на всех языках из входной карты параметров
merge t("nameList")=map("in","nameList")
#; устанавливаем в t("check") ошибку
#; для случая когда ни один язык не задан
set t("check")=-1
set t("lang")=""
for {
#; получаем следующий язык
set t("lang")=$order(t("nameList",t("lang")))
#; выходим из цикла, если следующего языка нет
quit:t("lang")=""
#; получаем название для текущего языка
set t("name")=$get(t("nameList",t("lang")),"")
#; если имя пустое
if t("name")="" {
#; устанавливаем в t("check") ошибку
set t("check")=-1
#; выходим из цикла
quit
}
else {
#; в t("check") записываем признак того, что всё в порядке
set t("check")=0
}
}
#; возвращаем результат проверки имён
quit t("check")
#; --------------------------------------------------------------------------------------------------
clearPunctuationAndControlCharAllLang()
set t("lang")=""
for {
#; цикл по всем языкам
set t("lang")=$order(t("nameList",t("lang")))
quit:t("lang")=""
#; очищаем пунктуацию и служебные символы из названия на текущем языке
set t("nameList",t("lang"))=$$clearPunctuationAndControlChar($get(t("nameList",t("lang")),""))
}
quit
#; --------------------------------------------------------------------------------------------------
checkUniqueNameElementAllLang()
set t("lang")=""
for {
#; цикл по всем языкам
set t("lang")=$order(t("nameList",t("lang")))
quit:t("lang")=""
#; получаем текущее название
set t("name")=$get(t("nameList",t("lang")),"")
#; в переменную check записываем статус уникальности имени (0 - значит уникально)
set check=$$checkUniqueNameElement()
#; выходим из цикла если какое-то имя не уникально
quit:check<0
}
quit
#; --------------------------------------------------------------------------------------------------
#; Функция удаляет из строки всю пунктуацию и служебные символы.
#; --------------------------------------------------------------------------------------------------
clearPunctuationAndControlChar(str)
new t
#; в t("str") будет хранится результирующая строка
set t("str")=""
#; цикл по всем символам строки
for t("i")=1:1:$length(str) {
#; получаем следующий символ
set t("ch")=$extract(str,t("i"))
if '((t("ch")?1P)||(t("ch")?1C)) {
#; добавляем к результитрующей строке текущий символ
set t("str")=t("str")_t("ch")
}
}
#; возвращаем полученную строку
quit t("str")
#; --------------------------------------------------------------------------------------------------
#; --------------------------------------------------------------------------------------------------
#; Функция проверяет на уникальность имя элемента, для указанного языка, типа и онтологии.
#; При проверке, регистр переводится вниз.
#; --------------------------------------------------------------------------------------------------
checkUniqueNameElement()
#; устанавливаем успешный результат
set t("q")=0
set t("uniqueId")=""
for {
#; проверяем, есть ли в индексном глобале идентификатор элемента справочника
#; для данной онтологии, типа,языка и названия (в нижнем регистре)
set t("uniqueId")=$order(^IndexDictionary(t("ontology"),t("type"),"name",t("lang"),$zconvert(t("name"),"l"),t("uniqueId")))
#; выходим если идентификатора нет
quit:t("uniqueId")=""
#; проверяем, равенство найденного идентификатора текущему элементу
#; это необходимо для метода update
if (t("uniqueId")'=t("id")) {
#; устанавливаем ошибку, если найденный идентификатор отличается от текущего
set t("q")=-1
quit
}
}
#; возвращаем результат
quit t("q")
#; --------------------------------------------------------------------------------------------------
#; Установка значения, для определённого свойства элемента справочника.
#; По умолчанию создаётся индекс и обратная ссылка на него.
#; --------------------------------------------------------------------------------------------------
setProperty(ontology,type,property,value,id,index="true")private
#; устанавливаем значение свойства в нулевую (текущую) версию элемента справочника
set ^Dictionary(ontology,type,id,0,property)=value
#; если свойство индексированное
if index="true" {
#; создаём индекс
set ^IndexDictionary(ontology,type,property,value,id)=1
#; сохраняем обратную ссылку на индекс
set ^RefsDictionary(id,$name(^|"MONTOLOGY"|IndexDictionary(ontology,type,property,value,id)))=1
}
quit 0
#; --------------------------------------------------------------------------------------------------
#; Установка значения имени, для определённого языка элемента справочника.
#; Создаётся индекс(в нижнем регистре) и обратная ссылка.
#; --------------------------------------------------------------------------------------------------
setName(ontology,type,lang,value,id)private
#; устанавливаем значение имени для текущей версии и языка
set ^NameDictionaryElement(id,lang,0)=value
#; сохраняем дату создания/обновления
set ^NameDictionaryElement(id,lang,0,"UpdateTime")=$horolog
#; создаём индекс по названию
set ^IndexDictionary(ontology,type,"name",lang,$zconvert(value,"l"),id)=1
#; сохраняем обратную ссылку
set ^RefsDictionary(id,$name(^|"MONTOLOGY"|IndexDictionary(ontology,type,"name",lang,$zconvert(value,"l"),id)))=1
quit 0
#; --------------------------------------------------------------------------------------------------
saveOntoAndTypeID
set ^IndexDictionary("ID",t("id"),"ontology")=t("ontology")
set ^RefsDictionary(t("id"),$name(^|"MONTOLOGY"|IndexDictionary("ID",t("id"),"ontology")))=1
set ^IndexDictionary("ID",t("id"),"type")=t("type")
set ^RefsDictionary(t("id"),$name(^|"MONTOLOGY"|IndexDictionary("ID",t("id"),"type")))=1
quit
#; --------------------------------------------------------------------------------------------------
saveElementPath
set ^IndexDictionary("ID",t("id"),"path")=t("path")
set ^RefsDictionary(t("id"),$name(^|"MONTOLOGY"|IndexDictionary("ID",t("id"),"path")))=1
quit
#; --------------------------------------------------------------------------------------------------
Теперь удалим все наши глобалы, кроме глобала правил ^RuleDictionary:
MONTOLOGY>kill ^Dictionary,^IndexDictionary,^NameDictionaryElement,^RefsDictionary
MONTOLOGY>
И создадим два первых элемента справочника:
MONTOLOGY>kill map
MONTOLOGY>set map("in","ontology")="Vehicle",map("in","type")="TransmissionType"
MONTOLOGY>set map("in","nameList","ru")=" АКП,",map("in","nameList","partUri")="akp"
MONTOLOGY>write $$create^Dictioanry()
1
MONTOLOGY>kill map
MONTOLOGY>set map("in","ontology")="Vehicle",map("in","type")="TransmissionType"
MONTOLOGY>set map("in","nameList","ru")="МЕХ",map("in","nameList","partUri")="meh"
MONTOLOGY>write $$Dictioanry^t()
2
MONTOLOGY>
Распечатаем все наши глобалы:
MONTOLOGY>zwrite ^Dictionary,^IndexDictionary,^NameDictionaryElement,^RefsDictionary
^Dictionary("MaxID")=2
^Dictionary("Vehicle","TransmissionType",1,0,"UpdateTime")="62948,47015"
^Dictionary("Vehicle","TransmissionType",1,0,"uid")=888
^Dictionary("Vehicle","TransmissionType",2,0,"UpdateTime")="62948,47022"
^Dictionary("Vehicle","TransmissionType",2,0,"uid")=888
^IndexDictionary("ID",1,"ontology")="Vehicle"
^IndexDictionary("ID",1,"path")="^Dictionary(""Vehicle"",""TransmissionType"",1)"
^IndexDictionary("ID",1,"type")="TransmissionType"
^IndexDictionary("ID",2,"ontology")="Vehicle"
^IndexDictionary("ID",2,"path")="^Dictionary(""Vehicle"",""TransmissionType"",2)"
^IndexDictionary("ID",2,"type")="TransmissionType"
^IndexDictionary("Vehicle","TransmissionType","name","partUri","akp",1)=1
^IndexDictionary("Vehicle","TransmissionType","name","partUri","meh",2)=1
^IndexDictionary("Vehicle","TransmissionType","name","ru","акп",1)=1
^IndexDictionary("Vehicle","TransmissionType","name","ru","мех",2)=1
^IndexDictionary("Vehicle","TransmissionType","uid",888,1)=1
^IndexDictionary("Vehicle","TransmissionType","uid",888,2)=1
^NameDictionaryElement(1,"partUri",0)="akp"
^NameDictionaryElement(1,"partUri",0,"UpdateTime")="62948,47015"
^NameDictionaryElement(1,"ru",0)="АКП"
^NameDictionaryElement(1,"ru",0,"UpdateTime")="62948,47015"
^NameDictionaryElement(2,"partUri",0)="meh"
^NameDictionaryElement(2,"partUri",0,"UpdateTime")="62948,47022"
^NameDictionaryElement(2,"ru",0)="МЕХ"
^NameDictionaryElement(2,"ru",0,"UpdateTime")="62948,47022"
^RefsDictionary(1,"^|""MONTOLOGY""|IndexDictionary(""ID"",1,""ontology"")")=1
^RefsDictionary(1,"^|""MONTOLOGY""|IndexDictionary(""ID"",1,""path"")")=1
^RefsDictionary(1,"^|""MONTOLOGY""|IndexDictionary(""ID"",1,""type"")")=1
^RefsDictionary(1,"^|""MONTOLOGY""|IndexDictionary(""Vehicle"",""TransmissionType"",""name"",""partUri"",""akp"",1)")=1
^RefsDictionary(1,"^|""MONTOLOGY""|IndexDictionary(""Vehicle"",""TransmissionType"",""name"",""ru"",""акп"",1)")=1
^RefsDictionary(1,"^|""MONTOLOGY""|IndexDictionary(""Vehicle"",""TransmissionType"",""uid"",888,1)")=1
^RefsDictionary(2,"^|""MONTOLOGY""|IndexDictionary(""ID"",2,""ontology"")")=1
^RefsDictionary(2,"^|""MONTOLOGY""|IndexDictionary(""ID"",2,""path"")")=1
^RefsDictionary(2,"^|""MONTOLOGY""|IndexDictionary(""ID"",2,""type"")")=1
^RefsDictionary(2,"^|""MONTOLOGY""|IndexDictionary(""Vehicle"",""TransmissionType"",""name"",""partUri"",""meh"",2)")=1
^RefsDictionary(2,"^|""MONTOLOGY""|IndexDictionary(""Vehicle"",""TransmissionType"",""name"",""ru"",""мех"",2)")=1
^RefsDictionary(2,"^|""MONTOLOGY""|IndexDictionary(""Vehicle"",""TransmissionType"",""uid"",888,2)")=1
Как видим, эти глобалы отличаются от тех, которые приведены в начале статьи, только значением «UpdateTime» — я выполнял эти действия сегодня — поэтому дата стоит текущая. Также появилась новая ветка ^IndexDictionary(«ID») и обратные ссылки на неё в глобале ^RefsDictionary. В ^Dictionary(«MaxID») хранится максимальный идентификатор элемента в нашей справочной подсистеме.
Код программы Dictionary используется в моём рабочем проекте, однако для наглядности и упрощения, я упустил некоторые конструкции и использовал полные названия команд. Возможно где-то закрались ошибки — если кто-то их обнаружит — пишите в комментариях.
В следующей статье я расскажу о вложенных структурах. Постараюсь сделать её более компактной.
Спасибо за внимание.
Буду рад вопросам, замечаниям и пожеланиям.
Автор: multik