БД. Справочники. Глобалы. Вложенные структуры. Живые примеры

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

Картинка для привлечения внимания демонстрирующая пример предстоящей «вложенности» камеры с парашютом в ранец.
БД. Справочники. Глобалы. Вложенные структуры. Живые примеры
Часть 1
Часть 2
Часть 3

В прошлый раз мы остановились на том, что у нас есть метод create(), который на основании глобала правил ^RuleDictionary создаёт элементы справочника. Нами был разобран пример создания элементов простейшего, одноуровневого справочника. Сегодня, рассмотрим каким образом, с помощью наших глобалов и методов, можно создавать вложенные структуры.

В коде программы, были использованы «прозрачные» переменные t и map, которые явно не передаются в методы, но доступны внутри них. Мне подсказали, что это не самый лучший способ демонстрации работы, особенно учитывая то, что для большинства, синтаксис Caché Object Script — нов. Поэтому, перед тем как приступить к вложенным структурам, внесём некоторые изменения в программу.


Макросы

Макросы позволяют задавать псевдонимы (другие названия) константам, глобальным и локальным переменным, отдельным кускам кода и прочему. Преимущество использования макросов заключаются в том, что в случае изменения фактического расположения ваших данных (названия пространства имён, глобалов и т.д.), в коде программы ничего менять не нужно, достаточно только изменить один макрос (в inc файле), перекомпилировать проект, и всё будет работать с новыми названиями. Также использование макросов позволяет сократить запись, не теряя при этом понятливости. Макрос объявляется служебным словом #define, далее через пробел передаётся имя макроса, ещё через пробел указывается то, что этот макрос означает. Макросы могут содержать внутри себя другие макросы, главное что бы они были определены ранее. В макросы можно передавать параметры (параметры макросов начинаются знаком %).

#define defNameSpace «MONTOLOGY»

Этот макрос создаёт псевдоним, вместо «MONTOLOGY» — теперь мы можем писать $$$defNameSpace. Три доллара означают что макрос создан разработчиком (не системный). Несмотря на то, что $$$defNameSpace, содержит больше символов и меньше ясности, чем «MONTOLOGY», этот макрос нам очень пригодится, так как будет использован в других макросах, а в коде программы, встречаться практически не будет.

Предположим у нас имеется следующий файл Dictionary.inc

#; пространство имён по умолчанию
#define defNameSpace "MONTOLOGY"

#; глобал данных справочника
#define dData ^|$$$defNameSpace|Dictionary

#; имена элементов справочника
#define dName ^|$$$defNameSpace|NameDictionaryElement

#; индекс элементов справочника
#define dIndex ^|$$$defNameSpace|IndexDictionary

#; обратные ссылки
#define dRefs ^|$$$defNameSpace|RefsDictionary

#; максимальный идентификатор элемента
#define dMaxID $increment($$$dData("MaxID"))

#; правила справочника
#define dRule ^|$$$defNameSpace|RuleDictionary

#; временный глобал для передачи параметров
#define dDTO ^|$$$defNameSpace|tmwOntology

#; получить параметр из входной ветки временного глобала
#define getDTOin(%param,%def) $get($$$dDTO($job,"in",%param),%def)

#; записать параметр в ветку обработки временного глобала
#define setDTOproc(%param,%val) set $$$dDTO($job,"proc",%param)=%val

#; получить параметр из ветки обработки временного глобала
#define getDTOproc(%param,%def) $get($$$dDTO($job,"proc",%param),%def)

#; записать параметр в выходную ветку временного глобала
#define setDTOout(%param,%val) set $$$dDTO($job,"out",%param)=%val

#; получить параметр из выходной ветки временного глобала
#define getDTOout(%param,%def) $get($$$dDTO($job,"out",%param),%def)

#; получить название по языку из входной ветки временного глобала
#define getDTOname(%lang) $get($$$dDTO($job,"in","nameList",%lang))

Теперь для того, что бы обратиться к глобалу: ^|«MONTOLOGY»|IndexDictionary в коде программы можно писать просто $$$dIndex. Обратите внимание на макрос $$$dMaxID: в нём выполняется операция инкримента. Компилятор, просто пройдёт по всей программе Dictionary и заменит все макросы, на то, что они фактически означают. Выполнит обычную замену текста. Макрос $$$dDTO (dictionary Data Transfer Object) — будем использовать вместо прозрачной переменной map, для передачи параметров между подпрограммами. $job — это системная переменная, внутри которой хранится номер текущего процесса. То есть, мы предполагаем, что с глобалом ^|«MONTOLOGY»|tmwOntology, доступным по макросу $$$dDTO, могут одновременно работать несколько процессов. Каждый из них будет обращаться к своему индексу $$$dDTO($job), а далее будут использованы следующие служебные ветки:

  • $$$dDTO($job,«in») — ветка входных параметров
  • $$$dDTO($job,«proc») — ветка параметров обработки
  • $$$dDTO($job,«out») — ветка выходных параметров

Наши глобалы, из прошлых статей, внутри кода программы доступны по следующим макросам:

Для примера перепишем первый метод программы Dictionary с учётом использования макросов

Было (без макросов):

#; --------------------------------------------------------------------------------------------------
#; Имя программы
#; --------------------------------------------------------------------------------------------------
Dictionary
#; --------------------------------------------------------------------------------------------------
#; Получить элемент справочника.
#; --------------------------------------------------------------------------------------------------
retrieve(id,lang="ru",version=0)
    quit $get(^NameDictionaryElement(id,lang,version),"")
#; --------------------------------------------------------------------------------------------------

Стало (с макросами):

#; --------------------------------------------------------------------------------------------------
#; подключаем заголовочный файл Dictionary.inc, расширение файла (inc) - при подключении писать не нужно
#include Dictionary 
#; --------------------------------------------------------------------------------------------------
#; Имя программы
#; --------------------------------------------------------------------------------------------------
Dictionary
#; --------------------------------------------------------------------------------------------------
#; Получить элемент справочника.
#; --------------------------------------------------------------------------------------------------
retrieve(id,lang="ru",version=0)
    quit $get($$$dName(id,lang,version),"")
#; --------------------------------------------------------------------------------------------------

Преобразуем весь код нашей программы с учётом использования макросов и без использования прозрачных переменных.

Преобразованная программа Dictionary
#; --------------------------------------------------------------------------------------------------
#; подключаем заголовочный файл Dictionary.inc, расширение файла (inc) - при подключении писать не нужно
#include Dictionary
#; --------------------------------------------------------------------------------------------------
#; Имя программы
#; --------------------------------------------------------------------------------------------------
Dictionary
#; --------------------------------------------------------------------------------------------------
#; Получить элемент справочника.
#; --------------------------------------------------------------------------------------------------
retrieve(id,lang="ru",version=0)
    quit $get($$$dName(id,lang,version),"")
#; --------------------------------------------------------------------------------------------------
#; Получить список элементов справочника по индексу.
#; --------------------------------------------------------------------------------------------------
retrieveListByIndex(ontology,type,index,value,str="",lang="ru") 

    #;принудительно переводим в нижний регистр строку, с которой должны начинатся названия элементов
    set str=$zconvert(str,"L")

    set id="" 
    for {
        #;получаем следующий идентификатор(индекс) на текущем уровне
        set id=$order($$$dIndex(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 $$$dDTO($job,"out")
	
	#; объявляем новую локальную переменну t
	#; в которой будет хранится вся временная информация метода create
	new t
	
	#; выполняем функцию проверки входных данных
	set t("err")=$$check("create")
	
	#; выходим, если есть ошибка
	if t("err")<0 { quit t("err") }
	
	s t("ontology")=$$$getDTOin("ontology","")
	s t("type")=$$$getDTOin("type","")
	s t("id")=$$$getDTOproc("id","")
	
	#; устанавливаем свойство "UpdateTime"
	do setProperty(t("ontology"),t("type"),"UpdateTime",$horolog,t("id"),"false")
	
	#; устанавливаем свойство "uid"
	do setProperty(t("ontology"),t("type"),"uid",$$$getDTOin("uid",888),t("id"),"true")
	
	set t("lang")="" 
	for {
		#; получаем следующий язык (на котором задано название элемента справочника)
		set t("lang")=$order($$$dDTO($job,"in","nameList",t("lang")))
		
		#; выходим из цикла, если следующего языка нет
		quit:t("lang")=""
		
		#; получаем название для текущего языка
		set t("name")=$get($$$dDTO($job,"in","nameList",t("lang")),"")
		
		#; устанавливаем название для элемента справочника
		do setName(t("ontology"),t("type"),t("lang"),t("name"),t("id"))
	}
	
	#; сохраняем онтологию и тип элемента справочника в индексном глобале
	do saveOntoAndTypeID(t("id"),t("ontology"),t("type"))
	
	#; сохраняем путь к элементу справочника в индексном глобале
	do saveElementPath(t("id"),$$$getDTOproc("path",""))
	
	#; записываем в карту выходных параметров идентификатор созданного элемента
	$$$setDTOout("id",t("id"))
	
	#; очищаем карту входных параметров
	kill $$$dDTO($job,"in")
	
	#; очищаем карту параметров обработки
	kill $$$dDTO($job,"porc")
	
	#; возвращаем идентификатор созданного элемента
	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
	
	#; проверяем, существуют ли ветка глобала правил "check", для данной онтологии типа справочника и действия
	if $data($$$dRule($$$getDTOin("ontology",""),$$$getDTOin("type",""),action,"check")) 
	{ 
		#; в check("map") записываем каноническое имя нужной ветки глобала правил
		set check("map")=$name($$$dRule($$$getDTOin("ontology",""),$$$getDTOin("type",""),action,"check")) 
	}
	else { 
		#; в check("map") записываем каноническое имя ветки глобала правил для онтологии и типа справочника по умолчанию
		set check("map")=$name($$$dRule("SimpleOntology","SimpleType",action,"check")) 
	}
	
	set check("i")="" 
	for {
		#; получаем следующий номер действия
		set check("i")=$order(@check("map")@(check("i")))
		
		#; выходим из цикла, если следующего номера нет
		quit:check("i")=""
		
		#; выполняем текущую функцию проверки
		xecute $get(@check("map")@(check("i")))
		
		#; выходим из цикла, если проверка не пройдена
		quit:check<0
	}
	
	#; возвращаем результат проверок
	quit check
#; --------------------------------------------------------------------------------------------------
#; Функция проверки элемента справочника при создании.
#; --------------------------------------------------------------------------------------------------
checkCreate()private
	
	new t
	
	#; возвращаем ошибку если онтология не задана
	quit:$$$getDTOin("ontology","")="" -1
	
	#; возвращаем ошибку, если тип справочника не задан
	quit:$$$getDTOin("type","")="" -1
	
	#; выполняем функцию проверки имён
	set t("check")=$$checkNames()
	
	#; возвращаем ошибку, если проверка не пройдена
	quit:t("check")<0 t("check")
	
	#; получаем идентификатор для создаваемого элемента справочника
	$$$setDTOproc("id",$$$dMaxID)
	
	#; получаем путь к ветке глобала ^Dictionary
	$$$setDTOproc("path",$name($$$dData($$$getDTOin("ontology",""),$$$getDTOin("type",""),$$$getDTOproc("id",""))))
	
	#; возвращаем успешный результат проверки
	quit 0
#; --------------------------------------------------------------------------------------------------
checkUpdate() quit 0
#; --------------------------------------------------------------------------------------------------
checkDelete() quit 0
#; --------------------------------------------------------------------------------------------------
#; Проверка имён на различных языках.
#; --------------------------------------------------------------------------------------------------
checkNames()private
	
	new t
	
	#; устанавливаем в t("check") ошибку
	#; для случая когда ни один язык не задан
	set t("check")=-1
	
	set t("lang")="" 
	for {
		#; получаем следующий язык
		set t("lang")=$order($$$dDTO($job,"in","nameList",t("lang")))
		
		#; выходим из цикла, если следующего языка нет
		quit:t("lang")="" 
		
		#; получаем название для текущего языка
		set t("name")=$get($$$dDTO($job,"in","nameList",t("lang")),"")
		
		#; если имя пустое
		if t("name")="" {  
			#; устанавливаем в t("check") ошибку
			set t("check")=-1
		
			#; выходим из цикла
			quit
		}
		else { 
			#; в t("check") записываем признак того, что всё в порядке 
			set t("check")=0
		}
	}
	
	#; возвращаем результат проверки имён
	quit t("check")
#; --------------------------------------------------------------------------------------------------
#; Функция предварительной обработки названий.
#; --------------------------------------------------------------------------------------------------
processingNames(function) 
	new t
	set t("lang")="" 
	for { 
		#; цикл по всем языкам
		set t("lang")=$order($$$dDTO($job,"in","nameList",t("lang")))
		quit:t("lang")=""  
		
		#; обрабатываем название на текущем языке требуемой функцией
		xecute "set "_$name($$$dDTO($job,"in","nameList",t("lang")))_"=$$"_function_"("""_$$$getDTOname(t("lang"))_""")"
	}
	quit
#; --------------------------------------------------------------------------------------------------
checkUniqueNameElementAllLang() 
	new t 
	set t("check")=0
	s t("ontology")=$$$getDTOin("ontology","")
	s t("type")=$$$getDTOin("type","")
	set t("lang")="" 
	for { 
		#; цикл по всем языкам
		set t("lang")=$order($$$dDTO($job,"in","nameList",t("lang")))
		quit:t("lang")=""  
		
		#; получаем текущее название
		set t("name")=$get($$$dDTO($job,"in","nameList",t("lang")),"")
		
		#; в переменную t("check") записываем статус уникальности имени (0 - значит уникально)
		s t("check")=$$checkUniqueNameElement(t("ontology"),t("type"),t("lang"),t("name"),$$$getDTOproc("id",""))
		
		#; выходим из цикла если какое-то имя не уникально
		quit:t("check")<0 
	}
	quit t("check")
#; --------------------------------------------------------------------------------------------------
#; Функция удаляет из строки всю пунктуацию и служебные символы.
#; --------------------------------------------------------------------------------------------------
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")
#; --------------------------------------------------------------------------------------------------
#; --------------------------------------------------------------------------------------------------
#; Функция удаляет из строки все служебные символы.
#; Удаляется вся пунктуация перед первым словом.
#; Между словами остаётся только один крайний разделитель (символ пунктуации по возможности не пробел).
#; После крайнего слова удаляется вся пунктуация, остаётся только "." если она есть.
#; --------------------------------------------------------------------------------------------------
specialClearStr(str)
	
	new t 
	
	set t("str")="",t("flag")="start",t("delim")=""
	for t("i")=1:1:$L(str) { 
	
		set t("ch")=$extract(str,t("i"))
		
		;# заменяем табуляцию на пробел
		set:t("ch")=$char(9) t("ch")=" "
		continue:(t("ch")?1C)
		if (t("flag")="start")&&`(t("ch")?1P) { set t("str")=t("ch"),t("flag")="word" }
		elseif t("flag")="word" { 
			if `(t("ch")?1P) { set t("str")=t("str")_t("ch") } 
			else { set t("delim")=t("ch"),t("flag")="delim" }
		}
		elseif t("flag")="delim" { if (t("ch")?1P) { set:t("ch")`=" " t("delim")=t("ch") }
			else { set t("str")=t("str")_t("delim")_t("ch"),t("flag")="word",t("delim")="" }
		}
	}
	set:t("delim")="." t("str")=t("str")_t("delim")
	
	quit t("str")
#; --------------------------------------------------------------------------------------------------
#; --------------------------------------------------------------------------------------------------
#; Функция проверяет на уникальность имя элемента, для указанного языка, типа и онтологии.
#; При проверке, регистр переводится вниз.
#; --------------------------------------------------------------------------------------------------
checkUniqueNameElement(ontology,type,lang,name,id)
	
	new t
	
	#; устанавливаем успешный результат
	set t("q")=0
	
	set t("uniqueId")="" 
	for {
		#; проверяем, есть ли в индексном глобале идентификатор элемента справочника
		#; для данной онтологии, типа,языка и названия (в нижнем регистре)
		set t("uniqueId")=$order($$$dIndex(ontology,type,"name",lang,$zconvert(name,"l"),t("uniqueId")))
		
		#; выходим если идентификатора нет
		quit:t("uniqueId")=""
		
		#; проверяем, равенство найденного идентификатора текущему элементу
		#; это необходимо для метода update 
		if (t("uniqueId")`=id) {
			
			#; устанавливаем ошибку, если найденный идентификатор отличается от текущего
			set t("q")=-1
			quit
		}
	}
	
	#; возвращаем результат
	quit t("q")
#; --------------------------------------------------------------------------------------------------
#; Установка значения, для определённого свойства элемента справочника.
#; По умолчанию создаётся индекс и обратная ссылка на него.
#; --------------------------------------------------------------------------------------------------
setProperty(ontology,type,property,value,id,index="true")private
	
	#; устанавливаем значение свойства в нулевую (текущую) версию элемента справочника
	set $$$dData(ontology,type,id,0,property)=value
	
	#; если свойство индексированное
	if index="true" {
		
		#; создаём индекс
		set $$$dIndex(ontology,type,property,value,id)=1
		
		#; сохраняем обратную ссылку на индекс
		set $$$dRefs(id,$name($$$dIndex(ontology,type,property,value,id)))=1
	}
		
	quit 0
#; --------------------------------------------------------------------------------------------------
#; Установка значения имени, для определённого языка элемента справочника.
#; Создаётся индекс(в нижнем регистре) и обратная ссылка.
#; --------------------------------------------------------------------------------------------------
setName(ontology,type,lang,value,id)private
	
	#; устанавливаем значение имени для текущей версии и языка
	set $$$dName(id,lang,0)=value
	
	#; сохраняем дату создания/обновления
	set $$$dName(id,lang,0,"UpdateTime")=$horolog
	
	#; создаём индекс по названию
	set $$$dIndex(ontology,type,"name",lang,$zconvert(value,"l"),id)=1
	
	#; сохраняем обратную ссылку
	set $$$dRefs(id,$name($$$dIndex(ontology,type,"name",lang,$zconvert(value,"l"),id)))=1
	
	quit 0
#; --------------------------------------------------------------------------------------------------
saveOntoAndTypeID(id,ontology,type)
	set $$$dIndex("ID",id,"ontology")=ontology
	set $$$dRefs(id,$name($$$dIndex("ID",id,"ontology")))=1
	set $$$dIndex("ID",id,"type")=type
	set $$$dRefs(id,$name($$$dIndex("ID",id,"type")))=1
	
	quit
#; --------------------------------------------------------------------------------------------------
saveElementPath(id,path)
	set $$$dIndex("ID",id,"path")=path
	set $$$dRefs(id,$name($$$dIndex("ID",id,"path")))=1
	
	quit
#; --------------------------------------------------------------------------------------------------

В листинге программы произведена замена символа ' на символ ` — это необходимо для более красивого отображения кода. В случае копирования кода программы в Caché — необходимо произвести обратную замену: заменить все символы ` на '

Преобразованный глобал правил:

MONTOLOGY>zwrite ^RuleDictionary("SimpleOntology")
^RuleDictionary("SimpleOntology","SimpleType","create","check",10)="do processingNames(""clearPunctuationAndControlChar"")"
^RuleDictionary("SimpleOntology","SimpleType","create","check",20)="s check=$$checkUniqueNameElementAllLang()"
^RuleDictionary("SimpleOntology","SimpleType","update","check",10)="do processingNames(""clearPunctuationAndControlChar"")"
^RuleDictionary("SimpleOntology","SimpleType","update","check",20)="s check=$$checkUniqueNameElementAllLang()"

MONTOLOGY>

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

set ^RuleDictionary("SimpleOntology","SimpleType","create","check",10)="do processingNames(""clearPunctuationAndControlChar"")"
set ^RuleDictionary("SimpleOntology","SimpleType","create","check",20)="s check=$$checkUniqueNameElementAllLang()"
set ^RuleDictionary("SimpleOntology","SimpleType","update","check",10)="do processingNames(""clearPunctuationAndControlChar"")"
set ^RuleDictionary("SimpleOntology","SimpleType","update","check",20)="s check=$$checkUniqueNameElementAllLang()"

Выполним очистку глобалов (кроме глобала правил) и вставку первых двух элементов справочника, как в прошлый раз, для того, что бы убедиться, что мы нигде не допустили ошибки, при переходе на макросы и ухода от прозрачных переменных.

Проверка преобразованной программы Dictionary

Удалим все наши глобалы, кроме глобала правил ^RuleDictionary:

MONTOLOGY>kill ^Dictionary,^IndexDictionary,^NameDictionaryElement,^RefsDictionary

MONTOLOGY>

Создадим два первых элемента справочника:

MONTOLOGY>kill ^tmwOntology($job)

MONTOLOGY>set ^tmwOntology($job,"in","ontology")="Vehicle",^tmwOntology($job,"in","type")="TransmissionType"

MONTOLOGY>set ^tmwOntology($job,"in","nameList","ru")=" АКП,",^tmwOntology($job,"in","nameList","partUri")="akp"

MONTOLOGY>write $$create^Dictionary()
1
MONTOLOGY>kill ^tmwOntology($job)

MONTOLOGY>set ^tmwOntology($job,"in","ontology")="Vehicle",^tmwOntology($job,"in","type")="TransmissionType"

MONTOLOGY>set ^tmwOntology($job,"in","nameList","ru")="МЕХ",^tmwOntology($job,"in","nameList","partUri")="meh"

MONTOLOGY>write $$create^Dictionary()
2
MONTOLOGY>

Распечатаем все наши глобалы:

MONTOLOGY>zwrite ^Dictionary,^IndexDictionary,^NameDictionaryElement,^RefsDictionary
^Dictionary("MaxID")=2
^Dictionary("Vehicle","TransmissionType",1,0,"UpdateTime")="62968,77638"
^Dictionary("Vehicle","TransmissionType",1,0,"uid")=888
^Dictionary("Vehicle","TransmissionType",2,0,"UpdateTime")="62968,77664"
^Dictionary("Vehicle","TransmissionType",2,0,"uid")=888
^IndexDictionary("ID",1,"ontology")="Vehicle"
^IndexDictionary("ID",1,"path")="^|""MONTOLOGY""|Dictionary(""Vehicle"",""TransmissionType"",1)"
^IndexDictionary("ID",1,"type")="TransmissionType"
^IndexDictionary("ID",2,"ontology")="Vehicle"
^IndexDictionary("ID",2,"path")="^|""MONTOLOGY""|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")="62968,77638"
^NameDictionaryElement(1,"ru",0)="АКП"
^NameDictionaryElement(1,"ru",0,"UpdateTime")="62968,77638"
^NameDictionaryElement(2,"partUri",0)="meh"
^NameDictionaryElement(2,"partUri",0,"UpdateTime")="62968,77664"
^NameDictionaryElement(2,"ru",0)="МЕХ"
^NameDictionaryElement(2,"ru",0,"UpdateTime")="62968,77664"
^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». Внесение изменений в программу Dictionary можно считать успешным.


Вложенные структуры

Рассмотрим простейший пример вложенных структур справочников. Пусть в онтологии Vehicle содержится справочник Make, внутри которого, содержится справочник Model (марки-модели транспортных средств). Опишем эти справочники в нашем глобале правил ^RuleDIctionary. Для удобства создадим программу InitRuleDic которая и будет инициализировать наш глобал правил. Также запишем в неё правила для справочников и онтологии по умолчанию. Напомню что вместо ^RuleDictionary, внутри программ, нам теперь доступна сокращённая запись $$$dRule.

#; ------------------------------------------------------------------------------------------------------------
#include Dictionary
#; ------------------------------------------------------------------------------------------------------------
InitRuleDic
#; ------------------------------------------------------------------------------------------------------------
	kill $$$dRule
	
	set $$$dRule("SimpleOntology","SimpleType","create","check",10)="do processingNames(""clearPunctuationAndControlChar"")"
	set $$$dRule("SimpleOntology","SimpleType","create","check",20)="s check=$$checkUniqueNameElementAllLang()"
	set $$$dRule("SimpleOntology","SimpleType","update","check",10)="do processingNames(""clearPunctuationAndControlChar"")"
	set $$$dRule("SimpleOntology","SimpleType","update","check",20)="s check=$$checkUniqueNameElementAllLang()"

	set $$$dRule("Vehicle","Make","property","lastId")="otherId" 
	set $$$dRule("Vehicle","Make","property","lastId","index")="true" 
	set $$$dRule("Vehicle","Make","create","check",10)="do processingNames(""specialClearStr"")"
	set $$$dRule("Vehicle","Make","create","check",20)="s check=$$checkUniqueNameElementAllLang()"
	set $$$dRule("Vehicle","Make","create","check",50)="s check=$$checkUniqueParamElement(""lastId"",""Vehicle"",""Make"")"
	set $$$dRule("Vehicle","Make","update","check",10)="do processingNames(""specialClearStr"")"
	set $$$dRule("Vehicle","Make","update","check",20)="s check=$$checkUniqueNameElementAllLang()"
	set $$$dRule("Vehicle","Make","update","check",50)="s check=$$checkUniqueParamElement(""lastId"",""Vehicle"",""Make"")"
	
	set $$$dRule("Vehicle","Model","parent")="Make"
	set $$$dRule("Vehicle","Model","property","parentId")="parent"
	set $$$dRule("Vehicle","Model","property","parentId","index")="true"
	set $$$dRule("Vehicle","Model","property","lastId")="otherId"
	set $$$dRule("Vehicle","Model","property","lastId","index")="true"
	set $$$dRule("Vehicle","Model","create","check",15)="do processingNames(""specialClearStr"")"
	set $$$dRule("Vehicle","Model","create","check",20)="s check=$$checkUniqueNameElementAllLang(""true"")"
	set $$$dRule("Vehicle","Model","create","check",50)="s check=$$checkUniqueParamElement(""lastId"",""Vehicle"",""Model"")"
	set $$$dRule("Vehicle","Model","update","check",15)="do processingNames(""specialClearStr"")"
	set $$$dRule("Vehicle","Model","update","check",20)="s check=$$checkUniqueNameElementAllLang(""true"")"
	set $$$dRule("Vehicle","Model","update","check",50)="s check=$$checkUniqueParamElement(""lastId"",""Vehicle"",""Model"")"
		
	zwrite $$$dRule 
	
  quit
#; ------------------------------------------------------------------------------------------------------------

Давайте внимательно посмотрим на наш глобал правил. В справочнике Make — появилось свойство lastId. Это идентификатор элемента справочника в старой БД (не глобалы). В некоторых местах, прошлый идентификатор, может использоваться для связывания, поэтому его необходимо сохранить. Задача поиска соответствия между элементами подобных справочников (в разных системах) до сих пор остаётся одной из самых актуальных в области миграции данных. Тип свойства otherId — никак не обрабатывается в программе, это просто текстовое напоминание о том, что внутри. При необходимости, ничто не помешает навесить обработчик, на такого типа свойства в методы обработки справочников. Свойство lastId индексируемое, на что указывает ветка index со значением «ture». Далее идёт набор из трёх функций используемых для проверки при создании/обновлении элемента. Функция checkUniqueParamElement() для нас новая — добавим её в программу.

checkUniqueParamElement()

#; --------------------------------------------------------------------------------------------------
#; Функция проверяет на уникальность параметр элемента.
#; --------------------------------------------------------------------------------------------------
checkUniqueParamElement(param,ontology,type)
	
	new t
	
	#; получаем значение параметра
	set t("value")=$$$getDTOin(param,"")
	
	#; возвращаем успешный результат, если параметр не задан - уникальность не нарушена
	quit:t("value")="" 0
	
	#; устанавливаем успешный результат
	set t("q")=0 
	
	set t("uniqueId")="" 
	
	for {
		#; проверяем, есть ли в индексном глобале идентификатор элемента справочника
		#; для данной онтологии, типа, языка свойства и значение
		set t("uniqueId")=$o($$$dIndex(ontology,type,param,t("value"),t("uniqueId")))
		
		#; выходим если идентификатора нет
		quit:t("uniqueId")=""
		
		#; проверяем, равенство найденного идентификатора текущему элементу
		#; это необходимо для метода update 
		if (t("uniqueId")`=$$$getDTOproc("id","")) {
			
			#; устанавливаем ошибку, если найденный идентификатор отличается от текущего
			set t("q")=-1
			quit
		}
	}
	
	quit t("q")
#; --------------------------------------------------------------------------------------------------

Расширим функциональность нашего метода create() — добавим в него обработку свойств:

	s t("property")="" 
	for {
		#; получаем следующее свойство
		s t("property")=$o($$$dDTO($j,"in",t("property"))) 
		
		#; выходим из цикла, если свойства нет
		quit:t("property")=""
		
		#; пропускаем свойство, если оно не описано в глобале правил для текущей онтологии и типа справочника
		continue:$get($$$dRule(t("ontology"),t("type"),"property",t("property")),"")=""
		
		#; получаем индексируемость свойства
		set t("index")=$get($$$dRule(t("ontology"),t("type"),"property",t("property"),"index"),"false")
		
		#; получаем значение свойства
		set t("value")=$get($$$dDTO($j,"in",t("property")),"")
		
		#; если значение не пустое - устанавливаем свойство
		if t("value")`="" { 
			do setProperty(t("ontology"),t("type"),t("property"),t("value"),t("id"),t("index"),$$$getDTOproc("pathParent",""))
		}
	}

Полная версия create

#; --------------------------------------------------------------------------------------------------
#; Создать элемент справочника.
#; При создании можно задать название элемента на разных языках.
#; --------------------------------------------------------------------------------------------------
create()
	
	#; очищаем карту выходных параметров
	kill $$$dDTO($job,"out")
	
	#; объявляем новую локальную переменну t
	#; в которой будет хранится вся временная информация метода create
	new t
	
	#; выполняем функцию проверки входных данных
	set t("err")=$$check("create")
	
	#; выходим, если есть ошибка
	if t("err")<0 { quit t("err") }
	
	s t("ontology")=$$$getDTOin("ontology","")
	s t("type")=$$$getDTOin("type","")
	s t("id")=$$$getDTOproc("id","")
	
	#; устанавливаем свойство "UpdateTime"
	do setProperty(t("ontology"),t("type"),"UpdateTime",$horolog,t("id"),"false",$$$getDTOproc("pathParent",""))
	
	#; устанавливаем свойство "uid"
	do setProperty(t("ontology"),t("type"),"uid",$$$getDTOin("uid",888),t("id"),"true",$$$getDTOproc("pathParent",""))
	
	set t("lang")="" 
	for {
		#; получаем следующий язык (на котором задано название элемента справочника)
		set t("lang")=$order($$$dDTO($job,"in","nameList",t("lang")))
		
		#; выходим из цикла, если следующего языка нет
		quit:t("lang")=""
		
		#; получаем название для текущего языка
		set t("name")=$get($$$dDTO($job,"in","nameList",t("lang")),"")
		
		#; устанавливаем название для элемента справочника
		do setName(t("ontology"),t("type"),t("lang"),t("name"),t("id"))
	}
	
	s t("property")="" 
	for {
		#; получаем следующее свойство
		s t("property")=$o($$$dDTO($j,"in",t("property"))) 
		
		#; выходим из цикла, если свойства нет
		quit:t("property")=""
		
		#; пропускаем свойство, если оно не описано в глобале правил для текущей онтологии и типа справочника
		continue:$get($$$dRule(t("ontology"),t("type"),"property",t("property")),"")=""
		
		#; получаем индексируемость свойства
		set t("index")=$get($$$dRule(t("ontology"),t("type"),"property",t("property"),"index"),"false")
		
		#; получаем значение свойства
		set t("value")=$get($$$dDTO($j,"in",t("property")),"")
		
		#; если значение не пустое - устанавливаем свойство
		if t("value")`="" { 
			do setProperty(t("ontology"),t("type"),t("property"),t("value"),t("id"),t("index"),$$$getDTOproc("pathParent",""))
		}
	}
	
	#; сохраняем онтологию и тип элемента справочника в индексном глобале
	do saveOntoAndTypeID(t("id"),t("ontology"),t("type"))
	
	#; сохраняем путь к элементу справочника в индексном глобале
	do saveElementPath(t("id"),$$$getDTOproc("path",""))
	
	#; записываем в карту выходных параметров идентификатор созданного элемента
	$$$setDTOout("id",t("id"))
	
	#; очищаем карту входных параметров
	kill $$$dDTO($job,"in")
	
	#; очищаем карту параметров обработки
	kill $$$dDTO($job,"porc")
	
	#; возвращаем идентификатор созданного элемента
	quit t("id")
#; --------------------------------------------------------------------------------------------------

Обратите внимание на строчку:

do setProperty(t(«ontology»),t(«type»),t(«property»),t(«value»),t(«id»),t(«index»),$$$getDTOproc(«pathParent»,""))

В вызов метода setProperty() добавился ещё один параметр pathParent — он необходим для обработки вложенности.

Добавим пару марок автомобилей в наш справочник (марки выбраны мной наугад - никакой рекламы тут нет).

MONTOLOGY>kill ^tmwOntology($job)

MONTOLOGY>set ^tmwOntology($job,"in","ontology")="Vehicle",^tmwOntology($job,"in","type")="Make"

MONTOLOGY>set ^tmwOntology($job,"in","nameList","ru")="MAZDA",^tmwOntology($job,"in","nameList","partUri")="mazda"

MONTOLOGY>set ^tmwOntology($job,"in","lastId")=122

MONTOLOGY>write $$create^Dictionary()
3
MONTOLOGY>kill ^tmwOntology($job)

MONTOLOGY>set ^tmwOntology($job,"in","ontology")="Vehicle",^tmwOntology($job,"in","type")="Make"

MONTOLOGY>set ^tmwOntology($job,"in","nameList","ru")="BMW",^tmwOntology($job,"in","nameList","partUri")="bmw"

MONTOLOGY>set ^tmwOntology($job,"in","lastId")=1269

MONTOLOGY>write $$create^Dictionary()
4
MONTOLOGY>

Внимательно посмотрим на правила для справочника Model. Появилась ветка parent со значением «Make» — это указание на то, что родителем, для справочника Model, является Make. Далее объявлено индексируемое свойство parentId, в котором будет хранится идентификатор родителя. Пусть элементы вложенного справочника, в глобале ^Dictionary, хранятся в ветке children родительского элемента. Количество уровней вложенности программно ограничивать не будем (остаются только ограничения на длину индекса переменной). Также примем, что индексы и имена для вложенных справочников будут храниться точно также как и для обычных. Напишем функцию проверки родителя.

#; --------------------------------------------------------------------------------------------------
#; Функция проверки родителя, если он есть.
#; Проверяет онтологию и тип родителя.
#; --------------------------------------------------------------------------------------------------
checkParent()private
	
	new t
	
	#; получаем тип родителя для данного типа справочника в глобале правил (если он есть)
	s t("parent")=$get($$$dRule($$$getDTOin("ontology",""),$$$getDTOin("type",""),"parent"),"")
	
	#; если родитель есть
	if t("parent")`="" {
		
		#; получаем значение свойства parentId из входной ветки служебного глобала
		set t("parentId")=$$$getDTOin("parentId","")
		
		#; возвращаем ошибку если родитель не задан
		if t("parentId")="" { quit -1 }
		
		#; проверяем онтологию родителя в индексном глобале 
		#; она должна совпадать с онтологией создаваемого элемента
		if $get($$$dIndex("ID",t("parentId"),"ontology"),"")`=$$$getDTOin("ontology","") { quit -1 }
		
		#; проверяем тип родителя в индексном глобале 
		if $get($$$dIndex("ID",t("parentId"),"type"),"")`=t("parent") { quit -1 }
		
		#; получаем путь к родителю
		s t("pathParent")=$get($$$dIndex("ID",t("parentId"),"path"),"")
		
		#; возвращаем ошибку если пути нет
		if t("pathParent")="" { quit -1 }
		
		#; устанавливаем путь к родителю в ветку обработки служебного глобала
		$$$setDTOproc("pathParent",t("pathParent"))
	}
		
	#; возвращаем успешный результат проверки родителя
	quit 0
#; --------------------------------------------------------------------------------------------------

Как видим функция проверяет в глобале правил является ли создаваемый элемент вложенным, и если да — то проверяет наличие и корректность переданного идентификатора родителя. Также придётся внести некоторые изменения в метод checkCreate(): добавить в него вызов проверки родителя, и изменить правило формирования пути к элементу справочника в глобале ^Dictionary.

#; --------------------------------------------------------------------------------------------------
#; Функция проверки элемента справочника при создании.
#; --------------------------------------------------------------------------------------------------
checkCreate()private
	
	new t
	
	#; возвращаем ошибку если онтология не задана
	quit:$$$getDTOin("ontology","")="" -1
	
	#; возвращаем ошибку, если тип справочника не задан
	quit:$$$getDTOin("type","")="" -1
	
	#; выполняем функцию проверки родителя
	s t("check")=$$checkParent()
	
	#; возвращаем ошибку, если проверка не пройдена
	quit:t("check")<0 t("check")
	
	#; выполняем функцию проверки имён
	set t("check")=$$checkNames()
	
	#; возвращаем ошибку, если проверка не пройдена
	quit:t("check")<0 t("check")
	
	#; получаем идентификатор для создаваемого элемента справочника
	$$$setDTOproc("id",$$$dMaxID)
	
	#; получаем путь к ветке глобала ^Dictionary
	if $$$getDTOproc("pathParent","")="" { 
		
		#; если родителя нет - устанавливаем начальный путь как ^Dictionary(ontology)
		set t("path")=$name($$$dData($$$getDTOin("ontology",""))) 
	}
	else { 
		
		#; если путь к родителю есть - дописываем в путь нулевой версии родителя ключевое слово children
		set t("path")=$name(@($$$getDTOproc("pathParent",""))@(0,"children")) 
	} 
	
	#; к полученному пути добавляем тип создаваемого элемента и его идентификатор
	s t("path")=$name(@(t("path"))@($$$getDTOin("type",""),$$$getDTOproc("id","")))
	
	$$$setDTOproc("path",t("path"))
	
	#; возвращаем успешный результат проверки
	quit 0
#; --------------------------------------------------------------------------------------------------

Также обратите внимание на следующую строку в глобале правил:

set $$$dRule(«Vehicle»,«Model»,«create»,«check»,20)=«s check=$$checkUniqueNameElementAllLang(»«true»")"

В метод checkUniqueNameElement() мы добавили один параметр inparent, который определяет, необходимо ли проверять на уникальность внутри всех элементов справочника или только внутри родителя. Для нашего случая это легко объяснить, существует много моделей автомобилей, которые одинаково называются, но марки у них разные. Нам же необходимо проверять, совпадения только внутри родителя — и в этом случае выдавать ошибку. Аналогичные требования могут возникнуть, например, при реализации справочника адреса. В одной стране есть огромное количество населённых пунктов с одинаковыми названиями. Сам был удивлён далеко не единичным наличием таких населённых пунктов в Украине как Николаев, Полтава, Черкассы и прочие прочие. Поэтому иногда необходимо ограничить домен, внутри которого проверяется уникальность.

#; --------------------------------------------------------------------------------------------------
#; Функция проверяет на уникальность имя элемента, для указанного языка, типа и онтологии.
#; При проверке, регистр переводится вниз.
#; --------------------------------------------------------------------------------------------------
checkUniqueNameElement(ontology,type,lang,name,id,inparent="false")
	
	new t
	
	#; устанавливаем успешный результат
	set t("q")=0
	
	set t("uniqueId")="" 
	for {
		#; проверяем, есть ли в индексном глобале идентификатор элемента справочника
		#; для данной онтологии, типа,языка и названия (в нижнем регистре)
		set t("uniqueId")=$order($$$dIndex(ontology,type,"name",lang,$zconvert(name,"l"),t("uniqueId")))
		
		#; выходим если идентификатора нет
		quit:t("uniqueId")=""
		
		#; проверяем, равенство найденного идентификатора текущему элементу
		#; это необходимо для метода update 
		if (t("uniqueId")`=id) {
			
			#; если уникальность надо проверять внутри всех элементов данного типа
			if inparent="false" {
				#; устанавливаем ошибку, если найденный идентификатор отличается от текущего
				set t("q")=-1
				quit
			}
			else {
				#; если уникальность надо проверять только внутри родителя
				if $data($$$dIndex(ontology,type,"parentId",$$$getDTOin("parentId",""),t("uniqueId"))) {
					#; устанавливаем ошибку если у найденного идентификатора такой же родитель 
					#; как и у создаваемого/обновляемого элемента
					set t("q")=-1
					quit
				}	
			}
		}
	}
	
	#; возвращаем результат
	quit t("q")
#; --------------------------------------------------------------------------------------------------

Весь код Dictionary

#; --------------------------------------------------------------------------------------------------
#; подключаем заголовочный файл Dictionary.inc, расширение файла (inc) - при подключении писать не нужно
#include Dictionary
#; --------------------------------------------------------------------------------------------------
#; Имя программы
#; --------------------------------------------------------------------------------------------------
Dictionary
#; --------------------------------------------------------------------------------------------------
#; Получить элемент справочника.
#; --------------------------------------------------------------------------------------------------
retrieve(id,lang="ru",version=0)
    quit $get($$$dName(id,lang,version),"")
#; --------------------------------------------------------------------------------------------------
#; Получить список элементов справочника по индексу.
#; --------------------------------------------------------------------------------------------------
retrieveListByIndex(ontology,type,index,value,str="",lang="ru") 

    #;принудительно переводим в нижний регистр строку, с которой должны начинатся названия элементов
    set str=$zconvert(str,"L")

    set id="" 
    for {
        #;получаем следующий идентификатор(индекс) на текущем уровне
        set id=$order($$$dIndex(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 $$$dDTO($job,"out")
	
	#; объявляем новую локальную переменну t
	#; в которой будет хранится вся временная информация метода create
	new t
	
	#; выполняем функцию проверки входных данных
	set t("err")=$$check("create")
	
	#; выходим, если есть ошибка
	if t("err")<0 { quit t("err") }
	
	s t("ontology")=$$$getDTOin("ontology","")
	s t("type")=$$$getDTOin("type","")
	s t("id")=$$$getDTOproc("id","")
	
	#; устанавливаем свойство "UpdateTime"
	do setProperty(t("ontology"),t("type"),"UpdateTime",$horolog,t("id"),"false",$$$getDTOproc("pathParent",""))
	
	#; устанавливаем свойство "uid"
	do setProperty(t("ontology"),t("type"),"uid",$$$getDTOin("uid",888),t("id"),"true",$$$getDTOproc("pathParent",""))
	
	set t("lang")="" 
	for {
		#; получаем следующий язык (на котором задано название элемента справочника)
		set t("lang")=$order($$$dDTO($job,"in","nameList",t("lang")))
		
		#; выходим из цикла, если следующего языка нет
		quit:t("lang")=""
		
		#; получаем название для текущего языка
		set t("name")=$get($$$dDTO($job,"in","nameList",t("lang")),"")
		
		#; устанавливаем название для элемента справочника
		do setName(t("ontology"),t("type"),t("lang"),t("name"),t("id"))
	}
	
	s t("property")="" 
	for {
		#; получаем следующее свойство
		s t("property")=$order($$$dDTO($j,"in",t("property"))) 
		
		#; выходим из цикла, если свойства нет
		quit:t("property")=""
		
		#; пропускаем свойство, если оно не описано в глобале правил для текущей онтологии и типа справочника
		continue:$get($$$dRule(t("ontology"),t("type"),"property",t("property")),"")=""
		
		#; получаем индексируемость свойства
		set t("index")=$get($$$dRule(t("ontology"),t("type"),"property",t("property"),"index"),"false")
		
		#; получаем значение свойства
		set t("value")=$get($$$dDTO($j,"in",t("property")),"")
		
		#; если значение не пустое - устанавливаем свойство
		if t("value")`="" { 
			do setProperty(t("ontology"),t("type"),t("property"),t("value"),t("id"),t("index"),$$$getDTOproc("pathParent",""))
		}
	}
	
	#; сохраняем онтологию и тип элемента справочника в индексном глобале
	do saveOntoAndTypeID(t("id"),t("ontology"),t("type"))
	
	#; сохраняем путь к элементу справочника в индексном глобале
	do saveElementPath(t("id"),$$$getDTOproc("path",""))
	
	#; записываем в карту выходных параметров идентификатор созданного элемента
	$$$setDTOout("id",t("id"))
	
	#; очищаем карту входных параметров
	kill $$$dDTO($job,"in")
	
	#; очищаем карту параметров обработки
	kill $$$dDTO($job,"porc")
	
	#; возвращаем идентификатор созданного элемента
	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
	
	#; проверяем, существуют ли ветка глобала правил "check", для данной онтологии типа справочника и действия
	if $data($$$dRule($$$getDTOin("ontology",""),$$$getDTOin("type",""),action,"check")) 
	{ 
		#; в check("map") записываем каноническое имя нужной ветки глобала правил
		set check("map")=$name($$$dRule($$$getDTOin("ontology",""),$$$getDTOin("type",""),action,"check")) 
	}
	else { 
		#; в check("map") записываем каноническое имя ветки глобала правил для онтологии и типа справочника по умолчанию
		set check("map")=$name($$$dRule("SimpleOntology","SimpleType",action,"check")) 
	}
	
	set check("i")="" 
	for {
		#; получаем следующий номер действия
		set check("i")=$order(@check("map")@(check("i")))
		
		#; выходим из цикла, если следующего номера нет
		quit:check("i")=""
		
		#; выполняем текущую функцию проверки
		xecute $get(@check("map")@(check("i")))
		
		#; выходим из цикла, если проверка не пройдена
		quit:check<0
	}
	
	#; возвращаем результат проверок
	quit check
#; --------------------------------------------------------------------------------------------------
#; Функция проверки элемента справочника при создании.
#; --------------------------------------------------------------------------------------------------
checkCreate()private
	
	new t
	
	#; возвращаем ошибку если онтология не задана
	quit:$$$getDTOin("ontology","")="" -1
	
	#; возвращаем ошибку, если тип справочника не задан
	quit:$$$getDTOin("type","")="" -1
	
	#; выполняем функцию проверки родителя
	s t("check")=$$checkParent()
	
	#; возвращаем ошибку, если проверка не пройдена
	quit:t("check")<0 t("check")
	
	#; выполняем функцию проверки имён
	set t("check")=$$checkNames()
	
	#; возвращаем ошибку, если проверка не пройдена
	quit:t("check")<0 t("check")
	
	#; получаем идентификатор для создаваемого элемента справочника
	$$$setDTOproc("id",$$$dMaxID)
	
	#; получаем путь к ветке глобала ^Dictionary
	if $$$getDTOproc("pathParent","")="" { 
		
		#; если родителя нет - устанавливаем начальный путь как ^Dictionary(ontology)
		set t("path")=$name($$$dData($$$getDTOin("ontology",""))) 
	}
	else { 
		
		#; если путь к родителю есть - дописываем в путь нулевой версии родителя ключевое слово children
		set t("path")=$name(@($$$getDTOproc("pathParent",""))@(0,"children")) 
	} 
	
	#; к полученному пути добавляем тип создаваемого элемента и его идентификатор
	s t("path")=$name(@(t("path"))@($$$getDTOin("type",""),$$$getDTOproc("id","")))
	
	$$$setDTOproc("path",t("path"))
	
	#; возвращаем успешный результат проверки
	quit 0
#; --------------------------------------------------------------------------------------------------
#; Функция проверки родителя, если он есть.
#; Проверяет онтологию и тип родителя.
#; --------------------------------------------------------------------------------------------------
checkParent()private
	
	new t
	
	#; получаем тип родителя для данного типа справочника в глобале правил (если он есть)
	s t("parent")=$get($$$dRule($$$getDTOin("ontology",""),$$$getDTOin("type",""),"parent"),"")
	
	#; если родитель есть
	if t("parent")`="" {
		
		#; получаем значение свойства parentId из входной ветки служебного глобала
		set t("parentId")=$$$getDTOin("parentId","")
		
		#; возвращаем ошибку если родитель не задан
		if t("parentId")="" { quit -1 }
		
		#; проверяем онтологию родителя в индексном глобале 
		#; она должна совпадать с онтологией создаваемого элемента
		if $get($$$dIndex("ID",t("parentId"),"ontology"),"")`=$$$getDTOin("ontology","") { quit -1 }
		
		#; проверяем тип родителя в индексном глобале 
		if $get($$$dIndex("ID",t("parentId"),"type"),"")`=t("parent") { quit -1 }
		
		#; получаем путь к родителю
		s t("pathParent")=$get($$$dIndex("ID",t("parentId"),"path"),"")
		
		#; возвращаем ошибку если пути нет
		if t("pathParent")="" { quit -1 }
		
		#; устанавливаем путь к родителю в ветку обработки служебного глобала
		$$$setDTOproc("pathParent",t("pathParent"))
	}
		
	#; возвращаем успешный результат проверки родителя
	quit 0
#; --------------------------------------------------------------------------------------------------
checkUpdate() quit 0
#; --------------------------------------------------------------------------------------------------
checkDelete() quit 0
#; --------------------------------------------------------------------------------------------------
#; Проверка имён на различных языках.
#; --------------------------------------------------------------------------------------------------
checkNames()private
	
	new t
	
	#; устанавливаем в t("check") ошибку
	#; для случая когда ни один язык не задан
	set t("check")=-1
	
	set t("lang")="" 
	for {
		#; получаем следующий язык
		set t("lang")=$order($$$dDTO($job,"in","nameList",t("lang")))
		
		#; выходим из цикла, если следующего языка нет
		quit:t("lang")="" 
		
		#; получаем название для текущего языка
		set t("name")=$get($$$dDTO($job,"in","nameList",t("lang")),"")
		
		#; если имя пустое
		if t("name")="" {  
			#; устанавливаем в t("check") ошибку
			set t("check")=-1
		
			#; выходим из цикла
			quit
		}
		else { 
			#; в t("check") записываем признак того, что всё в порядке 
			set t("check")=0
		}
	}
	
	#; возвращаем результат проверки имён
	quit t("check")
#; --------------------------------------------------------------------------------------------------
#; Функция предварительной обработки названий.
#; --------------------------------------------------------------------------------------------------
processingNames(function) 
	new t
	set t("lang")="" 
	for { 
		#; цикл по всем языкам
		set t("lang")=$order($$$dDTO($job,"in","nameList",t("lang")))
		quit:t("lang")=""  
		
		#; обрабатываем название на текущем языке требуемой функцией
		xecute "set "_$name($$$dDTO($job,"in","nameList",t("lang")))_"=$$"_function_"("""_$$$getDTOname(t("lang"))_""")"
	}
	quit
#; --------------------------------------------------------------------------------------------------
checkUniqueNameElementAllLang(inparent="false") 
	new t 
	set t("check")=0
	s t("ontology")=$$$getDTOin("ontology","")
	s t("type")=$$$getDTOin("type","")
	set t("lang")="" 
	for { 
		#; цикл по всем языкам
		set t("lang")=$order($$$dDTO($job,"in","nameList",t("lang")))
		quit:t("lang")=""  
		
		#; получаем текущее название
		set t("name")=$get($$$dDTO($job,"in","nameList",t("lang")),"")
		
		#; в переменную t("check") записываем статус уникальности имени (0 - значит уникально)
		s t("check")=$$checkUniqueNameElement(t("ontology"),t("type"),t("lang"),t("name"),$$$getDTOproc("id",""),inparent)
		
		#; выходим из цикла если какое-то имя не уникально
		quit:t("check")<0 
	}
	quit t("check")
#; --------------------------------------------------------------------------------------------------
#; Функция удаляет из строки всю пунктуацию и служебные символы.
#; --------------------------------------------------------------------------------------------------
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")
#; --------------------------------------------------------------------------------------------------
#; --------------------------------------------------------------------------------------------------
#; Функция удаляет из строки все служебные символы.
#; Удаляется вся пунктуация перед первым словом.
#; Между словами остаётся только один крайний разделитель (символ пунктуации по возможности не пробел).
#; После крайнего слова удаляется вся пунктуация, остаётся только "." если она есть.
#; --------------------------------------------------------------------------------------------------
specialClearStr(str)
	
	new t 
	
	set t("str")="",t("flag")="start",t("delim")=""
	for t("i")=1:1:$length(str) { 
	
		set t("ch")=$extract(str,t("i"))
		
		;# заменяем табуляцию на пробел
		set:t("ch")=$char(9) t("ch")=" "
		continue:(t("ch")?1C)
		if (t("flag")="start")&&`(t("ch")?1P) { set t("str")=t("ch"),t("flag")="word" }
		elseif t("flag")="word" { 
			if `(t("ch")?1P) { set t("str")=t("str")_t("ch") } 
			else { set t("delim")=t("ch"),t("flag")="delim" }
		}
		elseif t("flag")="delim" { if (t("ch")?1P) { set:t("ch")`=" " t("delim")=t("ch") }
			else { set t("str")=t("str")_t("delim")_t("ch"),t("flag")="word",t("delim")="" }
		}
	}
	set:t("delim")="." t("str")=t("str")_t("delim")
	
	quit t("str")
#; --------------------------------------------------------------------------------------------------
#; Функция проверяет на уникальность параметр элемента.
#; --------------------------------------------------------------------------------------------------
checkUniqueParamElement(param,ontology,type)
	
	new t
	
	#; получаем значение параметра
	set t("value")=$$$getDTOin(param,"")
	
	#; возвращаем успешный результат, если параметр не задан - уникальность не нарушена
	quit:t("value")="" 0
	
	#; устанавливаем успешный результат
	set t("q")=0 
	
	set t("uniqueId")="" 
	
	for {
		#; проверяем, есть ли в индексном глобале идентификатор элемента справочника
		#; для данной онтологии, типа, языка свойства и значение
		set t("uniqueId")=$order($$$dIndex(ontology,type,param,t("value"),t("uniqueId")))
		
		#; выходим если идентификатора нет
		quit:t("uniqueId")=""
		
		#; проверяем, равенство найденного идентификатора текущему элементу
		#; это необходимо для метода update 
		if (t("uniqueId")`=$$$getDTOproc("id","")) {
			
			#; устанавливаем ошибку, если найденный идентификатор отличается от текущего
			set t("q")=-1
			quit
		}
	}
	
	quit t("q")
#; --------------------------------------------------------------------------------------------------
#; Функция проверяет на уникальность имя элемента, для указанного языка, типа и онтологии.
#; При проверке, регистр переводится вниз.
#; --------------------------------------------------------------------------------------------------
checkUniqueNameElement(ontology,type,lang,name,id,inparent="false")
	
	new t
	
	#; устанавливаем успешный результат
	set t("q")=0
	
	set t("uniqueId")="" 
	for {
		#; проверяем, есть ли в индексном глобале идентификатор элемента справочника
		#; для данной онтологии, типа,языка и названия (в нижнем регистре)
		set t("uniqueId")=$order($$$dIndex(ontology,type,"name",lang,$zconvert(name,"l"),t("uniqueId")))
		
		#; выходим если идентификатора нет
		quit:t("uniqueId")=""
		
		#; проверяем, равенство найденного идентификатора текущему элементу
		#; это необходимо для метода update 
		if (t("uniqueId")`=id) {
			
			#; если уникальность надо проверять внутри всех элементов данного типа
			if inparent="false" {
				#; устанавливаем ошибку, если найденный идентификатор отличается от текущего
				set t("q")=-1
				quit
			}
			else {
				#; если уникальность надо проверять только внутри родителя
				if $data($$$dIndex(ontology,type,"parentId",$$$getDTOin("parentId",""),t("uniqueId"))) {
					#; устанавливаем ошибку если у найденного идентификатора такой же родитель 
					#; как и у создаваемого/обновляемого элемента
					set t("q")=-1
					quit
				}	
			}
		}
	}
	
	#; возвращаем результат
	quit t("q")
#; --------------------------------------------------------------------------------------------------
#; Установка значения, для определённого свойства элемента справочника.
#; По умолчанию создаётся индекс и обратная ссылка на него.
#; --------------------------------------------------------------------------------------------------
setProperty(ontology,type,property,value,id,index="true",pathParent="")private
	
	#; устанавливаем значение свойства в нулевую (текущую) версию элемента справочника
	if pathParent="" { 
		#; Если родителя нет
		set $$$dData(ontology,type,id,0,property)=value 
	}
	else { 
		#; Если родитель есть
		set @(pathParent)@(0,"children",type,id,0,property)=value 
	}
	
	#; если свойство индексированное
	if index="true" {
		
		#; создаём индекс
		set $$$dIndex(ontology,type,property,value,id)=1
		
		#; сохраняем обратную ссылку на индекс
		set $$$dRefs(id,$name($$$dIndex(ontology,type,property,value,id)))=1
	}
		
	quit 0
#; --------------------------------------------------------------------------------------------------
#; Установка значения имени, для определённого языка элемента справочника.
#; Создаётся индекс(в нижнем регистре) и обратная ссылка.
#; --------------------------------------------------------------------------------------------------
setName(ontology,type,lang,value,id)private
	
	#; устанавливаем значение имени для текущей версии и языка
	set $$$dName(id,lang,0)=value
	
	#; сохраняем дату создания/обновления
	set $$$dName(id,lang,0,"UpdateTime")=$horolog
	
	#; создаём индекс по названию
	set $$$dIndex(ontology,type,"name",lang,$zconvert(value,"l"),id)=1
	
	#; сохраняем обратную ссылку
	set $$$dRefs(id,$name($$$dIndex(ontology,type,"name",lang,$zconvert(value,"l"),id)))=1
	
	quit 0
#; --------------------------------------------------------------------------------------------------
saveOntoAndTypeID(id,ontology,type)
	set $$$dIndex("ID",id,"ontology")=ontology
	set $$$dRefs(id,$name($$$dIndex("ID",id,"ontology")))=1
	set $$$dIndex("ID",id,"type")=type
	set $$$dRefs(id,$name($$$dIndex("ID",id,"type")))=1
	
	quit
#; --------------------------------------------------------------------------------------------------
saveElementPath(id,path)
	set $$$dIndex("ID",id,"path")=path
	set $$$dRefs(id,$name($$$dIndex("ID",id,"path")))=1
	
	quit
#; --------------------------------------------------------------------------------------------------

В листинге программы произведена замена символа ' на символ ` — это необходимо для более красивого отображения кода. В случае копирования кода программы в Caché — необходимо произвести обратную замену: заменить все символы ` на '

Добавим пару моделей, распечатаем наши глобалы и посмотри как выглядит вложенность в глобале ^Dictionary.

MONTOLOGY>kill ^tmwOntology($job)

MONTOLOGY>set ^tmwOntology($job,"in","ontology")="Vehicle",^tmwOntology($job,"in","type")="Model"

MONTOLOGY>set ^tmwOntology($job,"in","nameList","ru")=6,^tmwOntology($job,"in","nameList","partUri")=6

MONTOLOGY>set ^tmwOntology($job,"in","parentId")=3

MONTOLOGY>write $$create^Dictionary()
5
MONTOLOGY>kill ^tmwOntology($job)

MONTOLOGY>set ^tmwOntology($job,"in","ontology")="Vehicle",^tmwOntology($job,"in","type")="Model"

MONTOLOGY>set ^tmwOntology($job,"in","nameList","ru")="BMW",^tmwOntology($job,"in","nameList","partUri")="bmw"

MONTOLOGY>^tmwOntology($job,"in","nameList","ru")=3,^tmwOntology($job,"in","nameList","partUri")=3

MONTOLOGY>write $$create^Dictionary()
6
MONTOLOGY>zw ^Dictionary
^Dictionary("MaxID")=6
^Dictionary("Vehicle","Make",3,0,"UpdateTime")="62970,58071"
^Dictionary("Vehicle","Make",3,0,"children","Model",5,0,"UpdateTime")="62970,58071"
^Dictionary("Vehicle","Make",3,0,"children","Model",5,0,"parentId")=3
^Dictionary("Vehicle","Make",3,0,"children","Model",5,0,"uid")=888
^Dictionary("Vehicle","Make",3,0,"children","Model",6,0,"UpdateTime")="62970,58071"
^Dictionary("Vehicle","Make",3,0,"children","Model",6,0,"parentId")=3
^Dictionary("Vehicle","Make",3,0,"children","Model",6,0,"uid")=888
^Dictionary("Vehicle","Make",3,0,"lastId")=122
^Dictionary("Vehicle","Make",3,0,"uid")=888
^Dictionary("Vehicle","Make",4,0,"UpdateTime")="62970,58071"
^Dictionary("Vehicle","Make",4,0,"lastId")=1269
^Dictionary("Vehicle","Make",4,0,"uid")=888
^Dictionary("Vehicle","TransmissionType",1,0,"UpdateTime")="62970,58071"
^Dictionary("Vehicle","TransmissionType",1,0,"uid")=888
^Dictionary("Vehicle","TransmissionType",2,0,"UpdateTime")="62970,58071"
^Dictionary("Vehicle","TransmissionType",2,0,"uid")=888

MONTOLOGY>zw ^IndexDictionaryT
^IndexDictionary("ID",1,"ontology")="Vehicle"
^IndexDictionary("ID",1,"path")="^|""MONTOLOGY""|Dictionary(""Vehicle"",""TransmissionType"",1)"
^IndexDictionary("ID",1,"type")="TransmissionType"
^IndexDictionary("ID",2,"ontology")="Vehicle"
^IndexDictionary("ID",2,"path")="^|""MONTOLOGY""|Dictionary(""Vehicle"",""TransmissionType"",2)"
^IndexDictionary("ID",2,"type")="TransmissionType"
^IndexDictionary("ID",3,"ontology")="Vehicle"
^IndexDictionary("ID",3,"path")="^|""MONTOLOGY""|Dictionary(""Vehicle"",""Make"",3)"
^IndexDictionary("ID",3,"type")="Make"
^IndexDictionary("ID",4,"ontology")="Vehicle"
^IndexDictionary("ID",4,"path")="^|""MONTOLOGY""|Dictionary(""Vehicle"",""Make"",4)"
^IndexDictionary("ID",4,"type")="Make"
^IndexDictionary("ID",5,"ontology")="Vehicle"
^IndexDictionary("ID",5,"path")="^|""MONTOLOGY""|Dictionary(""Vehicle"",""Make"",3,0,""children"",""Model"",5)"
^IndexDictionary("ID",5,"type")="Model"
^IndexDictionary("ID",6,"ontology")="Vehicle"
^IndexDictionary("ID",6,"path")="^|""MONTOLOGY""|Dictionary(""Vehicle"",""Make"",3,0,""children"",""Model"",6)"
^IndexDictionary("ID",6,"type")="Model"
^IndexDictionary("Vehicle","Make","lastId",122,3)=1
^IndexDictionary("Vehicle","Make","lastId",1269,4)=1
^IndexDictionary("Vehicle","Make","name","partUri","bmw",4)=1
^IndexDictionary("Vehicle","Make","name","partUri","mazda",3)=1
^IndexDictionary("Vehicle","Make","name","ru","bmw",4)=1
^IndexDictionary("Vehicle","Make","name","ru","mazda",3)=1
^IndexDictionary("Vehicle","Make","uid",888,3)=1
^IndexDictionary("Vehicle","Make","uid",888,4)=1
^IndexDictionary("Vehicle","Model","name","partUri",3,6)=1
^IndexDictionary("Vehicle","Model","name","partUri",6,5)=1
^IndexDictionary("Vehicle","Model","name","ru",3,6)=1
^IndexDictionary("Vehicle","Model","name","ru",6,5)=1
^IndexDictionary("Vehicle","Model","parentId",3,5)=1
^IndexDictionary("Vehicle","Model","parentId",3,6)=1
^IndexDictionary("Vehicle","Model","uid",888,5)=1
^IndexDictionary("Vehicle","Model","uid",888,6)=1
^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

MONTOLOGY>

Нами были добавлены две модели марки MAZDA: 3 и 6 (цифры это названия моделей).

Все команды очистки и создания справочников в одном месте

	kill ^Dictionary,^IndexDictionary,^NameDictionaryElement,^RefsDictionary
	d ^InitRuleDic
	kill ^tmwOntology($job)
	set ^tmwOntology($job,"in","ontology")="Vehicle",^tmwOntology($job,"in","type")="TransmissionType"
	set ^tmwOntology($job,"in","nameList","ru")=" АКП,",^tmwOntology($job,"in","nameList","partUri")="akp"
	write $$create^Dictionary(),!
	kill ^tmwOntology($job)
	set ^tmwOntology($job,"in","ontology")="Vehicle",^tmwOntology($job,"in","type")="TransmissionType"
	set ^tmwOntology($job,"in","nameList","ru")="МЕХ",^tmwOntology($job,"in","nameList","partUri")="meh"
	write $$create^Dictionary(),!
	kill ^tmwOntology($job)
	set ^tmwOntology($job,"in","ontology")="Vehicle",^tmwOntology($job,"in","type")="Make"
	set ^tmwOntology($job,"in","nameList","ru")="MAZDA",^tmwOntology($job,"in","nameList","partUri")="mazda"
	set ^tmwOntology($job,"in","lastId")=122
	write $$create^Dictionary(),!
	kill ^tmwOntology($job)
	set ^tmwOntology($job,"in","ontology")="Vehicle",^tmwOntology($job,"in","type")="Make"
	set ^tmwOntology($job,"in","nameList","ru")="BMW",^tmwOntology($job,"in","nameList","partUri")="bmw"
	set ^tmwOntology($job,"in","lastId")=1269
	write $$create^Dictionary(),!
	kill ^tmwOntology($job)
	set ^tmwOntology($job,"in","ontology")="Vehicle",^tmwOntology($job,"in","type")="Model"
	set ^tmwOntology($job,"in","nameList","ru")=6,^tmwOntology($job,"in","nameList","partUri")=6
	set ^tmwOntology($job,"in","parentId")=3
	write $$create^Dictionary(),!
	kill ^tmwOntology($job)
	set ^tmwOntology($job,"in","ontology")="Vehicle",^tmwOntology($job,"in","type")="Model"
	set ^tmwOntology($job,"in","nameList","ru")=3,^tmwOntology($job,"in","nameList","partUri")=3
	set ^tmwOntology($job,"in","parentId")=3
	write $$create^Dictionary(),!
	zwrite ^Dictionary,^IndexDictionary,^NameDictionaryElement,^RefsDictionary

Думаю многие из вас обратили внимание на то, что в данных статьях, совершенно не затрагиваются вопросы транзакций, блокировок и обработки ошибок (возвращать -1 в случае любой ошибки явно недостаточно). Именно эти вопросы мы обсудим в следующей статье, если в коментариях не будет других пожеланий.

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

Буду рад вопросам и замечаниям, рассказывайте как подобное реализовываете вы у себя в БД (пусть даже там нет глобалов а есть SQL).

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

Автор: multik

Источник

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


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