Roll back cached updates without losing changes (Updated)

Code samples and contributions from users for ZeosLib's DBOs of version 6.x

Moderators: gto, cipto_kh, EgonHugeist, mdaems

Post Reply
pgimeno
Fresh Boarder
Fresh Boarder
Posts: 5
Joined: 11.10.2007, 15:34

Roll back cached updates without losing changes (Updated)

Post by pgimeno »

Update: Actually this is a Zeos bug. ApplyUpdates shold NOT delete the cache buffer; that's the mission of CommitUpdates which should be used after a successful ApplyUpdates. So this patch actually implements what the ApplyUpdates/CommitUpdates combination should do. [End update]

The following two methods allow the user of a master-detail form to cancel the post and go on editing when a database error occurs during the post. See http://zeos.firmos.at/viewtopic.php?t=1475 for details.

This assumes that both Master and Detail have CachedUpdates set to True and that Master is in edit mode (either via Append, Insert or Edit).

To use them, instead of Master.ApplyUpdates and Detail.ApplyUpdates, write a fragment similar to the following:

Code: Select all

  Master.Post;
  Conn.StartTransaction;
  try
    Master.ApplyUpdatesNoFree;
    Detail.ApplyUpdatesNoFree;
  except
    Conn.Rollback;
    Master.Edit;
    // changes are still cached at this point
    raise;
  end;
  Conn.Commit;
  Master.FreeUpdates;
  Detail.FreeUpdates;
Notes:
- Both Master and Detail should be attached to the same connection, Conn.
- Conn must have transaction isolation level of tiReadCommitted or tiSerializable.
- The DataSource property of Detail must not be set. Use MasterSource et al to link the datasets; otherwise the changes will be lost after an error. It's possible to clear DataSource before starting the transaction and set it again after the FreeUpdates calls, but not after rollback, or the changes will also be lost in case of error.

Now, in case of a database error (PK duplication, FK violation, NULL value for a NOT NULL field, etc.) everything will be more or less back to the initial state before the attempt to apply the changes. I say "more or less" because the Modified flag of Master will be lost as will the editing state and Modified flag of Detail; also, if Master.State was dsInsert now it will be dsEdit. Other than that, things should be very much like they were before trying to apply updates and the transaction can be retried after any user edits.

Here's the patch. There are two files affected: src\component\ZAbstractDataset.pas and src\dbc\ZDbcCachedResultSet.pas. Here are the changes for ZDbcCachedResultSet.pas:

After this line:

procedure PostUpdates;

add these lines:

procedure PostUpdatesNoFree;
procedure FreeUpdates;

and after this line:

procedure PostUpdates; virtual;

add these lines:

procedure PostUpdatesNoFree; virtual;
procedure FreeUpdates; virtual;

Now, after the definition of procedure TZAbstractCachedResultSet.PostUpdates add the following:

Code: Select all

{**
  Posts all saved updates to the server without freeing them.
}
procedure TZAbstractCachedResultSet.PostUpdatesNoFree;
var
  i: Integer;
  C: Integer;
begin
  CheckClosed;
  C := FInitialRowsList.Count;
  if C > 0 then
  begin
    i := 0;
    while i < C do
    begin
      OldRowAccessor.RowBuffer := PZRowBuffer(FInitialRowsList[i]);
      NewRowAccessor.RowBuffer := PZRowBuffer(FCurrentRowsList[i]);
      i := i + 1;

      { Updates default field values. }
      if NewRowAccessor.RowBuffer.UpdateType = utInserted then
        CalculateRowDefaults(NewRowAccessor);

      { Posts row updates. }
      PostRowUpdates(OldRowAccessor, NewRowAccessor);
    end;
  end;
end;

{**
  Frees the updates and marks records as unmodified. Complements
  PostUpdatesNoFree.
}
procedure TZAbstractCachedResultSet.FreeUpdates;
begin
  while FInitialRowsList.Count > 0 do
  begin
    OldRowAccessor.RowBuffer := PZRowBuffer(FInitialRowsList[0]);
    NewRowAccessor.RowBuffer := PZRowBuffer(FCurrentRowsList[0]);

    if NewRowAccessor.RowBuffer.UpdateType <> utDeleted then
    begin
      NewRowAccessor.RowBuffer.UpdateType := utUnmodified;
      if (FSelectedRow <> nil)
        and (FSelectedRow.Index = NewRowAccessor.RowBuffer.Index) then
        FSelectedRow.UpdateType := utUnmodified;
    end;

    { Remove cached rows. }
    OldRowAccessor.Dispose;
    FInitialRowsList.Delete(0);
    FCurrentRowsList.Delete(0);
  end;
end;
The changes to ZAbstractDataset.pas are:

After this line:

procedure ApplyUpdates;

add these lines:

procedure ApplyUpdatesNoFree;
procedure FreeUpdates;

Now add the functions themselves (place them after TZAbstractDataSet.ApplyUpdates):

Code: Select all

procedure TZAbstractDataset.ApplyUpdatesNoFree;
begin
  if not Active then Exit;

  Connection.ShowSQLHourGlass;
  try
    if State in [dsEdit, dsInsert] then Post;

    DoBeforeApplyUpdates; {bangfauzan addition}

    if CachedResultSet <> nil then
      CachedResultSet.PostUpdatesNoFree;

    if not (State in [dsInactive]) then
      Resync([]);

    DoAfterApplyUpdates; {bangfauzan addition}

  finally
    Connection.HideSqlHourGlass;
  end;
end;

procedure TZAbstractDataset.FreeUpdates;
begin
  CheckBrowseMode;

  if CachedResultSet <> nil then
    CachedResultSet.FreeUpdates;
end;
Note, however, that I'm not sure of whether the Resync([]) call is properly placed. I'm afraid it should be in FreeUpdates instead. In the first place I'm not sure about its purpose; it's copied from the ApplyUpdates code. Any ideas?

Thanks in advance.
Last edited by pgimeno on 23.11.2009, 15:11, edited 1 time in total.
pgimeno
Fresh Boarder
Fresh Boarder
Posts: 5
Joined: 11.10.2007, 15:34

Names suck

Post by pgimeno »

Nb. I realize the function names suck. Something like ApplyUpdatesKeepChanges / DisposeOfChanges might better reflect the actual intention. Please feel free to choose better suitable ones.
ludviggf
Fresh Boarder
Fresh Boarder
Posts: 3
Joined: 18.10.2008, 00:39

Post by ludviggf »

I wonder if this solution will be implemented in zeos?

I have the following case:
Connection -> TransactIsolationLevel = tiReadCommitted
Master - CachedUpdates = True
Detail -> CachedUpdates = True

When I click the post button:


Connection.StartTransaction
try
Master.ApplyUpdates
Detail.ApplyUpdates
1 record applies
1 record occurs error
except
Connection.Rollback
Master.Edit
raise
End
Connection.Commit

Error: if you try to write again I will miss the first record
detail table because the UpdateType changed to utUnmodified

I need all records of the Master / Detail remain
with the same UpdateType that can be applied to all again.

Excuse my English, writing from Brazil.
pgimeno
Fresh Boarder
Fresh Boarder
Posts: 5
Joined: 11.10.2007, 15:34

Post by pgimeno »

Hi, sorry for missing this for more than a year! I've added myself to the notifications now.

That's what the patch was written for: to keep the updates instead of disposing of them. You would need to first apply the patch, then use ApplyUpdatesNoFree instead of ApplyUpdates, and then after commit, FreeUpdates, just as in the first code snippet I posted (see the first message).

The patch was for Zeos 6.6.1-beta though, which is now obsolete. I have not updated it to the latest version yet (I'm still using 6.6.2-rc), but maybe it hasn't changed much and you can still apply it.
pgimeno
Fresh Boarder
Fresh Boarder
Posts: 5
Joined: 11.10.2007, 15:34

Post by pgimeno »

After investigating this further, I see that ludviggf is right. As commented in the update in my first post, it is a bug in Zeos that ApplyUpdates deletes the cached modifications. ApplyUpdates should actually behave as the ApplyUpdatesNoFree procedure in my patch does, and CommitUpdates should do what FreeUpdates does in this patch. Posting it to the Bug tracker now.

[Update:] Posted as Mantis bug #206 http://zeosbugs.firmos.at/view.php?id=206 [End update]
User avatar
EgonHugeist
Zeos Project Manager
Zeos Project Manager
Posts: 1936
Joined: 31.03.2011, 22:38

Post by EgonHugeist »

pgimeno,

Added your proposal.

Some things i've chaged:

if Connection.AutoCommit and
not ( Connection.TransactIsolationLevel in [tiReadCommitted, tiSerializable] ) then
CachedResultSet.PostUpdates
else
CachedResultSet.PostUpdatesCached;

also don't you need to clear the rowbuffers by your selves. This i do now if call Connection.Commit. If you want to clear the cachedupdates then you can also call ZDataSet.CancelUpdates.

Can you test it? I know this patch cames latly! But better then never, right?

Zeos7 Rev1608 (SVN) http://svn.code.sf.net/p/zeoslib/code-0 ... es/testing

Michael
Best regards, Michael

You want to help? http://zeoslib.sourceforge.net/viewtopic.php?f=4&t=3671
You found a (possible) bug? Use the new bugtracker dude! http://sourceforge.net/p/zeoslib/tickets/

Image
Post Reply