Query.CachedUpdates := False is attempting to double-apply

The offical for ZeosLib 7.3 Report problems, ask for help, post proposals for the new version of Zeoslib 7.3/v8
Quick Info:
-We made two new drivers: odbc(raw and unicode version) and oledb
-GUID domain/field-defined support for FB
-extended error infos of Firebird
-performance ups are still in queue
In future some more feature will arrive, so stay tuned and don't hassitate to help
Post Reply
User avatar
aehimself
Zeos Dev Team
Zeos Dev Team
Posts: 799
Joined: 18.11.2018, 17:37
Location: Hungary

Query.CachedUpdates := False is attempting to double-apply

Post by aehimself »

Imagine the following scenario:

Code: Select all

CREATE TABLE `temp` (
  `ID` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `f` bigint(20) unsigned DEFAULT NULL,
  PRIMARY KEY (`ID`),
  UNIQUE KEY `ID_UNIQUE` (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf16;
Now, put one record in it... INSERT INTO temp (f) VALUES (0)

And then:

Code: Select all

 ZQuery1.SQL.Text('SELECT * FROM TEMP');
 ZQuery1.Open;
 ZQuery1.CachedUpdates := True;
 Try
  ZConnection1.StartTransaction;
  ZQuery1.Edit;
  ZQuery1.FieldByName('f').AsInteger := Random(100000);
  ZQuery1.Post;
  If ZQUery1.UpdatesPending Then ZQuery1.ApplyUpdates;
  ZConnection1.Commit;
 Finally
  ZQuery1.CachedUpdates := False;
 End;
This will end up in a:
"Project Project1.exe raised exception class EZSQLException with message '0 record(s) updated. Only one record should have been updated.'."
during setting cachedupdates to false.

SQLMonitor results:

'2020-06-05 16:06:06' cat: Execute, proto: mysql, msg: Native SetAutoCommit False call <- ZConnection1.StartTransaction
'2020-06-05 16:06:06' cat: Prepare, proto: mysql, msg: Statement 6 : SHOW KEYS FROM personal.temp
'2020-06-05 16:06:06' cat: Execute, proto: mysql, msg: Statement 6 : SHOW KEYS FROM personal.temp
'2020-06-05 16:06:06' cat: Prepare, proto: mysql, msg: Statement 7 : UPDATE personal.temp SET f=? WHERE `ID`=?
'2020-06-05 16:06:06' cat: Bind prepared, proto: mysql, msg: Statement 7 : 455,4
'2020-06-05 16:06:06' cat: Execute, proto: mysql, msg: Statement 7 : UPDATE personal.temp SET f=? WHERE `ID`=? <- ZQuery1.ApplyUpdates
'2020-06-05 16:06:06' cat: Execute, proto: mysql, msg: Native Commit call <- ZConnection1.Commit
'2020-06-05 16:06:06' cat: Bind prepared, proto: mysql, msg: Statement 7 : 455,4
'2020-06-05 16:06:06' cat: Execute, proto: mysql, msg: Statement 7 : UPDATE personal.temp SET f=? WHERE `ID`=? <- ZQuery1.CachedUpdates := False;

Stack trace is:
ZAbstractDataSet.TZAbstractDataSet.SetCachedUpdates(False);
ZDbcCachedResultSet.TZAbstractCachedResultSet.SetCachedUpdates(False);
ZDbcCachedResultSet.TZAbstractCachedResultSet.PostUpdates;
ZDbcCachedResultSet.TZAbstractCachedResultSet.PostRowUpdates;
ZDbcMySqlResultSet.TZMySQLCachedResolver.PostUpdates;
ZDbcGenericResolver.TZGenerateSQLCachedResolver.PostUpdates;

I attempted to modify the following:

Code: Select all

procedure TZAbstractCachedResultSet.SetCachedUpdates(Value: Boolean);
begin
  if FCachedUpdates <> Value then
  begin
    FCachedUpdates := Value;
    if not FCachedUpdates And Self.IsPendingUpdates then
      PostUpdates;
  end;
end;
but .IsPendingUpdates still returns True and the same error is thrown.
Delphi 12.2, Zeos 8 from latest GIT snapshot
Using:
- MySQL server 8.0.18; libmysql.dll 8.0.40 x64 5.7.19 x68, libmariadb.dll 3.3.11
- Oracle server 11.2.0, 12.1.0, 19.0.0; oci.dll 21.15
- MSSQL 2012, 2019; sybdb.dll FreeTDS_3102
- SQLite 3.47
User avatar
aehimself
Zeos Dev Team
Zeos Dev Team
Posts: 799
Joined: 18.11.2018, 17:37
Location: Hungary

Re: Query.CachedUpdates := False is attempting to double-apply

Post by aehimself »

.ApplyUpdates chooses like this:

Code: Select all

    if CachedResultSet <> nil then
      if Connection.AutoCommit and
        not ( Connection.TransactIsolationLevel in [tiReadCommitted, tiSerializable] ) then
        CachedResultSet.PostUpdates
      else
        CachedResultSet.PostUpdatesCached;
So the issue only raises if we are using transactions AND cached updates too. The main difference between PostUpdates and PostUpdatesCached is that the non-cached version actually does remove the modified record from FInitialRowsList; cached does not. It also states in a comment:

{**
Posts all saved updates to the server but keeps them cached.
}


When I set the .CachedUpdates property to false, it automatically calls .PostUpdates, which is going through all records in FInitialRowsList and attempts to apply the changes to the database. The issue is, it was already done in ApplyUpdates (and therefore PostUpdatesCached) it was just not removed from FInitialRowsList. Naturally, as there's nothing to update the exception is raised.

Is this by design? Why .PostUpdatedCached should keep the changes, if they are already in the database? Shouldn't ZConnection.Commit notify it's connected datasets to now empty their cached updates, as they were all written out successfully?

At this point I don't even know which is by design, I can't even decide which part to apply a patch to :(
Delphi 12.2, Zeos 8 from latest GIT snapshot
Using:
- MySQL server 8.0.18; libmysql.dll 8.0.40 x64 5.7.19 x68, libmariadb.dll 3.3.11
- Oracle server 11.2.0, 12.1.0, 19.0.0; oci.dll 21.15
- MSSQL 2012, 2019; sybdb.dll FreeTDS_3102
- SQLite 3.47
User avatar
aehimself
Zeos Dev Team
Zeos Dev Team
Posts: 799
Joined: 18.11.2018, 17:37
Location: Hungary

Re: Query.CachedUpdates := False is attempting to double-apply

Post by aehimself »

...turns out .Commit is supposed to do that. Created a pull request with the fix :)
Delphi 12.2, Zeos 8 from latest GIT snapshot
Using:
- MySQL server 8.0.18; libmysql.dll 8.0.40 x64 5.7.19 x68, libmariadb.dll 3.3.11
- Oracle server 11.2.0, 12.1.0, 19.0.0; oci.dll 21.15
- MSSQL 2012, 2019; sybdb.dll FreeTDS_3102
- SQLite 3.47
marsupilami
Platinum Boarder
Platinum Boarder
Posts: 1962
Joined: 17.01.2011, 14:17

Re: Query.CachedUpdates := False is attempting to double-apply

Post by marsupilami »

The pull request got applied ;)
Thanks :)
User avatar
aehimself
Zeos Dev Team
Zeos Dev Team
Posts: 799
Joined: 18.11.2018, 17:37
Location: Hungary

Re: Query.CachedUpdates := False is attempting to double-apply

Post by aehimself »

Np, glad I could help :)
Delphi 12.2, Zeos 8 from latest GIT snapshot
Using:
- MySQL server 8.0.18; libmysql.dll 8.0.40 x64 5.7.19 x68, libmariadb.dll 3.3.11
- Oracle server 11.2.0, 12.1.0, 19.0.0; oci.dll 21.15
- MSSQL 2012, 2019; sybdb.dll FreeTDS_3102
- SQLite 3.47
User avatar
EgonHugeist
Zeos Project Manager
Zeos Project Manager
Posts: 1936
Joined: 31.03.2011, 22:38

Re: Query.CachedUpdates := False is attempting to double-apply

Post by EgonHugeist »

Create job, thx!
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
User avatar
aehimself
Zeos Dev Team
Zeos Dev Team
Posts: 799
Joined: 18.11.2018, 17:37
Location: Hungary

Re: Query.CachedUpdates := False is attempting to double-apply

Post by aehimself »

Story does not end here!!!

Code: Select all

 ZQuery1.CachedUpdates := True;
 ZConnection1.StartTransaction;
 ZQuery1.Edit;
 ZQuery1.FieldByName('f').AsInteger := Random(100000);
 ZQuery1.Post;
 ZConnection1.Commit;
 ZQuery1.CachedUpdates := False;
 ZQuery1.Refresh;
 ShowMessage(ZQuery1.FieldByName('f').AsString);
ZSQLMonitor log:
'2020-06-18 15:06:41' cat: Execute, proto: mysql, msg: Native SetAutoCommit False call
'2020-06-18 15:06:41' cat: Execute, proto: mysql, msg: Native Commit call
'2020-06-18 15:06:41' cat: Execute, proto: mysql, msg: Statement 2 : SELECT * FROM Temp

Notice something funky? Yeah, changes only exist in the dataset, appearing to be committed, but not actually written to the database. Upon the refresh, data is re-read from the database therefore discarding the changes made. ShowMessage will pop up the previous state.

I'll start debugging, just wanted you to know that it's still not good. And it took me 8 days do recognize that my app wrote absolutely NOTHING in the database...!!!!!
Last edited by aehimself on 18.06.2020, 15:06, edited 1 time in total.
Delphi 12.2, Zeos 8 from latest GIT snapshot
Using:
- MySQL server 8.0.18; libmysql.dll 8.0.40 x64 5.7.19 x68, libmariadb.dll 3.3.11
- Oracle server 11.2.0, 12.1.0, 19.0.0; oci.dll 21.15
- MSSQL 2012, 2019; sybdb.dll FreeTDS_3102
- SQLite 3.47
User avatar
aehimself
Zeos Dev Team
Zeos Dev Team
Posts: 799
Joined: 18.11.2018, 17:37
Location: Hungary

Re: Query.CachedUpdates := False is attempting to double-apply

Post by aehimself »

Found the issue, fixed the issue.
Pull requst waiting to be accepted.
Delphi 12.2, Zeos 8 from latest GIT snapshot
Using:
- MySQL server 8.0.18; libmysql.dll 8.0.40 x64 5.7.19 x68, libmariadb.dll 3.3.11
- Oracle server 11.2.0, 12.1.0, 19.0.0; oci.dll 21.15
- MSSQL 2012, 2019; sybdb.dll FreeTDS_3102
- SQLite 3.47
marsupilami
Platinum Boarder
Platinum Boarder
Posts: 1962
Joined: 17.01.2011, 14:17

Re: Query.CachedUpdates := False is attempting to double-apply

Post by marsupilami »

Applied and synced the changes.
Thank you :)
User avatar
aehimself
Zeos Dev Team
Zeos Dev Team
Posts: 799
Joined: 18.11.2018, 17:37
Location: Hungary

Re: Query.CachedUpdates := False is attempting to double-apply

Post by aehimself »

The pleasure is mine. Too bad Egonhugeist wishes to rewrite this part so it’s not going to stay for long I suppose :) Not like it’s a beautiful piece of code - I feel it a bit hacky. It would be better to force the cached dataset to apply its changes during .Commit, but this is how far my knowledge goes.

In the mean time; it will be working correctly for me and for everyone in the 7.3 branch.
Delphi 12.2, Zeos 8 from latest GIT snapshot
Using:
- MySQL server 8.0.18; libmysql.dll 8.0.40 x64 5.7.19 x68, libmariadb.dll 3.3.11
- Oracle server 11.2.0, 12.1.0, 19.0.0; oci.dll 21.15
- MSSQL 2012, 2019; sybdb.dll FreeTDS_3102
- SQLite 3.47
User avatar
aehimself
Zeos Dev Team
Zeos Dev Team
Posts: 799
Joined: 18.11.2018, 17:37
Location: Hungary

Re: Query.CachedUpdates := False is attempting to double-apply

Post by aehimself »

Beautyful. Now data is flowing in to the database, even quicker than before! An average data processing cycle had been reduced from 800-4000 ms to about... 100-150?

So transactions and cached updates do worth to be used together!
Delphi 12.2, Zeos 8 from latest GIT snapshot
Using:
- MySQL server 8.0.18; libmysql.dll 8.0.40 x64 5.7.19 x68, libmariadb.dll 3.3.11
- Oracle server 11.2.0, 12.1.0, 19.0.0; oci.dll 21.15
- MSSQL 2012, 2019; sybdb.dll FreeTDS_3102
- SQLite 3.47
Post Reply