Build the server side change handler
- Last Updated: October 16, 2024
- 4 minute read
- OpenEdge
- Version 12.8
- Documentation
On the server side, there can also be a generic
procedure that applies all changes to a ProDataSet. As we explained
in Update Data with ProDataSets,
there is a SAVE-ROW-CHANGES method to apply one
row of changes to the database, but not a SAVE-CHANGES method
for a temp-table or for the entire ProDataSet. The reason for this
is there might be many ways in which you need to control the update
process, including:
-
The transaction scope — Are
all updates made as part of a single transaction that fails in its
entirety if any change is not valid? Are all updates to a single
table made together? Or is each individual change a separate transaction
that can succeed or fail independent of others? These questions
are the reason why we support both an
ERRORand aREJECTEDattribute for ProDataSet rows. A change that fails or conflicts with a change made by another user isREJECTEDand also made anERROR. Any other change that is backed out because it is part of the same transaction isREJECTEDwhether it caused an error or not. You can check these flags back in the caller to understand what made it into the database and what did not, and why. - The order of applying changes to different tables — In a parent-child ProDataSet or when there are multiple top-level tables, which changes should be applied first? Normally, you would apply changes from the top down, but there might be exceptions to that. And if there are multiple top-level tables, does it matter to you which order changes are applied in? By writing your own code you have complete control over this. No default would satisfy all situations.
-
The order of creates, deletes, and modifies to a single table —
Do you want deletes applied before or after creates and updates? This
might depend on your data definitions or business logic. The
ROW-STATEattribute has a numeric value that orders rows so that unmodified rows are first, followed by deleted rows, modified rows, and created rows, in that order. You can take advantage of this default ordering, but you can also change it as necessary.
This section
shows you a sample for a general purpose procedure that makes default
decisions about these variants. Each row is a single transaction. Tables
are processed from the top of the hierarchy down (and multiple top-level tables
are processed in the order in which they appear in the ProDataSet
definition). And changes are processed in ROW-STATE Order.
You could, of course, extend or modify this procedure for other
defaults and to accept parameters to change the behavior.
The
example procedure is commitChanges.p. It handles changes
to any ProDataSet with any number of tables in its hierarchy. It
takes the ProDataSet handle as its INPUT-OUTPUT parameter.
The variables represent the handle to the current top-level buffer
and a buffer counter, as shown:
|
The procedure starts its main loop through all
top-level buffers, using the NUM-TOP-BUFFERS counter
and the GET-TOP-BUFFER method to return each one
in turn. These are the temp-table buffers with no parent. They are
returned in the order they appear in the ProDataSet definition.
There is one special case for which the loop must check. Because
of the FILL behavior, children of a REPOSITION Data-Relation
show up in the list of top-level buffers, even though they are not
really top-level for the purpose of walking through the hierarchy
of the ProDataSet as this procedure does. For this reason, the loop
contains a check to see if there is a PARENT-RELATION for
the buffer. If there is, then it is really a child of a reposition
relation, not a true top-level buffer, and it is skipped.
For
each top-level buffer, the iterative procedure traverseBuffers is
run to walk down that branch of the ProDataSet definition, as shown:
|
Procedure traverseBuffers serves
only to iterate down through any number of levels of parent-child
tables. It runs another internal procedure saveBuffer on
itself and then loops through each child of the current buffer. NUM-CHILD-RELATIONS returns
the number of relations for which this buffer is the parent, and GET-CHILD-RELATION returns
each one in turn.
Since GET-CHILD-RELATION returns
the handle of the Data-Relation object, you need to follow that
to its CHILD-BUFFER to get the buffer handle of
each of the current's buffer's children, as shown:
|
Procedure saveBuffer does the
actual work of running SAVE-ROW-CHANGES, as shown:
|
The buffer handle passed in is actually the
after-table buffer for the current table. You want to run SAVE-ROW-CHANGES on
the before-table buffer, especially since in the case of
a Delete there is no after-table row to process.
Variable hBeforeBuff points to that buffer.
The VALID-HANDLE test
checks whether there is a before-table for this temp-table at all.
If the table does not have a BEFORE-TABLE in its
definition and has not been enabled for update by setting TRACKING-CHANGES to true,
then there will not be a before-table. Otherwise, the before-table
could contain zero, one, or many rows, depending on how many rows
in that table have been changed. The query simply walks through
all those rows and runs SAVE-ROW-CHANGES on each
one. SAVE-ROW-CHANGES starts its own transaction
if there is none active, so each change is saved independently.
If the save fails because of invalid data or because it had been
changed by another user, the AVM sets the ERROR attribute
on the row.
The procedure checks this and also sets the REJECTED attribute,
so that the caller knows row by row which rows were successfully
updated into the database and which ones were not. For example:
|
Remember that SAVE-ROW-CHANGES takes
two optional arguments:
- The buffer to save, if there is more than one buffer in the Data-Source and the modified buffer is not the first one.
- The name of a field not to copy to the database buffer because its value is assigned by a database trigger. Normally this is the primary key for a newly created row.
Because this is a generic save procedure, there is no straightforward way to specify these parameters if they are needed. This example uses the default.
That is the end of the code for commitChanges.p.