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

28 янв. 2022 г.

Зависание nodejs при запуске через окно терминала Windows

Есть неприятная особенность в окне терминала Windows. Если включен режим Quick Edit (Быстрая вставка или Быстрое редактирование в русскоязычной версии интерфейса), то в определенный момент терминал может перейти в режим ожидания клавиатурного ввода от пользователя. Выйти из этого режима можно нажав любую клавишу, но пока это не будет сделано, сервер node будет висеть и не отвечать на запросы по сети.

У данной проблемы есть следующие решения:

  1. Отключить режим Quick edit в окне Properties терминала (см. скриншот ниже)
  2. Использовать другой терминал (например, git bash)
  3. Запускать nodejs через Task Scheduler операционной системы.
Дополнительная информация на Stack Overflow.

5 мар. 2018 г.

Конкретизируем следующий шаг

  1. Есть разбор предложения "покажи всех клиентов из минска". сейчас он рисует дерево, где слова зеленые, а остальные узлы белые.
  2. Наша задача: найти сопоставления для сущностей и, там где найдено, мы будем закрашивать узлы дерева красным цветом.
  3. Для этого нам понадобится логическая ER модель базы данных.

    Текущая реализация строит ее на основе физической структуры, но это не совсем то, что нам надо. например, в физической модели будет присутствовать таблица GD_CONTACT, хотя такой сущности нет в логической модели, где мы имеем семь сущностей: Папка, Группа, Организация, Банк, Подразделение, Физическое лицо и Сотрудник предприятия.

    Все они базируются на одной физической таблице GD_CONTACT.

  4. Предполагается, что мы добавим в БД (в дополнительные таблицы или в существующую таблицу AT_RELATIONS) информацию, которая позволит нам правильно извлекать логическую структуру БД.
  5. Причем, если такая информация для таблицы не задана, то мы берем ее физическую структуру и на основе нее создаем сущность в логической модели.
  6. Возвращаемся к нашему предложению:

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

  7. Переходим к фразе "из минска". Тут возможны несколько вариантов сопоставления.

    а) Мы наделяем слова в нашем словаре семантическими категориями. Например, Минск -- это город, а город -- это место. Ищем в базе данных и находим справочник "Административно-территориальных единиц", а "Административно-территориальная единица" находится в смысловом ряду с городом, местом.

    б) Мы анализируем предлог "из" в предложной фразе "из Минска" и узнаем, что он имеет смысл места. Далее, через смысловой (синонимический) ряд выходим на справочник "Административно-территориальных единиц".

  8. Весь анализ должен происходить на клиенте. Т.е. надо подумать о передаче схемы с сервера на клиент, представлении ее в памяти и объектах для работы с ней.
  9. На сервере схема должна читаться однократно и храниться в оперативной памяти. Чтобы последующие обращения (покдлючения) клиентов не приводили к повторному длительному чтению всей структуры БД.

23 мая 2017 г.

Как не запутаться в зависимостях объектов ПИ

Уже миллион раз успел пожалеть, что сделал в Гедымине режим добавления объектов в ПИ "с зависимыми". Это мощная функция, которая требует от применяющего досконального знания теории реляционных баз данных, структуры конкретной БД, внутреннего устройства своего прикладного решения.

Добросовестная разработка с использованием данной галки требует следования определенной последовательности операций:

  1. Добавить объект в ПИ с зависимыми.
  2. Открыть список ПИ. Найти нужное и по-порядку, по списку входящих в него объектов, проверить что именно добавилось. Убрать лишнее.
  3. Открыть список зависимостей для выбранного ПИ и проверить какие зависимости добавились. При необходимости изменить.
  4. Сохранить ПИ в репозиторий.
  5. Взять чистый эталон и загрузить на него пакет (каждый функционально завершенный и обособленный модуль должен быть оформлен в виде пакета).
  6. Протестировать работоспособность загруженного пакета.
Очень важно! Добавление с зависимостями можно делать, если разработка ведется на отдельной, специально выделенной, чистой актуальной базе данных.

Почему?

Представим, что за основую для разработки прикладного пакета мы взяли старую клиентскую базу с данными:

  1. В такой базе может присутствовать целый букет устаревших, временных, давно забытых ПИ. При добавлении "с зависимостями" они подхватятся и затянутся в список зависимых ПИ.
  2. В таблицах на таких БД могут присутствовать устаревшие, уже не используемые поля. Мало того, что они затянутся в ПИ, так еще затянутся и объекты на которые они ссылаются.
  3. Устаревшие скрипт-функции могут привести к тому, что код, работающий на разработочной базе, не будет работать на чистой базе, собранной из актуальных ПИ.

Прозаическая реальность оказалась далека от ожиданий. Добавление с зависимостями применяется чтобы на скорую руку получить результат, а какого он будет качества мало кого волнует. Потом, когда выясняется что ПИ не устанавливается, требуется затратить в 10 раз больше усилий, чтобы распутать зависимости между объектами и файлами ПИ.

Если не уверен в себе и на все 100% не понимаешь что происходит "под капотом" системы, то каждый объект надо добавлять в ПИ вручную, по-отдельности, в порядке реляционной зависимости. Что особенно ценно, при добавлении вручную маловероятны ситуации "запутывания" связей между объектами или файлами ПИ.

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

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

  1. Поскольку решение не велико, то объекты разобъем по следующим ПИ:
    • GS.Зарплата.Наряды.Метаданные -- домены, таблицы, триггеры и т.п.
    • GS.Зарплата.Наряды.Хранилище -- экранные формы.
    • GS.Зарплата.Наряды.Макросы -- перекрытые методы, локальные макросы форм.
    • GS.Зарплата.Наряды.Отчеты -- печатные формы.
  2. Зависимости настроим следующим образом:
    • GS.Зарплата.Наряды.Метаданные зависит от пакета GS.Зарплата.
    • GS.Зарплата.Наряды.Хранилище зависит от GS.Зарплата.Наряды.Метаданные.
    • GS.Зарплата.Наряды.Макросы зависит от GS.Зарплата.Наряды.Хранилище.
    • GS.Зарплата.Наряды.Отчеты зависит от GS.Зарплата.Наряды.Макросы.
  3. Создадим пакет GS.Зарплата.Наряды и сделаем его зависимым от GS.Зарплата.Наряды.Отчеты. Обратите внимание, что нет необходимости в пакете ставить зависимость от всех ПИ нашего решения, так как между ними итак уже присутствуют зависимости.
  4. Сохраним ПИ в файлы и расположим их в подкаталоге Наряды в папке Зарплата и кадры.
  5. Запишем в репозиторий.
  6. Подключимся к чистой эталонной базе данных ипоставим на загрузку пакет GS.Зарплата.Наряды. Проверим, чтобы загрузка не кидала ошибок. Проверим работоспособность после загрузки.

8 дек. 2016 г.

External exception C0000006

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

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

Мы добавили в заголовок gedemin.exe, gedemin_upd.exe и gdcc.exe флаг IMAGE_FILE_NET_RUN_FROM_SWAP. Теперь при запуске программы с сетевого диска она будет целиком копироваться в swap файл локального компьютера. Это замедлит процесс начальной загрузки, но позволит избавиться от ошибки в случае обрыва соединения.

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

Следует протестировать новый выполняемый модуль в сетях, где возникала указанная проблема. Если установка флага не поможет, то, возможно, следует аналогичный флаг установить и для всех динамических библиотек, используемых Гедымином.

Также в сети есть информация, что если ошибка появляется при работе в терминальных сессиях Windows Terminal Server 2008, то рекомендуется установить в реестре флаг:

Subkey: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\MRxSmb\Parameters
Type: REG_DWORD
Entry: MultiUserEnabled
Value: 1

29 нояб. 2016 г.

GDMNN: Задача #1

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

Цель:
  • Унифицировать механизмы фиксации и учета движения, обобщив и распространив их не только на движение ТМЦ, но и на движение (изменение, трансформацию) любого объекта учета.
  • Избавиться от дублирования данных и непрозрачных функций преобразования (поля документа => аналитические признаки в проводках).
  • Любое поле документа может быть использовано в качестве аналитического признака при построении отчетов.
  • Отойти от ограниченной структуры шапка-позиции. Для сложных документов предусмотреть наличие нескольких датасетов с произвольным уровнем вложенности. 
  • Для сумовых данных, используемых с целью ускорения выборок (INV_BALANCE), предусмотреть неблокирующую схему обновления, чтобы отказаться от автокомита в складских документах (комита частично введенного документа). 
  • Статус документа: черновик, отложенный, готовый.
  • Уменьшить размер базы и, как следствие, увеличить скорость операций по изменению и выборке. 
Для новой структуры представить запрос на построение журнала ордера с использованием SQL window functions и другого функционала Firebird 3.

27 янв. 2016 г.

Поиск кольцевых ссылок в реляционной базе данных рекурсивным SQL запросом

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

Между тем, существует способ однозначного выявления циклов без "наматывания" кругов по графу. Рассмотрим граф, показанный на рисунке ниже:

В базе данных он хранится в виде матрицы смежностей в таблице t:

  CREATE TABLE t (
    a INTEGER,
    b INTEGER
  )

Соответственно, исходные данные:

  INSERT INTO t VALUES (1, 2);
  INSERT INTO t VALUES (2, 3);
  INSERT INTO t VALUES (3, 4);
  INSERT INTO t VALUES (3, 5);
  INSERT INTO t VALUES (3, 6);
  INSERT INTO t VALUES (2, 7);
  INSERT INTO t VALUES (6, 1);

Для поиска кольцевых ссылок напишем запрос:

with recursive tree as
  (
    select distinct
      a as initial, a, b, -1 as prev
    from
      t

    union all
    
    select
      iif(tr.initial <> tt.a, tr.initial, -1) as initial,
      tt.a,
      tt.b,
      tr.a as prev
    from
      t tt JOIN tree tr ON
        tr.b = tt.a and tr.initial > 0

  )
select
  *
from
  tree
where
  initial = -1

Запрос покажет все первые сегменты A - B (и предыдущие, т.е. сегменты непосредственно приведшие к циклу, PREV - A), всех возможных циклов в заданных данных. В нашем случае:

INITIAL A B PREV
-1 1 2 6
-1 2 3 1
-1 2 7 1
-1 3 4 2
-1 3 5 2
-1 3 6 2
-1 6 1 3

При необходимости проверить не входит ли в кольцо конкретный узел, следует прописать его ИД в первом запросе рекурсивного CTE:

with recursive tree as
  (
    select distinct
      a as initial, a, b, -1 as prev
    from
      t
    where 
      a = _some_id_

    union all
    
    select
      iif(tr.initial <> tt.a, tr.initial, -1) as initial,
      tt.a,
      tt.b,
      tr.a as prev
    from
      t tt JOIN tree tr ON
        tr.b = tt.a and tr.initial > 0

  )
select
  *
from
  tree
where
  initial = -1

14 нояб. 2015 г.

Перезапуск платформы по расписанию

В идеальном мире, где идеальные люди запускают идеальные программы, ошибкам просто нет места. Но в реальной жизни shit, к сожалению, happens. Когда сроки истекли еще вчера, а технические требования заказчик меняет каждый раз, когда видит очередную версию, приходится действовать по методике "run and fire". И, если ошибки на экране в худшем случае заставят пользователя произнести несколько нецензурных слов, то неявные утечки памяти и редкие баги в серверном коде, способны свести с ума даже закаленного аса разработки и отладки.

Здесь на помощь приходит "quick and dirty" решение в виде автоматического перезапуска платформы по расписанию:

На приведенном выше скриншоте показаны настройки автозадачи для перезапуска платформы каждые 10 минут.

Стоит сделать замечание, что перезапуск не произойдет, если на экране открыто диалоговое окно в модальном режиме.

15 авг. 2015 г.

Application hangs in Windows XP SP2 compatibility mode under Windows 10

Seems that WinXP SP2 compatibility mode in the new Windows 10 has some... incompatibilities. Half of the past week was spent for a struggle with a mysterious application hang. Through some experiments it was found that the hanging is a result of interaction between gdNotifierThread and assigned notifier window. Although an update of a visual control is carried through a call of Synchronize method the whole application becomes unresponsive. The problem was solved by replacement of Synchronize call with sending a custom message using PostMessage function.

One could ask what is the reason in running Gedemin in a compatibility mode in the first place. Well, this is quick and dirty solution to make Gedemin's windows look good. Newest Windows 10 display theme looks weird with a windows and dialog boxes designed in the times when Ctl3D was a first class fashion.

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 может применяться на таблицах, где индексов нет вообще. Совершенно очевидно, что удаление с поиском по неиндесированному полю выполнялось бы в нашем случае часами, если не сутками.
Так и осталось загадкой, почему количество неиндексированных чтений при скане таблицы с индексом дважды получилось меньше правильного по теории миллиона.

3 апр. 2014 г.

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

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

Пример: учет ведется с точностью до 0.1 кг, на весы загнали три единицы и получили общую массу 511.1 кг. Простое деление даст нам 170.3666666666667 кг на единицу или 170.4 кг после округления. Когда в итоговом отчете три единицы посуммируются получится 511.2 кг, т.е. возникнет расхождение с исходными показаниями весов.

Правильно в данном случае поступить следующим образом:

  1. Общую массу перевести в целочисленное количество минимальных единиц. В нашем случае такой единицей будет 0.1 кг, соответственно,
    511.1 кг = 5111 по 0.1 кг
  2. Целочисленно разделить полученное число на количество единиц в партии:
    5111 div 3 = 1703 = D
  3. Вычислить остаток от целочисленного деления:
    5111 mod 3 = 2 = M
  4. Для произвольных M единиц в партии принять учетную массу (D + 1) для остальных -- D.
Т.е. в нашем случае получится следующая учетная масса каждой единицы в партии:
  1. (1703 + 1) * 0.1 = 170.4 кг
  2. (1703 + 1) * 0.1 = 170.4 кг
  3. 1703 * 0.1 = 170.3 кг
В сумме -- ровно 511.1 кг.

5 февр. 2014 г.

Перезапись объекта по потенциальному ключу

При загрузке объектов приоритет имеет поиск по потенциальному ключу (там, где перекрыт метод CheckTheSameStatement) перед поиском по РУИДу. Например, пусть у нас имеется исходная база данных с двумя отчетами:

Разработчик копирует ее себе на локальную машину:

...и производит доработку: удаляет отчет "Реестр" и переименовывает отчет "Новый реестр" в просто "Реестр":

Сохраняет доработанный отчет в ПИ и загружает его на исходную БД. Для отчетов у нас определен поиск объектов по имени в пределах одной папки. Т.е. будет найден отчет с именем "Реестр" и перезаписан. Попутно, его РУИД_1 заменится на РУИД_2 из файла.

Перезапись РУИДа означает, что исходный отчет "Новый реестр" вообще останется без РУИДа, потому что РУИД_2 теперь указывает на отчет "Реестр":

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

27 янв. 2014 г.

Проблемы с загрузкой одиночного ПИ

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

Ее причина проста: в ПИ содержится ссылка на объект, который отсутствует в самом ПИ и в базе данных, или имеет в базе данных другой РУИД. В данном случае проблему вызывает ссылка на папку хранилища GLOBAL\DFM\Tgdc_dlgUserComplexDocument, которая в базе данных имеет РУИД отличный от того, который записан в файле ПИ.

Избежать проблемы можно, если настроить зависимости между файлами ПИ и не использовать загрузку одиночного файла.

Системные объекты, вроде папок хранилища, должны иметь единые стандартные РУИДы, которые у нас записаны в пакете "Общие данные". Данный пакет обязательно должен быть загружен на любую базу, на которой в последствии мы хотим разрабатывать и формировать пространства имен.

7 нояб. 2012 г.

Ручное изменение поля типа ссылка

Поменять поле типа ссылка, чтобы оно вместо одной таблицы ссылалось на другую, средствами Гедымина процесс длительный и непростой. Надо создать временное поле. Скопировать в него данные. Удалить исходное поле. Удалить домен. Создать новый домен. Создать новое поле. Перегнать в него исходные данные. Если к тому же поле участвует в триггере или процедуре, то придется предварительно перекомпилировать их с закоментированным текстом и восстановить в исходное состояние, после создания нового поля.

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

Предположим, в некоторой таблице TABLE_A находится поле USR$FIELD_A типа USR$DFIELD_A, которое ссылается на таблицу TABLE_B. Изменим его так, чтобы оно ссылалось на таблицу TABLE_C. Поле USR$NAME из TABLE_C используется для отображения наименований объектов в выпадающих списках.

  1. Идем Исследователь-Сервис-Атрибуты-Таблицы. Находим в списке нашу таблицу и открываем в диалоговом окне. Переходим на вкладку Скрипт.
  2. Ищем строку вида:

    ALTER TABLE TABLE_A ADD CONSTRAINT USR$FKTABLE_ANN FOREIGN KEY (USR$FIELD_A) REFERENCES TABLE_B (ID) ON UPDATE CASCADE;

  3. Из найденой строки узнаем имя ограничения. В нашем случае это USR$FKTABLE_ANN, где вместо NN будут какие-нибудь цифры.
  4. Идем в окно SQL редактора и выполняем команду:

    ALTER TABLE TABLE_A DROP CONSTRAINT USR$FKTABLE_ANN

  5. Там же набираем и выполняем команду:

    ALTER TABLE TABLE_A ADD CONSTRAINT USR$FKTABLE_ANN FOREIGN KEY (USR$FIELD_A) REFERENCES TABLE_C (ID) ON UPDATE CASCADE ON DELETE CASCADE

    Обратите внимание, что правила ON UPDATE и ON DELETE вы должны указать в соответствии с логикой вашей задачи.

  6. Как вы поняли, первая команда удалила старую ссылку, а вторая создала новую. Остается сообщить об изменившейся ссылке Гедымину. Для начала следует в разделе Исследователь-Сервис-Атрибуты-Таблицы узнать идентификаторы таблицы TABLE_C и ее поля USR$NAME.

    Узнать ИД проще простого: выделяем запись в гриде, правая кнопка мыши, команда Свойства...

  7. Не выходя из формы просмотра с таблицами ищем TABLE_A и в ней поле USR$FIELD_A. Затем, правая кнопка мыши, комадна Свойства..., вкладка Данные:
    1. DELETERULE -- прописываем правило для удаления записи, которое мы указали в команде создания FOREIGN KEY. Например, CASCADE, RESTRICT и т.д.
  8. Идем Исследователь-Сервис-Атрибуты-Домены и находим тип USR$DFIELD_A. Правая кнопка мыши, комадна Свойства..., вкладка Данные:
    1. REFTABLE -- Прописываем имя TABLE_C.
    2. REFLISTFIELD -- Имя поля из таблицы TABLE_C, которое будет использоваться для отображения в выпадающих списках.
    3. REFTABLEKEY -- ИД таблицы TABLE_C. Его мы определили выше.
    4. REFLISTFIELDKEY -- ИД поля из таблицы TABLE_C, которое будет использоваться для отображения в выпадающих списка.
    5. REFLNAME -- локализованное наименование таблицы справочника.
    6. REFLISTLNAME -- локализованное наименование поля для отображения в выпадающих списках.
    Нажимаем Ок для сохранения изменений.
В данном примере мы предполагаем, что тип USR$DFIELD_A используется единственный раз только для нашего поля USR$FIELD_A. В противном случае, следует создать новый тип, настроить его и вручную перевести на него ссылку, отредактировав запись для поля USR$FIELD_A в таблице AT_RELATION_FIELDS.

24 авг. 2012 г.

Ветка HTTPSRV

Создал ветку HTTPSRV на основе текущих исходников с компонентами Indy 9. Цель -- встроить в Гедымин HTTP сервер. Первый шаг:
  • Сделать нить с низким приоритетом в которую поместить компонент TidHTTPServer.
  • При обращении клиента запрашивать логин и пароль, а затем генерировать страничку с информацией о системе (такой, как в окне О программе...)
Второй шаг:
  1. Подумать как подключать скрипт-функции для обработки событий HTTP сервера.
  2. Реализовать вызов скрипт-функции из нити HTTP сервера.
  3. Реализовать формирование странички и взаимодействие с браузером в скрипт-функции.

24 июл. 2012 г.

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

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

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

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

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).
  • Вместо сдвига всего правого поддиапазона относительно вставляемой записи будем выискивать свободные места и перемещать отдельные "кусочки".

10 мар. 2011 г.

Проблема с интервальными деревьями в старых файлах настроек

Если при восстановлении базы (ранее сконвертированной под Firebird 2.5) из архива выдает сообщение об ошибке с текстом "Input parameter mismatch for procedure USR$_P_EXLIM_...", значит на базу с новой структурой интервальных деревьев была загружена настройка со старыми деревьями.

В таком случае надо на исходной базе выполнить в окне SQL редактора команду:
DELETE FROM fin_versioninfo WHERE id > 112
Затем подключиться к ней новейшей версией gedemin.exe (всегда можно скачать с нашего сайта) и дождаться окончания обновления структуры базы данных. В процессе обновления будут пересозданы все процедуры и триггеры интервальных деревьев.

После этого базу можно архивировать.

Почему проблемная процедура создается без ошибок при загрузке настройки -- вопрос к создателям Firebird.

Особое внимание разработчикам! Все настройки с интервальными деревьями должны быть пересохранены после обновления структуры базы данных.