Сохранение прикладных настроек в формате 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.
Признак загрузки из потока
Надо использовать существующий флаг sLoadingFromStream. Так как добавление нового (sLoadingNamespace) потребует анализа всего программного кода. Как мимнимум его надо будет добавить везде, где использовался старый флаг загрузки из потока. Это плохая идея.
Удаление ПИ
Конвертация существующих настроек
Для конвертации существующих настроек в БД должны быть созданы метаданные ПИ. В форме просмотра бизнес-объекта Настройка добавлены две кнопки. Одна переводит выбранную настройку в ПИ. Вторая конвертирует весь список с расстановкой зависимостей между ПИ и удалением дубликатов объектов.
Для конвертации используется существующий механизм "разматывания" настройки. Флаг 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 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.