Try OpenEdge Now
skip to main content
ABL Reference
ABL Syntax Reference : DO statement
 

DO statement

Groups statements into a single block, optionally specifying processing services or block properties. Use an END statement to end a DO block.

Syntax

[ 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
FOR record[ , record]...
Names the buffer you want to work with in the block and scopes the buffer to the block. The scope of a record determines when the buffer for that record is cleared and written back to the database. See OpenEdge Getting Started: ABL Essentials for more information on record scoping.
To work with a record in a table defined for multiple databases, you must qualify the record's table name with the database name. See the Record phrase reference entry for more information.
preselect-phrase
The PRESELECT phrase finds selected records from one or more tables. You can access those preselected records with statements such as FIND NEXT.
PRESELECT
  [ EACH | FIRST | LAST ]record-phrase
  [ , [ EACH | FIRST | LAST ] record-phrase ]...
  [
     [ BREAK ]
     { BY expression[ DESCENDING ]}...
  ]
For more information, see the PRESELECT phrase reference entry.
query-tuning-phrase
Allows programmatic control over the execution of a DataServer query.
QUERY-TUNING (
  {
     [ BIND-WHERE | NO-BIND-WHERE ]
     [ CACHE-SIZE integer ]
     [ DEBUG { SQL | EXTENDED } | NO-DEBUG ] 
    [ INDEX-HINT | NO-INDEX-HINT ]
     [ JOIN-BY-SQLDB | NO-JOIN-BY-SQLDB ]
     [ LOOKAHEAD | NO-LOOKAHEAD ]
     [ SEPARATE-CONNECTION | NO-SEPARATE-CONNECTION ]
  }
  )
For more information, see the OpenEdge DataServer Guides (OpenEdge Data Management: DataServer for Microsoft SQL Server and OpenEdge Data Management: DataServer for Oracle).
variable = expression1 TO expression2 [ BY k ]
The name of a field or variable whose value is incremented in a loop. The expression1 is the starting value for variable on the first iteration of the loop. The k is the amount to add to variable after each iteration, and it must be a constant. The k defaults to 1. The variable, expression1 and expression2 must be integers.
When variable exceeds expression2 (or is less than expression2 if k is negative) the loop ends. Since expression1 is compared to expression2 at the start of the first iteration of the block, the block can be executed zero times. The expression2 is re-evaluated on each iteration of the block.
WHILE expression
Indicates that the DO block continues processing the statements within it. Using the WHILE option turns a DO block into an iterating block. The block iterates as long as the condition specified by the expression is TRUE. The expression is any combination of constants, operators, field names, and variable names that yield a logical value.
TRANSACTION
Identifies the DO block as a system transaction block. The AVM starts a system transaction for each iteration of a transaction block if there is not already an active system transaction. See OpenEdge Getting Started: ABL Essentials for more information on transactions.
STOP-AFTER expression
The STOP-AFTER phrase specifies a time-out value for a DO, FOR, or REPEAT block. The integer expression specifies the number of seconds each iteration of a block has until a time-out occurs. If a time-out occurs, the AVM raises the STOP condition, throws a Progress.Lang.StopAfter object, and default STOP condition handling occurs. Use an ON STOP phrase on the block (or an enclosing block) to alter the default STOP condition handling. If enabled, you can also catch a Progress.Lang.StopAfter object using a CATCH block to provide alternate STOP condition handling. For more information, see the Progress.Lang.StopAfter class description.
If the block iteration completes before the specified time expires, the timer resets to expression for the next iteration. In other words, the timer is limited to the scope of a single block iteration. If a block with a STOP-AFTER phrase encloses another block or calls another block, the timer continues while the inner blocks execute.
If a block with a STOP-AFTER phrase contains a nested block with a STOP-AFTER phrase, then each has a timer in effect. If the outer block timer expires while the inner block is executing, the STOP condition is raised even if the timer for the inner block has not expired.
If the STOP condition is handled and execution resumes within the scope of a block with a STOP-AFTER phrase, no timer is in effect until the next iteration of a block with a STOP-AFTER phrase. In other words, all old timers are dismissed but new timers can now be established.
When the timer expires, the STOP condition is raised on the current statement.
Two important use cases for the STOP-AFTER phrase are to time-limit dynamic queries and to time-limit a procedure call. The following example time-limits a procedure call using a RUN statement:
DEFINE VARIABLE cnt as INTEGER INITIAL 0.
PROCEDURE bumpCnt:
cnt = cnt + 1.
END.

DO STOP-AFTER 5:
RUN bumpCnt.
END.
Use this technique to also make timed calls to class methods and user-defined functions.
The following example is simplified code that lets you try different STOP-AFTER cases.
DEFINE VARIABLE EndlessCount AS INTEGER INITIAL 0.

DO STOP-AFTER 5 ON STOP UNDO, LEAVE:

FOR EACH Customer STOP-AFTER 1:
ASSIGN EndlessCount = EndlessCount + 1.
/* Try a complex operation on a Customer record to use up
the timer in a single iteration and raise the STOP
condition in the inner block */
END.

MESSAGE "Procedure half complete. Endlesscount = " EndlessCount ".".

REPEAT STOP-AFTER 1:
ASSIGN EndlessCount = EndlessCount + 1.
/*IF EndlessCount > 2000 THEN LEAVE. */
END.

MESSAGE "Procedure nearly complete. Endlesscount =
" EndlessCount "." .

END.

MESSAGE "Procedure complete. Endlesscount = " EndlessCount "." .
If you run this code as is, the outer DO block establishes a 5 second time limit for the work of the DO block and all inner blocks. When the inner FOR EACH block starts, another timer is established for the first iteration of this block. When the first FOR EACH iteration completes, its timer is reset to 1 second for the next iteration. Meanwhile, the outer timer on the DO block continues without interruption.
The FOR EACH block completes and execution continues forward to the REPEAT block, which is an endless loop. The REPEAT block also has a 1 second timer for each iteration of the block. At some point, the outer 5 second timer elapses and the AVM raises the STOP condition. The STOP condition is raised on the statement the AVM was executing when the timer elapsed. Normal STOP handling proceeds from that point.
As the stack unwinds during STOP processing, the AVM encounters the ON STOP phrase on the DO block. The ON STOP phrase dismisses the STOP condition and resumes normal execution with the next statement following the DO block, as directed by the LEAVE option.
If you remove the comments from the IF statement in the REPEAT block, the block will complete within the outer time limit and the STOP condition is not raised.
If you want to experiment with elapsed timers on an inner block, insert a complex operation inside the FOR EACH block.
In the following example, the STOP-AFTER expression is modified during program execution:
DEFINE VARIABLE ix       AS INTEGER NO-UNDO.
DEFINE VARIABLE stopTime AS INTEGER NO-UNDO INITIAL 30.

DO WHILE TRUE STOP-AFTER stopTime ON STOP UNDO, LEAVE:
  RUN spinHere (10000).
  stopTime = stopTime / 2.
END.
MESSAGE "program finished".

PROCEDURE spinHere:
  DEFINE INPUT PARAMETER spinLimit AS INT64 NO-UNDO.

  DEFINE VARIABLE endTime  AS INT64   NO-UNDO.
  DEFINE VARIABLE loopFlag AS LOGICAL NO-UNDO.

  ASSIGN
    loopFlag = TRUE
    endTime  = ETIME(FALSE) + spinLimit.

  DO WHILE loopFlag:
    IF (ETIME(FALSE) > endTime) THEN
      loopFlag = FALSE.
  END.
END PROCEDURE.
Because the STOP-AFTER expression is re-evaluated for each iteration of a looping block, any changes made to the expression during the iteration effect the timer for the block. In the example, the STOP-AFTER time limit is specified by the variable stopTime, which is initially set to 30 seconds. The procedure contains an iterating block which runs a procedure that executes for 10 seconds.
On the first iteration of the DO WHILE TRUE loop, stopTime is 30 seconds. The loop executes for 10 seconds, and then divides stopTime by 2. On the second iteration, the stopTime is 15 seconds; again the loop executes for 10 seconds, and then divides stopTime by 2. On the third iteration, the stopTime is 8 seconds. This time, the procedure spinHere runs for 8 seconds and then raises STOP. The STOP condition is handled by the DO block, and then the program displays the message program finished.
If a code block is called with a time limit of zero, the block is executed as if the STOP-AFTER phrase was omitted from the block declaration.
Consider the following example:
DEFINE VARIABLE barLimit AS INTEGER NO-UNDO.
DEFINE VARIABLE ix       AS INTEGER NO-UNDO INITIAL 1.

DO STOP-AFTER 10 ON STOP UNDO, LEAVE:
  RUN foo.
END.

PROCEDURE foo:
  RUN bar.
END PROCEDURE.

PROCEDURE bar:
  DO WHILE ix > 0 STOP-AFTER barLimit:
    ix = ix + 1.
  END.
END PROCEDURE.
In this example, procedure foo is run from within a timed block with a 10 second time limit; procedure bar is called from within the timed block, and contains an iterating block that specifies the STOP-AFTER phrase. Because the value of the STOP-AFTER expression evaluates to zero (that is, the current value of the barLimit variable), the block within bar is executed as an untimed block. However, the rules for execution of an untimed block within a timed block apply, so the untimed block in bar is executed with an implicit iteration time limit of 10 seconds.
Other points to consider are:
*If the expression evaluates to zero or less, then this is the equivalent of not specifying a STOP-AFTER phrase.
*STOP-AFTER phrases are not intended to interact with user interfaces.
*Blocking calls to third party software components, where the AVM has transferred execution control, cannot be timed out. This category includes operating system calls, MS Windows system calls, and calls to any third party DLLs and Unix shared objects.
on-endkey-phrase
Describes the processing that takes place when the ENDKEY condition occurs during a block. This is the syntax for the ON ENDKEY phrase:
ON ENDKEY UNDO
  [ label1 ]
  [     , LEAVE [ label2 ]
     |  , NEXT [ label2 ]
     |  , RETRY [ label1 ]
      |  , RETURN [ return-value |
                  ERROR [ return-value | error-object-expression ] |
                  NO-APPLY ]
For more information, see the ON ENDKEY phrase reference entry.
on-error-phrase
Describes the processing that takes place when there is an error during a block. This is the syntax for ON ERROR phrase:
ON ERROR UNDO
  [ label1 ]
  [     , LEAVE [ label2 ]
     |  , NEXT [ label2 ]
     |  , RETRY [ label1 ]
     |  , RETURN [ return-value |
                 ERROR [return-value |error-object-expression]|
                  NO-APPLY ]
     | , THROW
]
For more information, see the ON ERROR phrase reference entry.
on-quit-phrase
Describes the processing that takes place when a QUIT statement is executed during a block. This is the syntax for ON QUIT phrase:
ON QUIT
  [ UNDO [ label1 ]]
  [     , LEAVE [ label2 ]
     |  , NEXT [ label2 ]
     |  , RETRY [ label1 ]
     |  , RETURN [ return-value |
                  ERROR [return-value |error-object-expression]|
                  NO-APPLY ]
]
For more information, see the ON QUIT phrase reference entry.
on-stop-phrase
Describes the processing that takes place when the STOP condition occurs during a block. This is the syntax for the ON STOP phrase:
ON STOP UNDO
  [ label1 ]
  [     , LEAVE [ label2 ]
     |  , NEXT [ label2 ]
     |  , RETRY [ label1 ]
     |  , RETURN [ return-value |
                  ERROR [return-value |error-object-expression]|
                  NO-APPLY ]
]
For more information, see the ON STOP phrase reference entry.
frame-phrase
Specifies the overall layout and processing properties of a frame. For more information on frame-phrase, see the Frame phrase reference entry.
do-body
The body of the DO block. Define do-body using the following syntax:
do-logic
.
.
.
   [ catch-block [ catch-block ... ] ]
[ finally-block ]
END.
do-logic
The logic of the DO block. This logic can contain the ABL statements allowed within the routine-level block (e.g., procedure, user-defined, or method) where the DO statement is defined.
Each logic statement must end with a period.
catch-block
Specifies a CATCH statement that defines error handling code for one or more error types. Since a DO block does not have any default error handling, a CATCH block is only valid within it if the block specifies error handling options. At this point, the block can raise ERROR and a CATCH block is valid to handle errors. If a DO block does not have error handling options and specifies a CATCH block, ABL generates a compiler warning. For more information on catch-block, see the CATCH statement reference entry.
finally-block
Specifies a FINALLY statement that defines the processing that must occur after all other processing in the block occurs. For more information on finally-block, see the FINALLY statement reference entry.
END
Specifies the end of the DO block definition. You must end the DO block definition with the END statement.

Example

This procedure goes through the Customer table and, for those Customers whose CreditLimit is over 80000, reduces CreditLimit to 80000. The procedure uses an unmodified DO block to process two statements if CreditLimit is over 80000. Unmodified DO blocks are most useful in conditional, or IF. . .THEN. . .ELSE situations.
r-do.p
FOR EACH Customer NO-LOCK:
DISPLAY Customer.Name Customer.CreditLimit.
PAUSE 3.
IF Customer.CreditLimit > 80000 THEN DO:
    Customer.CreditLimit = 80000.
    DISPLAY Customer.Name Customer.CreditLimit.
END.
END.

Notes

*Use a DO statement rather than a REPEAT statement when you loop through each element of an array. This way the AVM does not create separate subtransactions within a transaction.
For example, the first transaction is more efficient then the second:
DO ix = 1 TO 12:
SalesRep.MonthQuota[ix] = 0.
END.
REPEAT ix = 1 TO 12:
SalesRep.MonthQuota[ix] = 0.
END.
*For SpeedScript, the on-endkey-phrase and the on-quit-phrase do not apply.

See also

CATCH statement, FINALLY statement, FIND statement, FOR statement, Frame phrase, ON ENDKEY phrase, ON ERROR phrase, ON QUIT phrase, ON STOP phrase, Record phrase, REPEAT statement