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

в 11:00, , рубрики: MUMPS, nosql, бд, Веб-разработка, глобалы, справочники, структуры данных, субд Caché, метки: , , , , ,

БД. Справочники. Живые примеры на глобалах 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 используется в моём рабочем проекте, однако для наглядности и упрощения, я упустил некоторые конструкции и использовал полные названия команд. Возможно где-то закрались ошибки — если кто-то их обнаружит — пишите в комментариях.

В следующей статье я расскажу о вложенных структурах. Постараюсь сделать её более компактной.

Спасибо за внимание.

Буду рад вопросам, замечаниям и пожеланиям.

Автор: multik

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js