Часть 1
Часть 2
Слово «Живые», в названии статьи, означает, что механизмы, код и данные, из этих статей, используются в рабочем проекте.
Возможно, вам будет интересно посмотреть на некоторые варианты решений разработки БД (структур, механизмов).
На картинке изображён кусок кода, описывающего глобал правил справочника.
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 используется в моём рабочем проекте, однако для наглядности и упрощения, я упустил некоторые конструкции и использовал полные названия команд. Возможно где-то закрались ошибки — если кто-то их обнаружит — пишите в комментариях.
В следующей статье я расскажу о вложенных структурах. Постараюсь сделать её более компактной.
Спасибо за внимание.
Буду рад вопросам, замечаниям и пожеланиям.
ссылка на оригинал статьи http://habrahabr.ru/post/178563/
Добавить комментарий