Shouldn't UniDirectional datasets support .First...?

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: 796
Joined: 18.11.2018, 17:37
Location: Hungary

Shouldn't UniDirectional datasets support .First...?

Post by aehimself »

I had an issue with specific "Operation is not allowed in FORWARD ONLY mode" exceptions if dataset was opened from a background thread. As it turns out, the issue was half mine. Imagine the following code:

Code: Select all

Type
 TOpenThread = Class(TThread)
 strict private
  _dataset: TZAbstractRODataSet;
 protected
  Procedure Execute; Override;
 public
  Constructor Create(inDataSet: TZAbstractRODataSet); ReIntroduce;
 End;

constructor TOpenThread.Create(inDataSet: TZAbstractRODataSet);
begin
 inherited Create(False);

 _dataset := inDataSet;
end;

Procedure TOpenThread.Execute;
Begin
 _dataset.Open;
 _dataset.FetchAll;
// _dataset.First;
End;

procedure TForm2.Button2Click(Sender: TObject);
Var
 ot: TOpenThread;
 s: String;
begin
 ot := TOpenThread.Create(ZReadOnlyQuery1);
 ot.WaitFor;
 ot.Free;

 With ZReadOnlyQuery1 Do
  Begin
   While Not Eof Do
    Begin
     s := FieldByName('STRINGFIELD').AsString;
     Next;
    End;
   Close;
  End;
end;
If you make ZReadOnlyQuery1 UniDirectional, this code will throw an exception on FieldByName.AsString. The issue is, .FetchAll cycles through the dataset, leaving the active cursor in the resultset as the last, but rownum on 1 in the dataset. GetAsString tries to reposition the cursor, which is not allowed in an unidirectional dataset. So the solution is, call .First in the thread (see commented out).

But, according to Zeos this is not allowed (ZAbstractRODataSet.pas : 3848):

Code: Select all

procedure TZAbstractRODataset.InternalFirst;
begin
  if CurrentRow > 0 then
    CheckBiDirectional;
  CurrentRow := 0;
end;

procedure TZAbstractRODataset.CheckBiDirectional;
begin
  if IsUniDirectional then
    raise EZDatabaseError.Create(SOperationIsNotAllowed1);
end;
Clue #1:
According to this site:
The only supported navigation methods are the First and Next methods. Most others raise exceptions. Some, such as the methods involved in bookmark support, simply do nothing.
Clue #2:
If I comment out the check in InternalFirst and call .First in the background thread, the code no longer throws an exception and finishes the cycle.


The issue is reproducible on Oracle, in MySQL the original version (without .First) worked. The question is still there... Shouldn't be .First allowed in UniDirectional mode...?

If we agree that it should, I can create a pull request if you want.
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: 1956
Joined: 17.01.2011, 14:17

Re: Shouldn't UniDirectional datasets support .First...?

Post by marsupilami »

Hello aehimself,

being allowed to call First on an unidirectional dataset doesn't make sense to me: The idea is to process data as it comes in and then forget about it. Also I don't know what First should (semantically) do in a situation where Next was already called - it cannot go back to records that were already read. They most probably were already deleted from memory. So raising an exception is the right thing to do.

I also don't understand why you call FetchAll. FetchAll is meant to fetch any remaining data from the server and buffer it in memory. But if you want to buffer data in memory, why use an unidirectional dataset at all? If you want to buffer data in memory, you can use a regular dataset that allows random access to all records. We should add an exception to FetchAll that disallows calling it on unidirectional datasets.

Best regards,

Jan
User avatar
aehimself
Zeos Dev Team
Zeos Dev Team
Posts: 796
Joined: 18.11.2018, 17:37
Location: Hungary

Re: Shouldn't UniDirectional datasets support .First...?

Post by aehimself »

marsupilami wrote: 24.04.2021, 08:27being allowed to call First on an unidirectional dataset doesn't make sense to me
Well, the question is how close Zeos wants to behave to Delphi's original datasets. Because according to all information I was able to find, they allow. Zeos does not.
marsupilami wrote: 24.04.2021, 08:27I also don't understand why you call FetchAll. FetchAll is meant to fetch any remaining data from the server and buffer it in memory. But if you want to buffer data in memory, why use an unidirectional dataset at all?
This is exactly what I meant by "half my issue". I recently got introduced to unidirectional datasets and this code was meant to pull data into normal queries. Now, in the final code .FetchAll is only called if the dataset is NOT unidirectional.
marsupilami wrote: 24.04.2021, 08:27We should add an exception to FetchAll that disallows calling it on unidirectional datasets.
This is something I agree with.
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: 1956
Joined: 17.01.2011, 14:17

Re: Shouldn't UniDirectional datasets support .First...?

Post by marsupilami »

Hello :)

I am all in for being Delphi compatoble - as long as it makes sense. Being able to call First on an unidirectional dataset seems to be totally wrong. What would the semantics of First be on an unidirectional dataset? Being a NOP?
Also I couldn't find any good specification on what is expected from unidirectional datasets. I tried to browse the Delphi online help and find some good information but whenever I clicked on the description for the First method, I was redirected to a generic description that is part of TDataSet.

Maybe being able to call First on a dbExpress dataset is a bug?

Best regards,

Jan
Post Reply