From: <na...@us...> - 2010-09-19 10:23:24
|
Revision: 928 http://instantobjects.svn.sourceforge.net/instantobjects/revision/?rev=928&view=rev Author: nandod Date: 2010-09-19 10:23:17 +0000 (Sun, 19 Sep 2010) Log Message: ----------- + lmPartialBurst mode. * Fixed a bug that would leak memory if a broker's StatementCacheCapacity was changed from 0 to non-zero after one or more queries had been open. * Code cleanup and assorted small optimizations. Modified Paths: -------------- trunk/Demos/PrimerCross/QueryView.dfm trunk/Demos/PrimerCross/QueryView.pas trunk/Demos/PrimerCross/RandomData.pas trunk/Docs/Burst_Load_Modes.txt trunk/Source/Core/InstantBrokers.pas trunk/Source/Core/InstantPersistence.pas trunk/Source/Core/InstantTypes.pas Property Changed: ---------------- trunk/Demos/PrimerCross/ Property changes on: trunk/Demos/PrimerCross ___________________________________________________________________ Modified: svn:ignore - *.dcu *.~* *.ddp *.exe __history *.identcache *.local *.dsk *.MB XMLDB PrimerExternal_D2009.xml + *.dcu *.~* *.ddp *.exe __history *.identcache *.local *.dsk *.MB XMLDB PrimerExternal_D2009.xml Profiling Modified: trunk/Demos/PrimerCross/QueryView.dfm =================================================================== --- trunk/Demos/PrimerCross/QueryView.dfm 2010-09-18 08:36:12 UTC (rev 927) +++ trunk/Demos/PrimerCross/QueryView.dfm 2010-09-19 10:23:17 UTC (rev 928) @@ -105,17 +105,27 @@ Text = 'Keys First' Items.Strings = ( 'Keys First' + 'Partial Burst' 'Full Burst') end object FetchAllCheckBox: TCheckBox Left = 95 - Top = 124 - Width = 58 + Top = 116 + Width = 64 Height = 17 Alignment = taLeftJustify Caption = 'Fetch All' TabOrder = 2 end + object StatementCacheCheckBox: TCheckBox + Left = 95 + Top = 131 + Width = 64 + Height = 17 + Alignment = taLeftJustify + Caption = 'S. Cache' + TabOrder = 6 + end end object ResultPageControl: TPageControl Left = 0 @@ -180,7 +190,6 @@ end end object TestSelector: TInstantSelector - AfterScroll = TestSelectorAfterScroll AfterClose = TestSelectorAfterClose Left = 32 Top = 192 Modified: trunk/Demos/PrimerCross/QueryView.pas =================================================================== --- trunk/Demos/PrimerCross/QueryView.pas 2010-09-18 08:36:12 UTC (rev 927) +++ trunk/Demos/PrimerCross/QueryView.pas 2010-09-19 10:23:17 UTC (rev 928) @@ -45,6 +45,7 @@ StatsTabSheet: TTabSheet; StatsMemo: TMemo; FetchAllCheckBox: TCheckBox; + StatementCacheCheckBox: TCheckBox; procedure ExecuteActionExecute(Sender: TObject); procedure ExampleComboBoxClick(Sender: TObject); procedure TestSelectorAfterScroll(DataSet: TDataSet); @@ -129,13 +130,25 @@ {$ENDIF} with TestSelector do begin + if Connector.Broker is TInstantSQLBroker then + begin + if StatementCacheCheckBox.Checked then + TInstantSQLBroker(Connector.Broker).StatementCacheCapacity := -1 + else + TInstantSQLBroker(Connector.Broker).StatementCacheCapacity := 0; + end; Close; TestSelector.MaxCount := StrToInt(Trim(MaxCountEdit.Text)); Command.Text := CommandEdit.Text; - Open; - if FetchAllCheckBox.Checked then - while not Eof do - Next; + DisableControls; + try + Open; + if FetchAllCheckBox.Checked then + while not Eof do + Next; + finally + EnableControls; + end; ResultPageControl.ActivePage := ResultTabSheet; end; finally Modified: trunk/Demos/PrimerCross/RandomData.pas =================================================================== --- trunk/Demos/PrimerCross/RandomData.pas 2010-09-18 08:36:12 UTC (rev 927) +++ trunk/Demos/PrimerCross/RandomData.pas 2010-09-19 10:23:17 UTC (rev 928) @@ -24,7 +24,7 @@ implementation uses - SysUtils; + SysUtils, InstantUtils; const Letters = ['a'..'z']; @@ -113,14 +113,14 @@ begin repeat Result := RandomLetter; - until Result in Consonants; + until InstantCharInSet(Result, Consonants); end; function RandomVowel: Char; begin repeat Result := RandomLetter; - until Result in Vowels; + until InstantCharInSet(Result, Vowels); end; function RandomName: string; @@ -232,7 +232,7 @@ RandomStr(StreetBeginnings) + RandomStr(StreetEndings) + ' ' + RandomStr(StreetTypes) + ' ' + - IntToStr((Random(499) + 1) div (Random(9) + 1) + 1) + ' ' + '\x80 \xE0\xF2\xE8 aa'; + IntToStr((Random(499) + 1) div (Random(9) + 1) + 1); end; function RandomCity: string; Modified: trunk/Docs/Burst_Load_Modes.txt =================================================================== --- trunk/Docs/Burst_Load_Modes.txt 2010-09-18 08:36:12 UTC (rev 927) +++ trunk/Docs/Burst_Load_Modes.txt 2010-09-19 10:23:17 UTC (rev 928) @@ -1,11 +1,13 @@ -Burst Load Mode -Nando Dessena, 14/09/2010 +Burst Load Modes +Nando Dessena, 19/09/2010 -What is it ----------- -Burst Load Mode is an alternative way of retrieving objects in InstantObjects' SQL brokers. This new mode can be selected on a case by case basis by setting the new RequestedLoadMode property of an InstantSelector or InstantQuery to the value lmFullBurst. +What is burst load +------------------ +Burst Load Modes are alternative ways of retrieving objects in InstantObjects' SQL brokers. These new modes can be selected on a case by case basis by setting the new RequestedLoadMode property of an InstantSelector or InstantQuery to the value lmPartialBurst or lmFullBurst. The value lmKeysFirst means the standard, historical load mode. + + What does it do --------------- @@ -14,60 +16,59 @@ 2) Fetch all records (up to MaxCount). 3) For each record accessed, materialize the object. This implies executing one or more select statements to get all object data (main object query), containers and references (accessory queries). External storage implies more queries. The main query joins all the tables for the given class and ancestors. -Burst Load Mode causes these changes: +Full Burst mode causes these changes: 1) The primary query retrieves all object data, and not just the primary keys. -2) All objects are materialized (up to MaxCount) from the obtained data set; this causes the execution of all accessory and external queries that in the standard case are executed at point 3, minus the main query, which is not needed anymore. +2) lmFullBurst: All objects are materialized (up to MaxCount) from the obtained data set; this causes the execution of all accessory and external queries that in the standard case are executed at point 3, minus the main query, which is not needed anymore. lmPartialBurst: objects are materialized on demand, like in the standard mode, but using data retrieved by the primary query, like in the full burst mode. -This means that Burst Load Mode is much quicker when retrieving a dataset, whereas standard mode is best at getting the first records. +Partial Burst mode works this way: +1) Like Full Burst mode 1). +2) Like standard mode 2). +3) Like standard mode 3), but using data retrieved by the primary query and kept around. -When it is used ---------------- +This means that Full Burst mode is much quicker when retrieving a whole dataset, whereas standard mode is best at getting the first records. Partial Burst mode gives the best of both worlds, as it is almost as quick as Full Burst when fetching an entire dataset, and slightly quicker than standard mode at getting the first records. -Burst Load Mode is well suited for all cases in which objects are selected (typically through an IQL command with a WHERE clause) to be processed. If the entire dataset is going to be fetched and all records visited anyway, then it's much quicker to do it in Burst Load Mode. -The standard way is more of the "lazy load" kind, and as such it is more suited for when a list of objects is displayed for browsing, typically in a DBGrid, and not all objects are needed but just one or a few are selected to work with. In this case displaying the first records in the grid is much quicker in standard mode. +When to use burst load +---------------------- +Full Burst mode is well suited for all cases in which objects are selected (typically through an IQL command with a WHERE clause) to be processed. If the entire dataset is going to be fetched and all records visited anyway, then it's much quicker to do it in Full Burst mode. + +The standard mode is more of the "lazy load" kind, and as such it is more suited for when a list of objects is displayed for browsing, typically in a DBGrid, and not all objects are needed but just one or a few are selected to work with. In this case displaying the first records in the grid is much quicker in standard mode. + +Partial Burst Mode should cover both cases above, and as such is the preferred mode. The only glitch about Partial Burst mode is that it keeps the database connection open for longer than Full Burst mode. If that is a concern, then Full Burst mode should be used instead. + + Performance notes ----------------- -Here are some quick benchmarks done with the example queries of PrimerExternal. Operations timed include Open and Last (to ensure full fetching) on a selector. Datasets are small (a couple dozen records). The database is a local Firebird server and the compiler is Delphi 2010. All caches were flushed at each iteration. Three iterations for each test. Times in seconds. +Here are some quick benchmarks done with one of the example queries of PrimerExternal and a dataset with a couple thousand records. +The database is a local Firebird server and the compiler is Delphi XE. All caches were flushed at each iteration. Three iterations for each test (average value reported). Times in seconds. -SELECT * FROM TCompany +select * from TCompany -Burst Standard -2.1 3.5 -2.0 3.7 -2.4 3.7 - -SELECT * FROM TPerson +No fetch, just open an InstantSelector and fetch some 20 objects for the DBGrid. -Burst Standard -4.8 6.7 -4.6 7.0 -4.7 6.8 +KF PB FB +0,9 0,8 36,1 -A further test that fetches and materializes many more objects (in the thousands) shows where the gains are in Burst Load Mode. In this test all records are visited and materialized upon opening the selector (Open; while not Eof do Next;): +Fetch all records and materialize all objects (while not Eof do Next). -SELECT * FROM TCompany +KF PB FB +65,5 45,4 45,4 -Burst Standard -54.7 104.0 +Same as above, but with statement cache enabled. -Without the fetches, that is just opening the selector and letting some 20 materialized objects populate the DBGrid, the results are: +KF PB FB +38,6 28,9 29,5 -SELECT * FROM TCompany +This confirms that using Full Burst mode when not appropriate can hurt performance, which suggests to keep it disabled by default and only enable it on request. On the other hand, Partial Burst mode appears suitable for most cases. -Burst Standard -35.0 1.2 - -This tells us that using burst mode when not appropriate can hurt performance, which suggests to keep it disabled by default and only enable it on request (property BurstLoad: Boolean default False). - Caveats and future improvements ------------------------------- -- Currently Burst Load Mode is not supported for IQL commands using the ANY keywords. Doing so will require some significant refactorings and was postponed. +- Currently Burst Load modes are not supported for IQL commands using the ANY keyword. Doing so will require some significant refactorings and was postponed. The system will silently fall back to a supported mode, if the requested mode is not supported by the actual query. You can tell by inspecting the value of the ActualLoadMode property. - External atPart and all atReference attributes still need separate queries. It is difficult but not impossible to get them in the first query as well, thus reducing fetch time even more. -- A mixture of standard and Burst Load Mode looks interesting, and the work done on Burst Load Mode makes it easier to implement it. This mixture would be a standard mode in which one or more specified attributes are fetched as well as the primary keys in the main query. This would allow to display a list of objects in a DBGrid, or other multi-record control, without materializing them, as quickly as whendoing direct SQL queries. Thought should be given to design details such as when where and how to specify the attributes that should be loaded together with the primary key. The value lmPartialBurst is reserved for this. +- A mixture of standard and Burst modes looks interesting, and the work done on Burst modes makes it easier to implement it. This mixture would be a standard mode in which one or more specified attributes are fetched as well as the primary keys in the main query. This would allow to display a list of objects in a DBGrid, or other multi-record control, without materializing them, as quickly as when doing direct SQL queries. Thought should be given to design details such as when where and how to specify the attributes that should be loaded together with the primary key, and an attribute-level mechanism to signal incomplete vs complete data would probably be needed. \ No newline at end of file Modified: trunk/Source/Core/InstantBrokers.pas =================================================================== --- trunk/Source/Core/InstantBrokers.pas 2010-09-18 08:36:12 UTC (rev 927) +++ trunk/Source/Core/InstantBrokers.pas 2010-09-19 10:23:17 UTC (rev 928) @@ -109,7 +109,7 @@ Map: TInstantAttributeMap; ConflictAction: TInstantConflictAction; Info: PInstantOperationInfo); function GetStatementCache: TInstantStatementCache; - procedure SetStatementCacheCapacity(const Value: Integer); + procedure SetStatementCacheCapacity(const AValue: Integer); protected property StatementCache: TInstantStatementCache read GetStatementCache; function EnsureResolver(Map: TInstantAttributeMap): TInstantCustomResolver; @@ -535,14 +535,14 @@ procedure InternalClearAttributeLinkRecords; virtual; procedure InternalDisposeDeletedAttributeObjects( Attribute: TInstantContainer); virtual; - procedure InternalReadAttributeObjects(Attribute: TInstantContainer; + procedure InternalReadAttributeObjects(Attribute: TInstantContainer; const AObjectId: string); virtual; public constructor Create(AResolver: TInstantCustomResolver); procedure StoreAttributeObjects(Attribute: TInstantContainer); procedure ClearAttributeLinkRecords; procedure DisposeDeletedAttributeObjects(Attribute: TInstantContainer); - procedure ReadAttributeObjects(Attribute: TInstantContainer; + procedure ReadAttributeObjects(Attribute: TInstantContainer; const AObjectId: string); property Broker: TInstantCustomRelationalBroker read GetBroker; property Resolver: TInstantCustomResolver read GetResolver; @@ -621,6 +621,7 @@ property Resolver: TInstantSQLResolver read GetResolver; end; + // An item in the statement cache. TInstantStatement = class private FStatementImplementation: TComponent; @@ -630,6 +631,9 @@ property StatementImplementation: TComponent read FStatementImplementation; end; + // Caches objects that implement command statements in releational brokers. + // Most commonly, they are TDataSet descendants that implement SQL queries. + // brokers cache them to save on prepare/compilation time. TInstantStatementCache = class(TComponent) private FStatements: TStringList; @@ -649,6 +653,7 @@ function AddStatement(const StatementText: string; const StatementImplementation: TComponent): Integer; function RemoveStatement(const StatementText: string): Boolean; + function HasStatementImplementation(const StatementImplementation: TComponent): Boolean; end; // A TInstantCatalog that gathers its info from an existing database (through @@ -993,21 +998,36 @@ // Backward compatibility TInstantRelationalQuery = TInstantNavigationalQuery; + // Holds object data in the current record of a dataset specified upon + // creation. May hold data for several objects, locating the correct + // record each time it copies data to an object. + TInstantDataSetObjectData = class(TInstantAbstractObjectData) + private + FDataSet: TDataSet; + FRecNo: Integer; + FIdField: TField; + public + constructor CreateAndInit(const ADataSet: TDataSet); + property DataSet: TDataSet read FDataSet; + function Locate(const AObjectId: string): Boolean; + end; + TInstantSQLQuery = class(TInstantCustomRelationalQuery) private FObjectReferenceList: TInstantObjectReferenceList; FParamsObject: TParams; FStatement: string; + FDataSet: TDataSet; procedure DestroyObjectReferenceList; function GetObjectReferenceCount: Integer; function GetObjectReferenceList: TInstantObjectReferenceList; function GetParamsObject: TParams; - procedure InitObjectReferences(const ADataSet: TDataSet); + procedure InitObjectReferences; protected function GetActive: Boolean; override; function AcquireDataSet(const AStatement: string; AParams: TParams): TDataSet; virtual; - procedure ReleaseDataSet(const DataSet: TDataSet); + procedure ReleaseDataSet; function GetParams: TParams; override; function GetStatement: string; override; function InternalAddObject(AObject: TObject): Integer; override; @@ -1049,16 +1069,6 @@ default True; end; - // Holds object data in the current record of a dataset specified upon - // creation. Used in burst load mode. - TInstantDataSetObjectData = class(TInstantAbstractObjectData) - private - FDataSet: TDataSet; - public - constructor CreateAndInit(const ADataSet: TDataSet); - property DataSet: TDataSet read FDataSet; - end; - var InstantLogProc: procedure (const AString: string) of object; @@ -1317,13 +1327,16 @@ EnsureResolver(Map).RetrieveMap(AObject, AObjectId, Map, ConflictAction, Info, FObjectData); end; -procedure TInstantCustomRelationalBroker.SetStatementCacheCapacity(const Value: Integer); +procedure TInstantCustomRelationalBroker.SetStatementCacheCapacity(const AValue: Integer); begin - FStatementCacheCapacity := Value; - if FStatementCacheCapacity = 0 then - FreeAndNil(FStatementCache) - else if Assigned(FStatementCache) then - FStatementCache.Capacity := FStatementCacheCapacity; + if FStatementCacheCapacity <> AValue then + begin + FStatementCacheCapacity := AValue; + if FStatementCacheCapacity = 0 then + FreeAndNil(FStatementCache) + else if Assigned(FStatementCache) then + FStatementCache.Capacity := FStatementCacheCapacity; + end; end; procedure TInstantCustomRelationalBroker.StoreMap(AObject: TInstantObject; @@ -1553,7 +1566,7 @@ procedure TInstantSQLBroker.ReleaseDataSet(const ADataSet: TDataSet); begin - if FStatementCacheCapacity <> 0 then + if Assigned(FStatementCache) and FStatementCache.HasStatementImplementation(ADataSet) then ADataSet.Close else ADataSet.Free; @@ -1980,7 +1993,6 @@ procedure TInstantNavigationalResolver.ClearPart(Attribute: TInstantPart); begin if Attribute.Metadata.StorageKind = skExternal then -// Attribute.Value.ObjectStore.DisposeObject(Attribute.Value, caIgnore); DisposeObject(Attribute.Value, caIgnore); end; @@ -1992,7 +2004,6 @@ if Attribute.Metadata.StorageKind = skExternal then begin for I := 0 to Pred(Attribute.Count) do -// Attribute.Items[I].ObjectStore.DisposeObject(Attribute.Items[I], caIgnore); DisposeObject(Attribute.Items[I], caIgnore); LinkDatasetResolver := GetLinkDatasetResolver(Attribute.Metadata.ExternalStorageName); @@ -3063,7 +3074,7 @@ Assert(Assigned(AStream)); // Look in TInstantCustomResolver.CreateEmbeddedObjectOutputStream - // to see the stream type. Change there need to be propagated here. + // to see the stream type. Changes there need to be propagated here. if AConnector.BlobStreamFormat = sfBinary then begin LParam := AddParam(AParams, AParamName, ftBlob); @@ -3274,7 +3285,6 @@ as TInstantObject; try if Assigned(PartObject) then -// PartObject.ObjectStore.DisposeObject(PartObject, caIgnore); DisposeObject(PartObject, caIgnore); finally PartObject.Free; @@ -3325,8 +3335,6 @@ Attribute := TInstantContainer(AObject.AttributeByName( AttributeMetadata.Name)); for j := 0 to Pred(Attribute.Count) do -// Attribute.Items[j].ObjectStore.DisposeObject( -// Attribute.Items[j], caIgnore); DisposeObject(Attribute.Items[j], caIgnore); end; DeleteAllExternalLinks(i); @@ -3376,9 +3384,12 @@ LParams: TParams; begin // This resolver supports retrieving data from TInstantDataSetObjectData. - if Assigned(AObjectData) and (AObjectData is TInstantDataSetObjectData) then + if Assigned(AObjectData) and (AObjectData is TInstantDataSetObjectData) + and TInstantDataSetObjectData(AObjectData).Locate(AObjectId) then + begin RetrieveMapFromDataSet(AObject, AObjectId, Map, ConflictAction, AInfo, - TInstantDataSetObjectData(AObjectData).DataSet) + TInstantDataSetObjectData(AObjectData).DataSet); + end else begin LParams := TParams.Create; @@ -3476,7 +3487,6 @@ AObject.Connector) as TInstantObject; try if Assigned(PartObject) then -// PartObject.ObjectStore.DisposeObject(PartObject, caIgnore); DisposeObject(PartObject, caIgnore); finally PartObject.Free; @@ -3495,7 +3505,6 @@ // Store object PartObject := PartAttribute.Value; PartObject.CheckId; -// PartObject.ObjectStore.StoreObject(PartObject, caIgnore); StoreObject(PartObject, caIgnore); end; end; @@ -3513,7 +3522,6 @@ for I := 0 to Pred(Map.Count) do begin AttributeMetadata := Map[I]; -// if AttributeMetadata.AttributeType = atParts then if AttributeMetadata.AttributeType in [atParts, atReferences] then begin Attribute := TInstantContainer(AObject.AttributeByName( @@ -4080,7 +4088,6 @@ procedure TInstantNavigationalLinkResolver.InternalDisposeDeletedAttributeObjects( Attribute: TInstantContainer); var -// I: Integer; Obj: TInstantObject; AttributeMetadata: TInstantAttributeMetadata; ObjDisposed: Boolean; @@ -4096,43 +4103,14 @@ AttributeMetadata := Attribute.Metadata; while not Eof do begin -// PartDeleted := True; -// for I := 0 to Pred(Attribute.Count) do -// begin -// if InstantSameText(Attribute.Fields[I].Id, ADataSet.Fields[4].AsString, -// True) then -// begin -// PartDeleted := False; -// Break; -// end; -// end; -// if PartDeleted then -// begin -// PartObject := AttributeMetadata.ObjectClass.Retrieve( -// ADataSet.Fields[4].AsString, False, False, Attribute.Connector); -// try -// if Assigned(PartObject) then -// begin -// PartObject.ObjectStore.DisposeObject(PartObject, caIgnore); -// ADataSet.Delete; -// end; -// finally -// PartObject.Free; -// end; -// if not PartDeleted then -// ADataSet.Next; -// end; - ObjDisposed := False; Obj := AttributeMetadata.ObjectClass.Retrieve( - FieldByName(InstantChildIdFieldName).AsString, -// DataSet.Fields[4].AsString, - False, False, Attribute.Connector) as TInstantObject; + FieldByName(InstantChildIdFieldName).AsString, + False, False, Attribute.Connector) as TInstantObject; try if Assigned(Obj) and - (Attribute.IndexOf(Obj) = -1) then + (Attribute.IndexOf(Obj) = -1) then begin -// Obj.ObjectStore.DisposeObject(Obj, caIgnore); Resolver.DisposeObject(Obj, caIgnore); Delete; ObjDisposed := True; @@ -4153,6 +4131,7 @@ Attribute: TInstantContainer; const AObjectId: string); var WasOpen: Boolean; + LChildClassField, LChildIdField: TField; begin WasOpen := Dataset.Active; @@ -4162,13 +4141,11 @@ // Attribute.Owner.Id can be '', so do not use here. SetDatasetParentRange(Attribute.Owner.Classname, AObjectId); First; + LChildClassField := FieldByName(InstantChildClassFieldName); + LChildIdField := FieldByName(InstantChildIdFieldName); while not Eof do begin - Attribute.AddReference( - FieldByName(InstantChildClassFieldName).AsString, - FieldByName(InstantChildIdFieldName).AsString); -// DataSet.Fields[3].AsString, -// DataSet.Fields[4].AsString); + Attribute.AddReference(LChildClassField.AsString, LChildIdField.AsString); Next; end; finally @@ -4197,7 +4174,6 @@ Obj.CheckId; Append; try -// FieldByName(InstantIdFieldName).AsString := Obj.GenerateId; FieldByName(InstantIdFieldName).AsString := Attribute.Connector.GenerateId; FieldByName(InstantParentClassFieldName).AsString := @@ -4206,19 +4182,10 @@ FieldByName(InstantChildClassFieldName).AsString := Obj.ClassName; FieldByName(InstantChildIdFieldName).AsString := Obj.Id; FieldByName(InstantSequenceNoFieldName).AsInteger := Succ(I); -//// DataSet.Fields[0].AsString := Obj.GenerateId; -// DataSet.Fields[0].AsString := Obj.Id; -// DataSet.Fields[1].AsString := -// Attribute.Owner.ClassName; -// DataSet.Fields[2].AsString := Attribute.Owner.Id; -// DataSet.Fields[3].AsString := Obj.ClassName; -// DataSet.Fields[4].AsString := Obj.Id; -// DataSet.Fields[5].AsInteger := Succ(I); Post; except Cancel; end; -// Obj.ObjectStore.StoreObject(Obj, caIgnore); Resolver.StoreObject(Obj, caIgnore); end; finally @@ -4312,13 +4279,11 @@ while not DataSet.Eof do begin Obj := InstantFindClass(DataSet.FieldByName(InstantChildClassFieldName).AsString).Retrieve( -// DataSet.Fields[1].AsString, False, False, AObject.Connector); DataSet.FieldByName(InstantChildIdFieldName).AsString, False, False, Attribute.Connector) as TInstantObject; try if Assigned(Obj) and (Attribute.IndexOf(Obj) = -1) then -// Obj.ObjectStore.DisposeObject(Obj, caIgnore); Resolver.DisposeObject(Obj, caIgnore); finally Obj.Free; @@ -4336,28 +4301,29 @@ end; end; -procedure TInstantSQLLinkResolver.InternalReadAttributeObjects(Attribute: - TInstantContainer; const AObjectId: string); +procedure TInstantSQLLinkResolver.InternalReadAttributeObjects( + Attribute: TInstantContainer; const AObjectId: string); var Statement: string; Params: TParams; - Dataset: TDataset; + Dataset: TDataSet; + LChildClassField, LChildIdField: TField; begin Params := TParams.Create; try Statement := Format(Resolver.SelectExternalSQL, [TableName]); Resolver.AddIdParam(Params, InstantParentIdFieldName, AObjectId); Resolver.AddStringParam(Params, InstantParentClassFieldName, - AttributeOwner.ClassName); + AttributeOwner.ClassName); DataSet := Broker.AcquireDataSet(Statement, Params); try DataSet.Open; try + LChildClassField := DataSet.FieldByName(InstantChildClassFieldName); + LChildIdField := DataSet.FieldByName(InstantChildIdFieldName); while not DataSet.Eof do begin - Attribute.AddReference( - DataSet.FieldByName(InstantChildClassFieldName).AsString, - DataSet.FieldByName(InstantChildIdFieldName).AsString); + Attribute.AddReference(LChildClassField.AsString, LChildIdField.AsString); DataSet.Next; end; finally @@ -4385,7 +4351,6 @@ // Store object Obj := Attribute.Items[I]; Obj.CheckId; -// Obj.ObjectStore.StoreObject(Obj, caIgnore); Resolver.StoreObject(Obj, caIgnore); // Insert link @@ -4393,7 +4358,6 @@ try Statement := Format(Resolver.InsertExternalSQL, [TableName]); -// Resolver.AddIdParam(Params, InstantIdFieldName, AttributeOwner.GenerateId); Resolver.AddIdParam(Params, InstantIdFieldName, Attribute.Connector.GenerateId); Resolver.AddStringParam(Params, InstantParentClassFieldName, @@ -4489,6 +4453,22 @@ Result := nil; end; +function TInstantStatementCache.HasStatementImplementation( + const StatementImplementation: TComponent): Boolean; +var + I: Integer; +begin + Result := False; + for I := 0 to FStatements.Count - 1 do + begin + if TinstantStatement(FStatements.Objects[I]).StatementImplementation = StatementImplementation then + begin + Result := True; + Break; + end; + end; +end; + procedure TInstantStatementCache.Notification(AComponent: TComponent; Operation: TOperation); var I: Integer; @@ -5564,7 +5544,7 @@ // Mind that LContext.ActualBurstLoadMode might be different than // Self.RequestedBurstLoadMode. - if LContext.ActualLoadMode = lmFullBurst then + if IsBurstLoadMode(LContext.ActualLoadMode) then begin // Use the Id just to get the table path needed to add the updatecount // field. We could use anything we know is in the main table. @@ -5922,13 +5902,6 @@ end; end; -destructor TInstantSQLQuery.Destroy; -begin - DestroyObjectReferenceList; - FParamsObject.Free; - inherited; -end; - { TInstantSQLQuery } function TInstantSQLQuery.AcquireDataSet(const AStatement: string; @@ -5938,6 +5911,14 @@ AParams); end; +destructor TInstantSQLQuery.Destroy; +begin + DestroyObjectReferenceList; + ReleaseDataSet; + FParamsObject.Free; + inherited; +end; + procedure TInstantSQLQuery.DestroyObjectReferenceList; begin FreeAndNil(FObjectReferenceList); @@ -5978,50 +5959,45 @@ Result := FStatement; end; -procedure TInstantSQLQuery.InitObjectReferences(const ADataSet: TDataSet); - - function IsBurstLoadModeDataSet(const ADataSet: TDataSet): Boolean; - begin - // A trick to check if the dataset that came from the broker is actually - // a burst load mode dataset. Requesting burst load mode does not guarantee - // to get it, as not all IQL query types support it yet. - Result := Assigned(ADataSet.FindField(InstantUpdateCountFieldName)); - end; - +procedure TInstantSQLQuery.InitObjectReferences; var LObjRef: TInstantObjectReference; - LObjData: TInstantDataSetObjectData; + LClassField, LIdField: TField; begin - LObjData := nil; - if Assigned(ADataSet) then + Assert(Assigned(Connector)); + + if Assigned(FDataSet) then begin - ADataSet.DisableControls; try - if IsBurstLoadModeDataSet(ADataSet) then - LObjData := TInstantDataSetObjectData.CreateAndInit(ADataSet); + FDataSet.DisableControls; try - while not ADataSet.Eof do + LClassField := FDataSet.FieldByName(InstantClassFieldName); + LIdField := FDataSet.FieldByName(InstantIdFieldName); + while not FDataSet.Eof do begin LObjRef := ObjectReferenceList.Add; try - LObjRef.ReferenceObject( - ADataSet.FieldByName(InstantClassFieldName).AsString, - ADataSet.FieldByName(InstantIdFieldName).AsString); - if Assigned(LObjData) then - LObjRef.RetrieveObjectFromObjectData(LObjData); + if IsBurstLoadMode(ActualLoadMode) then + LObjRef.ReferenceObject(LClassField.AsString, LIdField.AsString, + TInstantDataSetObjectData.CreateAndInit(FDataSet)) + else + LObjRef.ReferenceObject(LClassField.AsString, LIdField.AsString); + if ActualLoadMode = lmFullBurst then + LObjRef.RetrieveObjectFromObjectData; except LObjRef.Free; raise; end; if (MaxCount > 0) and (ObjectReferenceList.Count = MaxCount) then Break; - ADataSet.Next; + FDataSet.Next; end; finally - FreeAndNil(LObjData); + FDataSet.EnableControls; end; finally - ADataSet.EnableControls; + if ActualLoadMode = lmFullBurst then + ReleaseDataSet; end; end; end; @@ -6034,6 +6010,7 @@ procedure TInstantSQLQuery.InternalClose; begin DestroyObjectReferenceList; + ReleaseDataSet; inherited; end; @@ -6072,18 +6049,18 @@ end; procedure TInstantSQLQuery.InternalOpen; -var - DataSet: TDataSet; begin inherited; - DataSet := AcquireDataSet(Statement, ParamsObject); - if Assigned(DataSet) then + ReleaseDataSet; + FDataSet := AcquireDataSet(Statement, ParamsObject); + if Assigned(FDataSet) then try - if not DataSet.Active then - DataSet.Open; - InitObjectReferences(DataSet); - finally - ReleaseDataSet(DataSet); + if not FDataSet.Active then + FDataSet.Open; + InitObjectReferences; + except + ReleaseDataSet; + raise; end; end; @@ -6106,9 +6083,13 @@ Result := ObjectReferenceList.RefItems[Index].HasInstance; end; -procedure TInstantSQLQuery.ReleaseDataSet(const DataSet: TDataSet); +procedure TInstantSQLQuery.ReleaseDataSet; begin - (Connector.Broker as TInstantSQLBroker).ReleaseDataSet(DataSet); + if Assigned(FDataSet) and Assigned(Connector) and Assigned(Connector.Broker) then + begin + (Connector.Broker as TInstantSQLBroker).ReleaseDataSet(FDataSet); + FDataSet := nil; + end; end; procedure TInstantSQLQuery.SetParams(Value: TParams); @@ -6446,7 +6427,7 @@ LTablePath := ObjectClassMetadata.TableName; end; AddTablePath(LTablePath); - if ActualLoadMode = lmFullBurst then + if IsBurstLoadMode(ActualLoadMode) then begin // Standard mode only adds the main table when needed, and not always. // A possible optimization would be to add it only if it does actually @@ -6482,7 +6463,7 @@ AddCriteria(Format('%s <> 0', [QualifyPath(ConcatPath(Specifier.Text, InstantIdFieldName))])); end; - if ActualLoadMode = lmFullBurst then + if IsBurstLoadMode(ActualLoadMode) then begin LClassMeta := ObjectClassMetadata.Parent; while Assigned(LClassMeta) do @@ -6793,6 +6774,14 @@ Assert(Assigned(ADataSet)); Create; FDataSet := ADataSet; + FRecNo := ADataSet.RecNo; + FIdField := ADataSet.FieldByName(InstantIdFieldName); end; +function TInstantDataSetObjectData.Locate(const AObjectId: string): Boolean; +begin + FDataSet.RecNo := FRecNo; + Result := FIdField.AsString = AObjectId; +end; + end. Modified: trunk/Source/Core/InstantPersistence.pas =================================================================== --- trunk/Source/Core/InstantPersistence.pas 2010-09-18 08:36:12 UTC (rev 927) +++ trunk/Source/Core/InstantPersistence.pas 2010-09-19 10:23:17 UTC (rev 928) @@ -105,6 +105,7 @@ FObjectId: string; FOwner: TInstantComplex; FOwnsInstance: Boolean; + FObjectData: TInstantAbstractObjectData; procedure ClearReference; procedure DoAssignInstance(AInstance: TInstantObject; AOwnsInstance: Boolean); function GetInstance: TInstantObject; @@ -136,13 +137,12 @@ function IsBroken: Boolean; procedure ReadAsObject(Reader: TInstantReader); virtual; procedure Reset; - procedure ReferenceObject(const AObjectClassName, - AObjectId: string); overload; - procedure ReferenceObject(AObjectClass: TInstantObjectClass; - const AObjectId: string); overload; - // Retrieves the referenced object from the specified data object. - // Used in burst load mode to get the data already fetched in a dataset. - procedure RetrieveObjectFromObjectData(const AObjectData: TInstantAbstractObjectData); + procedure ReferenceObject(const AObjectClassName, AObjectId: string; + const AObjectData: TInstantAbstractObjectData = nil); overload; + procedure ReferenceObject(const AObjectClass: TInstantObjectClass; + const AObjectId: string; const AObjectData: TInstantAbstractObjectData = nil); overload; + // Retrieves the referenced object from internal data object, if available. + procedure RetrieveObjectFromObjectData; procedure WriteAsObject(Writer: TInstantWriter); virtual; property Instance: TInstantObject read GetInstance write SetInstance; property ObjectClass: TInstantObjectClass read GetObjectClass; @@ -2036,15 +2036,18 @@ { TInstantObjectReference } procedure TInstantObjectReference.Assign(Source: TPersistent); +var + LSource: TInstantObjectReference; begin inherited; if Source is TInstantObjectReference then - with TInstantObjectReference(Source) do - begin - Self.FObjectClassName := FObjectClassName; - Self.FObjectId := FObjectId; - Self.Instance := Instance; - end; + begin + LSource := TInstantObjectReference(Source); + FObjectClassName := LSource.FObjectClassName; + FObjectId := LSource.FObjectId; + Instance := LSource.Instance; + FObjectData := LSource.FObjectData; + end; end; procedure TInstantObjectReference.AssignInstance(AInstance: TInstantObject); @@ -2056,6 +2059,7 @@ begin FObjectClassName := ''; FObjectId := ''; + FreeAndNil(FObjectData); end; constructor TInstantObjectReference.Clone(Source: TInstantObjectReference; @@ -2091,7 +2095,7 @@ InstantCheckConnector(Connector); if HasReference then begin - Obj := ObjectClass.Retrieve(ObjectId, False, False, Connector); + Obj := ObjectClass.Retrieve(ObjectId, False, False, Connector, FObjectData); DoAssignInstance(Obj, AOwnsInstance); end; if Assigned(FInstance) then @@ -2104,6 +2108,7 @@ destructor TInstantObjectReference.Destroy; begin + FreeAndNil(FObjectData); DestroyInstance; inherited; end; @@ -2224,27 +2229,36 @@ Reader.ReadStr; FObjectClassName := Reader.ReadStr; FObjectId := Reader.ReadStr; + FObjectData := nil; end; procedure TInstantObjectReference.ReferenceObject(const AObjectClassName, - AObjectId: string); + AObjectId: string; const AObjectData: TInstantAbstractObjectData = nil); begin if not Equals(AObjectClassName, AObjectId) then begin + FreeAndNil(FObjectData); DestroyInstance; FObjectClassName := AObjectClassName; FObjectId := AObjectId; + FObjectData := AObjectData; end; end; -procedure TInstantObjectReference.RetrieveObjectFromObjectData( - const AObjectData: TInstantAbstractObjectData); +procedure TInstantObjectReference.ReferenceObject( + const AObjectClass: TInstantObjectClass; const AObjectId: string; + const AObjectData: TInstantAbstractObjectData = nil); +begin + ReferenceObject(AObjectClass.ClassName, AObjectId, AObjectData); +end; + +procedure TInstantObjectReference.RetrieveObjectFromObjectData; var LObject: TInstantObject; begin - Assert(Assigned(AObjectData)); + Assert(Assigned(FObjectData)); - LObject := ObjectClass.Retrieve(ObjectId, False, False, nil, AObjectData); + LObject := ObjectClass.Retrieve(ObjectId, False, False, nil, FObjectData); DoAssignInstance(LObject, True); if Assigned(FInstance) then FInstance.Release @@ -2252,12 +2266,6 @@ Integer(FInstance) := -1; end; -procedure TInstantObjectReference.ReferenceObject( - AObjectClass: TInstantObjectClass; const AObjectId: string); -begin - ReferenceObject(AObjectClass.ClassName, AObjectId); -end; - procedure TInstantObjectReference.Reset; begin DestroyInstance; @@ -7567,6 +7575,7 @@ Result := Assigned(AObject) and AObject.Metadata.IsStored; if not Result then Exit; + CheckBroker(Broker); try AObject.DisableChanges; Modified: trunk/Source/Core/InstantTypes.pas =================================================================== --- trunk/Source/Core/InstantTypes.pas 2010-09-18 08:36:12 UTC (rev 927) +++ trunk/Source/Core/InstantTypes.pas 2010-09-19 10:23:17 UTC (rev 928) @@ -104,17 +104,25 @@ // working with a SQL broker. See documentation for implication of using the // different modes. TInstantLoadMode = ( - // Loads primary keys first, everything else on demand. + // Loads primary keys first, materializes objects on demand by loading + // all object data from the database. lmKeysFirst, - // Loads selected attributes first - not yet implemented. - // Falls back to lmKeysFirst. - //lmPartialBurst, + // Loads all simple attributes first, materializes objects on demand using + // loaded data from simple attributes and accessing the database for the + // rest. + lmPartialBurst, // Loads all simple attributes and internal containers in the first pass. - // In the future this might trigger loading of external atPart attributes - // as well. + // Materializes all objects when the cursor is open. lmFullBurst ); +function IsBurstLoadMode(const ALoadMode: TInstantLoadMode): Boolean; {$IFDEF D10+}inline;{$ENDIF} + implementation +function IsBurstLoadMode(const ALoadMode: TInstantLoadMode): Boolean; {$IFDEF D10+}inline;{$ENDIF} +begin + Result := ALoadMode in [lmPartialBurst, lmFullBurst]; +end; + end. |