БЛОКИ ABL

Блок в ABL представляет собой набор операторов объединенных в единое целое. Блок состоит из заголовка, в котором определяются свойства блока, тела блока - набора операторов входящих в блок и закрывающего блок оператора END.

Всего в ABL выделяют 4 вида блоков:

  • ВЫЗЫВАЕМЫЕ БЛОКИ;
  • БАЗОВЫЕ БЛОКИ;
  • ЗАВЕРШАЮЩИЕ БЛОКИ;
  • БЛОКИ КЛАССОВ.


ВЫЗЫВАЕМЫЕ БЛОКИ (ROUTINE-LAVEL BLOCKS)


Данное описание блоков ABL очень похоже на описание внутренних процедур и функций. Это действительно так, и внутренние процедуру, и функции, а так же внешние процедуры, являются rountine-lavel блоками. Внешние процедуры не имеют явного заголовка и закрывающего блока END, но имеют тело. Тем не менее, внешние процедуры так же являются блоками, операторы, составляющие тело которых, выполняются как единое целое при вызове процедуры оператором RUN.

К rountine-lavel блокам относятся также и триггеры, выполнение которых осуществляется по наступлению определенных событий в базе данных или в пользовательском интерфейсе.


БАЗОВЫЕ БЛОКИ (BASIC BLOCKS)


Мы уже сталкивались с такими блоками как: DO, FOR и REPEAT. Данные блоки являются базовыми блоками ABL и размещаются внутри rountine-lavel блоков.

Если блок FOR мы подробно рассмотрели в статье Поиск и выборка записей, то с блоками DO и REPEAT мы знакомы только по циклам. Рассмотрим их подробнее.


БЛОК DO

[label:]
DO

  {[FOR record [, record ] ... ]}
  [preselect-phrase]
  [query-tuning-phrase]
  [variable = expression1 TO expression2 [BY k]]
  [WHILE expression]
  [TRANSACTION]
  [STOP-AFTER expression]
  [on-endkey-phrase]
  [on-error-phrase]
  [on-quit-phrase]
  [on-stop-phrase]
  {[frame-phrase]} : 

  do-body

END.

Изначально, блок DO не является итерационным, а становится таковым только, если используется с опцией [variable = expression1 TO expression2 [BY k]] или [WHILE expression]. 

Основным назначением блока DO является объединение набора операторов в единое целое.

FIND FIRST acct WHERE acct.acct = "40817810000000000001" 
NO-LOCK NO-ERROR.

IF AVAIL acct THEN
DO:
      ...
END.

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


БЛОК REPEAT

[label:]
REPEAT

  [FOR record [, record] ...]
  [preselect-phrase]
  [query-tuning-phrase]
  [variable = expression1 TO expression2 [BY k]]
  [WHILE expression]
  [TRANSACTION]
  [STOP-AFTER expression]
  [on-endkey-phrase]
  [on-error-phrase]
  [on-quit-phrase]
  [on-stop-phrase]
  [frame-phrase]
  [catch-block [catch-block ...]]
  [finally-block]

  repeat-body

END.

Блок REPEAT, в отличие от блока DO, изначально является итерационным.

label - метка блока.

preselect-phrase осуществляет предварительную выборку записей таблиц, для последующей работы с ними в блоке. Выборка записей осуществляется перед входом в блок.

PRESELECT
  [EACH | FIRST | LAST ] record-phrase
  [, [EACH | FIRST | LAST] record-phrase]
    [BREAKE]
    {BY expression [DESCENDING]} ...
  ]

Для обращения к отобранным записям в теле блока применяется оператор FIND NEXT.

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

Давайте вспомним проблему изменения значений полей записей составляющих PRIMARY индекс, которую мы рассматривали в разделе СОЗДАНИЕ, УДАЛЕНИЕ И РЕДАКТИРОВАНИЕ ЗАПИСЕЙ.

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

Заменим блок FOR EACH на блок REPEAT PRESELECT:

REPEAT
   PRESELECT EACH tt-student:
   FIND NEXT tt-student.
   tt-student.kurs = tt-student.kurs + 1.
   DISPL tt-student.
END.

Теперь процедура не уходит в бесконечный цикл.

STOP-AFTER expression определяет лимит времени (time-out), выраженный в секундах expression, отводимый на выполнение каждой итерации блока. По истечению заданного периода времени выполнение тела блока будет принудительно завершено.

DO STOP-AFTER 10:
   ...
END.

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

Отсчет лимита отводимого на выполнение времени осуществляется отдельно для каждого блока, для которого он определен. Так один блок с опцией STOP-AFTER может включать в себя другой блок с опцией STOP-AFTER

DO STOP-AFTER 5:  
REPEAT STOP-AFTER 10:
...
END.
END.

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

Отводимое на выполнение итерации блока время может быть изменено внутри блока:

DEF VAR vTime AS INT NO-UNDO.
vTime = 10.
REPEAT STOP-AFTER vTime:
   ...
vTime = vTime + 5.
END.

В приведенном примере, на выполнение первой итерации блока REPEAT будет отведено 10 секунд, для второй итерации - этот интервал будет увеличен на 5 секунд и составит уже 15 секунд и т.д.

В случае, если указываемое время равно 0 или является отрицательным, то такие значения игнорируются и установка лимита времени не осуществляется.

Если отведенное на выполнение блока время истекло, а блок так и не завершен, то выполнение блока прерывается и возникает событие STOP.

Одним из основных примеров использования STOP-AFTER является установление time-out на выполнение вызываемой процедуры:

DO STOP-AFTER 10:
   RUN procedure1.p.
END.

Если процедура procedure1.p не завершиться за отведенные ей 10 секунд, то ее выполнение будет принудительно завершено. При этом как мы уже говорили возникнет событие STOP и нам необходимо будет определиться с тем, что делать в этом случае дальше.  

Во время выполнения операторов, составляющих тело блока, возможно возникновение ряда событий. К таким событиям относятся:

  • ENDKEY:
    при прерывании пользователем при вводе данных, по нажатию клавиши, определенной в системе как ENDKEY (клавиша ESC);
    при достижении конца потока ввода, например, при достижении конца файла, из которого осуществляется импорт данных;
    при достижении конца поиска, осуществляемого с помощью операторов FIND NEXT и FIND PREV. 
  • ERROR - возникновение ошибки в операторе;
  • QUIT - выполнение оператора QUIT;
  • STOP - возникновение системной ошибки, например, когда вызываемая процедура не найдена, либо по нажатию пользователем CTRL+C.

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

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

ON ENDKEY UNDO
  [label1]
    [  , LEAVE [label2]
     | , NEXT [label2]
     | , RETRY [label1]
     | , RETURN [return-value |
                ERROR [return-value | error-object-expression] |
                NO-APPLY]

on-error-phrase определяет поведение при возникновении события ERROR. По умолчанию, при возникновении ошибки осуществляется откат всех совершенных операций в текущей итерации блока, после чего AVM осуществляет попытку повторить итерацию в которой возникла ошибка.

ON ERROR UNDO
  [label1]
  [  , LEAVE [label2]
   | , NEXT [label2]
   | , RETRY [label1]
   | , RETURN [return-value |
               ERROR [return-value | error-object-expression] |
               NO-APPLY]
   | , THROW
  ]

on-quit-phrase определяет поведение при возникновении в блоке события QUIT. По умолчанию, при этом сохраняются все изменения совершенные в блоке, после чего осуществляется завершение приложения.

ON QUIT
  [UNDO [label1]]
  [  , LEAVE [label2]
   | , NEXT [label2]
   | , RETRY [label1]
   | , RETURN [return-value |
               ERROR [return-value | error-object-expression] |
               NO-APPLY]
  ]

on-stop-phrase определяет поведение при возникновении в блоке события STOP. По умолчанию, осуществляется откат всех активных транзакций блок за блоком, с последующим выходом в вызывающую процедуру или Procedure Editor, в зависимости от того откуда была вызвана процедура.

ON STOP UNDO
  [label1]
  [   , LEAVE [label2]
    | , NEXT [label2]
    | , RETRY [label1]
    | , RETURN [return-value |
                ERROR [return-value | error-object-expression] |
                NO-APPLY]
  ]

UNDO [label1] - осуществляется откат операций, выполненных в блоке с меткой label1. Если метка блока не указана, то осуществляется откат ближайшего транзакционного, или субтранзакционного блока.

В откатываемом блоке отменяются совершенные изменения записей базы данных. Отменяют присвоения переменных и изменения записей временных таблиц, если они не были объявлены с опцией NO-UNDO.

LEAVE [label2] -  осуществляется выход из блока с меткой label2. Если метка блока не указана, то осуществляется выход из откатываемого блока.

NEXT [label2] - осуществляется выполнение следующей итерации блока с меткой label2. Если метка блока не указана, то осуществляется выполнение следующей итерации откатываемого блока.

RETRY [label1] - осуществляется попытка повторного выполнения итерации блока с меткой label1 (откатываемого блока).

RETURN - осуществляется выход в вызывающую процедуру.

return-value - возвращаемое значение CHARACTER типа, которое может быть получено в вызывающей процедуре с помощью функции RETURN-VALUE.

ERROR [return-value | error-object-expression] - возвращает ошибку в вызывающую процедуру и откатывает текущую субтранзакцию.


ТРАНЗАКЦИИ

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

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

В OpenEdge ABL следующие блоки являются транзакционными:

  • Базовые блоки (DO, FOR, REPEAT ) с опцией TRANSACTION.
  • Процедурный  блок и каждая итерация блоков: DO с опциями [ON ERROR UNDO | ON ENDKEY UNDO], FOR и REPEAT в теле которых осуществляется создание, модификация и удаление записей таблиц базы данных, или их чтение с блокировкой EXCLUSIVE-LOCK. Блок DO по умолчанию не является транзакционным блоком.

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

В качестве примера, рассмотрим процедуру ввода данных во временную таблицу кодов городов:

DEF TEMP-TABLE tt-data /* NO-UNDO */
   FIELD code AS INT  FORMAT "99"
                      LABEL  "Код"
   FIELD city AS CHAR FORMAT "x(20)"
                      LABEL  "Город"
   INDEX idx-code IS UNIQUE code
.
REPEAT TRANSACTION ON ERROR  UNDO, RETRY:
   INSERT tt-data.
END.

FIND FIRST tt-data NO-ERROR.
IF AVAIL tt-data THEN
FOR EACH tt-data:
   DISPL code city SKIP.
END.
ELSE DISPL "Записей нет".

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

Если пользователь попытается ввести запись с кодом уже существующем в таблице, то возникнет событие ERROR, так как поле code составляет уникальный индекс в таблице. В результате чего, пользователю будет предоставлена возможность повторного ввода записи.

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

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

Одной из распространенных задач в АБС, является импорт-экспорт реестра платежей между ней и другими внешними системами. Например, загрузка реестра зачислений заработной платы по зарплатному проекту, или перечисления пенсионного фонда.

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

DEF VAR iReadRecords AS INT NO-UNDO.
DEF VAR iLoadRecords AS INT.
DEF STREAM sfile.
/* Открываем входящий поток из файла */
INPUT STREAM sfile FROM VALUE("operations.csv").
/* Читаем строки из файла в цикле */
REPEAT ON ERROR UNDO, NEXT:
iReadRecords = iReadRecords + 1.
iLoadRecords = iLoadRecords + 1.
   CREATE op.
CREATE op-entry.
   /* IMPORT STREAM sfile ... */
END.
/* Закрываем поток */
INPUT STREAM sfile CLOSE.

В приведенном примере, каждая итерация блока REPEAT является транзакцией, так как в его теле применяется оператор CREATE.

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

Для подсчета количества прочитанных из файла записей объявлена переменная iReadRecords. Данная переменная объявлена с опцией NO-UNDO и следовательно в случае возникновения ошибки, изменение ее значения не будет отменено при откате итерации блока REPEAT.

Переменная iLoadRecords объявлена без опции NO-UNDO и следовательно является откатываемой, т.е. в случае ошибки и отката итерации блока REPEAT, увеличение ее значения на единицу будет отменено. Тем самым, значение данной переменной будет отражать число успешно выполненных итераций блока REPEAT.

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

DEF STREAM sfile.
/* Открываем входящий поток из файла */
INPUT STREAM sfile FROM VALUE("operations.csv").
loop:
DO TRANSACTION:

   /* Читаем строки из файла в цикле */
   REPEAT ON ERROR UNDO loop, LEAVE
/* ON ENDKEY UNDO loop, LEAVE */
:
CREATE op.
      CREATE op-entry.
      /* IMPORT STREAM sfile ... */
   END.
END.
/* Закрываем поток */
INPUT STREAM sfile CLOSE.

Теперь вся обработка реестра осуществляется внутри транзакционного блока DO TRANSACTION с меткой loop и при возникновении ошибки будет осуществлен откат всех совершенных изменений, а не только одной итерации блока REPEAT, во время которой возникнет ошибка.

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

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

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

Транзакционный блок, вложенный в другой транзакционный блок - называется субтранзакцией. В нашем примере, блок REPEAT является субтранзакцией.

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

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

RUN p-trans.
DO TRANSACTION:
RUN p-trans.
END.

PROCEDURE p-trans:

IF TRANSACTION
THEN
   MESSAGE "Процедура является транзакцией" VIEW-AS ALERT-BOX
INFORMATION
BUTTONS ok.

ELSE
   MESSAGE "Процедура не является транзакцией" VIEW-AS ALERT-BOX
INFORMATION
BUTTONS ok.

END

Для реализации механизма транзакций в OpenEdge применяется bi-файл (befor-image), в который осуществляется запись всех осуществляемых в транзакции изменений базы данных. Впоследствии, в случае отката транзакции, используются записи данного файла.


КОНЕЧНЫЕ БЛОКИ (END BLOCKS)


Конечные блоки тесно связаны с другими блоками. Размещаются конечные блоки внутри блока с которым они связаны (ассоциированным блоком) в самом его конце. В OpenEdge ABL к конечным блокам относятся два блока:

  • CATCH - блок вызываемый при возникновении ошибки в ассоциированном блоке.
  • FINNALY - блок выполняемый гарантировано независимо от возникновения ошибки в ассоциированном блоке. Данный блок должен быть самым последним внутри ассоциированного блока.

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

   IF RETRY THEN 
DO:
MESSAGE "Вы действительно хотите прервать импорт?"
VIEW-AS ALERT-BOX QUESTION
BUTTONS YES-NO
SET lRes AS LOGICAL.
IF lRes THEN LEAVE loop.

END.
Вы здесь: Главная Основы ABL БЛОКИ ABL