Сохранение прикладных настроек в формате YAML (постановка)
- Пространство имен (ПИ) -- это содержащий объекты модуль. Пространство имен подобно настройке, с тем исключением, что настройки базируются на алгоритме сохранения в поток реляционных данных и при сохранении в файл "затягивают" объекты по ссылкам. ПИ в этом отношении легковесны -- в файл попадают только те объекты, которые непосредственно были включены разработчиком в ПИ, и в том порядке, который определил разработчик.
- Для группового включения объектов (например, при добавлении макроса в ПИ должны добавиться и объект макрос и объект скрипт-функция) предусмотрен отдельный внешний механизм. В частности, диалоговое окно добавления объекта в ПИ содержит таблицу зависимых объектов, в которой пользователь может выбрать что именно должно войти в ПИ. При этом, если выбранный для добавления зависимый объект входит в другое ПИ, то не сам объект добавляется в ПИ, а ссылка на его содержащее ПИ добавляется в список ПИ, от которых зависит текущее ПИ. Объект который уже содержится в ПИ в списке не отображается. Повторное добавление объектов в ПИ не допускается.
- Объекты, образующие логически единую сущность, связаны через поле headobjectkey.
- ПИ идентифицируется РУИДом.
- ПИ имеет имя.
- ПИ целиком сохраняется в один YAML файл, который состоит из: а) свойств ПИ, б) списка ПИ, от которых зависит данное ПИ, в) списка объектов. Последние два списка могут быть пустыми.
- Нециклический граф зависимости пространств имен (ГЗПИ) строится для заданного множества ПИ на основании информации о зависимостях ПИ.
- Процесс формирования файла с ПИ называется Сборкой. Процесс считывания файла с ПИ и созданием в БД объектов называется Загрузкой.
- Минимальная единица загрузки -- пространство имен.
- Загрузка может проходить с принудительной перезаписью всех объектов или с учетом флага AlwaysOverwrite.
- В файле ПИ мы не храним информацию о типах полей. Вместо этого ПИ содержит ссылки на ПИ со структурами данных (для пользовательских объектов) и номер требуемой версии БД (для системных объектов). Ссылки на ПИ со структурами данных добавляются автоматически при добавлении объекта в ПИ.
- При установленном свойстве AlwaysOverwrite, в результате загрузки ПИ на базу данных созданные объекты должны в точности соответствовать тому, что находится в дисковом файле. В процессе загрузки дополнительных вопросов о перезаписи не задается.
- Если в процессе загрузки ПИ с диска в базу, в базе уже присутствует прежняя версия ПИ, то объекты, которые отсутствуют во всех ПИ из ГЗПИ, но присутствуют в прежней версии, считаются удаленными и удаляются физически из БД.
- Список ПИ хранится в таблице AT_NAMESPACE. Список объектов в -- AT_OBJECT. Зависимости между ПИ в -- AT_NAMESPACE_LINK.
- Если при сборке ПИ в базе данных отсутствует объект, то в системный лог записывается предупреждение. У пользователя запрашивается: прервать сборку или нет. Если пользователь выбирает продолжение процесса, то запись об отсутствующем объекте удаляется из таблицы AT_OBJECT.
- Режим Разработчика (РР) -- это режим работы программы, доступный под учетной записью Administrator. РР активируется вручную или автоматически при обращении к Редактору скрипт-объектов, Дизайнеру форм или любой форме объектов метаданных или пространств имен.
- В РР объекты, которые были созданы, удалены или изменены фиксируются в таблице AT_OBJECT_LOG. Фиксация происходит на уровне бизнес-объектов.
- Из таблицы AT_OBJECT_LOG соответствующие записи удаляются: а) при сборке ПИ; б) при загрузке ПИ; в) вручную.
Свойства ПИ
| Свойство | Примечание |
|---|---|
| Name | Наименование. При сборке и сохранении на диске используется как имя файла. |
| Caption | Если задано, то отображается в окне загрузки пакетов. Если не задано, то используется Name. |
| FileName | Полное имя файла из которого было загружено или в который было сохранено ПИ. |
| FileTimeStamp | Дата файла с ПИ. Заполняется при загрузке ПИ из файла или при сборке ПИ и записи в файл. Может быть пустой для ПИ, которые созданы и еще не разу не собирались. |
| Version | Номер версии. Проставляется вручную. Номер билда инкрементируется при каждом сохранении ПИ в дисковом файле. |
| Optional | При загрузке в пакетном режиме пользователю будет дана возможность выбрать загружать/не загружать. По умолчанию False. |
| Internal | Не будет видна при загрузке в пакетном режиме. По умолчанию True. |
| DBVersion | Требует версию структуры БД не ниже чем. |
| Comment | Произвольный комментарий. |
Свойства объекта в ПИ
| Свойство | Примечание |
|---|---|
| Class | Имя бизнес-класса. |
| SubType | Подтип. Может отсутствовать, если подтип пустой. |
| AlwaysOverwrite | При загрузке ПИ объект с таким флагом перезаписывает объект, существующий в БД. Если флаг не установлен, то существующий объект будет сохранен в неизменном виде. По умолчанию True. |
| DontRemove | Если флаг установлен, то при удалении ПИ с объектами из базы, такой объект будет пропущен. По умолчанию False. |
| IncludeSiblings | Если флаг установлен, то при сохранении объекта в файл автоматом добавятся в ПИ и сохранятся вложенные объекты. Например, если объект -- элемент дерева, то добавятся все подуровни. При этом HeadObject будет ссылаться на главный объект. |
| HeadObject | Для составных частей сложного объекта свойство содержит РУИД главного объекта. |
Файл ПИ
Файл ПИ начинается с указания номера версии структуры. Текущая версия 1.0. Тип строковый. Далее идут свойства ПИ, список зависимостей и объекты.
Пример файла ПИ
%YAML 1.1
StructureVersion: '1.0'
Properties:
RUID: 147108575_2060335630
Name: 'Бухгалтерские счета'
Caption: 'Бухгалтерские счета'
Version: 1.0.0.13
Optional: False
Internal: True
Uses:
- 147115598_2060335630 'Общие данные'
Objects:
-
Properties:
Class: 'TgdcAcctChart'
RUID: 300001_17
AlwaysOverwrite: False
DontRemove: False
IncludeSiblings: False
Fields:
PARENT: ~
NAME: 'План счетов'
ALIAS: 'План счетов'
ANALYTICALFIELD: ~
ACTIVITY: 'A'
...
-
...
Окна для работы с ПИ
Диалоговое окно добавление объекта в ПИ
Содержит:
- Выпадающий список для выбора ПИ.
- Флаги AlwaysOverwrite, DontRemove и IncludeSiblings.
- Список связанных объектов, который содержит флаг для выбора объекта, название объекта, название ПИ, в которое он входит.
- Если объект не входит ни в какое ПИ, то последняя колонка для него не заполняется.
- Для объекта, который уже входит в некоторое ПИ флаг всегда снят и проставить его нельзя.
- Вверху списка находится кнопка Показать связи и числовое поле ввода Ограничить количество, в котором по умолчанию проставлено число 60.
Добавление в ПИ объекта, который еще не входит ни в какое ПИ:
- Поле выпадающего списка пустое или содержит наименование ПИ, сохраненное при последнем обращении к окну.
- Флаги AlwaysOverwrite, DontRemove и IncludeSiblings установлены в соответствии со значениями по умолчанию.
- Для сложных объектов список связанных объектов заполнен изначально. Например, если добавляем отчет, то в этом списке должны находятся: скрипт-функции Основная, Параметров и Событий, а также шаблон отчета и команда вызова в Исследователе.
- Объекты, которые не входят ни в какое ПИ изначально помечены галочками для включения в выбранное ПИ.
- По нажатию на кнопку Показать связи в список добавляются объекты, от которых в реляционной БД зависит текущий объект.
- У добавленных таким образом в список объектов галочка изначально не выставляется.
- По нажатию на кнопку Ок объекты и объекты, выбранные флагами в списке, добавляются в пространство имен.
Объект входит только в одно ПИ:
- Выпадающий список для выбора ПИ заполнен.
- Флаги AlwaysOverwrite и DontRemove заполнены значениями из базы данных.
- Для сложных объектов список связанных объектов заполнен изначально.
- Для перемещения объекта в другое ПИ необходимо выбрать в списке имя ПИ.
- При перемещении или удалении объекта также перемещаются или удаляются все объекты, отмеченные галочками в списке.
Для последнего предусмотрена кнопка, которая очищает поле выбора ПИ. Т.е. пустое поле ПИ и означает удалить объект из ПИ.
Объект входит в несколько ПИ:
Форма просмотра списка ПИ
Мастер-дитэйл. Сверху список ПИ, снизу список объектов по выбранному ПИ.
Диалоговое окно установки очередности объектов в ПИ
Форма синхронизации списка ПИ в базе данных с файлами на диске
- Основное пространство занимает список, состоящий из трех частей. Идея организации списка позаимствована у Total Commander. Слева идет информация о ПИ в базе данных, справа -- о ПИ на диске, а между ними располагается колонка с пиктограммкой выбранного действия. Сверху располагается панель команд и фильтров, а снизу -- область вывода сообщений.
- Список отсортирован по имени ПИ. Кроме имени выводится номер версии и дата изменения.
- Пользователь указывает каталог, где искать файлы с ПИ. Пользователь определяет надо ли сканировать подкаталоги.
- Если в процессе сканирования каталогов найдены одноименные файлы, то только первый файл берется для синхронизации. Предупреждения о всех дубликатах выводятся в панели сообщений.
- Если структура файла не соответствует файлу ПИ, то такой файл пропускается, а информация о нем выводится в панели сообщений.
- Действие для синхронизации определяется следующим образом:
- Если ПИ присутствует слева, но отсутствует справа, то оно помечается для сборки и записи на диск. Если присутствуют подкаталоги, то перед записью файла у пользователя будет запрошено место для размещения файла.
- Если ПИ присутствует справа, но отсутствует слева, то оно помечается для загрузки.
- Если ПИ присутствует с обоих сторон, то направление синхронизации определяется сначала по номерам версий, а при их равенстве по дате изменения файла.
- Пользователь может менять операцию синхронизации.
- Пользователь может открыть окно для сравнения версий ПИ из базы данных и дискового файла.
Форма установки пакетов
Форма содержит:
- поле с путем к папке, в которой лежат файлы пространств имен
- кнопку, которая открывает окно выбора папки
- кнопку, которая запускает сканирование папки
- древовидный список пакетов с чекбоксами
- информационное поле, где выводится наименование, версия, дата, расположение файла, комментарий к пакету и т.п.
- легенду
- кнопку, которая запускает установку выбранных пакетов
Форма похожа на существующее окно установки пакетов, но вместо простого списка содержит древовидный. Дерево строится на основании зависимостей между найденными в папке и ее подпапках файлах пространств имен.
Пример дерева:
Комплексная автоматизация
Отдел кадров
Зарплата
Материальный склад
Учет основных средств
Торговля и склад
Бухгалтерия
Касса
Драйвер кассы ...
Драйвер кассы ...
Пример информации о пакете:
Фискальные регистраторы Версия: 20 - новая версия Изменен: 25.06.2012 Требуемые версии: EXE-файла: ограничений нет. Базы данных: ограничений нет. РУИД: 147014815_1094345388 Путь: C:\Golden\setting\ККС\ Файл: Фискальные регистраторы.xml
Изначально все чекбоксы сняты. Цветом элементов и начертанием шрифта показываются неустановленные пакеты, новые версии и т.п.
Установка/снятие чекбокса распространяется на все вложенные уровни.
Форма сравнения версий ПИ
Форма отражения хода выполнения операции по сборке/загрузке ПИ
Форма для работы с дубликатами объектов в разных ПИ
Сценарии работы
- Создание ПИ:
- Через форму просмотра.
- Через диалоговое окно добавления объекта в ПИ.
- При загрузке с диска, если такого ПИ нет в базе.
- Добавление объекта в ПИ:
- Вручную с использованием диалогового окна добавления объекта.
- Автоматически для сложных объектов (макрос, отчет, форма и т.п.).
- В полуавтоматическом режиме на основе списка зависимых объектов.
- В полуавтоматическом режиме на основе списка из AT_OBJECT_LOG.
- На основе списка объектов базы данных (метаданные, макросы, отчеты и т.п.), не входящих в ПИ.
- Для объектов с установленным флагом Вложенные в момент сборки ПИ происходит анализ данных и автоматическое добавление в ПИ объектов, которых там еще нет.
- При загрузке ПИ с диска, если в нем присутствую объекты, которых нет в ПИ в базе данных.
- Удаление объектов из ПИ:
- Вручную, с использованием диалогового окна.
- Автоматически, при удалении сложного объекта (макрос, отчет, форма и т.п.).
- При загрузке ПИ, если в загружаемом ПИ такой объект отсутствует.
- В момент сборки, если объект не найден в базе данных.
- В полуавтоматическом режиме на основе списка из AT_OBJECT_LOG.
- На основе списка объектов, входящих в ПИ, но отсутствующих в базе данных.
- Перемещение объекта в другое ПИ:
- Вручную с помощью диалогового окна редактирования объекта ПИ.
- При загрузке с диска, если такой объект уже присутствует в БД, но находится в другом ПИ, он перемещается в загружаемое ПИ.
- Просмотр изменений, сделанных на базе, на основе сравнения сгенерированного текста с файлом.
- Откат изменений через загрузку ПИ из файла.
- Удаление ПИ.
- Удаление ПИ через форму просмотра. Все объекты остаются в базе данных.
- Удаление ПИ через форму просмотра. Объекты из базы данных удаляются, за исключением помеченных флагом DontRemove.
Загрузка ПИ
- Для загрузки ПИ пользователь задает папку. Программа сканирует папку вместе со вложенными папками и строит граф зависимостей ПИ. Граф отображается в диалоговом окне загрузки ввиде набора деревьев зависимости. В окне можно управлять режимом отображения: показывать все ПИ или только те, у которых флаг Internal=False (аналог пакетов в нашей старой идеологии).
- Если при построении графа обнаружена циклическая ссылка, то выдается предупреждение. Циклическая ссылка в итоговый граф не заносится. Построение графа не прерывается.
- Если при построении графа обнаружен отсутствующий файл, то выдается предупреждение. Построение графа не прерывается.
- Цветом и атрибутами шрифта в дереве показывается: черный -- ПИ отсутствует в БД или ПИ в БД имеет более старую версию. Серый -- ПИ в БД имеет более новую версию. Красный -- ПИ присутствует в списке зависимостей, но соответствующий файл отсутствует. Перечеркнуто -- версия текущей БД меньше, чем запрашиваемая версия структуры БД для ПИ.
- ПИ в файле определяется по РУИДу в файле, но не по его названию или пути.
- В дереве отображается заголовок (Caption) ПИ. При установке курсора на элемент в дереве на отдельной панели пользователь видит: РУИД, наименование, заголовок, расположение и имя файла, версию в файле, дату файла, размер файла, версию в БД, дату сохранения из БД.
- Если при построении графа обнаружен дубликат ПИ, то выдается подробное предупреждение, которое содержит имя и расположение первого встреченного файла ПИ и второго. Дубликат ПИ не обрабатывается.
- Против каждого ПИ в дереве можно поставить галку.
- При выделении пакета автоматом выбираются все вложенные ПИ, даже если они не отображаются в дереве.
- ПИ помеченные флагом Optional=True отображаются в дереве всегда. При выборе вышележащего ПИ они не выбираются. Пользователь должен выбрать их вручную.
- Если версия ПИ в БД более новая, чем на диске, то при выборе вышележащего ПИ оно не выбирается.
- По правой кнопке мыши доступна команда "Выбрать все". Она выбирает все нижележащие ПИ вне зависимости от флагов и номеров версий.
- Находясь в дереве можно вызвать следующие команды: удалить файл (или выбранные файлы) с диска (после выполнения запускается пересканирование дерева); открыть файл для просмотра; открыть объект ПИ в БД для просмотра; открыть текст ПИ в БД для просмотра; открыть окно сравнения файла на диске с текстом ПИ в БД.
- После окончания выбора пользователь запускает загрузку нажатием кнопки. При этом программа анализирует граф зависимости выбранных ПИ и начинает загрузки снизу (от менее зависимых к более).
- Перед началом загрузки по базе данных строится список РУИДов объектов, входящих в ПИ, выбранные для загрузки.
- В процессе загрузки строится список РУИДов загруженных из файлов объектов.
- По окончании загрузки разность этих списков используется для удаления объектов из БД.
- Объекты, помеченные флагом DontRemove=False, из БД не удаляются.
- При удалении объекта удаляются все связанные с ним через headobjectkey, вне зависимости от установленного у них флага DontRemove.
- В окне загрузки есть флаг "Всегда перезаписывать". Если пользователь установит его, то объекты будут всегда перезаписываться. Если этот флаг снят, то объект перезаписывается в зависимости от того, как установлен его признак AlwaysOverwrite.
- В окне загрузки есть флаг "Не удалять объекты".
- Программный интерфейс загрузки двухступенчатый. Сначала вызывается метод построения графа зависимостей. После чего может быть вызван метод загрузки. На вход ему передается неупорядоченный список ПИ и значения флагов.
- Загружать объекты можно также через окно синхронизации.
Перед началом загрузки всех выбранных ПИ:
- Вся загрузка ведется на одной транзакции.
- Создадим пул бизнес-объектов. Каждый объект свяжем с единой транзакцией.
Загрузка ПИ:
- Откроем транзакцию, если она закрыта.
- Создадим ассоциацивный массив РУИД => список РУИДов. Он понадобится нам для хранения связей между head object и подчиненными объектами.
- Создадим в таблице AT_NAMESPACE временную запись куда будем помещать объекты загружаемого ПИ.
- Объекты загружаются последовательно.
- При заполнении ссылки смотрим на существование РУИДА записи в БД. Если нет, то, возможно, это кольцевая ссылка. Проверим существование объекта с таким РУИДом позже в файле ПИ. Если есть, то формируем запрос отложенного изменения.
- Если объект в БД существует, то руководствуемся флагом AlwaysOverwrite, перезаписывать его или нет.
Окончание загрузки ПИ:
- Обновляем запись о ПИ в БД.
- Заменяем список объектов ПИ в БД на список загруженных объектов, который мы записывали во временное ПИ. Временное ПИ удаляем.
- Заменяем список USES для ПИ в БД списком из файла. При этом, если в USES загружаемого файла встречается ПИ, которое отсутствует в БД, то создаем его.
- Если в процессе загрузки встречались объекты метаданных, то комитим транзакцию и делаем переподключение к БД.
- Если это последнее ПИ в списке, то комитим транзакцию.
Обработка ошибок:
- При возникновении ошибки процесс прерываем, сообщаем пользователю и откатываем транзакцию.
Переподключение
Переподключение возникло во времена Yaffil из-за проблем при создании внешних ключей. Тогда же была добавлена таблица AT_TRANSACTION, куда в случае не монопольного режима записываются отложенные SQL скрипты. В процессе коннекта к базе платформа проверяет данную таблицу и при наличии в ней записей последовательно выполняет SQL скрипты.
ФБ 2.5 не требует переподключений к БД при создании метаданных. Главное, чтобы операции выполнялись в отдельной транзакции, которая завершалась комитом. В будущем следует персмотреть логику бизнес-объектов метаданных и отказаться от использования таблицы AT_TRANSACTION.
Вторая причина переподключения -- это считывание информации о структуре БД (atDatabase) после ее изменения. atDatabase вообще следует полностью переписать. Объект сейчас работает слишком медленно. В качестве быстрого решения проблемы можно предложить полную очистку и повторное считывание структуры БД, после изменения метаданных. Такое решение чревато возникновением AV, если где-то сохранились ссылки на объекты структуры atDatabase.
Отличие структуры объекта
Может так получиться, что при загрузке объекта, структура его таблиц в БД будет отличаться от структуры в YAML.
Удаление ПИ
Конвертация существующих настроек
Для конвертации существующих настроек в БД должны быть созданы метаданные ПИ. В форме просмотра бизнес-объекта Настройка добавлены две кнопки. Одна переводит выбранную настройку в ПИ. Вторая конвертирует весь список с расстановкой зависимостей между ПИ и удалением дубликатов объектов.
Для конвертации используется существующий механизм "разматывания" настройки. Флаг NeedModify переводится в AlwaysOverwrite=True.
Основная проблема при конвертации существующих настроек в новый формат -- это избавление от дубликатов объектов в потоке. Сами дубликаты после конвертации можно найти с помощью следующего запроса:
SELECT o.objectclass, o.subtype, o.objectname, o.xid, o.dbid, list(n.name), COUNT(*) FROM at_object o JOIN at_namespace n ON n.id = o.namespacekey WHERE o.xid > 147000000 GROUP BY o.objectclass, o.subtype, o.objectname, o.xid, o.dbid HAVING COUNT(*) > 1 ORDER BY COUNT(*) DESC
Предпоследняя колонка показывает список ПИ, включающих объект, а последняя -- их количество.
Удаление дубликатов осуществляется с помощью следующего кода:
RECREATE PROCEDURE at_del_duplicates ( DeleteFromID INTEGER, CurrentID INTEGER, Stack VARCHAR(32000)) AS DECLARE VARIABLE id INTEGER; DECLARE VARIABLE nsid INTEGER; BEGIN IF (:DeleteFromID <> :CurrentID) THEN BEGIN FOR SELECT o1.id FROM at_object o1 JOIN at_object o2 ON o1.xid = o2.xid AND o1.dbid = o2.dbid WHERE o1.NAMESPACEKEY = :DeleteFromID AND o2.NAMESPACEKEY = :CurrentID AND o2.headobjectkey IS NULL AND o1.headobjectkey IS NULL INTO :id DO BEGIN DELETE FROM at_object WHERE id = :id; END END FOR SELECT l.useskey FROM at_namespace_link l WHERE l.namespacekey = :CurrentID AND POSITION(('(' || l.useskey || ')') IN :Stack) = 0 INTO :nsid DO BEGIN EXECUTE PROCEDURE at_del_duplicates (:DeleteFromID, :nsid, :Stack || '(' || :nsid || ')'); END END EXECUTE block AS DECLARE variable id INTEGER; BEGIN FOR SELECT id FROM at_namespace INTO :id do EXECUTE PROCEDURE at_del_duplicates(:id, :id, ''); END
Поиск кольцевых ссылок
CREATE OR ALTER PROCEDURE at_p_findnsrec (InPath VARCHAR(32000), InFirstID INTEGER, InID INTEGER) RETURNS (OutPath VARCHAR(32000), OutFirstID INTEGER, OutID INTEGER) AS DECLARE VARIABLE ID INTEGER; DECLARE VARIABLE NAME VARCHAR(255); BEGIN FOR SELECT l.useskey, n.name FROM at_namespace_link l JOIN at_namespace n ON n.id = l.useskey WHERE l.namespacekey = :InID INTO :ID, :NAME DO BEGIN IF (POSITION(:ID || '=' || :NAME || ',' IN :InPath) > 0) THEN BEGIN OutPath = :InPath || :ID || '=' || :NAME; OutID = :ID; OutFirstID = :InFirstID; SUSPEND; END ELSE BEGIN FOR SELECT OutPath, OutFirstID, OutID FROM at_p_findnsrec(:InPath || :ID || '=' || :NAME || ',', :InFirstID, :ID) INTO :OutPath, :OutFirstID, :OutID DO BEGIN IF (:OutPath > '') THEN SUSPEND; END END END END
SELECT DISTINCT f.OutPath FROM at_namespace n LEFT JOIN at_p_findnsrec('', n.id, n.id) f ON f.OutFirstID = n.id WHERE f.OutPath > ''
Методика тестирования
- Берем чистую эталонную базу и загружаем на нее пакет настроек из двоичного потока. Сохраняем ее копию. Это исходная база данных.
- После загрузки, преобразуем настройки в пространства имен и сохраняем в YAML файлы.
- Берем чистую эталонную базу и загружаем на нее пакет из пространств имен. Это конечная база данных.
- Сравниваем метаданные исходной и конечной баз данных. Они должны полностью совпадать.
- Сравниваем данные исходной и конечной баз данных. Они должны совпадать за исключением идентификаторов.
Для сравнения можно воспользоваться выгрузкой метаданных и данных базы в SQL скрипт с помощью утилиты IBExpert.