Показаны сообщения с ярлыком архитектура. Показать все сообщения
Показаны сообщения с ярлыком архитектура. Показать все сообщения

14 авг. 2022 г.

Как приступить к разработке платформы Гедымин

Для разработки платформы необходим компьютер с операционной системой Windows не ниже Windows XP и 8 или 16 Гб ОЗУ. Мы рекомендуем больше памяти при использовании виртуальной машины.

Существует два варианта подготовки инструментария для компиляции. Первый -- установить Delphi 5 и Firebird 2.5 непосредственно на компьютер следуя этой инструкции.

Второй вариант -- это обратиться к специалистам нашей компании за готовой виртуальной машиной с предустановленной системой Delphi и сервером баз данных.

Существует два режима работы Гедымина с сервером базы данных. В режим клиент-сервер необходима клиентская библиотека (fbclient.dll или gds32.dll -- обязательно разрядностью 32 бита!) и запущенный сервер (на этом же компьютере или доступный по сети).

В режиме встроенного сервера необходима библиотека fbembed.dll которая, собственно, представляет собой весь сервер Firebird выполняющийся в адресном пространстве gedemin.exe.

В настоящее время Гедымин поддерживает серверы Firebird 2.5, Firebird 3 и Firebird 4. Для двух последних необходимо дополнительно настроить файл конфигурации firebird.conf

Некоторые базы данных могут использовать внешние функции из библиотеки GUDF.DLL. Ее следует скачать с нашего сайта и поместить в подкаталог UDF сервера.

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

Далее, переходим к получению исходного кода, который находится на github.com в приватном репозитории. Обратитесь к сотрудникам компании, чтобы они предоставили вам доступ к нему.

Мы работаем по такому алгоритму:

0. Настраиваем git. Прописываем свое имя пользователя и email командами:

git config --global user.name "FIRST_NAME LAST_NAME"

git config --global user.email "MY_NAME@example.com"

1. Создаем папку \Golden, переходим в нее и клонируем репозиторий себе на компьютер командой:

git clone https://github.com/GoldenSoftwareLtd/gedemin-private.git gedemin

Выполняем действия для подготовки исходников к компиляции, которые указаны в инструкции выше. Компилируем все библиотеки и сам Гедымин согласно инструкции.

2. Основная ветка -- master -- сразу идет в продакшн. В эту ветку мы не комитим изменения напрямую. Перед тем как делать какие-то изменения снимаем код с сервера и создаем новую ветку:

git pull

git checkout -b IssueXXXX

Команду выполняем в папке Gedemin. Имя ветки на ваш выбор.

3. Делаем изменения. Затем комитим их в свою ветку:

Важно! Перед комитом проверяем, что исходники компилируются в Delphi без ошибок.

git commit -a -m "Пишем комментарий"

Перед комитом можно просмотреть сделанные изменения командой:

git status -uno

Важно! Не следует включать в комит следующие файлы, даже если они были изменены:

  • gedemin.res, и, вообще, все *.res файлы
  • файлы экранных форм *.DFM, где только поменялись координаты экранных элементов при открытии формы в Delphi

Если такие файлы показывает git как измененные, следует вернуть их в исходное состояние. Например, в окне комита оболочки TortoiseGit выбрать такой файл в списке и из контекстного меню вызвать команду Revert.

4. Загружаем изменения в свою ветку в github:

Первый раз следует выполнить команду для загрузки на сервер вашей ветки:

git push --set-upstream origin <YOUR BRANCH NAME>

В последствии можно выполнять:

git push

5. Идем на сайт github.com и создаем Pull Request из вашей ветки в мастер. После того, как ведущий разработчик подтвердит ваши изменения, они попадут в мастер и пойдут в продакшн.

6. На своем компьютере переключаетесь на ветку мастер и снимаете последние изменения:

git checkout master

git pull

7. Подключив телеграм бот @gbuilderbot можно получать уведомления об автоматической компиляции проекта и сборке дистрибутивов.

С чего начать?

Мы планируем доработки и регистрируем ошибки и пожелания в списке Issues в этом репозитории.

Проконсультируйтесь в ведущим разработчиком и выберите себе для начала что-нибудь простое, чтобы познакомиться с организацией исходного кода и внутренней структурой платформы.

Например, доработки вроде этой вообще не требует написания кода, а только правки экранных форм. 

По мере ознакомления и вникания в нюансы внутренней организации платформы можно переходить к более сложным заданиям. 

14 мая 2014 г.

CURRENT OF vs поиск по ключу

Какое же утро без хорошего эксперимента. Берем встроенный сервер Firebird 2.5.3 и чистую БД:
Page size               8192 
ODS version             11.2 
Page buffers            75 
Database dialect        3 
Attributes              force write 
Создаем две идентичных по структуре таблицы:
create table t1 (i integer not null)
create table t2 (i integer not null)
Одинаково заполняем каждую из них миллионом случайных значений:
execute block
as
  declare variable i integer = 0;
  declare variable v integer = 0;
begin
  while (i < 1000000) do
  begin
    v = round(rand() * 2000000000);
    insert into t1 values (:v);
    insert into t2 values (:v);
    i = :i + 1;
  end
end
Для таблицы t1 создаем индекс:
create index t1x on t1 (i)
Делаем коммит транзакции и переподключение к базе данных. Организуем скан таблицы t1 и случайным образом удаляем четверть записей через поиск по индексированному полю:
execute block
as
  declare variable i integer;
begin
  for select i from t1 into :i
  do
  begin
    if (rand() < 0.25) then
      delete from t1 where i = :i;
  end
end
Статистика выполнения:
Execute       : 10 717.00 ms
Read          : 239 855
Writes        : 6 250
Fetches       : 4 264 681
Marks         : 250 286
IR            : 250 286
NIR           : 999 934
Deletes       : 250 286
Не совсем понятно почему NIR не миллион. Все остальное вполне логично. Коммит транзакции и еще раз удаляем четверть, теперь от оставшихся записей:
Execute       : 582 243.00 ms
Read          : 414 519
Writes        : 246 390
Fetches       : 5 712 020
Marks         : 944 206
IR            : 187 214
NIR           : 749 693 
Deletes       : 187 214
Expunges      : 250 286
Почти 10 минут! Обратите внимание на сумасшедшее количество Writes и Marks. Сборка мусора и перестроение индекса?

Далее, берем таблицу t2 и удаляем из нее четверть записей, но с помощью курсора и конструкции CURRENT OF:

execute block
as
  declare variable c cursor for
    (select i from t2);
  declare variable i integer;
begin
  open c;
  while (1=1) do
  begin
    fetch c into :i;
    if (row_count = 0) then
      leave;
    if (rand() < 0.25) then
      delete from t2 where current of c;
  end
  close c;
end
Статистика:
Execute       : 8 362.00 ms
Read          : 6 138
Writes        : 6 064
Fetches       : 2 762 582
Marks         : 250 100
NIR           : 1 000 000
Deletes       : 250 100
Обратите внимание, насколько меньше чтений по сравнению с удалением по индексированному полю. Выполнение на 20% быстрее за счет того, что не надо дергать индекс.

Коммит транзакции и еще раз удаляем четверть записей:

Execute       : 8 252.00 ms
Read          : 6 137
Writes        : 6 067
Fetches       : 3 587 315
Marks         : 693 789
NIR           : 749 900
Deletes       : 187 455
Expunges      : 250 100
Выполнился даже быстрее чем первый запрос, несмотря на сборку мусора, и в 70 (!!!) раз быстрее чем запрос с удалением по индексированному полю. Непонятно почему так велико значение Marks, ведь удаляется всего 187 455 записей.

Подсчитаем количество записей в первой таблице:

select count(*) from t1
Статистика:
Execute       : 493 697.00 ms
Read          : 181 339
Writes        : 181 084
Fetches       : 3 009 620
Marks         : 561 642
NIR           : 562 500
Expunges      : 187 214
Все ясно, сборка мусора напоролась на индекс. 8 минут на кофе. Теперь считаем во второй:
select count(*) from t2
Статистика:
Execute       : 6 443.00 ms
Read          : 6 137
Writes        : 6 064
Fetches       : 2 261 901
Marks         : 374 910
NIR           : 562 445
Expunges      : 187 455
Упс! Мы ожидали увидеть сравнимые цифры по времени выполнения для первой и второй таблицы, но разница составила 76 (!!!) раз. Все из-за наличия в первой таблице индекса, который при подсчете количества никак не используется, но катастрофически тормозит сборку мусора.

Попытаемся выяснить как влияет наличие индекса на выполнение операции CURRENT OF. Возвращаемся к исходной базе данных. Будем удалять записи из таблицы t1 с помощью конструкции CURRENT OF:

Execute       : 8 050.00 ms
Read          : 6 138
Writes        : 6 064
Fetches       : 2 765 717
Marks         : 251 145
NIR           : 1 000 000
Deletes       : 251 145
В одно время с удалением из таблицы, по которой нет индекса.

Второй цикл удаления:

Execute       : 688 480.00 ms
Read          : 240 628
Writes        : 240 341
Fetches       : 4 592 695
Marks         : 945 817
NIR           : 748 855
Deletes       : 186 248
Expunges      : 251 145
Самое большое время, которое нам пришлось наблюдать сегодня. Сборка мусора и индекс явно не дружат друг с другом.

Для оценки влияния сборки мусора повторим все операции с самого начала на исходной базе данных с флагом подключения no_garbage_collect. Первое удаление из таблицы t1 (поиск по индексированному полю):

Execute       : 12 418.00 ms
Read          : 239 735
Writes        : 6 231
Fetches       : 4 260 434
Marks         : 249 809
NIR           : 999 939
IR            : 249 809
Deletes       : 249 809
Странно, но NIR снова не миллион. Куда деваются около шестидесяти чтений непонятно.

Второе удаление:

Execute       : 9 563.00 ms
Read          : 181 842
Writes        : 6 189
Fetches       : 3 702 914
Marks         : 187 849
NIR           : 750 162
IR            : 187 849
Deletes       : 187 849
Записей стало меньше и время сканирования и удаления уменьшилось соответственно.

Для второй таблицы удаление через курсор. Первый проход:

Execute       : 8 112.00 ms
Read          : 6 138
Writes        : 6 064
Fetches       : 2 764 184
Marks         : 250 634
NIR           : 1 000 000
Deletes       : 250 634
Второй:
Execute       : 8 034.00 ms
Read          : 6 137
Writes        : 6 064
Fetches       : 2 574 131
Marks         : 187 283
NIR           : 749 366
Deletes       : 187 283
Обратите внимание, что цифры идентичны тем, которые мы имели при включенной сборке мусора. Т.е. нет индекса и сборка мусора не проблема.

Получаем количество записей для первой таблицы:

Execute       : 546.00 ms
Read          : 6 137
Writes        : 2
Fetches       : 2 012 281
Marks         : 0
NIR           : 562 342
Для второй:
Execute       : 531.00 ms
Read          : 6 137
Writes        : 2
Fetches       : 2 012 281
Marks         : 0
NIR           : 562 083
Выводы:

  • Нижесказанное применимо к частным случаям массовой обработки данных.
  • Если по таблице нет индексов, то включение/выключение сборки мусора практически не влияет на производительность.
  • Если по таблице созданы индексы, то сборка мусора способна радикально, в десятки и сотни раз, замедлить ход процесса.
  • В нашем примере CURRENT OF был на 20-25% быстрее чем поиск по индексированному полю.
  • CURRENT OF может применяться на таблицах, где индексов нет вообще. Совершенно очевидно, что удаление с поиском по неиндесированному полю выполнялось бы в нашем случае часами, если не сутками.
Так и осталось загадкой, почему количество неиндексированных чтений при скане таблицы с индексом дважды получилось меньше правильного по теории миллиона.

29 авг. 2013 г.

Подключение Embedded SWI-Prolog в Гедымин

Есть два варианта подключения Пролога. Первый -- мы обращаемся к embedded SWI-Prolog из кода на VBScript через глобальный объект (назовем его PL). Второй -- алгоритм пишется непосредственно на Прологе, а доступ к функционалу платформы происходит через Foreign Predicates.

Обращение к Прологу через глобальный объект мы рассмотрим на сравнительном примере доступа к реляционным данным из макроса на Гедымине. Пусть данные находятся в двух таблицах: GD_CONTACT(ID, CITYKEY, NAME) и GD_PLACE(ID, NAME):

ID  CITYKEY  NAME
======================
1   1        Company A
2   1        Company B
3   2        Company C

ID  NAME
======================
1   Minsk
2   Berioza 
Для извлечения cписка компаний по заданному наименованию города мы пишем SQL запрос, который выполняем через объект класса TIBSQL:
...
Dim q
Set q = Creator.GetObject(nil, "TIBSQL", "")
Set q.Transaction = gdcBaseManager.ReadTransaction
q.SQL.Text = _
  "SELECT c.name " & _
  "FROM gd_contact c JOIN gd_place p " & _
  "  ON p.id = c.citykey " & _
  "WHERE p.name = 'Minsk' "
q.ExecQuery
While Not q.EOF 
  ...
  q.Next
WEnd 
...
Теперь изобразим тоже самое на Prolog.

Исходные данные в виде предикатов Prolog выглядят так:

gd_contact(1, 1, 'Company A').
gd_contact(2, 1, 'Company B').
gd_contact(3, 2, 'Company C').

gd_place(1, 'Minsk').
gd_place(2, 'Berioza').
Правило связи компании с городом через ключи:
bycity(City, Name) :- 
  gd_place(CityID, City),
  gd_contact(_, CityID, Name).
Тогда запрос на извлечение всех минских компаний будет выглядеть следующим образом:
? bycity('Minsk', Name).
Что мы видим из данного примера?

  1. Необходим класс для доступа к Прологу из макросов. Далее по тексту будем называть его PL. Экземпляр данного класса создается стандартным образом через глобальный объект Designer/Creator. Каждый экземпляр может иметь свое имя.
  2. PL должен иметь метод для формирования предикатов на основании заданной SQL выборки. Пример кода:
    ...
    Call PL.MakePredicates(_
      "SELECT id, citykey, name FROM gd_contact", _
      gdcBaseManager.ReadTransaction, _ 
      "gd_contact", "name")
    ...
    
    Последний параметр -- имя выборки, которое будет использоваться для формирования имени .pl файла в режиме отладки.

    Пусть это и не касается непосредственно сопряжения Пролога с Гедымином, но здесь стоит вспомнить об идее изоляции разработчика от работы с SQL запросами и инкапсуляции последних внутрь т.н. SQL объекта. SQL объект -- это заранее написанный, проверенный и оптимизированный SQL запрос, загружаемый на базу стандартным образом через настройки или пространства имен. Для выполнения, программист обращается к нужному SQL объекту по его РУИДу.

  3. PL должен иметь метод для формирования предикатов на основании бизнес-класса. На вход передается класс-подтип, подмножество, дополнительные условия выборки. Сразу следует договориться о стандартном механизме (способе) загрузки данных мастер-дитэйл (например, документ накладная с позициями).
    ...
    Call PL.MakePredicates2(_
      "TgdcCompany", "", "ByID", Array(777777), "", _
      gdcBaseManager.ReadTransaction, _ 
      "gd_contact", "name")
    ...
    
  4. PL должен иметь метод для формирования предикатов на основании существующего набора данных (дата-сета).
  5. PL должен иметь метод для ручного формирования произвольных предикатов.
  6. PL должен иметь метод для вычисления цели. Результат вычисления может быть как одиночное значение, так и выборка. Для работы с последней предусмотрены методы итерации (EOF, First, Next) и методы для обращения к параметрам по имени.
  7. С помощью соответствующих методов результат вычисления может быть напрямую помещен в указанную таблицу в базе, клиент-датасет в оперативной памяти, или на его основе могут быть созданы новые бизнес-объекты в БД.
  8. Предикаты, которые создает разработчик, должны храниться в базе данных аналогично тому, как сейчас хранится код VBScript.
  9. В дереве Проводника Редактора скрипт-объектов следует добавить корневой раздел для кода на Прологе. Внутри раздела пролог-скрипты могут размещаться в папках.
  10. Минимальная единица для загрузки в объект PL -- один пролог-скрипт. Каждый скрипт имеет уникальное имя и РУИД. Загрузка осуществляется по РУИДу. Внутри скрипта могут встречаться директивы %#INCLUDE для загрузки других пролог-скриптов (аналогично директиве '#INCLUDE для VBScript).
  11. Стандартные библиотеки SWI-Prolog, которые в дистрибутиве занимают 8 Мб в 377 файлах, будут храниться в базе данных точно так же, как и обычные пролог-скрипты.
  12. Отладка:
    1. Для отладки на компьютере должен быть установлен полный вариант SWI-Prolog и, возможно, SWI-Prolog Editor.
    2. Местоположение SWI-Prolog и каталога для временных файлов задается в параметрах платформы.
    3. Режим отладки активизируется в параметрах редактора скрипт-объектов для списка конкретных экземпляров PL, заданных их именами. Если список пуст, то режим активируется для всех PL объектов.
    4. Когда активирован режим дебаггера предикаты записываются в текстовые файлы .pl программ во временном каталоге. При этом файлы формируются следующим образом:
      • Для каждого пролог-скрипта отдельный файл.
      • Результаты вызова MakePredicatesX помещаются в файлы в соответствии с заданными именами.
    5. При вызове метода вычисления цели в режиме дебаггера загружается SWI-Prolog или SWI-Prolog Editor. В среду выполнения/отладки подгружаются созданные временные файлы.
    6. Гедымин ждет завершения работы процесса SWI-Prolog.
    7. После окончания отладки проверяются файлы для выгруженных пролог-скриптов. Если обнаружены изменения Гедымин предлагает загрузить их в базу данных.

24 июл. 2012 г.

Сохранение состояния бизнес-объекта

В случаях редактирования или удаления записи из набора данных бизнес-объекта сначала мы получаем ее тип с помощью метода GetCurrRecordClass и, если он отличается от типа самого бизнес-объекта, создаем отдельный экземпляр, в котором выполняем операцию. После чего порожденный бизнес-объект уничтожается.

Проблема в том, что у нас не предусмотрена передача состояния между исходным и созданным бизнес-объектами. Пример из жизни: идет удаление нескольких выделенных записей. В процессе, для каждой создается отдельный экземпляр БО. На одной из записей возникает ошибка. Мы хотим спросить пользователя: прервать весь процесс или продолжить, пропуская проблемные записи. Вопрос в том, как и где сохранить ответ пользователя, чтобы использовать его при отработке следующих по списку записей.

Найденное на скорую руку решение — добавить флаги в поле BaseState и копировать их после создания и перед удалением созданного бизнес-объекта — выглядит поверхностным. По-хорошему, следует ввести понятие порожденного бизнес-объекта и внутри него обрабатывать перенос состояния до и после выполнения операции.

12 апр. 2012 г.

Опять двадцать пять...

...или почему задвоились объекты при переносе потоком.

Когда 3 апреля грохнулась база предприятия Б, мы восстановили архив на 30 марта. Так как часть информации из исходной базы данных могла быть прочитана, объекты за 1-2 апреля были сохранены в поток и загружены на новую базу. В результате, некоторые записи задвоились. Почему?

Как известно, при загрузке из потока система пытается найти объект по его РУИДу или с помощью запроса из метода CheckTheSameStatement. Последний определен только для узкого круга классов, так что вся надежда на РУИДы. Если РУИД загружаемого объекта не найден, то считается что такого объекта нет и создается новая запись.

Предположим, некоторая запись была создана 20 марта на рабочей еще базе простым пользователем. РУИДа у этой записи не будет, потому как он автоматически присваивается только в трех случаях:

  1. Если работа идет под учетной записью Administrator.
  2. В момент открытия диалогового окна Свойства объекта.
  3. В процессе сохранения объекта в поток.
Первый раз РУИД сформируется при сохранении в поток на поврежденной базе данных. Естественно, что поиск по этому РУИДу при загрузке из потока на базу данных из архива ничего не даст. Система примет загружаемую запись за новую и создаст дубликат.

Можно ли было избежать такого неприятного развития событий? Да. Так как архивная база является копией исходной по состоянию на 30 марта, все объекты в обеих базах, созданные до указанной даты, имеют одинаковые идентификаторы. Следовательно, если мы:

  1. Не меняли идентификатор базы данных, восстановленной из архива. (Заметим, что даже если идентификатор базы был изменен, ничего страшного не произойдет. Главное — обеспечить равенство идентификаторов двух баз до начала выполнения шагов 2 и 3.)
  2. В поврежденной базе создали вручную РУИДы для всех записей.
  3. В архивной базе создали вручную РУИДы для всех записей.
то, можно спокойно переносить объекты потоком без угрозы задвоения.

Принудительно создать РУИДы для всех записей в базе можно с помощью приведенного здесь запроса. Обратите внимание на использование оператора MERGE.

1 апр. 2012 г.

Специфика ORM модели платформы Гедымин

ORM Гедымина имеет ряд нестыковок. Известны они давно и очередной раз привлекли к себе внимание во время разработки автоматизированного теста (см. предыдущий пост). Ниже перечислены некоторые из них:
  • Не учитывается разница между истинным абстрактным базовым классом (например, TgdcBase), и базовым классом c конкретной ListTable, который служит фундаментом для иерархии наследованных классов (например, TgdcBaseContact).
  • Некоторые объекты могут использоваться только внутри кода других объектов с выполнением магических действий по программной настройке:
    1. Все наследники TgdcInvBaseRemains
    2. TgdcInvCard
    3. TgdcLink
    4. TgdcAcctDocument
    5. TgdcAcctEntryRegister
  • К списку выше можно добавить все объекты позиций документов, создать запись в которых можно только при настроенной связи мастер-дитэйл с шапкой или непосредственно указывая ИД головной записи. При этом связь надо настраивать вручную, так как объекты шапки и позиций автономны и ничего не знают друг о друге. Частично задача решена для документов и только с вовлечением интерфейса пользователя — платформа формирует экранную форму просмотра на основе информации о типе документа.
  • В ситуациях, когда бизнес-сущность хранится в нескольких таблицах, список последних нигде не задан явно. Мы можем полагаться на сформированный запрос или обращаться к метаданным. В последнем случае мы рискуем получить слишком большой список связанных таблиц, не все из которых являются частью нашего бизнес-класса. Например, окно Свойства объекта выдает такое множество связанных таблиц для документа Отпуск товара на сторону из стандартного пакета настроек:
    BN_BANKCATALOGUE, BN_BANKCATALOGUELINE, BN_BANKSTATEMENT, BN_BANKSTATEMENTLINE, BN_CURRCOMMISSION, BN_CHECKLIST, BN_CURRBUYCONTRACT, BN_CURRCOMMISSSELL, BN_CURRCONVCONTRACT, BN_CURRLISTALLOCATION, BN_CURRSELLCONTRACT, BN_DEMANDPAYMENT, CTL_INVOICE, CTL_RECEIPT, GD_TAXDESIGNDATE, GD_TAXRESULT, GD_CONTRACT, GD_DOCREALIZATION, INV_PRICE, INV_PRICELINE, USR$INV_ADDINFO, USR$BN_ACCMAKE, USR$BN_ACCMAKELINE, USR$BN_BILL, USR$BN_BILLLINE, USR$BN_BILLNDS, USR$BN_BILLNDSLINE, USR$BN_BLANKTRIP, USR$BN_BTRIPMONEY, USR$BN_BTRIPMONEYLINE, USR$BN_BUYCURR, USR$BN_CASHBOOK, USR$BN_CASHBOOKLINE, USR$BN_CASHDEPOSIT, USR$BN_CASHDEPOSITLINE, USR$BN_CASHREPORT, USR$BN_CASHREPORTLINE, USR$BN_CHANGECURR, USR$BN_COLLECT, USR$BN_COMMISS, USR$BN_COMMISSLINE, USR$BN_DATABTRIP, USR$BN_DATABTRIPLINE, USR$BN_DEMAND, USR$BN_INCOMECURR, USR$BN_INCOMECURRLINE, USR$BN_LISTDEM, USR$BN_LISTDEMLINE, USR$BN_NOTICE, USR$BN_NOTICELINE, USR$BN_ORDERBTRIP, USR$BN_ORDERBTRIPLINE, USR$BN_PAYMENT, USR$BN_SALECURR, USR$BN_SALECURRLINE, USR$BN_SHARECURR, USR$BN_SHARECURRLINE, USR$BN_TRANSFER, USR$BN_TTN, USR$BN_TTNLINE, USR$BNF_ACTS, USR$BNF_ACTSLINE, USR$BNF_CONTRACT, USR$BNF_CONTRACTLINE, USR$CHECKREGISTER, USR$CHECKREGISTERLINE, USR$CURR_PAYMENT, USR$FA_ACTNEW, USR$FA_ACTNEWLINE, USR$FA_AMORT, USR$FA_AMORTLINE, USR$FA_CHANGEPROP, USR$FA_CHANGEPROPLINE, USR$FA_COMPLECT, USR$FA_COMPLECTLINE, USR$FA_CONSERVATIONLINE, USR$FA_CONSERVATION, USR$FA_MOVEMENT, USR$FA_MOVEMENTLINE, USR$FA_REAPP_B, USR$FA_REAPP_BLINE, USR$FA_REAPP, USR$FA_REAPPLINE, USR$FA_REMONT, USR$FA_REMONTLINE, USR$FA_REPAIR, USR$FA_REPAIRLINE, USR$GS_ACCBALANCE, USR$GS_ACTWORK, USR$GS_ACTWORKLINE, USR$INCOME_ORD, USR$INCOME_ORDLINE, USR$INV_ACTWORK, USR$INV_ACTWORKLINE, USR$INV_ADDWBILL, USR$INV_ADDWBILLLINE, USR$INV_ATTORNEY, USR$INV_ATTORNEYLINE, USR$INV_BILL, USR$INV_BILLLINE, USR$INV_CARDPRMET, USR$INV_CARDPRMETLINE, USR$INV_CERT, USR$INV_CHANGEPERC, USR$INV_CHANGEPERCLINE, USR$INV_COMP, USR$INV_COMPLINE, USR$INV_CONTRACT, USR$INV_CONTRACTLINE, USR$INV_DECOMP, USR$INV_DECOMPLINE, USR$INV_GOODORDER, USR$INV_GOODORDERLINE, USR$INV_INVENT, USR$INV_INVENTLINE, USR$INV_INVMOVE, USR$INV_INVMOVELINE, USR$INV_NEWCOST, USR$INV_NEWCOSTLINE, USR$INV_OUTLAYS, USR$INV_PROCESSING, USR$INV_PROCESSINGLINE, USR$INV_PRODUCT, USR$INV_PRODUCTLINE, USR$INV_PRREC, USR$INV_PRRECLINE, USR$INV_REALCOMMIS, USR$INV_REALCOMMISLINE, USR$INV_RETAIL, USR$INV_RETAILLINE, USR$INV_RETCUST, USR$INV_RETCUSTLINE, USR$INV_RETPROV, USR$INV_RETPROVLINE, USR$INV_SELLBILL, USR$INV_SELLBILLLINE, USR$INV_SPEND, USR$INV_SPENDLINE, USR$INV_SPENDPREC, USR$INV_SPENDPRECLINE, USR$INV_TOTRADE, USR$INV_TOTRADELINE, USR$MOG_DEBTACC, USR$MOG_DEBTCONTR, USR$MOG_INBILLVAT, USR$MOG_INBILLVATLINE, USR$MOG_SERVICE, USR$REN_DIVSTATEMNLINE, USR$SKIDKI, USR$SKIDKILINE, USR$WG_ADDPAYBYPERIODLINE, USR$WG_ADDPAYBYPERIOD, USR$WG_AGREEMENTLONGLINE, USR$WG_AGREEMENT, USR$WG_AGREEMENTLINE, USR$WG_AGREEMENTLONG, USR$WG_ALIMONY, USR$WG_ALIMONYDEBT, USR$WG_ATTEST, USR$WG_AVGADDPAY, USR$WG_AVGADDPAYLINE, USR$WG_BANKCALC, USR$WG_BRIGADEORDERLINE, USR$WG_BRIGADEORDER, USR$WG_CHARGEREG, USR$WG_CHILDAID, USR$WG_CONTRACT, USR$WG_CONTRACTLINE, USR$WG_CORRECT, USR$WG_CORRECTLINE, USR$WG_FAMCHAG, USR$WG_HAZARDS, USR$WG_HAZARDSLINE, USR$WG_HOLIDAYWORK, USR$WG_HOLIDAYWORKLINE, USR$WG_INCTAXDEDUCTION, USR$WG_INCTAXOTHERPAYLINE, USR$WG_INCTAXOTHER, USR$WG_INCTAXOTHERPAY, USR$WG_INCTAXSECURITIES, USR$WG_INITINCOME, USR$WG_INITINCOMELINE, USR$WG_KINDDAY, USR$WG_KINDDAYLINE, USR$WG_LEAVEDOC, USR$WG_LEAVEDOCLINE, USR$WG_LEAVEPASS, USR$WG_LEAVESCHED, USR$WG_LEAVESCHEDLINE, USR$WG_LEAVESTOPDOC, USR$WG_MANUALINPUT, USR$WG_MANUALINPUTLINE, USR$WG_MOVEMENT, USR$WG_MOVEMENTLINE, USR$WG_MULTIORDER, USR$WG_MULTIORDERLINE, USR$WG_PARTDAY, USR$WG_PARTDAYLINE, USR$WG_PENALTYDOC, USR$WG_PERSONALORDERLINE, USR$WG_PERSONALORDER, USR$WG_PIECEWORK, USR$WG_PIECEWORKLINE, USR$WG_PROFDEVELOP, USR$WG_PU6, USR$WG_PU6LINE, USR$WG_RETRAINING, USR$WG_REWARDDOC, USR$WG_SALARYCALC, USR$WG_SALARYCALCLINE, USR$WG_SALARYPAY, USR$WG_SALARYPAYLINE, USR$WG_SCIENCEDEVELOP, USR$WG_SENCALC, USR$WG_SENCALCLINE, USR$WG_SETPAYMENT, USR$WG_SETPAYMENTLINE, USR$WG_SETSENBONUS, USR$WG_SICKLIST, USR$WG_SICKLISTJOURNAL, USR$WG_SICKLISTLINE, USR$WG_SINKDEBT, USR$WG_SINKDEBTLINE, USR$WG_STAFFLIST, USR$WG_STAFFLISTLINE, USR$WG_TAXATION, USR$WG_TBLCAL, USR$WG_TBLCALLINE, USR$WG_TIMEWORK, USR$WG_TIMEWORKLINE, USR$WG_TOTAL, USR$WG_TOTALLINE, USR$WG_VACATION, USR$WG_VACATIONLINE, USR$WG_WRITEOFF, USR$WG_WRITEOFFLINE, USR$WS_DISTANCE_LINE, USR$WS_FUEL_LINE, USR$WS_WAYSHEET
  • Как уже отмечалось выше, связь мастер-дитэйл обрабатывается платформой только для документов и только на уровне пользовательского интерфейса. Связь мастер-дитэйл-сабдитэйл не обрабатывается вообще никак.
  • Наследование подтипов невозможно.
  • Не лишней была бы возможность создать атрибут типа бизнес-класс. То, что сейчас решается созданием автономного объекта и ручным связыванием его с головной записью. Пример: дополнительная информация по товарно-транспортной накладной.
Список всех бизнес-классов платформы и пакета типовых настроек Комплексная автоматизация теперь выглядит так.

28 нояб. 2011 г.

Брюки превращаются, превращаются брюки...

Помните как там было, в Бриллиантовой руке: Легким движением руки брюки превращаются, превращаются брюки...

В Гедымине для некоторых классов возможно превращение экземпляров в объекты одного из родительских классов. Например, Физическое лицо может стать Сотрудником предприятия и наоборот. Рабочая организация — просто организацией. Естественно, что РУИД при таких метаморфозах сохраняется, а это влечет за собой проблемы при загрузке из потока. День убит на разборку такой ситуации: была рабочая организация, которая когда-то настройкой перенесена на другую базу. Впоследствии, на той базе она была удалена из списка рабочих организаций и стала просто компанией. Теперь, с очередной настройкой, эта же запись, все того же типа Рабочая организация и с тем же РУИДом, снова пытается загрузиться на базу. В процессе загрузки создается экземпляр TgdcOurCompany, который не видит уже существующую компанию, что дает основание механизму загрузки создать новый экземпляр и попытаться сохранить его в базе. При сохранении возникает исключение, так как в GD_CONTACT запись с таким ИД уже есть.

По хорошему, радикальное решение данной проблемы — это полный запрет на превращения типов. Т.е. гражданин Фунт не может стать зицпредседателем Фунтом. Он должен (ради чистоты парадигмы!) погибнуть и воскреснуть уже как ипостась другого класса и с новым РУИДом. Но, такое изменение может вылезти боком в местах, которые сейчас даже не представляется возможным предугадать.

Пока остановимся на том, что научим Рабочую организацию при загрузке из потока распознавать указанную выше ситуацию и автоматически корректировать записи в базе. Конкретную проблему мы решим, а над исправлением общей модели еще стоит поразмыслить.

8 нояб. 2011 г.

USING INDEX

Век живи — век учись. Оказывается, уже начиная с версии 1.5 в Firebird можно именовать индексы ограничений и указывать их сортировку. Синтаксис соответствующей команды:
[CONSTRAINT constraint-name]
   <constraint-type> <constraint-definition>
   [USING [ASC[ENDING] | DESC[ENDING]] INDEX index_name]
В Гедымине мы используем суррогатные целочисленные первичные ключи. Идентификатор конкретной записи уникален в пределах всех таблиц базы и является положительным числом. Никакого дополнительного смысла в идентификатор не вкладывается, за исключением диапазона от 0 до 146999999, зарезервированного для системных объектов.

Дополнительный смысл — иными словами говоря функциональная зависимость первичного ключа от некоторого поля (полей) записи помогла бы ускорить выполнение некоторых запросов и уменьшить размер базы данных. Вот несколько возможных случаев:

  • Для таблицы gd_document присваивать идентификатор в соответствии с хронологией документов. Тогда сортировка по дате и выборка за период (две наиболее частых операции со списком документов) будут выполняться с использованием индекса первичного ключа, который и так всегда под рукой у оптимизатора для соединений с дополнительными таблицами. Для быстрой выборки за период можно держать под рукой соответствие начальных ИД на каждую дату.
  • Идентификатор для интервальных деревьев присваивать в соответствии с порядковым номером узла при обходе дерева вглубь. Отпадает необходимость в полях LB, RB. Интервал для дочерних элементов будет определяться как разность между ИД соседних узлов. Для совместимости с существующим кодом можно ввести вычисляемые поля LB (просто будет возвращать значение идентификатора записи) и RB (идентификатор следующей записи на этом же уровне минус единица).
  • Для складских карточек (таблица INV_CARD) присваивать идентификатор в соответствии с очередностью цепочки, заменив поле parent на признак первой карточки в последовательности.
  • и т.д.
Можно сказать, что идет речь о симуляции кластерного индекса для таблиц, абсолютное большинство запросов к которым требует сортировки по определенному полю и/или отсечению заданного диапазона значений. Разумеется, что множество значений рассматриваемого поля должно быть линейно упорядоченным.

Основная проблема реализации такой концепции — это вставка идентификатора внутрь существующей цепочки. При отсутствии свободного промежутка придется сдвигать все идентификаторы вправо, что может вызвать цепную реакцию — каскадное обновление огромного количества записей в базе (представьте сдвиг вправо нескольких миллионов записей в таблице gd_document, к которым привязаны дополнительные таблицы документов, бухгалтерские проводки и складское движение). Мы можем минимизировать последствия, если:

  • Откажемся от единого на всю базу генератора gd_g_unique в пользу выделенного генератора для каждой таблицы. Будем формировать идентификаторы с шагом 10 (для менее населенных таблиц можно использовать шаг 100 и даже 1000).
  • Вместо сдвига всего правого поддиапазона относительно вставляемой записи будем выискивать свободные места и перемещать отдельные "кусочки".

3 авг. 2009 г.

Четыре списка параметров запроса

В окне SQL редактора (TfrmSQLEditorSyn) список параметров запроса присутствует четыре (!) раза:
  1. Непосредственно в самой компоненте ibqryWork.
  2. В отдельном списке FParams, который служит для хранения значений параметров между выполнениями запроса.
  3. Внутри окна запроса значений параметров у пользователя — TdlgInputParam.
  4. В поле SQL_PARAMS в таблице GD_SQL_HISTORY.
В добавок существуют два алгоритма преобразования списка параметров в текст и обратно:
  1. В окне TdlgInputParam для сохранения в файл.
  2. Для сохранения в таблице с историей запросов.
Для сохранения в файле (мемо поле) используется TStringList, который заполняется следующим образом: каждое поле — отдельная строка в формате имя_поля=значение_поля. Разумеется, что значение поля получается преобразованием в строковый формат согласно региональных установок, действующих в данный момент на компьютере.