Сохранение прикладных настроек в формате YAML (постановка)

Материал из GedeminWiki
Перейти к: навигация, поиск
  1. Пространство имен (ПИ) -- это содержащий объекты модуль. Пространство имен подобно настройке, с тем исключением, что настройки базируются на алгоритме сохранения в поток реляционных данных и при сохранении в файл "затягивают" объекты по ссылкам. ПИ в этом отношении легковесны -- в файл попадают только те объекты, которые непосредственно были включены разработчиком в ПИ, и в том порядке, который определил разработчик.
  2. Для группового включения объектов (например, при добавлении макроса в ПИ должны добавиться и объект макрос и объект скрипт-функция) предусмотрен отдельный внешний механизм. В частности, диалоговое окно добавления объекта в ПИ содержит таблицу зависимых объектов, в которой пользователь может выбрать что именно должно войти в ПИ.
  3. ПИ идентифицируется РУИДом.
  4. ПИ имеет имя.
  5. ПИ целиком сохраняется в один YAML файл, который состоит из: а) свойств ПИ, б) списка ПИ, от которых зависит данное ПИ, в) списка объектов. Последние два списка могут быть пустыми.
  6. Дерево зависимых пространств имен (ДЗПИ) для некоторого заданного ПИ строится начиная с самого ПИ и далее рекурсивно по списку ПИ, от которых оно зависит. Обработка ветви прекращается, если встречается циклическая ссылка.
  7. Процесс формирования файла с ПИ называется Сборкой. Процесс считывания файла с ПИ и созданием в БД объектов называется Загрузкой.
  8. Единица загрузки -- дерево зависимых пространств имен.
  9. Загрузка может проходить с принудительной перезаписью всех ПИ (кроме помеченных флагом AlwaysOverwrite = False) или с учетом номера версии и/или даты изменения.
  10. В файле ПИ мы не храним информацию о типах полей. Вместо этого ПИ содержит ссылки на ПИ со структурами данных (для пользовательских объектов) и номер требуемой версии БД (для системных объектов). Ссылки на ПИ со структурами данных добавляются автоматически при добавлении объекта в ПИ.
  11. При установленном свойстве AlwaysOverwrite, в результате загрузки ПИ на базу данных созданные объекты должны в точности соответствовать тому, что находится в дисковом файле. В процессе загрузки дополнительных вопросов о перезаписи не задается.
  12. Если в процессе загрузки ПИ с диска в базу, в базе уже присутствует прежняя версия ПИ, то объекты которые отсутствуют в дисковом файле (дисковых файлах), но присутствуют в прежней версии, считаются удаленными и удаляются физически из БД.
  13. Список ПИ хранится в таблице AT_NAMESPACE. Список объектов в -- AT_OBJECT. Зависимости между ПИ в -- AT_NAMESPACE_LINK.
  14. Если при сборке ПИ в базе данных отсутствует объект, то в системный лог записывается предупреждение. У пользователя запрашивается: превать сборку или нет. Если пользователь выбирает продолжение процесса, то запись об отсутствующем объекте удаляется из таблицы AT_OBJECT.
  15. Режим Разработчика (РР) -- это режим работы программы, доступный под учетной записью Administrator. РР активируется вручную или автоматически при обращении к Редактору скрипт-объектов, Дизайнеру форм или любой форме объектов метаданных или пространств имен.
  16. В РР объекты, которые были созданы, удалены или изменены фиксируются в таблице AT_OBJECT_LOG. Фиксация происходит на уровне бизнес-объектов.
  17. Из таблицы 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'
       ...
   - 
     ...

Окна для работы с ПИ

Диалоговое окно добавление объекта в ПИ

Содержит:

  1. Выпадающий список для выбора ПИ.
  2. Флаги AlwaysOverwrite, DontRemove и IncludeSiblings.
  3. Список связанных объектов, который содержит флаг для выбора объекта, название объекта, название ПИ, в которое он входит.
    1. Если объект не входит ни в какое ПИ, то последняя колонка для него не заполняется.
    2. Для объекта, который уже входит в некоторое ПИ флаг всегда снят и проставить его нельзя.
    3. Вверху списка находится кнопка Показать связи и числовое поле ввода Ограничить количество, в котором по умолчанию проставлено число 60.

Добавление в ПИ объекта, который еще не входит ни в какое ПИ:

  1. Поле выпадающего списка пустое или содержит наименование ПИ, сохраненное при последнем обращении к окну.
  2. Флаги AlwaysOverwrite, DontRemove и IncludeSiblings установлены в соответствии со значениями по умолчанию.
  3. Для сложных объектов список связанных объектов заполнен изначально. Например, если добавляем отчет, то в этом списке должны находятся: скрипт-функции Основная, Параметров и Событий, а также шаблон отчета и команда вызова в Исследователе.
    1. Объекты, которые не входят ни в какое ПИ изначально помечены галочками для включения в выбранное ПИ.
  4. По нажатию на кнопку Показать связи в список добавляются объекты, от которых в реляционной БД зависит текущий объект.
    1. У добавленных таким образом в список объектов галочка изначально не выставляется.
  5. По нажатию на кнопку Ок объекты и объекты, выбранные флагами в списке, добавляются в пространство имен.

Объект входит только в одно ПИ:

  1. Выпадающий список для выбора ПИ заполнен.
  2. Флаги AlwaysOverwrite и DontRemove заполнены значениями из базы данных.
  3. Для сложных объектов список связанных объектов заполнен изначально.
  4. Для перемещения объекта в другое ПИ необходимо выбрать в списке имя ПИ.
    1. При перемещении или удалении объекта также перемещаются или удаляются все объекты, отмеченные галочками в списке.


Для последнего предусмотрена кнопка, которая очищает поле выбора ПИ. Т.е. пустое поле ПИ и означает удалить объект из ПИ.

Объект входит в несколько ПИ:

Форма просмотра списка ПИ

Мастер-дитэйл. Сверху список ПИ, снизу список объектов по выбранному ПИ.

Диалоговое окно установки очередности объектов в ПИ

Форма синхронизации списка ПИ в базе данных с файлами на диске

  1. Основное пространство занимает список, состоящий из трех частей. Идея организации списка позаимствована у Total Commander. Слева идет информация о ПИ в базе данных, справа -- о ПИ на диске, а между ними располагается колонка с пиктограммкой выбранного действия. Сверху располагается панель команд и фильтров, а снизу -- область вывода сообщений.
  2. Список отсортирован по имени ПИ. Кроме имени выводится номер версии и дата изменения.
  3. Пользователь указывает каталог, где искать файлы с ПИ. Пользователь определяет надо ли сканировать подкаталоги.
  4. Если в процессе сканирования каталогов найдены одноименные файлы, то только первый файл берется для синхронизации. Предупреждения о всех дубликатах выводятся в панели сообщений.
  5. Если структура файла не соответствует файлу ПИ, то такой файл пропускается, а информация о нем выводится в панели сообщений.
  6. Действие для синхронизации определяется следующим образом:
    1. Если ПИ присутствует слева, но отсутствует справа, то оно помечается для сборки и записи на диск. Если присутствуют подкаталоги, то перед записью файла у пользователя будет запрошено место для размещения файла.
    2. Если ПИ присутствует справа, но отсутствует слева, то оно помечается для загрузки.
    3. Если ПИ присутствует с обоих сторон, то направление синхронизации определяется сначала по номерам версий, а при их равенстве по дате изменения файла.
  7. Пользователь может менять операцию синхронизации.
  8. Пользователь может открыть окно для сравнения версий ПИ из базы данных и дискового файла.

Форма установки пакетов

Форма содержит:

  1. поле с путем к папке, в которой лежат файлы пространств имен
  2. кнопку, которая открывает окно выбора папки
  3. кнопку, которая запускает сканирование папки
  4. древовидный список пакетов с чекбоксами
  5. информационное поле, где выводится наименование, версия, дата, расположение файла, комментарий к пакету и т.п.
  6. легенду
  7. кнопку, которая запускает установку выбранных пакетов

Форма похожа на существующее окно установки пакетов, но вместо простого списка содержит древовидный. Дерево строится на основании зависимостей между найденными в папке и ее подпапках файлах пространств имен.

Пример дерева:

 Комплексная автоматизация
   Отдел кадров
     Зарплата
   Материальный склад
     Учет основных средств
   Торговля и склад
   Бухгалтерия
 Касса
   Драйвер кассы ...
   Драйвер кассы ...

Пример информации о пакете:

 Фискальные регистраторы
 Версия: 20 - новая версия
 Изменен: 25.06.2012

 Требуемые версии: 
   EXE-файла: ограничений нет.
   Базы данных: ограничений нет.

 РУИД: 147014815_1094345388

 Путь: C:\Golden\setting\ККС\
 Файл: Фискальные регистраторы.xml

Изначально все чекбоксы сняты. Цветом элементов и начертанием шрифта показываются неустановленные пакеты, новые версии и т.п.

Установка/снятие чекбокса распространяется на все вложенные уровни.

Форма сравнения версий ПИ

Форма отражения хода выполнения операции по сборке/загрузке ПИ

Форма для работы с дубликатами объектов в разных ПИ

Сценарии работы

  1. Создание ПИ:
    1. Через форму просмотра.
    2. Через диалоговое окно добавления объекта в ПИ.
    3. При загрузке с диска, если такого ПИ нет в базе.
  2. Добавление объекта в ПИ:
    1. Вручную с использованием диалогового окна добавления объекта.
    2. Автоматически для сложных объектов (макрос, отчет, форма и т.п.).
    3. В полуавтоматическом режиме на основе списка зависимых объектов.
    4. В полуавтоматическом режиме на основе списка из AT_OBJECT_LOG.
    5. На основе списка объектов базы данных (метаданные, макросы, отчеты и т.п.), не входящих в ПИ.
    6. Для объектов с установленным флагом Вложенные в момент сборки ПИ происходит анализ данных и автоматическое добавление в ПИ объектов, которых там еще нет.
    7. При загрузке ПИ с диска, если в нем присутствую объекты, которых нет в ПИ в базе данных.
  3. Удаление объектов из ПИ:
    1. Вручную, с использованием диалогового окна.
    2. Автоматически, при удалении сложного объекта (макрос, отчет, форма и т.п.).
    3. При загрузке ПИ, если в загружаемом ПИ такой объект отсутствует.
    4. В момент сборки, если объект не найден в базе данных.
    5. В полуавтоматическом режиме на основе списка из AT_OBJECT_LOG.
    6. На основе списка объектов, входящих в ПИ, но отсутствующих в базе данных.
  4. Перемещение объекта в другое ПИ:
    1. Вручную с помощью диалогового окна редактирования объекта ПИ.
    2. При загрузке с диска, если такой объект уже присутствует в БД, но находится в другом ПИ, он перемещается в загружаемое ПИ.
  5. Просмотр изменений, сделанных на базе, на основе сравнения сгенерированного текста с файлом.
  6. Откат изменений через загрузку ПИ из файла.
  7. Удаление ПИ.
    1. Удаление ПИ через форму просмотра. Все объекты остаются в базе данных.
    2. Удаление ПИ через форму просмотра. Объекты из базы данных удаляются, за исключением помеченных флагом DontRemove.

Загрузка ПИ

  1. Будем называть текущей версию ПИ в базе данных и новой -- в файле на диске. При первой загрузке ПИ на базу текущая версия отсутствует.
  2. Модифицируем YAML парсер следующим образом:
    1. по мере считывания файл (поток) удерживается в памяти целиком.
    2. добавляем возможность запоминать позицию конкретного ключа в маппинге и возвращаться к этому месту в файле.
    3. добавляем возможность перемещаться к следующему заданному ключу, например, Properties\RUID, начиная с текущей позиции без полного разбора файла. (Тут YAML дает нам замечательную возможность, так как все Properties\RUID будут располагаться с одним и тем же отступом от начала строки). В последствии, мы используем эту возможность для извлечения всех РУИДов, находящихся в файле, без разбора всего его содержимого.
  3. Создадим класс для представления файла ПИ. Включим в него парсер и список объектов файлов ПИ, от которых он зависит.
  4. Из объектов такого класса строим дерево зависимых ПИ при сканировании заданного каталога на диске или дерево зависимых пространств имен для заданного ПИ.
  5. Создадим ассоциацивный массив РУИД => Позиция в файле ПИ. Для этого сканируем все файлы ПИ по дереву зависимостей, построенному для загружаемого ПИ.
    1. в случае отсутствия файла ПИ, указанного в секции USES, выдаем предупреждение.
    2. в случае встречи дубликата ПИ (дубликат считается при совпадении имени и/или РУИДа) выдаем предупреждение. Обрабатываем только первый встреченный файл ПИ.
    3. в случае встречи дубликата объекта, используем только первый экземпляр. О всех последующих выдаем предупреждение и пропускаем.
    4. в случае обнаружения циклической зависимости ПИ выдаем предупреждение.
  6. Введем список руидов загруженных объектов.
  7. Введем список руидов загружаемых объектов.
  8. Введем пул бизнес-объектов -- ассоциацивный массив полный класс бизнес объекта => экземпляр бизнес объекта.
  9. Минимальная единица загрузки -- дерево зависимых пространств имен для заданного ПИ.
  10. При загрузке последовательно обрабатываем все объекты в файле ПИ следующим образом:
    1. Добавляем объект в список загружаемых.
    2. Для объекта просматриваем все ссылки (включая ссылки на объекты из справочников для элементов множеств) и сверяем их со списком загруженных РУИДов.
    3. Если ссылка отсутствует в списке загруженных, то переходим к загрузке отсутствующего объекта.
    4. Если обнаружена кольцевая ссылка (для этого нам нужен список загружаемых руидов), то формируем SQL запрос отложенного изменения и добавляем его в список отложенных запросов.
    5. Если объект отсутствует в дереве ПИ, то пытаемся получить его ИД из текущей базы по РУИДу. Возможно нужный нам объект есть в БД, тогда используем его. Если не найден -- очищаем поле ссылку и выдаем предупреждение. Процесс не прерываем.
    6. После обработки всех ссылок берем в пуле объект нужного класса и заполняем его поля. Сохраняем объект в БД. При возникновении ошибки выдаем сообщение и прерываем процесс. При удаче -- добавляем РУИД в список загруженных.

переподключение

транзакция

структура объекта

sLoadingFromStream sLoadingNamespace

правка текстов макросов и их перезапись

обработка множеств

Удаление ПИ

Конвертация существующих настроек

Основная проблема при конвертации существующих настроек в новый формат -- это избавление от дубликатов объектов в потоке. Сами дубликаты после конвертации можно найти с помощью следующего запроса:

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 > ''

Методика тестирования

  1. Берем чистую эталонную базу и загружаем на нее пакет настроек из двоичного потока. Сохраняем ее копию. Это исходная база данных.
  2. После загрузки, преобразуем настройки в пространства имен и сохраняем в YAML файлы.
  3. Берем чистую эталонную базу и загружаем на нее пакет из пространств имен. Это конечная база данных.
  4. Сравниваем метаданные исходной и конечной баз данных. Они должны полностью совпадать.
  5. Сравниваем данные исходной и конечной баз данных. Они должны совпадать за исключением идентификаторов.

Для сравнения можно воспользоваться выгрузкой метаданных и данных базы в SQL скрипт с помощью утилиты IBExpert.

Персональные инструменты
Пространства имён

Варианты
Действия
Навигация
Инструменты