1.1. What is an ABL Transaction?
- Transactions ensure that the data in your database maintains integrity.
- Imagine we need to update an Order and one of its Order Lines. Both updates must happen together. If one fails, the other should not be saved.
Without a transaction, the two updates are independent. If the second update fails, the Order ShipDate is already saved but the OrderLine Quantity is not. This leaves the database in an inconsistent state.
PROCEDURE WithoutTransaction:
DEFINE BUFFER bufOrder FOR Order.
DEFINE BUFFER bufOrderLine FOR OrderLine.
FIND bufOrder EXCLUSIVE-LOCK
WHERE bufOrder.OrderNum = 1 NO-ERROR.
IF AVAILABLE bufOrder THEN
bufOrder.ShipDate = TODAY + 7.
FIND bufOrderLine EXCLUSIVE-LOCK
WHERE bufOrderLine.OrderNum = 1
AND bufOrderLine.LineNum = 1 NO-ERROR.
IF AVAILABLE bufOrderLine THEN
bufOrderLine.Qty = 10.
ELSE
MESSAGE "OrderLine not found, Order was already updated"
VIEW-AS ALERT-BOX WARNING.
END PROCEDURE.
By wrapping both updates in a DO TRANSACTION block, we guarantee that either both changes are committed together or neither of them is saved. This is exactly what we mean by data integrity.
PROCEDURE WithTransaction:
DEFINE BUFFER bufOrder FOR Order.
DEFINE BUFFER bufOrderLine FOR OrderLine.
DO TRANSACTION:
FIND bufOrder EXCLUSIVE-LOCK
WHERE bufOrder.OrderNum = 1 NO-ERROR.
IF AVAILABLE bufOrder THEN
bufOrder.ShipDate = TODAY + 7.
FIND bufOrderLine EXCLUSIVE-LOCK
WHERE bufOrderLine.OrderNum = 1
AND bufOrderLine.LineNum = 1 NO-ERROR.
IF AVAILABLE bufOrderLine THEN
bufOrderLine.Qty = 10.
ELSE
UNDO, LEAVE.
END.
END PROCEDURE.
- When all operations inside a transaction complete successfully, the changes are committed (saved to the database). If something goes wrong, all changes are rolled back (leaving the database exactly as it was before the transaction started).
1.2. TRANSACTION Keyword
- In ABL, a transaction can be started in two ways: explicitly, by using the TRANSACTION keyword, or implicitly, by ABL itself when it detects that a block modifies data.
Explicit transaction
- An explicit transaction is one you declare yourself using the TRANSACTION keyword. You can also start a transaction by adding the TRANSACTION keyword to a DO, FOR, or REPEAT block.
DO FOR TRANSACTION example:
PROCEDURE UpdateCustomer:
DEFINE BUFFER bufCustomer FOR Customer.
DEFINE BUFFER bufOrder FOR Order.
DO FOR bufCustomer TRANSACTION:
FIND bufCustomer EXCLUSIVE-LOCK
WHERE bufCustomer.CustNum = 1 NO-ERROR.
IF AVAILABLE bufCustomer THEN
bufCustomer.Balance = bufCustomer.Balance + 1000.
END.
END PROCEDURE.
By adding bufCustomer to the DO FOR clause, we are telling ABL that bufCustomer is scoped to this block — meaning the record lock is held only for the duration of this block and released automatically when END is reached. The transaction also commits at that point.
DO TRANSACTION example:
PROCEDURE UpdateOrderAndLine:
DEFINE INPUT PARAMETER ipOrderNum AS INTEGER NO-UNDO.
DEFINE BUFFER bufOrder FOR Order.
DEFINE BUFFER bufOrderLine FOR OrderLine.
DO TRANSACTION:
FIND bufOrder EXCLUSIVE-LOCK
WHERE bufOrder.OrderNum = ipOrderNum NO-ERROR.
IF NOT AVAILABLE bufOrder THEN
RETURN.
ASSIGN
bufOrder.ShipDate = TODAY + 7
bufOrder.PromiseDate = TODAY + 14.
FOR EACH bufOrderLine OF bufOrder EXCLUSIVE-LOCK:
IF HasStock(bufOrderLine.Itemnum, bufOrderLine.Qty) THEN
bufOrderLine.OrderLineStatus = "In Warehouse".
ELSE
bufOrderLine.OrderLineStatus = "Pending".
END.
END.
END PROCEDURE.
This procedure updates an Order and all its related Order Lines in a single transaction. If the order is not found, it exits immediately. For each order line, HasStock() checks availability and sets the status accordingly. All changes commit together or not at all.
Implicit transaction
- An implicit transaction is started automatically by ABL when a data-modifying statement (CREATE, UPDATE, DELETE) is found inside a block that is not already marked as a transaction.
The statements which start an implicit transaction are:
- FOR blocks that directly update the database
PROCEDURE UpdateOrder:
DEFINE BUFFER bufOrder FOR Order.
DEFINE BUFFER bufCustomer FOR Customer.
DEFINE VARIABLE iCount AS INTEGER NO-UNDO.
FOR EACH bufOrder EXCLUSIVE-LOCK:
iCount = iCount + 1.
IF iCount >= 5 THEN
UNDO, LEAVE.
bufOrder.OrderDate = TODAY - 20.
END.
END PROCEDURE.
Without a wrapping DO TRANSACTION, each iteration has its own implicit transaction. When iCount reaches 5, UNDO, LEAVE rolls back only the current iteration — the first 4 updates are already committed and cannot be undone.
PROCEDURE UpdateOrder:
DEFINE BUFFER bufOrder FOR Order.
DEFINE BUFFER bufCustomer FOR Customer.
DEFINE VARIABLE iCount AS INTEGER NO-UNDO.
#trans_block:
DO TRANSACTION:
FOR EACH bufOrder EXCLUSIVE-LOCK:
iCount = iCount + 1.
IF iCount >= 5 THEN
UNDO #trans_block, LEAVE.
bufOrder.OrderDate = TODAY - 20.
END.
END.
END PROCEDURE.
By wrapping the FOR EACH inside a labeled DO TRANSACTION block, all iterations share a single transaction. If the condition is met — in this case, when iCount reaches 5 — UNDO #trans_block, LEAVE rolls back every change made so far, not just the current iteration.
- REPEAT blocks that directly update the database
- Procedure blocks that directly update the database
- DO blocks with the ON ERROR phrase that contain statements that update the database
PROCEDURE CreateOrder:
DO ON ERROR UNDO, LEAVE:
/* DB UPDATE OPERATION */
END.
END PROCEDURE.
- Even though we did not write the TRANSACTION keyword, ABL automatically promoted this DO block to a transaction block because it contains a CREATE statement. The transaction commits at the END of the block.
ABL decides the scope automatically, the transaction boundary may not be where you expect it to be.
UNDO
- The AVM undoes a transaction automatically if it detects an error at the database level, for example, because of a unique key violation.
- UNDO can be combined with LEAVE, NEXT, or RETRY depending on what you want to happen after the rollback.
1.3. Transaction Scoping and Compile Listing
What is “scope”?
- Scoping defines which block owns the transaction.
- The transaction and the buffer are both scoped to the procedure block.
How do we actually know what scope ABL assigned to our transaction? Looking at the code alone is not always enough, especially in large and complex procedures. This is where the COMPILE LISTING comes in.
Compile listing
To do this, use the LISTING option on the COMPILE statement.
To generate the listing file, create a separate procedure and use COMPILE with the LISTING option. The example below locates transaction.p using SEARCH(), then writes both the compiled output and the .lis file in the same directory. The directory containing transaction.p must be in your PROPATH, otherwise SEARCH() returns ?
DEFINE VARIABLE cFullPath AS CHARACTER NO-UNDO.
cFullPath = SEARCH("transaction.p").
COMPILE VALUE(cFullPath)
LISTING VALUE(REPLACE(cFullPath, "transaction.p", "transaction.lis")).
The Transaction Scope Table uses the following columns:
- Line — the line number in your source file
- Blk. Type — the block type (Procedure, Do, For, etc.)
- Tran — Yes if this block owns the transaction, No if it does not
- Blk. Label — the procedure name or block label
- Buffers — listed only under the transaction owner — this is the signal that those buffers are scoped to that block
Let’s walk through the COMPILE LISTING output for CreateOrderLine, section by section.
Section 1 — Code with line and block numbers
- Line — the line number in your source code
- Blk — no block numbers here because there is no explicit transaction block — everything is at procedure level
Section 2 — Transaction Scope Table
- Line 6 Procedure Tran: Yes → the procedure itself owns the transaction
- Buffers → both bufOrder and bufOrderLine are scoped to the procedure
Procedure level scoping
- Occurs when ABL promotes the entire procedure to a transaction block.
- There is no explicit transaction block — just a DO block with no TRANSACTION keyword. ABL promotes the entire procedure to a transaction.
BLOCK-LEVEL ON ERROR UNDO, THROW.
/* *************************** Main Block **************** */
PROCEDURE CreateOrderLine:
DEFINE BUFFER bufOrder FOR Order.
DEFINE BUFFER bufOrderLine FOR OrderLine.
FIND bufOrder EXCLUSIVE-LOCK
WHERE bufOrder.OrderNum = 1 NO-ERROR.
IF AVAILABLE bufOrder THEN DO:
CREATE bufOrderLine.
ASSIGN
bufOrderLine.OrderNum = bufOrder.OrderNum
bufOrderLine.LineNum = 1
bufOrderLine.ItemNum = 1
bufOrderLine.Qty = 5.
END.
END PROCEDURE.
File Name Line Blk. Type Tran Blk. Label
---------------------- ---- --------- ---- ----------------------
...ing\transaction.p 6 Procedure Yes Procedure CreateOrderLine
Buffers: sports2000.bufOrderLine
sports2000.bufOrder
...ing\transaction.p 13 Do No
...ing\transaction.p 0 Procedure No
Block level scoping
- Block level scoping means that the transaction is no longer owned by the procedure — instead, it is owned by a specific block inside the procedure.
- This gives you precise control over where the transaction starts and ends, rather than letting ABL decide at the procedure level.
BLOCK-LEVEL ON ERROR UNDO, THROW.
/* *************************** Main Block **************** */
PROCEDURE CreateOrderLine:
DEFINE BUFFER bufOrder FOR Order.
DEFINE BUFFER bufOrderLine FOR OrderLine.
DO FOR bufOrder, bufOrderLine TRANSACTION:
FIND bufOrder EXCLUSIVE-LOCK
WHERE bufOrder.OrderNum = 1 NO-ERROR.
IF AVAILABLE bufOrder THEN DO:
CREATE bufOrderLine.
ASSIGN
bufOrderLine.OrderNum = bufOrder.OrderNum
bufOrderLine.LineNum = 1
bufOrderLine.ItemNum = 1
bufOrderLine.Qty = 5.
END.
END.
END PROCEDURE.
File Name Line Blk. Type Tran Blk. Label
---------------------- ---- --------- ---- ----------------------
..ing\transaction.p 7 Procedure No Procedure CreateOrderLine
..ing\transaction.p 11 Do Yes
Buffers: sports2000.bufOrderLine
sports2000.bufOrder
..ing\transaction.p 16 Do No
...ing\transaction.p 0 Procedure No
As we can see in the listing, the procedure itself shows “Tran: No” confirming that the procedure is no longer the transaction owner. The DO block at line 11 shows “Tran: Yes” — this is where the transaction lives.
Conclusion
In this article, we covered three important concepts for Progress OpenEdge: the TRANSACTION keyword, Transaction Scoping, and COMPILE LISTING. These concepts are essential for writing correct and maintainable code.
- Always use explicit transactions — do not rely on ABL to decide the scope for you.
- Use COMPILE LISTING to verify your transaction scope before deploying to production.
I hope this article helped you better understand how transactions work in ABL and how to avoid the common pitfalls that ABL’s implicit scoping can introduce.
Author: Denisa Ghiorghioiu, Junior Developer
Denisa is passionate about software development, web technologies, and building efficient systems. She enjoys solving problems and continuously expanding her skills across both frontend and backend development.



