View Issue Details

IDProjectCategoryView StatusLast Update
0038344pas2jsrtlpublic2021-01-23 11:32
Reporterhenrique Assigned ToMichael Van Canneyt  
PrioritynormalSeverityminorReproducibilityalways
Status resolvedResolutionfixed 
PlatformPas2JsOSWindows 
Fixed in Versiontrunk 
Summary0038344: TDataSet field implementation
DescriptionI started the implementation of TDataSetField.
TagsNo tags attached.
Fixed in Revision1068
Attached Files

Activities

henrique

2021-01-11 12:28

reporter  

0001-Implementa-o-inicial-do-DataSetField.patch (7,543 bytes)   
From f4d5025c95f88df69c8a2d19d7b3f822a19ee28c Mon Sep 17 00:00:00 2001
From: Henrique Gottardi Werlang <henriquewerlang@hotmail.com>
Date: Fri, 8 Jan 2021 10:58:45 -0300
Subject: [PATCH] =?UTF-8?q?Implementa=C3=A7=C3=A3o=20inicial=20do=20DataSe?=
 =?UTF-8?q?tField.?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 packages/fcl-db/db.pas      | 105 +++++++++++++++++++++++++++++++++---
 packages/fcl-db/dbconst.pas |   2 +
 2 files changed, 101 insertions(+), 6 deletions(-)

diff --git a/packages/fcl-db/db.pas b/packages/fcl-db/db.pas
index 56fa3fa..e7ddc03 100644
--- a/packages/fcl-db/db.pas
+++ b/packages/fcl-db/db.pas
@@ -20,10 +20,9 @@ unit DB;
 { $define dsdebug}
 interface
 
-uses Classes, SysUtils, JS, Types, DateUtils;
+uses Generics.Collections, Classes, SysUtils, JS, Types, DateUtils;
 
 const
-
   dsMaxBufferCount = MAXINT div 8;
   dsMaxStringSize = 8192;
 
@@ -34,7 +33,6 @@ const
   SQLDelimiterCharacters = [';',',',' ','(',')',#13,#10,#9];
 
 type
-
 { Misc Dataset types }
 
   TDataSetState = (dsInactive, dsBrowse, dsEdit, dsInsert, dsSetKey,
@@ -729,6 +727,17 @@ type
     constructor Create(AOwner: TComponent); override;
   end;
 
+  TDataSetField = class(TField)
+  private
+    FNestedDataSet: TDataSet;
+
+    procedure AssignNestedDataSet(Value: TDataSet);
+  protected
+    procedure Bind(Binding: Boolean); override;
+  public
+    constructor Create(AOwner: TComponent); override;
+  end;
+
 { TIndexDef }
 
   TIndexDefs = class;
@@ -1054,6 +1063,8 @@ type
   TOnRecordResolveEvent = Procedure (Sender : TDataset; info : TResolveInfo) of object;
   TApplyUpdatesEvent = Procedure (Sender : TDataset; info : TResolveResults) of object;
 
+  TNestedDataSetsList = specialize TList<TDataSet>;
+
 {------------------------------------------------------------------------------}
 
   TDataSet = class(TComponent)
@@ -1127,6 +1138,9 @@ type
     FInApplyupdates : Boolean;
     FLoadCount : Integer;
     FMinLoadID : Integer;
+    FDataSetField: TDataSetField;
+    FNestedDataSets: TNestedDataSetsList;
+    FNestedDataSetClass: TDataSetClass;
     Procedure DoInsertAppend(DoAppend : Boolean);
     Procedure DoInternalOpen;
     Function  GetBuffer (Index : longint) : TDataRecord;
@@ -1146,6 +1160,7 @@ type
     // Callback for Tdataproxy.DoGetData;
     function ResolveRecordUpdate(anUpdate: TRecordUpdateDescriptor): Boolean;
     procedure HandleRequestResponse(ARequest: TDataRequest);
+    function GetNestedDataSets: TNestedDataSetsList;
   protected
     // Proxy methods
     // Override this to integrate package in local data
@@ -1283,6 +1298,7 @@ type
     procedure SetBookmarkData(Var Buffer{%H-}: TDataRecord; Data{%H-}: TBookmark); virtual;
     procedure SetUniDirectional(const Value: Boolean);
     procedure Notification(AComponent: TComponent; Operation: TOperation); override;
+    procedure SetDataSetField(const Value: TDataSetField); virtual;
     // These use the active buffer
     function GetFieldData(Field: TField): JSValue;  virtual; overload;
     procedure SetFieldData(Field: TField; AValue : JSValue);  virtual; overload;
@@ -1290,6 +1306,7 @@ type
     procedure SetFieldData(Field: TField; var Buffer: TDatarecord; AValue : JSValue);  virtual; overload;
     class function FieldDefsClass : TFieldDefsClass; virtual;
     class function FieldsClass : TFieldsClass; virtual;
+    property NestedDataSets: TNestedDataSetsList read GetNestedDataSets;
   protected { abstract methods }
     function GetRecord(var Buffer: TDataRecord; GetMode: TGetMode; DoCheck: Boolean): TGetResult; virtual; abstract;
     procedure InternalClose; virtual; abstract;
@@ -1360,6 +1377,7 @@ type
     procedure UpdateCursorPos;
     procedure UpdateRecord;
     Function GetPendingUpdates : TResolveInfoArray;
+    property DataSetField: TDataSetField read FDataSetField write SetDataSetField;
     Property Loading : Boolean Read GetIsLoading;
     property BlockReadSize: Integer read FBlockReadSize write SetBlockReadSize;
     property BOF: Boolean read FBOF;
@@ -2343,10 +2361,9 @@ begin
   FIsUniDirectional := False;
   FAutoCalcFields := True;
   FDataRequestID:=0;
+  FNestedDataSetClass := TDataSetClass(Self.ClassType);
 end;
 
-
-
 destructor TDataSet.Destroy;
 
 var
@@ -2356,6 +2373,7 @@ begin
   Active:=False;
   FFieldDefs.Free;
   FFieldList.Free;
+  FNestedDataSets.Free;
   With FDataSources do
     begin
     While Count>0 do
@@ -2544,9 +2562,16 @@ procedure TDataSet.DataEvent(Event: TDataEvent; Info: JSValue);
   end;
   
   procedure HandleScrollOrChange;
+  var
+    NestedDataSet: TDataSet;
   begin
     if State <> dsInsert then
       UpdateCursorPos;
+
+    if Assigned(FNestedDataSets) then
+      for NestedDataSet in FNestedDataSets do
+        if NestedDataSet.Active then
+          NestedDataSet.DataEvent(deParentScroll, 0);
   end;
 
 var
@@ -3157,6 +3182,35 @@ begin
   // empty stub
 end;
 
+procedure TDataSet.SetDataSetField(const Value: TDataSetField);
+begin
+  if Value <> FDataSetField then
+  begin
+    if (Value <> nil) and ((Value.DataSet = Self) or
+       ((Value.DataSet.GetDataSource <> nil) and
+        (Value.DataSet.GetDataSource.DataSet = Self))) then
+      DatabaseError(SCircularDataLink, Self);
+    if Assigned(Value) and not InheritsFrom(Value.DataSet.FNestedDataSetClass) then
+      DatabaseErrorFmt(SNestedDataSetClass, [Value.DataSet.FNestedDataSetClass.ClassName], Self);
+    if Active then Close;
+    if Assigned(FDataSetField) then
+      FDataSetField.AssignNestedDataSet(nil);
+    FDataSetField := Value;
+    if Assigned(Value) then
+    begin
+      Value.AssignNestedDataSet(Self);
+      if Value.DataSet.Active then Open;
+    end;
+  end;
+end;
+
+function TDataSet.GetNestedDataSets: TNestedDataSetsList;
+begin
+  if not Assigned(FNestedDataSets) then
+    FNestedDataSets := TNestedDataSetsList.Create;
+
+  Result := FNestedDataSets;
+end;
 
 function TDataSet.GetFieldData(Field: TField; Buffer: TDatarecord): JSValue;
 
@@ -9001,6 +9055,45 @@ begin
     end;
 end;
 
-initialization
+{ TDataSetField }
+
+constructor TDataSetField.Create(AOwner: TComponent);
+begin
+  inherited;
+
+  SetDataType(ftDataSet);
+end;
+
+procedure TDataSetField.Bind(Binding: Boolean);
+begin
+  inherited;
+  if Assigned(FNestedDataSet) then
+    if Binding then
+    begin
+      if FNestedDataSet.State = dsInActive then
+        FNestedDataSet.Open;
+    end
+    else
+      FNestedDataSet.Close;
+end;
+
+procedure TDataSetField.AssignNestedDataSet(Value: TDataSet);
+begin
+  if Assigned(FNestedDataSet) then
+  begin
+    FNestedDataSet.Close;
+    FNestedDataSet.FDataSetField := nil;
+    if Assigned(DataSet) then
+      DataSet.NestedDataSets.Remove(FNestedDataSet);
+  end;
+  if Assigned(Value) then
+  begin
+    DataSet.NestedDataSets.Add(Value);
+    FFields := Value.Fields;
+  end
+  else
+    FFields := nil;
+  FNestedDataSet := Value;
+end;
 
 end.
diff --git a/packages/fcl-db/dbconst.pas b/packages/fcl-db/dbconst.pas
index 19443ad..3e8f02d 100644
--- a/packages/fcl-db/dbconst.pas
+++ b/packages/fcl-db/dbconst.pas
@@ -128,6 +128,8 @@ Resourcestring
   SatEOFInternalOnly          = 'loAtEOF is for internal use only.';
   SErrInsertingSameRecordtwice = 'Attempt to insert the same record twice.';
   SErrDoApplyUpdatesNeedsProxy = 'Cannot apply updates without Data proxy';
+  SNestedDataSetClass = 'Nested dataset must inherit from %s';
+  SCircularDataLink = 'Circular datalinks are not allowed';
 
 Implementation
 
-- 
2.29.2.windows.2

Michael Van Canneyt

2021-01-11 13:01

administrator   ~0128264

Remove the dependency on generics please. No generics in DB units.

henrique

2021-01-14 12:51

reporter   ~0128315

OK. But may I know why?

Michael Van Canneyt

2021-01-14 13:39

administrator   ~0128317

3 reasons:
1. It creates an extra dependency. Less units is better.
2. Every generic specialization adds code to the binary. I am not OK with that unless there is a good reason.
3. TDataset stems from a time when generics were not available and is programmed a certain way.
    I don't think units that predate generics must now be populated with generic concepts.

Maybe one day the whole DB unit can be reprogrammed with generics;
but I don't think the 2 concepts should be mixed.

henrique

2021-01-15 12:51

reporter   ~0128341

Okey!

Attached the implementation without the generics reference.
0001-Implementa-o-inicial-do-DataSetField-2.patch (7,530 bytes)   
From 8d4adba303f78fe202e2078634a11eccf8dd77c6 Mon Sep 17 00:00:00 2001
From: Henrique Gottardi Werlang <henriquewerlang@hotmail.com>
Date: Fri, 15 Jan 2021 08:48:47 -0300
Subject: [PATCH] =?UTF-8?q?Implementa=C3=A7=C3=A3o=20inicial=20do=20DataSe?=
 =?UTF-8?q?tField.?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 packages/fcl-db/db.pas      | 110 ++++++++++++++++++++++++++++++++++--
 packages/fcl-db/dbconst.pas |   2 +
 2 files changed, 107 insertions(+), 5 deletions(-)

diff --git a/packages/fcl-db/db.pas b/packages/fcl-db/db.pas
index 56fa3fa..8ef67cf 100644
--- a/packages/fcl-db/db.pas
+++ b/packages/fcl-db/db.pas
@@ -23,7 +23,6 @@ interface
 uses Classes, SysUtils, JS, Types, DateUtils;
 
 const
-
   dsMaxBufferCount = MAXINT div 8;
   dsMaxStringSize = 8192;
 
@@ -34,7 +33,6 @@ const
   SQLDelimiterCharacters = [';',',',' ','(',')',#13,#10,#9];
 
 type
-
 { Misc Dataset types }
 
   TDataSetState = (dsInactive, dsBrowse, dsEdit, dsInsert, dsSetKey,
@@ -729,6 +727,17 @@ type
     constructor Create(AOwner: TComponent); override;
   end;
 
+  TDataSetField = class(TField)
+  private
+    FNestedDataSet: TDataSet;
+
+    procedure AssignNestedDataSet(Value: TDataSet);
+  protected
+    procedure Bind(Binding: Boolean); override;
+  public
+    constructor Create(AOwner: TComponent); override;
+  end;
+
 { TIndexDef }
 
   TIndexDefs = class;
@@ -1054,6 +1063,8 @@ type
   TOnRecordResolveEvent = Procedure (Sender : TDataset; info : TResolveInfo) of object;
   TApplyUpdatesEvent = Procedure (Sender : TDataset; info : TResolveResults) of object;
 
+  TNestedDataSetsList = TFPList;
+
 {------------------------------------------------------------------------------}
 
   TDataSet = class(TComponent)
@@ -1127,6 +1138,9 @@ type
     FInApplyupdates : Boolean;
     FLoadCount : Integer;
     FMinLoadID : Integer;
+    FDataSetField: TDataSetField;
+    FNestedDataSets: TNestedDataSetsList;
+    FNestedDataSetClass: TDataSetClass;
     Procedure DoInsertAppend(DoAppend : Boolean);
     Procedure DoInternalOpen;
     Function  GetBuffer (Index : longint) : TDataRecord;
@@ -1146,6 +1160,7 @@ type
     // Callback for Tdataproxy.DoGetData;
     function ResolveRecordUpdate(anUpdate: TRecordUpdateDescriptor): Boolean;
     procedure HandleRequestResponse(ARequest: TDataRequest);
+    function GetNestedDataSets: TNestedDataSetsList;
   protected
     // Proxy methods
     // Override this to integrate package in local data
@@ -1283,6 +1298,7 @@ type
     procedure SetBookmarkData(Var Buffer{%H-}: TDataRecord; Data{%H-}: TBookmark); virtual;
     procedure SetUniDirectional(const Value: Boolean);
     procedure Notification(AComponent: TComponent; Operation: TOperation); override;
+    procedure SetDataSetField(const Value: TDataSetField); virtual;
     // These use the active buffer
     function GetFieldData(Field: TField): JSValue;  virtual; overload;
     procedure SetFieldData(Field: TField; AValue : JSValue);  virtual; overload;
@@ -1290,6 +1306,7 @@ type
     procedure SetFieldData(Field: TField; var Buffer: TDatarecord; AValue : JSValue);  virtual; overload;
     class function FieldDefsClass : TFieldDefsClass; virtual;
     class function FieldsClass : TFieldsClass; virtual;
+    property NestedDataSets: TNestedDataSetsList read GetNestedDataSets;
   protected { abstract methods }
     function GetRecord(var Buffer: TDataRecord; GetMode: TGetMode; DoCheck: Boolean): TGetResult; virtual; abstract;
     procedure InternalClose; virtual; abstract;
@@ -1360,6 +1377,7 @@ type
     procedure UpdateCursorPos;
     procedure UpdateRecord;
     Function GetPendingUpdates : TResolveInfoArray;
+    property DataSetField: TDataSetField read FDataSetField write SetDataSetField;
     Property Loading : Boolean Read GetIsLoading;
     property BlockReadSize: Integer read FBlockReadSize write SetBlockReadSize;
     property BOF: Boolean read FBOF;
@@ -2343,10 +2361,9 @@ begin
   FIsUniDirectional := False;
   FAutoCalcFields := True;
   FDataRequestID:=0;
+  FNestedDataSetClass := TDataSetClass(Self.ClassType);
 end;
 
-
-
 destructor TDataSet.Destroy;
 
 var
@@ -2356,6 +2373,7 @@ begin
   Active:=False;
   FFieldDefs.Free;
   FFieldList.Free;
+  FNestedDataSets.Free;
   With FDataSources do
     begin
     While Count>0 do
@@ -2544,9 +2562,23 @@ procedure TDataSet.DataEvent(Event: TDataEvent; Info: JSValue);
   end;
   
   procedure HandleScrollOrChange;
+  var
+    A: Integer;
+
+    NestedDataSet: TDataSet;
+
   begin
     if State <> dsInsert then
       UpdateCursorPos;
+
+    if Assigned(FNestedDataSets) then
+      for A := 0 to Pred(NestedDataSets.Count) do
+      begin
+        NestedDataSet := TDataSet(NestedDataSets[A]);
+
+        if NestedDataSet.Active then
+          NestedDataSet.DataEvent(deParentScroll, 0);
+      end;
   end;
 
 var
@@ -3157,6 +3189,35 @@ begin
   // empty stub
 end;
 
+procedure TDataSet.SetDataSetField(const Value: TDataSetField);
+begin
+  if Value <> FDataSetField then
+  begin
+    if (Value <> nil) and ((Value.DataSet = Self) or
+       ((Value.DataSet.GetDataSource <> nil) and
+        (Value.DataSet.GetDataSource.DataSet = Self))) then
+      DatabaseError(SCircularDataLink, Self);
+    if Assigned(Value) and not InheritsFrom(Value.DataSet.FNestedDataSetClass) then
+      DatabaseErrorFmt(SNestedDataSetClass, [Value.DataSet.FNestedDataSetClass.ClassName], Self);
+    if Active then Close;
+    if Assigned(FDataSetField) then
+      FDataSetField.AssignNestedDataSet(nil);
+    FDataSetField := Value;
+    if Assigned(Value) then
+    begin
+      Value.AssignNestedDataSet(Self);
+      if Value.DataSet.Active then Open;
+    end;
+  end;
+end;
+
+function TDataSet.GetNestedDataSets: TNestedDataSetsList;
+begin
+  if not Assigned(FNestedDataSets) then
+    FNestedDataSets := TNestedDataSetsList.Create;
+
+  Result := FNestedDataSets;
+end;
 
 function TDataSet.GetFieldData(Field: TField; Buffer: TDatarecord): JSValue;
 
@@ -9001,6 +9062,45 @@ begin
     end;
 end;
 
-initialization
+{ TDataSetField }
+
+constructor TDataSetField.Create(AOwner: TComponent);
+begin
+  inherited;
+
+  SetDataType(ftDataSet);
+end;
+
+procedure TDataSetField.Bind(Binding: Boolean);
+begin
+  inherited;
+  if Assigned(FNestedDataSet) then
+    if Binding then
+    begin
+      if FNestedDataSet.State = dsInActive then
+        FNestedDataSet.Open;
+    end
+    else
+      FNestedDataSet.Close;
+end;
+
+procedure TDataSetField.AssignNestedDataSet(Value: TDataSet);
+begin
+  if Assigned(FNestedDataSet) then
+  begin
+    FNestedDataSet.Close;
+    FNestedDataSet.FDataSetField := nil;
+    if Assigned(DataSet) then
+      DataSet.NestedDataSets.Remove(FNestedDataSet);
+  end;
+  if Assigned(Value) then
+  begin
+    DataSet.NestedDataSets.Add(Value);
+    FFields := Value.Fields;
+  end
+  else
+    FFields := nil;
+  FNestedDataSet := Value;
+end;
 
 end.
diff --git a/packages/fcl-db/dbconst.pas b/packages/fcl-db/dbconst.pas
index 19443ad..3e8f02d 100644
--- a/packages/fcl-db/dbconst.pas
+++ b/packages/fcl-db/dbconst.pas
@@ -128,6 +128,8 @@ Resourcestring
   SatEOFInternalOnly          = 'loAtEOF is for internal use only.';
   SErrInsertingSameRecordtwice = 'Attempt to insert the same record twice.';
   SErrDoApplyUpdatesNeedsProxy = 'Cannot apply updates without Data proxy';
+  SNestedDataSetClass = 'Nested dataset must inherit from %s';
+  SCircularDataLink = 'Circular datalinks are not allowed';
 
 Implementation
 
-- 
2.30.0.windows.1

Michael Van Canneyt

2021-01-23 11:32

administrator   ~0128508

Last edited: 2021-01-23 11:32

View 2 revisions

Applied with cosmetic changes, thank you very much !

Issue History

Date Modified Username Field Change
2021-01-11 12:28 henrique New Issue
2021-01-11 12:28 henrique File Added: 0001-Implementa-o-inicial-do-DataSetField.patch
2021-01-11 13:01 Michael Van Canneyt Note Added: 0128264
2021-01-11 13:01 Michael Van Canneyt Assigned To => Michael Van Canneyt
2021-01-11 13:01 Michael Van Canneyt Status new => assigned
2021-01-14 12:51 henrique Note Added: 0128315
2021-01-14 13:39 Michael Van Canneyt Note Added: 0128317
2021-01-15 12:51 henrique Note Added: 0128341
2021-01-15 12:51 henrique File Added: 0001-Implementa-o-inicial-do-DataSetField-2.patch
2021-01-23 11:32 Michael Van Canneyt Status assigned => resolved
2021-01-23 11:32 Michael Van Canneyt Resolution open => fixed
2021-01-23 11:32 Michael Van Canneyt Fixed in Version => trunk
2021-01-23 11:32 Michael Van Canneyt Fixed in Revision => 1068
2021-01-23 11:32 Michael Van Canneyt Note Added: 0128508
2021-01-23 11:32 Michael Van Canneyt Note Edited: 0128508 View Revisions