Транзакции 19.6

Транзакции 19.6

Андрей Волков

В версии 19.6 разработчики даровали нам новую возможность - проводить операции с данными в режиме транзакции.

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

Файлмейкер, как мы знаем, может производить сброс изменений, сделанных в одной текущей записи макета или в нескольких связанных записях в портале (команда Revert Record). Но переход между записями на макете или переход на другой макет автоматически вызывает запись изменений (Commit Record), после которой возврат к прежнему состоянию уже невозможен. Чтобы обойти это ограничение, разработчикам приходилось использовать всякие трюки наподобие редактирования каждой новой записи в новом окне. Это все более-менее приемлемо, если количество операций не так уж велико. Но что, если таких записей не одна сотня?

Теперь у нас есть полноценный транзактный режим, и если вы спросите, где его использовать, я отвечу: да хотя бы для контроля команды Replace Field Contents. До сего момента в случае ошибки при выполнении этой команды файлмейкер только указывал количество записей, которые НЕ были изменены. Сейчас, используя режим транзакции, вы можете быть уверены, что команда изменит все записи, либо не изменит ни одной. Вообще же транзакции просто необходимы для контроля особо ответственных процедур, связанных с учетом ТМЦ или денежных средств. Чтобы гарантировать качественный результат...


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

Режим транзакций включается внутри блока Open Transaction - Commit Transaction. Это парная команда, такая же как Loop - End Loop или If - End If

Все строки внутри блока имеют соответственно отступ

Пример блока Транзакция

Работа разработчика максимально упрощена: никаких ошибок "ловить" не нужно, файлмейкер всё сделает за вас. Если на каком-то шаге возникает ошибка, препятствующая изменению базы данных (то есть невозможно создание, удаление или изменение записи/записей), то файлмейкер сделает две вещи:

1) отменит ранее прошедшие изменения

2) выбросит скрипт из блока Транзакция

То есть если внутри блока произошла ошибка, то скрипт исполняется так, будто всего этого блока вообще не было.

Как вообще понять, была ли транзакция успешной или нет? Наиболее простой способ - перед командой Commit Transaction записать соответствующий флаг в переменную, а после выхода из блока - проверить наличие флага. Но я рекомендую другой способ: проверить ошибки, которые генерирует сам файлмейкер непосредственно после выхода из блока. В случае успешной транзакции Get(LastError) возвращает ноль. В случае же ошибки вы получите максимальную информацию о ней из функций Get(LastErrorDetail) и Get(LastErrorLocation). Важно: функции должны быть вычислены за один шаг сразу после завершения транзакции. Можно, например, таким способом:

Считывание результата транзакции в переменную $$ERROR

Сразу все сообщения об ошибках вы можете записать в JSON:

$$ERROR = JSONSetElement ( "{}";
["Error"; Get(LastError ); 1];
["Detail"; Get(LastErrorDetail ); 1];
["Location"; Get(LastErrorLocation ); 1]
)

Давайте проверим, как это работает. Намеренно вызовем ошибку попыткой записи в калькулируемое поле.

Трассируем сценарий с ошибкой в строке 8

При попытке выполнить команду в строке 8 возникает ошибка 201 (Field cannot be modified). Сценарий немедленно перебрасывается в строку 13. Она выполняется, соответственно, тоже с ошибкой. На строке 14 мы считываем функции в переменную $$ERROR и она имеет следующее содержание:

Парсим переменную $$ERROR

Get(LastError) возвращает Код ошибки

Get(LastErrorDetail) возвращает: а) сценарий, в котором произошла ошибка; б) название команды, которая не была исполнена из-за ошибки; в) номер строки, в которой возникла ошибка. То есть данная функция дополняет функцию предыдущую.

Get(LastErrorLocation) возвращает: а) сценарий, в котором произошла ошибка; б) название последней команды, которая не была исполнена из-за ошибки; в) номер строки, в которой возникла последняя ошибка. В данном случае последней командой была Commit Transaction

Как видим, наиболее ценной для разработчика является функция Get(LastErrorDetail), именно она позволяет локализовать ошибку. Get(LastErrorLocation) тоже может быть полезна, если у нас не один сценарий, а несколько вложенных, и блок с транзакциями повторяется не один раз.

Неприятный сюрприз в том, что мы не можем использовать функцию Get(LastErrorDetail), если банально используем для обработки ошибки блок If - End If. Смотрите пример на скрине ниже:

Строка 15: Функция Get(LastErrorDetail) уже ничего не возвращает

Хотя использование блока кажется наиболее рациональным (экономным и логичным) сценарием, мы не можем ТАК делать. Блок можно добавить, только вычислив предварительно все три функции...


Осталось рассказать про команду Revert Transaction. Поначалу я пытался применять эту команду для отката транзакции, как это делается классически. Потом я понял, что делать это вовсе не нужно, потому что файлмейкер выполняет всю "грязную" работу автоматически. Это замечательно, потому что не засоряет код. Но для чего нужна команда Revert Transaction?

Она нужна для того, чтобы принудительно откатить транзакцию, если соблюдается некое условие, которое сам файлмейкер НЕ считает ошибкой обработки данных. DB Services в своем поясняющем ролике приводит пример с вычислением результата поиска записей: если записи не найдены (not Get(FoundCount), то производится принудительно откат транзакции.

Команда Revert Transaction имеет три опции: "условие" (Condition), "код ошибки" (Error Code) и "описание ошибки" (Error Message). Опции не являются обязательными. Без заполненных опций команда все равно будет выполнена.

"Условие" позволяет задать формулу, которая определит срабатывание команды. "Код ошибки" и "описание ошибки", указанные разработчиком, могут затем быть прочитаны в функции Get(LastErrorDetail). Например, для следующего сценария я задал все три опции:

Переменная $$ERROR на выходе из строки 14 получает такой результат:

Custom Error Code / Custom error description

То есть функция Get(LastErrorDetail) возвращает дополнительно текстовое описание ошибки.

Вот, пожалуй, и все, что я хотел рассказать о транзакциях.

Теперь о неожиданных моментах. Режим транзакции автоматически отменяется, если внутри блока выполняется команда New Window или если внутри блока произойдет выход из скрипта. Если у вас нечто подобное в скрипте происходит, то считайте, что у вас режим транзакции не включался вовсе: все изменения пройдут как в обычном режиме, на ошибки изменения данных файлмейкер не будет реагировать никак. Фишка в том, что новое окно может быть открыто во вложенном скрипте и вы это сразу не заметите.

Что я не тестировал? Я не тестировал работу с вложенными скриптами, которые тоже содержат блок с транзакцией. Внутри одного скрипта вложить один блок в другой технически невозможно: такой скрипт файлмейкер не даст сохранить. Практически может оказаться, что внутри одного блока с транзакцией вызывается вложенный скрипт, который ТОЖЕ содержит блок с транзакцией. Что происходит в этом случае - я не проверял.

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






Report Page