Hello,
Can anyone tell me the difference between:
DO TRANSACTION ON ERROR UNDO, RETURN ERROR:
END.
and
DO ON ERROR UNDO, RETURN ERROR:
END.
Both seem to undo the current block and return error, so both seem to be transactions... or not?
Hope this isn't a silly question
Edit: Solved it myself I think, a DO ON ERROR can cause an active transaction to be rolled back, or when a transaction is active the DO itself can be undone. If there is no active transaction the UNDO does not work!
Perhaps someone wants to share other ideas or opinions, so I'm reopening this question for a while...
You're certainly on the right track. A DO block doesn't have transaction scoping capabilities by default. Adding the TRANSACTION keyword will force the DO block to start a transaction if there isn't one going yet. Adding the ON ERROR phrase will give the DO block transaction scoping capabilities. As a result, if a database update is done somewhere directly in the DO block (and not in a nested transaction scoping block), the DO block will start a transaction.
You pointed out a little know fact: that the UNDO keyword does not actually undo anything if there is no active transaction. Particularly for the client side of N-tier applications, this can have a significant impact on what the code actually does.
This was indeed not a silly question
When you write code to create, modify, or delete a record, OpenEdge groups changes
to the database into a unit of work called a transaction. A transaction is a unit of work
performed against a database that is either done completely or not done at all.
The scope of a transaction is from the starting point to the ending point of a unit of
work.
To know more about the scope of a transaction you can use the listing option.
COMPILE test.p LISTING listing.lst.
You may decide that, because of certain business requirements, you want the
transaction to encompass a different unit of work than what OpenEdge uses for the
default transaction size. The OpenEdge 4GL provides you with the TRANSACTION
option to override the default transaction scope, making the scope either larger or
smaller. The TRANSACTION option acts to blocks type: FOR, REPEAT, DO.
this example NON transacted:
DEF VAR li AS INTE NO-UNDO.
FOR EACH Customer NO-LOCK:
li = li + 1.
END.
In this case the transaction scope is the simple iteration.
FOR EACH Customer SHARE-LOCK:
UPDATE Name.
END.
UPDATE Name. AAAA ... commit
UPDATE Name. BBBB ... commit
UPDATE Name. CCCC ... system error ... then we loose only the last update.
In this case we force the transaction scope to all updates:
(ALL OR NOTHING)
FOR EACH Customer SHARE-LOCK TRANSACTION:
UPDATE Name.
END.
UPDATE Name. AAAA
UPDATE Name. BBBB
UPDATE Name. CCCC ... system error ... then we loose all modifications
To know more about the scope of a transaction you can
use the listing option.
COMPILE test.p LISTING listing.lst.
This is a very good place to start and I'm glad you mentioned it, but make sure you understand what it's saying. Even procedure (x.p) that doesn't start a transaction (ie. COMPILE x.p LISTING x.txt) shows all No's the the Tran column, it doesn't mean that it's not part of a transaction at runtime. If another program starts a transaction and then calls x.p, everything in x.p is part of the transaction.
Linking back into the original post, that means that x.p might behave differently when it comes to any UNDO's, depending on whether one of it's runtime ancestors started a transaction.
In this case the transaction scope is the simple
iteration.
FOR EACH Customer SHARE-LOCK:
UPDATE Name.
ND.
UPDATE Name. AAAA ... commit
UPDATE Name. BBBB ... commit
UPDATE Name. CCCC ... system error ... then we
loose only the last update.
This is correct as a FOR EACH block is inherently transaction scoping. The UPDATE directly inside the FOR EACH block automatically creates a transaction scoped to the FOR EACH block. Each iteration is a separate transaction.
In this case we force the transaction scope to all
updates:
(ALL OR NOTHING)
FOR EACH Customer SHARE-LOCK TRANSACTION:
UPDATE Name.
ND.
UPDATE Name. AAAA
UPDATE Name. BBBB
UPDATE Name. CCCC ... system error ... then we
loose all modifications
This is incorrect. In this particular example, the TRANSACTION keyword serves only to show the developer that the intent is to scope a transaction to each iteration of the FOR EACH block. The behaviour is identical to the previous example. (You can also see this in the LISTING.)
To achieve a situation where a system error will cause all record updates to be rolled-back, you could do the following:
Yes this is true.
At the runtime the transaction scope can be changed.
RUN procA.p -> RUN procB.p -> RUN procC.p
The procC.p is a not transacted procedure.
He will becomes transacted if procA.p starts the transaction.
At the runtime we can investigate the transaction state using the
TRANSACTION function.
MESSAGE TRANSACTION
VIEW-AS ALERT-BOX INFO BUTTONS OK.
I want to say that we can change the default scope which is the iteration.
Your example show this beter.
In this case the system commits each time , for each repeat iteration, and
we can loose only the last modification if a error occurs.
REPEAT:
CREATE Customer.
.... RUN something.
CREATE Order.
Update something ...
IF error THEN UNDO, LEAVE.
END.
and in this case the commit is at the end of big transaction,
and if a error occurs, the system roll-back all modifications.
_tr:
DO li = 1 TO 10 TRANSACTION:
REPEAT:
CREATE Customer.
.... RUN something.
CREATE Order.
Update something ...
IF error THEN UNDO _tr, LEAVE tr.
END.
END.
Thanks for the information guys, I'll mark this question as answered.