БД. Справочники. Живые примеры на глобалах 3

от автора


Часть 1
Часть 2

Слово «Живые», в названии статьи, означает, что механизмы, код и данные, из этих статей, используются в рабочем проекте.

Возможно, вам будет интересно посмотреть на некоторые варианты решений разработки БД (структур, механизмов).

На картинке изображён кусок кода, описывающего глобал правил справочника.

CRUD методы, в процессе своей работы, постоянно обращаются к этим правилам чтобы узнать, какие именно действия необходимо выполнить.

Ранее, мы остановились на том, что у нас есть следующие глобалы:

Посмотреть глобалы

^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 

Создать глобалы Ctrl+С/V

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 — пусть его индексы означают следующее:

  1. онтология
  2. тип справочника
  3. название одного из CRUD методов
  4. указание на тип выполняемого действия
  5. очерёдность выполняемого действия

Обращаю ваше внимание, что индексы глобала правил, начиная с третьего, в других случаях, могут означать что-то другое (когда мы столкнёмся с этим на примере — я опишу это подробно).

Распечатаем все правила для онтологии 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> 

Создать глобалы Ctrl+С/V

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. Структура его индексов проста:

  1. идентификатор элемента
  2. каноническое имя ветки глобала индекса

То есть при необходимости (например при удалении элемента) — мы быстро получим все различные наборы индексов, которые содержат в себе упоминание данного элемента.

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:

#; -------------------------------------------------------------------------------------------------- #; Имя программы #; -------------------------------------------------------------------------------------------------- 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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *