1 {
2     This file is part of the Free Pascal run time library.
3     Copyright (c) 1999-2014 by Joost van der Sluis and other members of the
4     Free Pascal development team
5 
6     BufDataset implementation
7 
8     See the file COPYING.FPC, included in this distribution,
9     for details about the copyright.
10 
11     This program is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14 
15  **********************************************************************}
16 
17 unit BufDataset;
18 
19 {$mode objfpc}
20 {$h+}
21 
22 interface
23 
24 uses Classes,Sysutils,db,bufdataset_parser;
25 
26 type
27   TCustomBufDataset = Class;
28 
29   TResolverErrorEvent = procedure(Sender: TObject; DataSet: TCustomBufDataset; E: EUpdateError;
30     UpdateKind: TUpdateKind; var Response: TResolverResponse) of object;
31 
32   { TBlobBuffer }
33 
34   PBlobBuffer = ^TBlobBuffer;
35   TBlobBuffer = record
36     FieldNo : integer;
37     OrgBufID: integer;
38     Buffer  : pointer;
39     Size    : PtrInt;
40   end;
41 
42   PBufBlobField = ^TBufBlobField;
43   TBufBlobField = record
44     ConnBlobBuffer : array[0..11] of byte; // DB specific data is stored here
45     BlobBuffer     : PBlobBuffer;
46   end;
47 
48   { TBufBlobStream }
49 
50   TBufBlobStream = class(TStream)
51   private
52     FField      : TBlobField;
53     FDataSet    : TCustomBufDataset;
54     FBlobBuffer : PBlobBuffer;
55     FPosition   : PtrInt;
56     FModified   : boolean;
57   protected
Seeknull58     function Seek(Offset: Longint; Origin: Word): Longint; override;
Readnull59     function Read(var Buffer; Count: Longint): Longint; override;
Writenull60     function Write(const Buffer; Count: Longint): Longint; override;
61   public
62     constructor Create(Field: TBlobField; Mode: TBlobStreamMode);
63     destructor Destroy; override;
64   end;
65 
66 
67   PBufRecLinkItem = ^TBufRecLinkItem;
68   TBufRecLinkItem = record
69     prior   : PBufRecLinkItem;
70     next    : PBufRecLinkItem;
71   end;
72 
73   PBufBookmark = ^TBufBookmark;
74   TBufBookmark = record
75     BookmarkData : PBufRecLinkItem;
76     BookmarkInt  : integer; // was used by TArrayBufIndex
77     BookmarkFlag : TBookmarkFlag;
78   end;
79 
80   TRecUpdateBuffer = record
81     UpdateKind         : TUpdateKind;
82 {  BookMarkData:
83      - Is -1 if the update has canceled out. For example: an appended record has been deleted again
84      - If UpdateKind is ukInsert, it contains a bookmark to the newly created record
85      - If UpdateKind is ukModify, it contains a bookmark to the record with the new data
86      - If UpdateKind is ukDelete, it contains a bookmark to the deleted record (ie: the record is still there)
87 }
88     BookmarkData       : TBufBookmark;
89 {  NextBookMarkData:
90      - If UpdateKind is ukDelete, it contains a bookmark to the record just after the deleted record
91 }
92     NextBookmarkData   : TBufBookmark;
93 {  OldValuesBuffer:
94      - If UpdateKind is ukModify, it contains a record buffer which contains the old data
95      - If UpdateKind is ukDelete, it contains a record buffer with the data of the deleted record
96 }
97     OldValuesBuffer    : TRecordBuffer;
98   end;
99   TRecordsUpdateBuffer = array of TRecUpdateBuffer;
100 
ubValuenull101   TCompareFunc = function(subValue, aValue: pointer; size: integer; options: TLocateOptions): int64;
102 
103   TDBCompareRec = record
104                    CompareFunc : TCompareFunc;
105                    Off         : PtrInt;
106                    NullBOff    : PtrInt;
107                    FieldInd    : longint;
108                    Size        : integer;
109                    Options     : TLocateOptions;
110                    Desc        : Boolean;
111                   end;
112   TDBCompareStruct = array of TDBCompareRec;
113 
114   { TBufIndex }
115 
116   TBufIndex = class(TObject)
117   private
118     FDataset : TCustomBufDataset;
119   protected
GetBookmarkSizenull120     function GetBookmarkSize: integer; virtual; abstract;
GetCurrentBuffernull121     function GetCurrentBuffer: Pointer; virtual; abstract;
GetCurrentRecordnull122     function GetCurrentRecord: TRecordBuffer; virtual; abstract;
GetIsInitializednull123     function GetIsInitialized: boolean; virtual; abstract;
GetSpareBuffernull124     function GetSpareBuffer: TRecordBuffer; virtual; abstract;
GetSpareRecordnull125     function GetSpareRecord: TRecordBuffer; virtual; abstract;
GetRecNonull126     function GetRecNo: Longint; virtual; abstract;
127     procedure SetRecNo(ARecNo: Longint); virtual; abstract;
128   public
129     DBCompareStruct : TDBCompareStruct;
130     Name            : String;
131     FieldsName      : String;
132     CaseinsFields   : String;
133     DescFields      : String;
134     Options         : TIndexOptions;
135     IndNr           : integer;
136 
137     constructor Create(const ADataset : TCustomBufDataset); virtual;
ScrollBackwardnull138     function ScrollBackward : TGetResult; virtual; abstract;
ScrollForwardnull139     function ScrollForward : TGetResult;  virtual; abstract;
GetCurrentnull140     function GetCurrent : TGetResult;  virtual; abstract;
ScrollFirstnull141     function ScrollFirst : TGetResult;  virtual; abstract;
142     procedure ScrollLast; virtual; abstract;
143     // Gets prior/next record relative to given bookmark; does not change current record
GetRecordnull144     function GetRecord(ABookmark: PBufBookmark; GetMode: TGetMode): TGetResult; virtual;
145 
146     procedure SetToFirstRecord; virtual; abstract;
147     procedure SetToLastRecord; virtual; abstract;
148 
149     procedure StoreCurrentRecord;  virtual; abstract;
150     procedure RestoreCurrentRecord;  virtual; abstract;
151 
CanScrollForwardnull152     function CanScrollForward : Boolean;  virtual; abstract;
153     procedure DoScrollForward;  virtual; abstract;
154 
155     procedure StoreCurrentRecIntoBookmark(const ABookmark: PBufBookmark);  virtual; abstract;
156     procedure StoreSpareRecIntoBookmark(const ABookmark: PBufBookmark);  virtual; abstract;
157     procedure GotoBookmark(const ABookmark : PBufBookmark); virtual; abstract;
BookmarkValidnull158     function BookmarkValid(const ABookmark: PBufBookmark): boolean; virtual;
CompareBookmarksnull159     function CompareBookmarks(const ABookmark1, ABookmark2 : PBufBookmark) : integer; virtual;
SameBookmarksnull160     function SameBookmarks(const ABookmark1, ABookmark2 : PBufBookmark) : boolean; virtual;
161 
162     procedure InitialiseIndex; virtual; abstract;
163 
164     procedure InitialiseSpareRecord(const ASpareRecord : TRecordBuffer); virtual; abstract;
165     procedure ReleaseSpareRecord; virtual; abstract;
166 
167     procedure BeginUpdate; virtual; abstract;
168     // Adds a record to the end of the index as the new last record (spare record)
169     // Normally only used in GetNextPacket
170     procedure AddRecord; virtual; abstract;
171     // Inserts a record before the current record, or if the record is sorted,
172     // inserts it in the proper position
173     procedure InsertRecordBeforeCurrentRecord(Const ARecord : TRecordBuffer); virtual; abstract;
174     procedure RemoveRecordFromIndex(const ABookmark : TBufBookmark); virtual; abstract;
175     procedure OrderCurrentRecord; virtual; abstract;
176     procedure EndUpdate; virtual; abstract;
177 
178     property SpareRecord : TRecordBuffer read GetSpareRecord;
179     property SpareBuffer : TRecordBuffer read GetSpareBuffer;
180     property CurrentRecord : TRecordBuffer read GetCurrentRecord;
181     property CurrentBuffer : Pointer read GetCurrentBuffer;
182     property IsInitialized : boolean read GetIsInitialized;
183     property BookmarkSize : integer read GetBookmarkSize;
184     property RecNo : Longint read GetRecNo write SetRecNo;
185   end;
186 
187   { TDoubleLinkedBufIndex }
188 
189   TDoubleLinkedBufIndex = class(TBufIndex)
190   private
191     FCursOnFirstRec : boolean;
192 
193     FStoredRecBuf  : PBufRecLinkItem;
194     FCurrentRecBuf  : PBufRecLinkItem;
195   protected
GetBookmarkSizenull196     function GetBookmarkSize: integer; override;
GetCurrentBuffernull197     function GetCurrentBuffer: Pointer; override;
GetCurrentRecordnull198     function GetCurrentRecord: TRecordBuffer; override;
GetIsInitializednull199     function GetIsInitialized: boolean; override;
GetSpareBuffernull200     function GetSpareBuffer: TRecordBuffer; override;
GetSpareRecordnull201     function GetSpareRecord: TRecordBuffer; override;
GetRecNonull202     function GetRecNo: Longint; override;
203     procedure SetRecNo(ARecNo: Longint); override;
204   public
205     FLastRecBuf     : PBufRecLinkItem;
206     FFirstRecBuf    : PBufRecLinkItem;
207     FNeedScroll     : Boolean;
208 
ScrollBackwardnull209     function ScrollBackward : TGetResult; override;
ScrollForwardnull210     function ScrollForward : TGetResult; override;
GetCurrentnull211     function GetCurrent : TGetResult; override;
ScrollFirstnull212     function ScrollFirst : TGetResult; override;
213     procedure ScrollLast; override;
GetRecordnull214     function GetRecord(ABookmark: PBufBookmark; GetMode: TGetMode): TGetResult; override;
215 
216     procedure SetToFirstRecord; override;
217     procedure SetToLastRecord; override;
218 
219     procedure StoreCurrentRecord; override;
220     procedure RestoreCurrentRecord; override;
221 
CanScrollForwardnull222     function CanScrollForward : Boolean; override;
223     procedure DoScrollForward; override;
224 
225     procedure StoreCurrentRecIntoBookmark(const ABookmark: PBufBookmark); override;
226     procedure StoreSpareRecIntoBookmark(const ABookmark: PBufBookmark); override;
227     procedure GotoBookmark(const ABookmark : PBufBookmark); override;
CompareBookmarksnull228     function CompareBookmarks(const ABookmark1, ABookmark2: PBufBookmark): integer; override;
SameBookmarksnull229     function SameBookmarks(const ABookmark1, ABookmark2 : PBufBookmark) : boolean; override;
230     procedure InitialiseIndex; override;
231 
232     procedure InitialiseSpareRecord(const ASpareRecord : TRecordBuffer); override;
233     procedure ReleaseSpareRecord; override;
234 
235     procedure BeginUpdate; override;
236     procedure AddRecord; override;
237     procedure InsertRecordBeforeCurrentRecord(Const ARecord : TRecordBuffer); override;
238     procedure RemoveRecordFromIndex(const ABookmark : TBufBookmark); override;
239     procedure OrderCurrentRecord; override;
240     procedure EndUpdate; override;
241   end;
242 
243   { TUniDirectionalBufIndex }
244 
245   TUniDirectionalBufIndex = class(TBufIndex)
246   private
247     FSPareBuffer:  TRecordBuffer;
248   protected
GetBookmarkSizenull249     function GetBookmarkSize: integer; override;
GetCurrentBuffernull250     function GetCurrentBuffer: Pointer; override;
GetCurrentRecordnull251     function GetCurrentRecord: TRecordBuffer; override;
GetIsInitializednull252     function GetIsInitialized: boolean; override;
GetSpareBuffernull253     function GetSpareBuffer: TRecordBuffer; override;
GetSpareRecordnull254     function GetSpareRecord: TRecordBuffer; override;
GetRecNonull255     function GetRecNo: Longint; override;
256     procedure SetRecNo(ARecNo: Longint); override;
257   public
ScrollBackwardnull258     function ScrollBackward : TGetResult; override;
ScrollForwardnull259     function ScrollForward : TGetResult; override;
GetCurrentnull260     function GetCurrent : TGetResult; override;
ScrollFirstnull261     function ScrollFirst : TGetResult; override;
262     procedure ScrollLast; override;
263 
264     procedure SetToFirstRecord; override;
265     procedure SetToLastRecord; override;
266 
267     procedure StoreCurrentRecord; override;
268     procedure RestoreCurrentRecord; override;
269 
CanScrollForwardnull270     function CanScrollForward : Boolean; override;
271     procedure DoScrollForward; override;
272 
273     procedure StoreCurrentRecIntoBookmark(const ABookmark: PBufBookmark); override;
274     procedure StoreSpareRecIntoBookmark(const ABookmark: PBufBookmark); override;
275     procedure GotoBookmark(const ABookmark : PBufBookmark); override;
276 
277     procedure InitialiseIndex; override;
278     procedure InitialiseSpareRecord(const ASpareRecord : TRecordBuffer); override;
279     procedure ReleaseSpareRecord; override;
280 
281     procedure BeginUpdate; override;
282     procedure AddRecord; override;
283     procedure InsertRecordBeforeCurrentRecord(Const ARecord : TRecordBuffer); override;
284     procedure RemoveRecordFromIndex(const ABookmark : TBufBookmark); override;
285     procedure OrderCurrentRecord; override;
286     procedure EndUpdate; override;
287   end;
288 
289 
290   { TArrayBufIndex }
291 
292   TArrayBufIndex = class(TBufIndex)
293   private
294     FStoredRecBuf  : integer;
295 
296     FInitialBuffers,
297     FGrowBuffer     : integer;
GetRecordFromBookmarknull298     Function GetRecordFromBookmark(ABookmark: TBufBookmark) : integer;
299   protected
GetBookmarkSizenull300     function GetBookmarkSize: integer; override;
GetCurrentBuffernull301     function GetCurrentBuffer: Pointer; override;
GetCurrentRecordnull302     function GetCurrentRecord: TRecordBuffer; override;
GetIsInitializednull303     function GetIsInitialized: boolean; override;
GetSpareBuffernull304     function GetSpareBuffer: TRecordBuffer; override;
GetSpareRecordnull305     function GetSpareRecord: TRecordBuffer; override;
GetRecNonull306     function GetRecNo: Longint; override;
307     procedure SetRecNo(ARecNo: Longint); override;
308   public
309     FRecordArray    : array of Pointer;
310     FCurrentRecInd  : integer;
311     FLastRecInd     : integer;
312     FNeedScroll     : Boolean;
313     constructor Create(const ADataset: TCustomBufDataset); override;
ScrollBackwardnull314     function ScrollBackward : TGetResult; override;
ScrollForwardnull315     function ScrollForward : TGetResult; override;
GetCurrentnull316     function GetCurrent : TGetResult; override;
ScrollFirstnull317     function ScrollFirst : TGetResult; override;
318     procedure ScrollLast; override;
319 
320     procedure SetToFirstRecord; override;
321     procedure SetToLastRecord; override;
322 
323     procedure StoreCurrentRecord; override;
324     procedure RestoreCurrentRecord; override;
325 
CanScrollForwardnull326     function CanScrollForward : Boolean; override;
327     procedure DoScrollForward; override;
328 
329     procedure StoreCurrentRecIntoBookmark(const ABookmark: PBufBookmark); override;
330     procedure StoreSpareRecIntoBookmark(const ABookmark: PBufBookmark); override;
331     procedure GotoBookmark(const ABookmark : PBufBookmark); override;
332 
333     procedure InitialiseIndex; override;
334 
335     procedure InitialiseSpareRecord(const ASpareRecord : TRecordBuffer); override;
336     procedure ReleaseSpareRecord; override;
337 
338     procedure BeginUpdate; override;
339     procedure AddRecord; override;
340     procedure InsertRecordBeforeCurrentRecord(Const ARecord : TRecordBuffer); override;
341     procedure RemoveRecordFromIndex(const ABookmark : TBufBookmark); override;
342     procedure EndUpdate; override;
343   end;
344 
345 
346   { TBufDatasetReader }
347 
348 type
349   TRowStateValue = (rsvOriginal, rsvDeleted, rsvInserted, rsvUpdated, rsvDetailUpdates);
350   TRowState = set of TRowStateValue;
351 
352 type
353 
354   { TDataPacketReader }
355 
356   TDataPacketFormat = (dfBinary,dfXML,dfXMLUTF8,dfAny,dfDefault);
357 
358   TDatapacketReaderClass = class of TDatapacketReader;
359   TDataPacketReader = class(TObject)
360     FDataSet: TCustomBufDataset;
361     FStream : TStream;
362   protected
RowStateToBytenull363     class function RowStateToByte(const ARowState : TRowState) : byte;
ByteToRowStatenull364     class function ByteToRowState(const AByte : Byte) : TRowState;
365     procedure RestoreBlobField(AField: TField; ASource: pointer; ASize: integer);
366     property DataSet: TCustomBufDataset read FDataSet;
367     property Stream: TStream read FStream;
368   public
369     constructor Create(ADataSet: TCustomBufDataset; AStream : TStream); virtual;
370     // Load a dataset from stream:
371     // Load the field definitions from a stream.
372     procedure LoadFieldDefs(var AnAutoIncValue : integer); virtual; abstract;
373     // Is called before the records are loaded
374     procedure InitLoadRecords; virtual; abstract;
375     // Returns if there is at least one more record available in the stream
GetCurrentRecordnull376     function GetCurrentRecord : boolean; virtual; abstract;
377     // Return the RowState of the current record, and the order of the update
GetRecordRowStatenull378     function GetRecordRowState(out AUpdOrder : Integer) : TRowState; virtual; abstract;
379     // Store a record from stream in the current record buffer
380     procedure RestoreRecord; virtual; abstract;
381     // Move the stream to the next record
382     procedure GotoNextRecord; virtual; abstract;
383 
384     // Store a dataset to stream:
385     // Save the field definitions to a stream.
386     procedure StoreFieldDefs(AnAutoIncValue : integer); virtual; abstract;
387     // Save a record from the current record buffer to the stream
388     procedure StoreRecord(ARowState : TRowState; AUpdOrder : integer = 0); virtual; abstract;
389     // Is called after all records are stored
390     procedure FinalizeStoreRecords; virtual; abstract;
391     // Checks if the provided stream is of the right format for this class
RecognizeStreamnull392     class function RecognizeStream(AStream : TStream) : boolean; virtual; abstract;
393   end;
394 
395   { TFpcBinaryDatapacketReader }
396 
397   { Data layout:
398      Header section:
399        Identification: 13 bytes: 'BinBufDataSet'
400        Version: 1 byte
401      Columns section:
402        Number of Fields: 2 bytes
403        For each FieldDef: Name, DisplayName, Size: 2 bytes, DataType: 2 bytes, ReadOnlyAttr: 1 byte
404      Parameter section:
405        AutoInc Value: 4 bytes
406      Rows section:
407        Row header: each row begins with $fe: 1 byte
408                    row state: 1 byte (original, deleted, inserted, modified)
409                    update order: 4 bytes
410                    null bitmap: 1 byte per each 8 fields (if field is null corresponding bit is 1)
411        Row data: variable length data are prefixed with 4 byte length indicator
412                  null fields are not stored (see: null bitmap)
413   }
414 
415   TFpcBinaryDatapacketReader = class(TDataPacketReader)
416   private
417     const
418       FpcBinaryIdent1 = 'BinBufDataset'; // Old version 1; support for transient period;
419       FpcBinaryIdent2 = 'BinBufDataSet';
420       StringFieldTypes = [ftString,ftFixedChar,ftWideString,ftFixedWideChar];
421       BlobFieldTypes = [ftBlob,ftMemo,ftGraphic,ftWideMemo];
422       VarLenFieldTypes = StringFieldTypes + BlobFieldTypes + [ftBytes,ftVarBytes];
423     var
424       FNullBitmapSize: integer;
425       FNullBitmap: TBytes;
426   protected
427     var
428       FVersion: byte;
429   public
430     constructor Create(ADataSet: TCustomBufDataset; AStream : TStream); override;
431     procedure LoadFieldDefs(var AnAutoIncValue : integer); override;
432     procedure StoreFieldDefs(AnAutoIncValue : integer); override;
433     procedure InitLoadRecords; override;
GetCurrentRecordnull434     function GetCurrentRecord : boolean; override;
GetRecordRowStatenull435     function GetRecordRowState(out AUpdOrder : Integer) : TRowState; override;
436     procedure RestoreRecord; override;
437     procedure GotoNextRecord; override;
438     procedure StoreRecord(ARowState : TRowState; AUpdOrder : integer = 0); override;
439     procedure FinalizeStoreRecords; override;
RecognizeStreamnull440     class function RecognizeStream(AStream : TStream) : boolean; override;
441   end;
442 
443   { TCustomBufDataset }
444 
445   TCustomBufDataset = class(TDBDataSet)
446   Private
447     Type
448 
449       { TBufDatasetIndex }
450       TIndexType = (itNormal,itDefault,itCustom);
451       TBufDatasetIndex = Class(TIndexDef)
452       private
453         FBufferIndex: TBufIndex;
454         FDiscardOnClose: Boolean;
455         FIndexType : TIndexType;
456       Public
457         Destructor Destroy; override;
458         // Free FBufferIndex;
459         Procedure Clearindex;
460         // Set TIndexDef properties on FBufferIndex;
461         Procedure SetIndexProperties;
462         // Return true if the buffer must be built.
463         // Default buffer must not be built, custom only when it is not the current.
MustBuildnull464         Function MustBuild(aCurrent : TBufDatasetIndex) : Boolean;
465         // Return true if the buffer must be updated
466         // This are all indexes except custom, unless it is the active index
IsActiveIndexnull467         Function IsActiveIndex(aCurrent : TBufDatasetIndex) : Boolean;
468         // The actual buffer.
469         Property BufferIndex : TBufIndex Read FBufferIndex Write FBufferIndex;
470         // If the Index is created after Open, then it will be discarded on close.
471         Property DiscardOnClose : Boolean Read FDiscardOnClose;
472         // Skip build of this index
473         Property IndexType : TIndexType Read FIndexType Write FIndexType;
474       end;
475 
476       { TBufDatasetIndexDefs }
477       TBufDatasetIndexDefs = Class(TIndexDefs)
478       private
GetBufDatasetIndexnull479         function GetBufDatasetIndex(AIndex : Integer): TBufDatasetIndex;
GetBufferIndexnull480         function GetBufferIndex(AIndex : Integer): TBufIndex;
481       Public
482         Constructor Create(aDataset : TDataset); override;
483         // Does not raise an exception if not found.
FindIndexnull484         function FindIndex(const IndexName: string): TBufDatasetIndex;
485         Property BufIndexdefs [AIndex : Integer] : TBufDatasetIndex Read GetBufDatasetIndex;
486         Property BufIndexes [AIndex : Integer] : TBufIndex Read GetBufferIndex;
487       end;
488 
489     procedure BuildCustomIndex;
GetBufIndexnull490     function GetBufIndex(Aindex : Integer): TBufIndex;
GetBufIndexDefnull491     function GetBufIndexDef(Aindex : Integer): TBufDatasetIndex;
GetCurrentIndexBufnull492     function GetCurrentIndexBuf: TBufIndex;
493     procedure InitUserIndexes;
494   private
495     FFileName: TFileName;
496     FReadFromFile   : boolean;
497     FFileStream     : TFileStream;
498     FDatasetReader  : TDataPacketReader;
499     FMaxIndexesCount: integer;
500     FDefaultIndex,
501     FCurrentIndexDef : TBufDatasetIndex;
502     FFilterBuffer   : TRecordBuffer;
503     FBRecordCount   : integer;
504     FReadOnly       : Boolean;
505     FSavedState     : TDatasetState;
506     FPacketRecords  : integer;
507     FRecordSize     : Integer;
508     FIndexFieldNames : String;
509     FIndexName      : String;
510     FNullmaskSize   : byte;
511     FOpen           : Boolean;
512     FUpdateBuffer   : TRecordsUpdateBuffer;
513     FCurrentUpdateBuffer : integer;
514     FAutoIncValue   : longint;
515     FAutoIncField   : TAutoIncField;
516     FIndexes        : TBufDataSetIndexDefs;
517     FParser         : TBufDatasetParser;
518     FFieldBufPositions : array of longint;
519     FAllPacketsFetched : boolean;
520     FOnUpdateError  : TResolverErrorEvent;
521 
522     FBlobBuffers      : array of PBlobBuffer;
523     FUpdateBlobBuffers: array of PBlobBuffer;
524     FManualMergeChangeLog : Boolean;
525     FRefreshing : Boolean;
526 
527     procedure ProcessFieldsToCompareStruct(const AFields, ADescFields, ACInsFields: TList;
528       const AIndexOptions: TIndexOptions; const ALocateOptions: TLocateOptions; out ACompareStruct: TDBCompareStruct);
BufferOffsetnull529     function BufferOffset: integer;
GetFieldSizenull530     function GetFieldSize(FieldDef : TFieldDef) : longint;
531     procedure CalcRecordSize;
IntAllocRecordBuffernull532     function  IntAllocRecordBuffer: TRecordBuffer;
533     procedure IntLoadFieldDefsFromFile;
534     procedure IntLoadRecordsFromFile;
GetCurrentBuffernull535     function  GetCurrentBuffer: TRecordBuffer;
536     procedure CurrentRecordToBuffer(Buffer: TRecordBuffer);
LoadBuffernull537     function LoadBuffer(Buffer : TRecordBuffer): TGetResult;
538     procedure FetchAll;
GetRecordUpdateBuffernull539     function GetRecordUpdateBuffer(const ABookmark : TBufBookmark; IncludePrior : boolean = false; AFindNext : boolean = false) : boolean;
GetRecordUpdateBufferCachednull540     function GetRecordUpdateBufferCached(const ABookmark : TBufBookmark; IncludePrior : boolean = false) : boolean;
GetActiveRecordUpdateBuffernull541     function GetActiveRecordUpdateBuffer : boolean;
542     procedure CancelRecordUpdateBuffer(AUpdateBufferIndex: integer; var ABookmark: TBufBookmark);
543     procedure ParseFilter(const AFilter: string);
544 
GetBufUniDirectionalnull545     function GetBufUniDirectional: boolean;
546     // indexes handling
GetIndexDefsnull547     function GetIndexDefs : TIndexDefs;
GetIndexFieldNamesnull548     function GetIndexFieldNames: String;
GetIndexNamenull549     function GetIndexName: String;
550     procedure SetIndexFieldNames(const AValue: String);
551     procedure SetIndexName(AValue: String);
552     procedure SetMaxIndexesCount(const AValue: Integer);
553     procedure SetBufUniDirectional(const AValue: boolean);
DefaultIndexnull554     Function DefaultIndex : TBufDatasetIndex;
DefaultBufferIndexnull555     Function DefaultBufferIndex : TBufIndex;
556     procedure InitDefaultIndexes;
557     procedure BuildIndex(AIndex : TBufIndex);
558     procedure BuildIndexes;
559     procedure RemoveRecordFromIndexes(const ABookmark : TBufBookmark);
560     procedure InternalCreateIndex(F: TBufDataSetIndex); virtual;
561     Property CurrentIndexBuf : TBufIndex Read GetCurrentIndexBuf;
562     Property CurrentIndexDef : TBufDatasetIndex Read FCurrentIndexDef;
563     Property BufIndexDefs[Aindex : Integer] : TBufDatasetIndex Read GetBufIndexDef;
564     Property BufIndexes[Aindex : Integer] : TBufIndex Read GetBufIndex;
565   protected
566     // abstract & virtual methods of TDataset
DefaultReadFileFormatnull567     class function DefaultReadFileFormat : TDataPacketFormat; virtual;
DefaultWriteFileFormatnull568     class function DefaultWriteFileFormat : TDataPacketFormat; virtual;
DefaultPacketClassnull569     class function DefaultPacketClass : TDataPacketReaderClass ; virtual;
CreateDefaultPacketReadernull570     function CreateDefaultPacketReader(aStream : TStream): TDataPacketReader ; virtual;
571     procedure SetPacketRecords(aValue : integer); virtual;
572     procedure SetRecNo(Value: Longint); override;
GetRecNonull573     function  GetRecNo: Longint; override;
GetChangeCountnull574     function GetChangeCount: integer; virtual;
AllocRecordBuffernull575     function  AllocRecordBuffer: TRecordBuffer; override;
576     procedure FreeRecordBuffer(var Buffer: TRecordBuffer); override;
577     procedure ClearCalcFields(Buffer: TRecordBuffer); override;
578     procedure InternalInitRecord(Buffer: TRecordBuffer); override;
GetCanModifynull579     function  GetCanModify: Boolean; override;
GetRecordnull580     function GetRecord(Buffer: TRecordBuffer; GetMode: TGetMode; DoCheck: Boolean): TGetResult; override;
581     procedure DoBeforeClose; override;
582     procedure InternalInitFieldDefs; override;
583     procedure InternalOpen; override;
584     procedure InternalClose; override;
GetRecordSizenull585     function GetRecordSize: Word; override;
586     procedure InternalPost; override;
587     procedure InternalCancel; Override;
588     procedure InternalDelete; override;
589     procedure InternalFirst; override;
590     procedure InternalLast; override;
591     procedure InternalSetToRecord(Buffer: TRecordBuffer); override;
592     procedure InternalGotoBookmark(ABookmark: Pointer); override;
593     procedure SetBookmarkData(Buffer: TRecordBuffer; Data: Pointer); override;
594     procedure SetBookmarkFlag(Buffer: TRecordBuffer; Value: TBookmarkFlag); override;
595     procedure GetBookmarkData(Buffer: TRecordBuffer; Data: Pointer); override;
GetBookmarkFlagnull596     function GetBookmarkFlag(Buffer: TRecordBuffer): TBookmarkFlag; override;
IsCursorOpennull597     function IsCursorOpen: Boolean; override;
GetRecordCountnull598     function  GetRecordCount: Longint; override;
599     procedure ApplyRecUpdate(UpdateKind : TUpdateKind); virtual;
600     procedure SetOnUpdateError(const AValue: TResolverErrorEvent);
601     procedure SetFilterText(const Value: String); override; {virtual;}
602     procedure SetFiltered(Value: Boolean); override; {virtual;}
603     procedure InternalRefresh; override;
604     procedure DataEvent(Event: TDataEvent; Info: PtrInt); override;
605     // virtual or methods, which can be used by descendants
GetNewBlobBuffernull606     function GetNewBlobBuffer : PBlobBuffer;
GetNewWriteBlobBuffernull607     function GetNewWriteBlobBuffer : PBlobBuffer;
608     procedure FreeBlobBuffer(var ABlobBuffer: PBlobBuffer);
InternalAddIndexnull609     Function InternalAddIndex(const AName, AFields : string; AOptions : TIndexOptions; const ADescFields: string;
610       const ACaseInsFields: string) : TBufDatasetIndex; virtual;
611     procedure BeforeRefreshOpenCursor; virtual;
612     procedure DoFilterRecord(out Acceptable: Boolean); virtual;
613     procedure SetReadOnly(AValue: Boolean); virtual;
IsReadFromPacketnull614     function IsReadFromPacket : Boolean;
getnextpacketnull615     function getnextpacket : integer;
GetPacketReadernull616     function GetPacketReader(const Format: TDataPacketFormat; const AStream: TStream): TDataPacketReader; virtual;
617     // abstracts, must be overidden by descendents
Fetchnull618     function Fetch : boolean; virtual;
LoadFieldnull619     function LoadField(FieldDef : TFieldDef;buffer : pointer; out CreateBlob : boolean) : boolean; virtual;
620     procedure LoadBlobIntoBuffer(FieldDef: TFieldDef;ABlobBuf: PBufBlobField); virtual; abstract;
DoLocatenull621     function DoLocate(const KeyFields: string; const KeyValues: Variant; Options: TLocateOptions; DoEvents : Boolean) : boolean;
622     Property Refreshing : Boolean Read FRefreshing;
623   public
624     constructor Create(AOwner: TComponent); override;
GetFieldDatanull625     function GetFieldData(Field: TField; Buffer: Pointer;
626       NativeFormat: Boolean): Boolean; override;
GetFieldDatanull627     function GetFieldData(Field: TField; Buffer: Pointer): Boolean; override;
628     procedure SetFieldData(Field: TField; Buffer: Pointer;
629       NativeFormat: Boolean); override;
630     procedure SetFieldData(Field: TField; Buffer: Pointer); override;
631     procedure ApplyUpdates; virtual; overload;
632     procedure ApplyUpdates(MaxErrors: Integer); virtual; overload;
633     procedure MergeChangeLog;
634     procedure RevertRecord;
635     procedure CancelUpdates; virtual;
636     destructor Destroy; override;
Locatenull637     function Locate(const KeyFields: string; const KeyValues: Variant; Options: TLocateOptions) : boolean; override;
Lookupnull638     function Lookup(const KeyFields: string; const KeyValues: Variant; const ResultFields: string): Variant; override;
UpdateStatusnull639     function UpdateStatus: TUpdateStatus; override;
CreateBlobStreamnull640     function CreateBlobStream(Field: TField; Mode: TBlobStreamMode): TStream; override;
641     procedure AddIndex(const AName, AFields : string; AOptions : TIndexOptions; const ADescFields: string = '';
642       const ACaseInsFields: string = ''); virtual;
643     procedure ClearIndexes;
644 
645     procedure SetDatasetPacket(AReader : TDataPacketReader);
646     procedure GetDatasetPacket(AWriter : TDataPacketReader);
647     procedure LoadFromStream(AStream : TStream; Format: TDataPacketFormat = dfDefault);
648     procedure SaveToStream(AStream : TStream; Format: TDataPacketFormat = dfBinary);
649     procedure LoadFromFile(AFileName: string = ''; Format: TDataPacketFormat = dfDefault);
650     procedure SaveToFile(AFileName: string = ''; Format: TDataPacketFormat = dfBinary);
651     procedure CreateDataset;
652     Procedure Clear; // Will close and remove all field definitions.
BookmarkValidnull653     function BookmarkValid(ABookmark: TBookmark): Boolean; override;
CompareBookmarksnull654     function CompareBookmarks(Bookmark1, Bookmark2: TBookmark): Longint; override;
655     Procedure CopyFromDataset(DataSet : TDataSet;CopyData : Boolean=True);
656     property ChangeCount : Integer read GetChangeCount;
657     property MaxIndexesCount : Integer read FMaxIndexesCount write SetMaxIndexesCount default 2;
658     property ReadOnly : Boolean read FReadOnly write SetReadOnly default false;
659     property ManualMergeChangeLog : Boolean read FManualMergeChangeLog write FManualMergeChangeLog default False;
660   published
661     property FileName : TFileName read FFileName write FFileName;
662     property PacketRecords : Integer read FPacketRecords write SetPacketRecords default 10;
663     property OnUpdateError: TResolverErrorEvent read FOnUpdateError write SetOnUpdateError;
664     property IndexDefs : TIndexDefs read GetIndexDefs;
665     property IndexName : String read GetIndexName write SetIndexName;
666     property IndexFieldNames : String read GetIndexFieldNames write SetIndexFieldNames;
667     property UniDirectional: boolean read GetBufUniDirectional write SetBufUniDirectional default False;
668   end;
669 
670   TBufDataset = class(TCustomBufDataset)
671   published
672     property MaxIndexesCount;
673     // TDataset stuff
674     property FieldDefs;
675     Property Active;
676     Property AutoCalcFields;
677     Property Filter;
678     Property Filtered;
679     Property ReadOnly;
680     Property AfterCancel;
681     Property AfterClose;
682     Property AfterDelete;
683     Property AfterEdit;
684     Property AfterInsert;
685     Property AfterOpen;
686     Property AfterPost;
687     Property AfterScroll;
688     Property BeforeCancel;
689     Property BeforeClose;
690     Property BeforeDelete;
691     Property BeforeEdit;
692     Property BeforeInsert;
693     Property BeforeOpen;
694     Property BeforePost;
695     Property BeforeScroll;
696     Property OnCalcFields;
697     Property OnDeleteError;
698     Property OnEditError;
699     Property OnFilterRecord;
700     Property OnNewRecord;
701     Property OnPostError;
702   end;
703 
704 
705 procedure RegisterDatapacketReader(ADatapacketReaderClass : TDatapacketReaderClass; AFormat : TDataPacketFormat);
706 
707 implementation
708 
709 uses variants, dbconst, FmtBCD, strutils;
710 
711 Const
712   SDefaultIndex = 'DEFAULT_ORDER';
713   SCustomIndex = 'CUSTOM_ORDER';
714   Desc=' DESC';     //leading space is important
715   LenDesc : integer = Length(Desc);
716   Limiter=';';
717 
718 Type
719   TDatapacketReaderRegistration = record
720     ReaderClass : TDatapacketReaderClass;
721     Format      : TDataPacketFormat;
722   end;
723 
724 var
725   RegisteredDatapacketReaders : Array of TDatapacketReaderRegistration;
726 
727 procedure RegisterDatapacketReader(ADatapacketReaderClass : TDatapacketReaderClass; AFormat : TDataPacketFormat);
728 
729 begin
730   setlength(RegisteredDatapacketReaders,length(RegisteredDatapacketReaders)+1);
731   with RegisteredDatapacketReaders[length(RegisteredDatapacketReaders)-1] do
732     begin
733     Readerclass := ADatapacketReaderClass;
734     Format      := AFormat;
735     end;
736 end;
737 
GetRegisterDatapacketReadernull738 function GetRegisterDatapacketReader(AStream : TStream; AFormat : TDataPacketFormat; out ADataReaderClass : TDatapacketReaderRegistration) : boolean;
739 
740 var
741   i : integer;
742 
743 begin
744   Result := False;
745   for i := 0 to length(RegisteredDatapacketReaders)-1 do
746     if ((AFormat=dfAny) or (AFormat=RegisteredDatapacketReaders[i].Format)) then
747       begin
748       if (AStream=nil) or (RegisteredDatapacketReaders[i].ReaderClass.RecognizeStream(AStream)) then
749         begin
750         ADataReaderClass := RegisteredDatapacketReaders[i];
751         Result := True;
752         if (AStream <> nil) then AStream.Seek(0,soFromBeginning);
753         break;
754         end;
755       AStream.Seek(0,soFromBeginning);
756       end;
757 end;
758 
DBCompareTextnull759 function DBCompareText(subValue, aValue: pointer; size: integer; options: TLocateOptions): LargeInt;
760 
761 begin
762   if [loCaseInsensitive,loPartialKey]=options then
763     Result := AnsiStrLIComp(pchar(subValue),pchar(aValue),length(pchar(subValue)))
764   else if [loPartialKey] = options then
765     Result := AnsiStrLComp(pchar(subValue),pchar(aValue),length(pchar(subValue)))
766   else if [loCaseInsensitive] = options then
767     Result := AnsiCompareText(pchar(subValue),pchar(aValue))
768   else
769     Result := AnsiCompareStr(pchar(subValue),pchar(aValue));
770 end;
771 
DBCompareWideTextnull772 function DBCompareWideText(subValue, aValue: pointer; size: integer; options: TLocateOptions): LargeInt;
773 
774 begin
775   if [loCaseInsensitive,loPartialKey]=options then
776     Result := WideCompareText(pwidechar(subValue),LeftStr(pwidechar(aValue), Length(pwidechar(subValue))))
777   else if [loPartialKey] = options then
778       Result := WideCompareStr(pwidechar(subValue),LeftStr(pwidechar(aValue), Length(pwidechar(subValue))))
779     else if [loCaseInsensitive] = options then
780          Result := WideCompareText(pwidechar(subValue),pwidechar(aValue))
781        else
782          Result := WideCompareStr(pwidechar(subValue),pwidechar(aValue));
783 end;
784 
DBCompareBytenull785 function DBCompareByte(subValue, aValue: pointer; size: integer; options: TLocateOptions): LargeInt;
786 
787 begin
788   Result := PByte(subValue)^-PByte(aValue)^;
789 end;
790 
DBCompareSmallIntnull791 function DBCompareSmallInt(subValue, aValue: pointer; size: integer; options: TLocateOptions): LargeInt;
792 
793 begin
794   Result := PSmallInt(subValue)^-PSmallInt(aValue)^;
795 end;
796 
DBCompareIntnull797 function DBCompareInt(subValue, aValue: pointer; size: integer; options: TLocateOptions): LargeInt;
798 
799 begin
800   Result := PInteger(subValue)^-PInteger(aValue)^;
801 end;
802 
DBCompareLargeIntnull803 function DBCompareLargeInt(subValue, aValue: pointer; size: integer; options: TLocateOptions): LargeInt;
804 
805 begin
806   // A simple subtraction doesn't work, since it could be that the result
807   // doesn't fit into a LargeInt
808   if PLargeInt(subValue)^ < PLargeInt(aValue)^ then
809     result := -1
810   else if PLargeInt(subValue)^  > PLargeInt(aValue)^ then
811     result := 1
812   else
813     result := 0;
814 end;
815 
DBCompareWordnull816 function DBCompareWord(subValue, aValue: pointer; size: integer; options: TLocateOptions): LargeInt;
817 
818 begin
819   Result := PWord(subValue)^-PWord(aValue)^;
820 end;
821 
DBCompareQWordnull822 function DBCompareQWord(subValue, aValue: pointer; size: integer; options: TLocateOptions): LargeInt;
823 
824 begin
825   // A simple subtraction doesn't work, since it could be that the result
826   // doesn't fit into a LargeInt
827   if PQWord(subValue)^ < PQWord(aValue)^ then
828     result := -1
829   else if PQWord(subValue)^  > PQWord(aValue)^ then
830     result := 1
831   else
832     result := 0;
833 end;
834 
DBCompareDoublenull835 function DBCompareDouble(subValue, aValue: pointer; size: integer; options: TLocateOptions): LargeInt;
836 begin
837   // A simple subtraction doesn't work, since it could be that the result
838   // doesn't fit into a LargeInt
839   if PDouble(subValue)^ < PDouble(aValue)^ then
840     result := -1
841   else if PDouble(subValue)^  > PDouble(aValue)^ then
842     result := 1
843   else
844     result := 0;
845 end;
846 
DBCompareBCDnull847 function DBCompareBCD(subValue, aValue: pointer; size: integer; options: TLocateOptions): LargeInt;
848 begin
849   result:=BCDCompare(PBCD(subValue)^, PBCD(aValue)^);
850 end;
851 
DBCompareBytesnull852 function DBCompareBytes(subValue, aValue: pointer; size: integer; options: TLocateOptions): LargeInt;
853 begin
854   Result := CompareByte(subValue^, aValue^, size);
855 end;
856 
DBCompareVarBytesnull857 function DBCompareVarBytes(subValue, aValue: pointer; size: integer; options: TLocateOptions): LargeInt;
858 var len1, len2: LongInt;
859 begin
860   len1 := PWord(subValue)^;
861   len2 := PWord(aValue)^;
862   inc(subValue, sizeof(Word));
863   inc(aValue, sizeof(Word));
864   if len1 > len2 then
865     Result := CompareByte(subValue^, aValue^, len2)
866   else
867     Result := CompareByte(subValue^, aValue^, len1);
868   if Result = 0 then
869     Result := len1 - len2;
870 end;
871 
872 procedure unSetFieldIsNull(NullMask : pbyte;x : longint); //inline;
873 begin
874   NullMask[x div 8] := (NullMask[x div 8]) and not (1 shl (x mod 8));
875 end;
876 
877 procedure SetFieldIsNull(NullMask : pbyte;x : longint); //inline;
878 begin
879   NullMask[x div 8] := (NullMask[x div 8]) or (1 shl (x mod 8));
880 end;
881 
GetFieldIsNullnull882 function GetFieldIsNull(NullMask : pbyte;x : longint) : boolean; //inline;
883 begin
884   result := ord(NullMask[x div 8]) and (1 shl (x mod 8)) > 0
885 end;
886 
IndexCompareRecordsnull887 function IndexCompareRecords(Rec1,Rec2 : pointer; ADBCompareRecs : TDBCompareStruct) : LargeInt;
888 var IndexFieldNr : Integer;
889     IsNull1, IsNull2 : boolean;
890 begin
891   for IndexFieldNr:=0 to length(ADBCompareRecs)-1 do with ADBCompareRecs[IndexFieldNr] do
892     begin
893     IsNull1:=GetFieldIsNull(rec1+NullBOff,FieldInd);
894     IsNull2:=GetFieldIsNull(rec2+NullBOff,FieldInd);
895     if IsNull1 and IsNull2 then
896       Result := 0
897     else if IsNull1 then
898       Result := -1
899     else if IsNull2 then
900       Result := 1
901     else
902       Result := CompareFunc(Rec1+Off, Rec2+Off, Size, Options);
903 
904     if Result <> 0 then
905       begin
906       if Desc then
907         Result := -Result;
908       break;
909       end;
910     end;
911 end;
912 
913 { TCustomBufDataset.TBufDatasetIndex }
914 
915 destructor TCustomBufDataset.TBufDatasetIndex.Destroy;
916 begin
917   ClearIndex;
918   inherited Destroy;
919 end;
920 
921 procedure TCustomBufDataset.TBufDatasetIndex.Clearindex;
922 begin
923   FreeAndNil(FBufferIndex);
924 end;
925 
926 procedure TCustomBufDataset.TBufDatasetIndex.SetIndexProperties;
927 begin
928   If not Assigned(FBufferIndex) then
929     exit;
930   FBufferIndex.IndNr:=Index;
931   FBufferIndex.Name:=Name;
932   FBufferIndex.FieldsName:=Fields;
933   FBufferIndex.DescFields:=DescFields;
934   FBufferIndex.CaseinsFields:=CaseInsFields;
935   FBufferIndex.Options:=Options;
936 end;
937 
TCustomBufDataset.TBufDatasetIndex.MustBuildnull938 function TCustomBufDataset.TBufDatasetIndex.MustBuild(aCurrent: TBufDatasetIndex): Boolean;
939 begin
940   Result:=(FIndexType<>itDefault) and IsActiveIndex(aCurrent);
941 end;
942 
TCustomBufDataset.TBufDatasetIndex.IsActiveIndexnull943 function TCustomBufDataset.TBufDatasetIndex.IsActiveIndex(aCurrent: TBufDatasetIndex): Boolean;
944 begin
945   Result:=(FIndexType<>itCustom) or (Self=aCurrent);
946 end;
947 
948 
949 { TCustomBufDataset.TBufDatasetIndexDefs }
950 
TBufDatasetIndexDefsnull951 function TCustomBufDataset.TBufDatasetIndexDefs.GetBufDatasetIndex(AIndex : Integer): TBufDatasetIndex;
952 begin
953   Result:=Items[Aindex] as TBufDatasetIndex;
954 end;
955 
TBufDatasetIndexDefsnull956 function TCustomBufDataset.TBufDatasetIndexDefs.GetBufferIndex(AIndex : Integer): TBufIndex;
957 begin
958   Result:=BufIndexdefs[AIndex].BufferIndex;
959 end;
960 
961 constructor TCustomBufDataset.TBufDatasetIndexDefs.Create(aDataset: TDataset);
962 begin
963   inherited Create(aDataset,aDataset,TBufDatasetIndex);
964 end;
965 
TCustomBufDataset.TBufDatasetIndexDefs.FindIndexnull966 function TCustomBufDataset.TBufDatasetIndexDefs.FindIndex(const IndexName: string): TBufDatasetIndex;
967 
968 Var
969   I: Integer;
970 
971 begin
972   I:=IndexOf(IndexName);
973   if I<>-1 then
974     Result:=BufIndexdefs[I]
975   else
976     Result:=Nil;
977 end;
978 
979 { ---------------------------------------------------------------------
980     TCustomBufDataset
981   ---------------------------------------------------------------------}
982 
983 constructor TCustomBufDataset.Create(AOwner : TComponent);
984 begin
985   Inherited Create(AOwner);
986   FManualMergeChangeLog := False;
987   FMaxIndexesCount:=2;
988   FIndexes:=TBufDatasetIndexDefs.Create(Self);
989   FAutoIncValue:=-1;
990   SetLength(FUpdateBuffer,0);
991   SetLength(FBlobBuffers,0);
992   SetLength(FUpdateBlobBuffers,0);
993   FParser := nil;
994   FPacketRecords := 10;
995 end;
996 
997 procedure TCustomBufDataset.SetPacketRecords(aValue : integer);
998 begin
999   if (aValue = -1) or (aValue > 0) then
1000     begin
1001     if (IndexFieldNames<>'') and (aValue<>-1) then
1002       DatabaseError(SInvPacketRecordsValueFieldNames)
1003     else
1004     if UniDirectional and (aValue=-1) then
1005       DatabaseError(SInvPacketRecordsValueUniDirectional)
1006     else
1007       FPacketRecords := aValue
1008     end
1009   else
1010     DatabaseError(SInvPacketRecordsValue);
1011 end;
1012 
1013 destructor TCustomBufDataset.Destroy;
1014 
1015 begin
1016   if Active then Close;
1017   SetLength(FUpdateBuffer,0);
1018   SetLength(FBlobBuffers,0);
1019   SetLength(FUpdateBlobBuffers,0);
1020   ClearIndexes;
1021   FreeAndNil(FIndexes);
1022   inherited destroy;
1023 end;
1024 
1025 procedure TCustomBufDataset.FetchAll;
1026 begin
1027   repeat
1028   until (getnextpacket < FPacketRecords) or (FPacketRecords = -1);
1029 end;
1030 
1031 {
1032 // Code to dump raw dataset data, including indexes information, useful for debugging
1033   procedure DumpRawMem(const Data: pointer; ALength: PtrInt);
1034   var
1035     b: integer;
1036     s1,s2: string;
1037   begin
1038     s1 := '';
1039     s2 := '';
1040     for b := 0 to ALength-1 do
1041       begin
1042       s1 := s1 + ' ' + hexStr(pbyte(Data)[b],2);
1043       if pchar(Data)[b] in ['a'..'z','A'..'Z','1'..'9',' '..'/',':'..'@'] then
1044         s2 := s2 + pchar(Data)[b]
1045       else
1046         s2 := s2 + '.';
1047       if length(s2)=16 then
1048         begin
1049         write('    ',s1,'    ');
1050         writeln(s2);
1051         s1 := '';
1052         s2 := '';
1053         end;
1054       end;
1055     write('    ',s1,'    ');
1056     writeln(s2);
1057   end;
1058 
1059   procedure DumpRecord(Dataset: TCustomBufDataset; RecBuf: PBufRecLinkItem; RawData: boolean = false);
1060   var ptr: pointer;
1061       NullMask: pointer;
1062       FieldData: pointer;
1063       NullMaskSize: integer;
1064       i: integer;
1065   begin
1066     if RawData then
1067       DumpRawMem(RecBuf,Dataset.RecordSize)
1068     else
1069       begin
1070       ptr := RecBuf;
1071       NullMask:= ptr + (sizeof(TBufRecLinkItem)*Dataset.MaxIndexesCount);
1072       NullMaskSize := 1+(Dataset.Fields.Count-1) div 8;
1073       FieldData:= ptr + (sizeof(TBufRecLinkItem)*Dataset.MaxIndexesCount) +NullMaskSize;
1074       write('record: $',hexstr(ptr),'  nullmask: $');
1075       for i := 0 to NullMaskSize-1 do
1076         write(hexStr(byte((NullMask+i)^),2));
1077       write('=');
1078       for i := 0 to NullMaskSize-1 do
1079         write(binStr(byte((NullMask+i)^),8));
1080       writeln('%');
1081       for i := 0 to Dataset.MaxIndexesCount-1 do
1082         writeln('  ','Index ',inttostr(i),' Prior rec: ' + hexstr(pointer((ptr+(i*2)*sizeof(ptr))^)) + ' Next rec: ' + hexstr(pointer((ptr+((i*2)+1)*sizeof(ptr))^)));
1083       DumpRawMem(FieldData,Dataset.RecordSize-((sizeof(TBufRecLinkItem)*Dataset.MaxIndexesCount) +NullMaskSize));
1084       end;
1085   end;
1086 
1087   procedure DumpDataset(AIndex: TBufIndex;RawData: boolean = false);
1088   var RecBuf: PBufRecLinkItem;
1089   begin
1090     writeln('Dump records, order based on index ',AIndex.IndNr);
1091     writeln('Current record:',hexstr(AIndex.CurrentRecord));
1092 
1093     RecBuf:=(AIndex as TDoubleLinkedBufIndex).FFirstRecBuf;
1094     while RecBuf<>(AIndex as TDoubleLinkedBufIndex).FLastRecBuf do
1095       begin
1096       DumpRecord(AIndex.FDataset,RecBuf,RawData);
1097       RecBuf:=RecBuf[(AIndex as TDoubleLinkedBufIndex).IndNr].next;
1098       end;
1099   end;
1100 }
1101 
1102 procedure TCustomBufDataset.BuildIndex(AIndex: TBufIndex);
1103 
1104 var PCurRecLinkItem : PBufRecLinkItem;
1105     p,l,q           : PBufRecLinkItem;
1106     i,k,psize,qsize : integer;
1107     myIdx,defIdx    : Integer;
1108     MergeAmount     : integer;
1109     PlaceQRec       : boolean;
1110 
1111     IndexFields     : TList;
1112     DescIndexFields : TList;
1113     CInsIndexFields : TList;
1114 
1115     Index0,
1116     DblLinkIndex    : TDoubleLinkedBufIndex;
1117 
1118   procedure PlaceNewRec(var e: PBufRecLinkItem; var esize: integer);
1119   begin
1120     if DblLinkIndex.FFirstRecBuf=nil then
1121      begin
1122      DblLinkIndex.FFirstRecBuf:=e;
1123      e[myIdx].prior:=nil;
1124      l:=e;
1125      end
1126    else
1127      begin
1128      l[myIdx].next:=e;
1129      e[myIdx].prior:=l;
1130      l:=e;
1131      end;
1132    e := e[myIdx].next;
1133    dec(esize);
1134   end;
1135 
1136 begin
1137   // Build the DBCompareStructure
1138   // One AS is enough, and makes debugging easier.
1139   DblLinkIndex:=(AIndex as TDoubleLinkedBufIndex);
1140   Index0:=DefaultIndex.BufferIndex as TDoubleLinkedBufIndex;
1141   myIdx:=DblLinkIndex.IndNr;
1142   defIdx:=Index0.IndNr;
1143   with DblLinkIndex do
1144     begin
1145     IndexFields := TList.Create;
1146     DescIndexFields := TList.Create;
1147     CInsIndexFields := TList.Create;
1148     try
1149       GetFieldList(IndexFields,FieldsName);
1150       GetFieldList(DescIndexFields,DescFields);
1151       GetFieldList(CInsIndexFields,CaseinsFields);
1152       if IndexFields.Count=0 then
1153         DatabaseErrorFmt(SNoIndexFieldNameGiven,[DblLinkIndex.Name],Self);
1154       ProcessFieldsToCompareStruct(IndexFields, DescIndexFields, CInsIndexFields, Options, [], DBCompareStruct);
1155     finally
1156       CInsIndexFields.Free;
1157       DescIndexFields.Free;
1158       IndexFields.Free;
1159     end;
1160     end;
1161 
1162   // This simply copies the index...
1163   PCurRecLinkItem:=Index0.FFirstRecBuf;
1164   PCurRecLinkItem[myIdx].next := PCurRecLinkItem[defIdx].next;
1165   PCurRecLinkItem[myIdx].prior := PCurRecLinkItem[defIdx].prior;
1166 
1167   if PCurRecLinkItem <> Index0.FLastRecBuf then
1168     begin
1169     while PCurRecLinkItem[defIdx].next<>Index0.FLastRecBuf do
1170       begin
1171       PCurRecLinkItem:=PCurRecLinkItem[defIdx].next;
1172 
1173       PCurRecLinkItem[myIdx].next := PCurRecLinkItem[defIdx].next;
1174       PCurRecLinkItem[myIdx].prior := PCurRecLinkItem[defIdx].prior;
1175       end;
1176     end
1177   else
1178     // Empty dataset
1179     Exit;
1180 
1181   // Set FirstRecBuf and FCurrentRecBuf
1182   DblLinkIndex.FFirstRecBuf:=Index0.FFirstRecBuf;
1183   DblLinkIndex.FCurrentRecBuf:=DblLinkIndex.FFirstRecBuf;
1184   // Link in the FLastRecBuf that belongs to this index
1185   PCurRecLinkItem[myIdx].next:=DblLinkIndex.FLastRecBuf;
1186   DblLinkIndex.FLastRecBuf[myIdx].prior:=PCurRecLinkItem;
1187 
1188   // Mergesort. Used the algorithm as described here by Simon Tatham
1189   // http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html
1190   // The comments in the code are from this website.
1191 
1192   // In each pass, we are merging lists of size K into lists of size 2K.
1193   // (Initially K equals 1.)
1194   k:=1;
1195 
1196   repeat
1197 
1198   // So we start by pointing a temporary pointer p at the head of the list,
1199   // and also preparing an empty list L which we will add elements to the end
1200   // of as we finish dealing with them.
1201   p := DblLinkIndex.FFirstRecBuf;
1202   DblLinkIndex.FFirstRecBuf := nil;
1203   q := p;
1204   MergeAmount := 0;
1205 
1206   // Then:
1207   // * If p is null, terminate this pass.
1208   while p <> DblLinkIndex.FLastRecBuf do
1209     begin
1210 
1211     //  * Otherwise, there is at least one element in the next pair of length-K
1212     //    lists, so increment the number of merges performed in this pass.
1213     inc(MergeAmount);
1214 
1215     //  * Point another temporary pointer, q, at the same place as p. Step q along
1216     //    the list by K places, or until the end of the list, whichever comes
1217     //    first. Let psize be the number of elements you managed to step q past.
1218     i:=0;
1219     while (i<k) and (q<>DblLinkIndex.FLastRecBuf) do
1220       begin
1221       inc(i);
1222       q := q[myIDx].next;
1223       end;
1224     psize :=i;
1225 
1226     //  * Let qsize equal K. Now we need to merge a list starting at p, of length
1227     //    psize, with a list starting at q of length at most qsize.
1228     qsize:=k;
1229 
1230     //  * So, as long as either the p-list is non-empty (psize > 0) or the q-list
1231     //    is non-empty (qsize > 0 and q points to something non-null):
1232     while (psize>0) or ((qsize>0) and (q <> DblLinkIndex.FLastRecBuf)) do
1233       begin
1234       //  * Choose which list to take the next element from. If either list
1235       //    is empty, we must choose from the other one. (By assumption, at
1236       //    least one is non-empty at this point.) If both lists are
1237       //    non-empty, compare the first element of each and choose the lower
1238       //    one. If the first elements compare equal, choose from the p-list.
1239       //    (This ensures that any two elements which compare equal are never
1240       //    swapped, so stability is guaranteed.)
1241       if (psize=0)  then
1242         PlaceQRec := true
1243       else if (qsize=0) or (q = DblLinkIndex.FLastRecBuf) then
1244         PlaceQRec := False
1245       else if IndexCompareRecords(p,q,DblLinkIndex.DBCompareStruct) <= 0 then
1246         PlaceQRec := False
1247       else
1248         PlaceQRec := True;
1249 
1250       //  * Remove that element, e, from the start of its list, by advancing
1251       //    p or q to the next element along, and decrementing psize or qsize.
1252       //  * Add e to the end of the list L we are building up.
1253       if PlaceQRec then
1254         PlaceNewRec(q,qsize)
1255       else
1256         PlaceNewRec(p,psize);
1257       end;
1258 
1259     //  * Now we have advanced p until it is where q started out, and we have
1260     //    advanced q until it is pointing at the next pair of length-K lists to
1261     //    merge. So set p to the value of q, and go back to the start of this loop.
1262     p:=q;
1263     end;
1264 
1265   // As soon as a pass like this is performed and only needs to do one merge, the
1266   // algorithm terminates, and the output list L is sorted. Otherwise, double the
1267   // value of K, and go back to the beginning.
1268 
1269   l[myIdx].next:=DblLinkIndex.FLastRecBuf;
1270 
1271   k:=k*2;
1272 
1273   until MergeAmount = 1;
1274   DblLinkIndex.FLastRecBuf[myIdx].next:=DblLinkIndex.FFirstRecBuf;
1275   DblLinkIndex.FLastRecBuf[myIdx].prior:=l;
1276 end;
1277 
1278 procedure TCustomBufDataset.BuildIndexes;
1279 
1280 var
1281   i: integer;
1282 
1283 begin
1284   for i:=0 to FIndexes.Count-1 do
1285     if BufIndexDefs[i].MustBuild(FCurrentIndexDef) then
1286       BuildIndex(BufIndexes[i]);
1287 end;
1288 
1289 procedure TCustomBufDataset.ClearIndexes;
1290 
1291 var
1292   i:integer;
1293 
1294 begin
1295   CheckInactive;
1296   For I:=0 to FIndexes.Count-1 do
1297     BufIndexDefs[i].Clearindex;
1298 end;
1299 
1300 procedure TCustomBufDataset.RemoveRecordFromIndexes(const ABookmark: TBufBookmark);
1301 
1302 var
1303   i: integer;
1304   F : TBufDatasetIndex;
1305 
1306 begin
1307   for i:=0 to FIndexes.Count-1 do
1308     begin
1309     F:=BufIndexDefs[i];
1310     if F.IsActiveIndex(FCurrentIndexDef) then
1311       F.BufferIndex.RemoveRecordFromIndex(ABookmark);
1312     end;
1313 end;
1314 
GetIndexDefsnull1315 function TCustomBufDataset.GetIndexDefs : TIndexDefs;
1316 
1317 begin
1318   Result:=FIndexes;
1319 end;
1320 
GetCanModifynull1321 function TCustomBufDataset.GetCanModify: Boolean;
1322 begin
1323   Result:=not (UniDirectional or ReadOnly);
1324 end;
1325 
BufferOffsetnull1326 function TCustomBufDataset.BufferOffset: integer;
1327 begin
1328   // Returns the offset of data buffer in bufdataset record
1329   Result := sizeof(TBufRecLinkItem) * FMaxIndexesCount;
1330 end;
1331 
TCustomBufDataset.IntAllocRecordBuffernull1332 function TCustomBufDataset.IntAllocRecordBuffer: TRecordBuffer;
1333 begin
1334   // Note: Only the internal buffers of TDataset provide bookmark information
1335   result := AllocMem(FRecordSize+BufferOffset);
1336 end;
1337 
TCustomBufDataset.AllocRecordBuffernull1338 function TCustomBufDataset.AllocRecordBuffer: TRecordBuffer;
1339 begin
1340   result := AllocMem(FRecordSize + BookmarkSize + CalcFieldsSize);
1341   // The records are initialised, or else the fields of an empty, just-opened dataset
1342   // are not null
1343   InitRecord(result);
1344 end;
1345 
1346 procedure TCustomBufDataset.FreeRecordBuffer(var Buffer: TRecordBuffer);
1347 begin
1348   ReAllocMem(Buffer,0);
1349 end;
1350 
1351 procedure TCustomBufDataset.ClearCalcFields(Buffer: TRecordBuffer);
1352 begin
1353   if CalcFieldsSize > 0 then
1354     FillByte((Buffer+RecordSize)^,CalcFieldsSize,0);
1355 end;
1356 
1357 procedure TCustomBufDataset.InternalInitFieldDefs;
1358 begin
1359   if FileName<>'' then
1360     begin
1361     IntLoadFieldDefsFromFile;
1362     FreeAndNil(FDatasetReader);
1363     FreeAndNil(FFileStream);
1364     end;
1365 end;
1366 
1367 procedure TCustomBufDataset.InitUserIndexes;
1368 
1369 var
1370   i : integer;
1371 
1372 begin
1373   For I:=0 to FIndexes.Count-1 do
1374     if BufIndexDefs[i].IndexType=itNormal then
1375        InternalCreateIndex(BufIndexDefs[i]);
1376 end;
1377 
1378 procedure TCustomBufDataset.InternalOpen;
1379 
1380 var IndexNr : integer;
1381     i : integer;
1382 
1383 begin
1384   if assigned(FDatasetReader) or (FileName<>'') then
1385     IntLoadFieldDefsFromFile;
1386 
1387   // This checks if the dataset is actually created (by calling CreateDataset,
1388   // or reading from a stream in some other way implemented by a descendent)
1389   // If there are less fields than FieldDefs we know for sure that the dataset
1390   // is not (correctly) created.
1391 
1392   // If there are constant expressions in the select statement (for PostgreSQL)
1393   // they are of type ftUnknown (in FieldDefs), and are not created (in Fields).
1394   // So Fields.Count < FieldDefs.Count in this case
1395   // See mantis #22030
1396 
1397   //  if Fields.Count<FieldDefs.Count then
1398   if (Fields.Count = 0) or (FieldDefs.Count=0) then
1399     DatabaseError(SErrNoDataset);
1400 
1401   // search for autoinc field
1402   FAutoIncField:=nil;
1403   if FAutoIncValue>-1 then
1404   begin
1405     for i := 0 to Fields.Count-1 do
1406       if Fields[i] is TAutoIncField then
1407       begin
1408         FAutoIncField := TAutoIncField(Fields[i]);
1409         Break;
1410       end;
1411   end;
1412 
1413   InitDefaultIndexes;
1414   InitUserIndexes;
1415   If FIndexName<>'' then
1416     FCurrentIndexDef:=TBufDatasetIndex(FIndexes.Find(FIndexName))
1417   else if (FIndexFieldNames<>'') then
1418     BuildCustomIndex;
1419 
1420   CalcRecordSize;
1421 
1422   FBRecordCount := 0;
1423 
1424   for IndexNr:=0 to FIndexes.Count-1 do
1425     if Assigned(BufIndexdefs[IndexNr]) then
1426       With BufIndexes[IndexNr] do
1427         InitialiseSpareRecord(IntAllocRecordBuffer);
1428 
1429   FAllPacketsFetched := False;
1430 
1431   FOpen:=True;
1432 
1433   // parse filter expression
1434   ParseFilter(Filter);
1435 
1436   if assigned(FDatasetReader) then IntLoadRecordsFromFile;
1437 end;
1438 
1439 procedure TCustomBufDataset.DoBeforeClose;
1440 begin
1441   inherited DoBeforeClose;
1442   if (FFileName<>'') then
1443     SaveToFile(FFileName,dfDefault);
1444 end;
1445 
1446 procedure TCustomBufDataset.InternalClose;
1447 
1448 var
1449   i,r  : integer;
1450   iGetResult : TGetResult;
1451   pc : TRecordBuffer;
1452   CurBufIndex: TBufDatasetIndex;
1453 
1454 begin
1455   FOpen:=False;
1456   FReadFromFile:=False;
1457   FBRecordCount:=0;
1458   if (FIndexes.Count>0) then
1459     with DefaultBufferIndex do
1460       if IsInitialized then
1461         begin
1462         iGetResult:=ScrollFirst;
1463         while iGetResult = grOK do
1464           begin
1465           pc:=pointer(CurrentRecord);
1466           iGetResult:=ScrollForward;
1467           FreeRecordBuffer(pc);
1468           end;
1469         end;
1470 
1471   for r := 0 to FIndexes.Count-1 do
1472     with FIndexes.BufIndexes[r] do
1473       if IsInitialized then
1474         begin
1475         pc:=SpareRecord;
1476         ReleaseSpareRecord;
1477         FreeRecordBuffer(pc);
1478         end;
1479 
1480   if Length(FUpdateBuffer) > 0 then
1481     begin
1482     for r := 0 to length(FUpdateBuffer)-1 do with FUpdateBuffer[r] do
1483       begin
1484       if assigned(OldValuesBuffer) then
1485         FreeRecordBuffer(OldValuesBuffer);
1486       if (UpdateKind = ukDelete) and assigned(BookmarkData.BookmarkData) then
1487         FreeRecordBuffer(TRecordBuffer(BookmarkData.BookmarkData));
1488       end;
1489     end;
1490   SetLength(FUpdateBuffer,0);
1491 
1492   for r := 0 to High(FBlobBuffers) do
1493     FreeBlobBuffer(FBlobBuffers[r]);
1494   for r := 0 to High(FUpdateBlobBuffers) do
1495     FreeBlobBuffer(FUpdateBlobBuffers[r]);
1496   SetLength(FBlobBuffers,0);
1497   SetLength(FUpdateBlobBuffers,0);
1498   SetLength(FFieldBufPositions,0);
1499   if FAutoIncValue>-1 then FAutoIncValue:=1;
1500   if assigned(FParser) then FreeAndNil(FParser);
1501   For I:=FIndexes.Count-1 downto 0 do
1502     begin
1503     CurBufIndex:=BufIndexDefs[i];
1504     if (CurBufIndex.IndexType in [itDefault,itCustom]) or (CurBufIndex.DiscardOnClose) then
1505       begin
1506       if FCurrentIndexDef=CurBufIndex then
1507         FCurrentIndexDef:=nil;
1508       CurBufIndex.Free;
1509       end
1510     else
1511       FreeAndNil(CurBufIndex.FBufferIndex);
1512     end;
1513 end;
1514 
1515 procedure TCustomBufDataset.InternalFirst;
1516 
1517 begin
1518   with CurrentIndexBuf do
1519     // if FCurrentRecBuf = FLastRecBuf then the dataset is just opened and empty
1520     // in which case InternalFirst should do nothing (bug 7211)
1521     SetToFirstRecord;
1522 end;
1523 
1524 procedure TCustomBufDataset.InternalLast;
1525 begin
1526   FetchAll;
1527   with CurrentIndexBuf do
1528     SetToLastRecord;
1529 end;
1530 
1531 procedure TCustomBufDataset.CopyFromDataset(DataSet: TDataSet; CopyData: Boolean);
1532 
1533 Const
1534   UseStreams = ftBlobTypes;
1535 
1536 Var
1537   I  : Integer;
1538   F,F1,F2 : TField;
1539   L1,L2  : TList;
1540   N : String;
1541   OriginalPosition: TBookMark;
1542   S : TMemoryStream;
1543 
1544 begin
1545   Close;
1546   Fields.Clear;
1547   FieldDefs.Clear;
1548   For I:=0 to Dataset.FieldCount-1 do
1549     begin
1550     F:=Dataset.Fields[I];
1551     TFieldDef.Create(FieldDefs,F.FieldName,F.DataType,F.Size,F.Required,F.FieldNo);
1552     end;
1553   CreateDataset;
1554   L1:=Nil;
1555   L2:=Nil;
1556   S:=Nil;
1557   If CopyData then
1558     try
1559       L1:=TList.Create;
1560       L2:=TList.Create;
1561       Open;
1562       For I:=0 to FieldDefs.Count-1 do
1563         begin
1564         N:=FieldDefs[I].Name;
1565         F1:=FieldByName(N);
1566         F2:=DataSet.FieldByName(N);
1567         L1.Add(F1);
1568         L2.Add(F2);
1569         If (FieldDefs[I].DataType in UseStreams) and (S=Nil) then
1570           S:=TMemoryStream.Create;
1571         end;
1572       DisableControls;
1573       Dataset.DisableControls;
1574       OriginalPosition:=Dataset.GetBookmark;
1575       Try
1576         Dataset.Open;
1577         Dataset.First;
1578         While not Dataset.EOF do
1579           begin
1580           Append;
1581           For I:=0 to L1.Count-1 do
1582             begin
1583             F1:=TField(L1[i]);
1584             F2:=TField(L2[I]);
1585             If Not F2.IsNull then
1586               Case F1.DataType of
1587                  ftFixedChar,
1588                  ftString   : F1.AsString:=F2.AsString;
1589                  ftFixedWideChar,
1590                  ftWideString : F1.AsWideString:=F2.AsWideString;
1591                  ftBoolean  : F1.AsBoolean:=F2.AsBoolean;
1592                  ftFloat    : F1.AsFloat:=F2.AsFloat;
1593                  ftAutoInc,
1594                  ftSmallInt,
1595                  ftInteger  : F1.AsInteger:=F2.AsInteger;
1596                  ftLargeInt : F1.AsLargeInt:=F2.AsLargeInt;
1597                  ftDate     : F1.AsDateTime:=F2.AsDateTime;
1598                  ftTime     : F1.AsDateTime:=F2.AsDateTime;
1599                  ftTimestamp,
1600                  ftDateTime : F1.AsDateTime:=F2.AsDateTime;
1601                  ftCurrency : F1.AsCurrency:=F2.AsCurrency;
1602                  ftBCD,
1603                  ftFmtBCD   : F1.AsBCD:=F2.AsBCD;
1604             else
1605               if (F1.DataType in UseStreams) then
1606                 begin
1607                 S.Clear;
1608                 TBlobField(F2).SaveToStream(S);
1609                 S.Position:=0;
1610                 TBlobField(F1).LoadFromStream(S);
1611                 end
1612               else
1613                 F1.AsString:=F2.AsString;
1614             end;
1615           end;
1616           Try
1617             Post;
1618           except
1619             Cancel;
1620             Raise;
1621           end;
1622           Dataset.Next;
1623           end;
1624       Finally
1625         DataSet.GotoBookmark(OriginalPosition); //Return to original record
1626         Dataset.EnableControls;
1627         EnableControls;
1628       end;
1629     finally
1630       L2.Free;
1631       l1.Free;
1632       S.Free;
1633     end;
1634 end;
1635 
1636 { TBufIndex }
1637 
1638 constructor TBufIndex.Create(const ADataset: TCustomBufDataset);
1639 begin
1640   inherited create;
1641   FDataset := ADataset;
1642 end;
1643 
TBufIndex.BookmarkValidnull1644 function TBufIndex.BookmarkValid(const ABookmark: PBufBookmark): boolean;
1645 begin
1646   Result := assigned(ABookmark) and assigned(ABookmark^.BookmarkData);
1647 end;
1648 
TBufIndex.CompareBookmarksnull1649 function TBufIndex.CompareBookmarks(const ABookmark1, ABookmark2: PBufBookmark): integer;
1650 begin
1651   Result := 0;
1652 end;
1653 
SameBookmarksnull1654 function TBufIndex.SameBookmarks(const ABookmark1, ABookmark2: PBufBookmark): boolean;
1655 begin
1656   Result := Assigned(ABookmark1) and Assigned(ABookmark2) and (CompareBookmarks(ABookmark1, ABookmark2) = 0);
1657 end;
1658 
TBufIndex.GetRecordnull1659 function TBufIndex.GetRecord(ABookmark: PBufBookmark; GetMode: TGetMode): TGetResult;
1660 begin
1661   Result := grError;
1662 end;
1663 
1664 { TDoubleLinkedBufIndex }
1665 
TDoubleLinkedBufIndex.GetBookmarkSizenull1666 function TDoubleLinkedBufIndex.GetBookmarkSize: integer;
1667 begin
1668   Result:=sizeof(TBufBookmark);
1669 end;
1670 
GetCurrentBuffernull1671 function TDoubleLinkedBufIndex.GetCurrentBuffer: Pointer;
1672 begin
1673   Result := pointer(FCurrentRecBuf) + FDataset.BufferOffset;
1674 end;
1675 
GetCurrentRecordnull1676 function TDoubleLinkedBufIndex.GetCurrentRecord: TRecordBuffer;
1677 begin
1678   Result := TRecordBuffer(FCurrentRecBuf);
1679 end;
1680 
TDoubleLinkedBufIndex.GetIsInitializednull1681 function TDoubleLinkedBufIndex.GetIsInitialized: boolean;
1682 begin
1683   Result := (FFirstRecBuf<>nil);
1684 end;
1685 
GetSpareBuffernull1686 function TDoubleLinkedBufIndex.GetSpareBuffer: TRecordBuffer;
1687 begin
1688   Result := pointer(FLastRecBuf) + FDataset.BufferOffset;
1689 end;
1690 
TDoubleLinkedBufIndex.GetSpareRecordnull1691 function TDoubleLinkedBufIndex.GetSpareRecord: TRecordBuffer;
1692 begin
1693   Result := TRecordBuffer(FLastRecBuf);
1694 end;
1695 
TDoubleLinkedBufIndex.ScrollBackwardnull1696 function TDoubleLinkedBufIndex.ScrollBackward: TGetResult;
1697 begin
1698   if not assigned(FCurrentRecBuf[IndNr].prior) then
1699     begin
1700     Result := grBOF;
1701     end
1702   else
1703     begin
1704     Result := grOK;
1705     FCurrentRecBuf := FCurrentRecBuf[IndNr].prior;
1706     end;
1707 end;
1708 
TDoubleLinkedBufIndex.ScrollForwardnull1709 function TDoubleLinkedBufIndex.ScrollForward: TGetResult;
1710 begin
1711   if (FCurrentRecBuf = FLastRecBuf) or // just opened
1712      (FCurrentRecBuf[IndNr].next = FLastRecBuf) then
1713     result := grEOF
1714   else
1715     begin
1716     FCurrentRecBuf := FCurrentRecBuf[IndNr].next;
1717     Result := grOK;
1718     end;
1719 end;
1720 
TDoubleLinkedBufIndex.GetCurrentnull1721 function TDoubleLinkedBufIndex.GetCurrent: TGetResult;
1722 begin
1723   if FFirstRecBuf = FLastRecBuf then
1724     Result := grError
1725   else
1726     begin
1727     Result := grOK;
1728     if FCurrentRecBuf = FLastRecBuf then
1729       FCurrentRecBuf:=FLastRecBuf[IndNr].prior;
1730     end;
1731 end;
1732 
ScrollFirstnull1733 function TDoubleLinkedBufIndex.ScrollFirst: TGetResult;
1734 begin
1735   FCurrentRecBuf:=FFirstRecBuf;
1736   if (FCurrentRecBuf = FLastRecBuf) then
1737     result := grEOF
1738   else
1739     result := grOK;
1740 end;
1741 
1742 procedure TDoubleLinkedBufIndex.ScrollLast;
1743 begin
1744   FCurrentRecBuf:=FLastRecBuf;
1745 end;
1746 
TDoubleLinkedBufIndex.GetRecordnull1747 function TDoubleLinkedBufIndex.GetRecord(ABookmark: PBufBookmark; GetMode: TGetMode): TGetResult;
1748 var ARecord : PBufRecLinkItem;
1749 begin
1750   Result := grOK;
1751   case GetMode of
1752     gmPrior:
1753       begin
1754       if assigned(ABookmark^.BookmarkData) then
1755         ARecord := ABookmark^.BookmarkData[IndNr].prior
1756       else
1757         ARecord := nil;
1758       if not assigned(ARecord) then
1759         Result := grBOF;
1760       end;
1761     gmNext:
1762       begin
1763       if assigned(ABookmark^.BookmarkData) then
1764         ARecord := ABookmark^.BookmarkData[IndNr].next
1765       else
1766         ARecord := FFirstRecBuf;
1767       end;
1768     else
1769       Result := grError;
1770   end;
1771 
1772   if ARecord = FLastRecBuf then
1773     Result := grEOF;
1774   // store into BookmarkData pointer to prior/next record
1775   ABookmark^.BookmarkData:=ARecord;
1776 end;
1777 
1778 procedure TDoubleLinkedBufIndex.SetToFirstRecord;
1779 begin
1780   FLastRecBuf[IndNr].next:=FFirstRecBuf;
1781   FCurrentRecBuf := FLastRecBuf;
1782 end;
1783 
1784 procedure TDoubleLinkedBufIndex.SetToLastRecord;
1785 begin
1786   if FLastRecBuf <> FFirstRecBuf then FCurrentRecBuf := FLastRecBuf;
1787 end;
1788 
1789 procedure TDoubleLinkedBufIndex.StoreCurrentRecord;
1790 begin
1791   FStoredRecBuf:=FCurrentRecBuf;
1792 end;
1793 
1794 procedure TDoubleLinkedBufIndex.RestoreCurrentRecord;
1795 begin
1796   FCurrentRecBuf:=FStoredRecBuf;
1797 end;
1798 
1799 procedure TDoubleLinkedBufIndex.DoScrollForward;
1800 begin
1801   FCurrentRecBuf := FCurrentRecBuf[IndNr].next;
1802 end;
1803 
1804 procedure TDoubleLinkedBufIndex.StoreCurrentRecIntoBookmark(const ABookmark: PBufBookmark);
1805 begin
1806   ABookmark^.BookmarkData:=FCurrentRecBuf;
1807 end;
1808 
1809 procedure TDoubleLinkedBufIndex.StoreSpareRecIntoBookmark(
1810   const ABookmark: PBufBookmark);
1811 begin
1812   ABookmark^.BookmarkData:=FLastRecBuf;
1813 end;
1814 
1815 procedure TDoubleLinkedBufIndex.GotoBookmark(const ABookmark : PBufBookmark);
1816 begin
1817   FCurrentRecBuf := ABookmark^.BookmarkData;
1818 end;
1819 
TDoubleLinkedBufIndex.CompareBookmarksnull1820 function TDoubleLinkedBufIndex.CompareBookmarks(const ABookmark1,ABookmark2: PBufBookmark): integer;
1821 var ARecord1, ARecord2 : PBufRecLinkItem;
1822 begin
1823   // valid bookmarks expected
1824   // estimate result using memory addresses of records
1825   Result := ABookmark1^.BookmarkData - ABookmark2^.BookmarkData;
1826   if Result = 0 then
1827     Exit
1828   else if Result < 0 then
1829   begin
1830     Result   := -1;
1831     ARecord1 := ABookmark1^.BookmarkData;
1832     ARecord2 := ABookmark2^.BookmarkData;
1833   end
1834   else
1835   begin
1836     Result   := +1;
1837     ARecord1 := ABookmark2^.BookmarkData;
1838     ARecord2 := ABookmark1^.BookmarkData;
1839   end;
1840   // if we need relative position of records with given bookmarks we must
1841   // traverse through index until we reach lower bookmark or 1st record
1842   while assigned(ARecord2) and (ARecord2 <> ARecord1) and (ARecord2 <> FFirstRecBuf) do
1843     ARecord2 := ARecord2[IndNr].prior;
1844   // if we found lower bookmark as first, then estimated position is correct
1845   if ARecord1 <> ARecord2 then
1846     Result := -Result;
1847 end;
1848 
SameBookmarksnull1849 function TDoubleLinkedBufIndex.SameBookmarks(const ABookmark1, ABookmark2: PBufBookmark): boolean;
1850 begin
1851   Result := Assigned(ABookmark1) and Assigned(ABookmark2) and (ABookmark1^.BookmarkData = ABookmark2^.BookmarkData);
1852 end;
1853 
1854 procedure TDoubleLinkedBufIndex.InitialiseIndex;
1855 begin
1856   // Do nothing
1857 end;
1858 
CanScrollForwardnull1859 function TDoubleLinkedBufIndex.CanScrollForward: Boolean;
1860 begin
1861   if (FCurrentRecBuf[IndNr].next = FLastRecBuf) then
1862     Result := False
1863   else
1864     Result := True;
1865 end;
1866 
1867 procedure TDoubleLinkedBufIndex.InitialiseSpareRecord(const ASpareRecord : TRecordBuffer);
1868 begin
1869   FFirstRecBuf := pointer(ASpareRecord);
1870   FLastRecBuf := FFirstRecBuf;
1871   FLastRecBuf[IndNr].prior:=nil;
1872   FLastRecBuf[IndNr].next:=FLastRecBuf;
1873   FCurrentRecBuf := FLastRecBuf;
1874 end;
1875 
1876 procedure TDoubleLinkedBufIndex.ReleaseSpareRecord;
1877 begin
1878   FFirstRecBuf:= nil;
1879 end;
1880 
TDoubleLinkedBufIndex.GetRecNonull1881 function TDoubleLinkedBufIndex.GetRecNo: Longint;
1882 var ARecord : PBufRecLinkItem;
1883 begin
1884   ARecord := FCurrentRecBuf;
1885   Result := 1;
1886   while ARecord <> FFirstRecBuf do
1887     begin
1888     inc(Result);
1889     ARecord := ARecord[IndNr].prior;
1890     end;
1891 end;
1892 
1893 procedure TDoubleLinkedBufIndex.SetRecNo(ARecNo: Longint);
1894 var ARecord : PBufRecLinkItem;
1895 begin
1896   ARecord := FFirstRecBuf;
1897   while (ARecNo > 1) and (ARecord <> FLastRecBuf) do
1898     begin
1899     dec(ARecNo);
1900     ARecord := ARecord[IndNr].next;
1901     end;
1902   FCurrentRecBuf := ARecord;
1903 end;
1904 
1905 procedure TDoubleLinkedBufIndex.BeginUpdate;
1906 begin
1907   if FCurrentRecBuf = FLastRecBuf then
1908     FCursOnFirstRec := True
1909   else
1910     FCursOnFirstRec := False;
1911 end;
1912 
1913 procedure TDoubleLinkedBufIndex.AddRecord;
1914 var ARecord: TRecordBuffer;
1915 begin
1916   ARecord := FDataset.IntAllocRecordBuffer;
1917   FLastRecBuf[IndNr].next := pointer(ARecord);
1918   FLastRecBuf[IndNr].next[IndNr].prior := FLastRecBuf;
1919 
1920   FLastRecBuf := FLastRecBuf[IndNr].next;
1921 end;
1922 
1923 procedure TDoubleLinkedBufIndex.InsertRecordBeforeCurrentRecord(const ARecord: TRecordBuffer);
1924 var ANewRecord : PBufRecLinkItem;
1925 begin
1926   ANewRecord:=PBufRecLinkItem(ARecord);
1927   ANewRecord[IndNr].prior:=FCurrentRecBuf[IndNr].prior;
1928   ANewRecord[IndNr].Next:=FCurrentRecBuf;
1929 
1930   if FCurrentRecBuf=FFirstRecBuf then
1931     begin
1932     FFirstRecBuf:=ANewRecord;
1933     ANewRecord[IndNr].prior:=nil;
1934     end
1935   else
1936     ANewRecord[IndNr].Prior[IndNr].next:=ANewRecord;
1937   ANewRecord[IndNr].next[IndNr].prior:=ANewRecord;
1938 end;
1939 
1940 procedure TDoubleLinkedBufIndex.RemoveRecordFromIndex(const ABookmark : TBufBookmark);
1941 var ARecord : PBufRecLinkItem;
1942 begin
1943   ARecord := ABookmark.BookmarkData;
1944   if ARecord = FCurrentRecBuf then DoScrollForward;
1945   if ARecord <> FFirstRecBuf then
1946     ARecord[IndNr].prior[IndNr].next := ARecord[IndNr].next
1947   else
1948     begin
1949     FFirstRecBuf := ARecord[IndNr].next;
1950     FLastRecBuf[IndNr].next := FFirstRecBuf;
1951     end;
1952   ARecord[IndNr].next[IndNr].prior := ARecord[IndNr].prior;
1953 end;
1954 
1955 procedure TDoubleLinkedBufIndex.OrderCurrentRecord;
1956 var ARecord: PBufRecLinkItem;
1957     ABookmark: TBufBookmark;
1958 begin
1959   // all records except current are already sorted
1960   // check prior records
1961   ARecord := FCurrentRecBuf;
1962   repeat
1963     ARecord := ARecord[IndNr].prior;
1964   until not assigned(ARecord) or (IndexCompareRecords(ARecord, FCurrentRecBuf, DBCompareStruct) <= 0);
1965   if assigned(ARecord) then
1966     ARecord := ARecord[IndNr].next
1967   else
1968     ARecord := FFirstRecBuf;
1969   if ARecord = FCurrentRecBuf then
1970   begin
1971     // prior record is less equal than current
1972     // check next records
1973     repeat
1974       ARecord := ARecord[IndNr].next;
1975     until (ARecord=FLastRecBuf) or (IndexCompareRecords(ARecord, FCurrentRecBuf, DBCompareStruct) >= 0);
1976     if ARecord = FCurrentRecBuf[IndNr].next then
1977       Exit; // current record is on proper position
1978   end;
1979   StoreCurrentRecIntoBookmark(@ABookmark);
1980   RemoveRecordFromIndex(ABookmark);
1981   FCurrentRecBuf := ARecord;
1982   InsertRecordBeforeCurrentRecord(TRecordBuffer(ABookmark.BookmarkData));
1983   GotoBookmark(@ABookmark);
1984 end;
1985 
1986 procedure TDoubleLinkedBufIndex.EndUpdate;
1987 begin
1988   FLastRecBuf[IndNr].next := FFirstRecBuf;
1989   if FCursOnFirstRec then FCurrentRecBuf:=FLastRecBuf;
1990 end;
1991 
1992 procedure TCustomBufDataset.CurrentRecordToBuffer(Buffer: TRecordBuffer);
1993 var ABookMark : PBufBookmark;
1994 begin
1995   with CurrentIndexBuf do
1996     begin
1997     move(CurrentBuffer^,buffer^,FRecordSize);
1998     ABookMark:=PBufBookmark(Buffer + FRecordSize);
1999     ABookmark^.BookmarkFlag:=bfCurrent;
2000     StoreCurrentRecIntoBookmark(ABookMark);
2001     end;
2002 
2003   GetCalcFields(Buffer);
2004 end;
2005 
2006 procedure TCustomBufDataset.SetBufUniDirectional(const AValue: boolean);
2007 begin
2008   CheckInactive;
2009   if (AValue<>IsUniDirectional) then
2010     begin
2011     SetUniDirectional(AValue);
2012     ClearIndexes;
2013     FPacketRecords := 1; // temporary
2014     end;
2015 end;
2016 
DefaultIndexnull2017 function TCustomBufDataset.DefaultIndex: TBufDatasetIndex;
2018 begin
2019   Result:=FDefaultIndex;
2020   if Result=Nil then
2021     Result:=FIndexes.FindIndex(SDefaultIndex);
2022 end;
2023 
DefaultBufferIndexnull2024 function TCustomBufDataset.DefaultBufferIndex: TBufIndex;
2025 begin
2026   if Assigned(DefaultIndex) then
2027     Result:=DefaultIndex.BufferIndex
2028   else
2029     Result:=Nil;
2030 end;
2031 
2032 procedure TCustomBufDataset.SetReadOnly(AValue: Boolean);
2033 begin
2034   FReadOnly:=AValue;
2035 end;
2036 
GetRecordnull2037 function TCustomBufDataset.GetRecord(Buffer: TRecordBuffer; GetMode: TGetMode; DoCheck: Boolean): TGetResult;
2038 
2039 var Acceptable : Boolean;
2040     SavedState : TDataSetState;
2041 
2042 begin
2043   Result := grOK;
2044   with CurrentIndexBuf do
2045     repeat
2046     Acceptable := True;
2047     case GetMode of
2048       gmPrior : Result := ScrollBackward;
2049       gmCurrent : Result := GetCurrent;
2050       gmNext : begin
2051                if not CanScrollForward and (getnextpacket = 0) then
2052                  Result := grEOF
2053                else
2054                  begin
2055                  Result := grOK;
2056                  DoScrollForward;
2057                  end;
2058                end;
2059     end;
2060 
2061     if Result = grOK then
2062       begin
2063       CurrentRecordToBuffer(Buffer);
2064 
2065       if Filtered then
2066         begin
2067         FFilterBuffer := Buffer;
2068         SavedState := SetTempState(dsFilter);
2069         DoFilterRecord(Acceptable);
2070         if (GetMode = gmCurrent) and not Acceptable then
2071           begin
2072           Acceptable := True;
2073           Result := grError;
2074           end;
2075         RestoreState(SavedState);
2076         end;
2077       end
2078     else if (Result = grError) and DoCheck then
2079       DatabaseError('No record');
2080     until Acceptable;
2081 end;
2082 
TCustomBufDataset.GetActiveRecordUpdateBuffernull2083 function TCustomBufDataset.GetActiveRecordUpdateBuffer : boolean;
2084 
2085 var ABookmark : TBufBookmark;
2086 
2087 begin
2088   GetBookmarkData(ActiveBuffer,@ABookmark);
2089   result := GetRecordUpdateBufferCached(ABookmark);
2090 end;
2091 
TCustomBufDataset.GetCurrentIndexBufnull2092 function TCustomBufDataset.GetCurrentIndexBuf: TBufIndex;
2093 begin
2094   if Assigned(FCurrentIndexDef) then
2095     Result:=FCurrentIndexDef.BufferIndex
2096   else
2097     Result:=Nil;
2098 end;
2099 
GetBufIndexnull2100 function TCustomBufDataset.GetBufIndex(Aindex : Integer): TBufIndex;
2101 begin
2102   Result:=FIndexes.BufIndexes[AIndex]
2103 end;
2104 
TCustomBufDataset.GetBufIndexDefnull2105 function TCustomBufDataset.GetBufIndexDef(Aindex : Integer): TBufDatasetIndex;
2106 begin
2107   Result:=FIndexes.BufIndexdefs[AIndex]
2108 end;
2109 
2110 procedure TCustomBufDataset.ProcessFieldsToCompareStruct(const AFields, ADescFields, ACInsFields: TList;
2111       const AIndexOptions: TIndexOptions; const ALocateOptions: TLocateOptions; out ACompareStruct: TDBCompareStruct);
2112 var i: integer;
2113     AField: TField;
2114     ACompareRec: TDBCompareRec;
2115 begin
2116   SetLength(ACompareStruct, AFields.Count);
2117   for i:=0 to high(ACompareStruct) do
2118     begin
2119     AField := TField(AFields[i]);
2120 
2121     case AField.DataType of
2122       ftString, ftFixedChar, ftGuid:
2123         ACompareRec.CompareFunc := @DBCompareText;
2124       ftWideString, ftFixedWideChar:
2125         ACompareRec.CompareFunc := @DBCompareWideText;
2126       ftSmallint:
2127         ACompareRec.CompareFunc := @DBCompareSmallInt;
2128       ftInteger, ftAutoInc:
2129         ACompareRec.CompareFunc := @DBCompareInt;
2130       ftLargeint, ftBCD:
2131         ACompareRec.CompareFunc := @DBCompareLargeInt;
2132       ftWord:
2133         ACompareRec.CompareFunc := @DBCompareWord;
2134       ftBoolean:
2135         ACompareRec.CompareFunc := @DBCompareByte;
2136       ftDate, ftTime, ftDateTime,
2137       ftFloat, ftCurrency:
2138         ACompareRec.CompareFunc := @DBCompareDouble;
2139       ftFmtBCD:
2140         ACompareRec.CompareFunc := @DBCompareBCD;
2141       ftVarBytes:
2142         ACompareRec.CompareFunc := @DBCompareVarBytes;
2143       ftBytes:
2144         ACompareRec.CompareFunc := @DBCompareBytes;
2145     else
2146       DatabaseErrorFmt(SErrIndexBasedOnInvField, [AField.FieldName,Fieldtypenames[AField.DataType]]);
2147     end;
2148 
2149     ACompareRec.Off:=BufferOffset + FFieldBufPositions[AField.FieldNo-1];
2150     ACompareRec.NullBOff:=BufferOffset;
2151 
2152     ACompareRec.FieldInd:=AField.FieldNo-1;
2153     ACompareRec.Size:=GetFieldSize(FieldDefs[ACompareRec.FieldInd]);
2154 
2155     ACompareRec.Desc := ixDescending in AIndexOptions;
2156     if assigned(ADescFields) then
2157       ACompareRec.Desc := ACompareRec.Desc or (ADescFields.IndexOf(AField)>-1);
2158 
2159     ACompareRec.Options := ALocateOptions;
2160     if assigned(ACInsFields) and (ACInsFields.IndexOf(AField)>-1) then
2161       ACompareRec.Options := ACompareRec.Options + [loCaseInsensitive];
2162 
2163     ACompareStruct[i] := ACompareRec;
2164     end;
2165 end;
2166 
2167 
2168 procedure TCustomBufDataset.InitDefaultIndexes;
2169 
2170 {
2171   This procedure makes sure there are 2 default indexes:
2172   DEFAULT_ORDER, which is simply the order in which the server records arrived.
2173   CUSTOM_ORDER, which is an internal index to accomodate the 'IndexFieldNames' property.
2174 }
2175 
2176 Var
2177   FD,FC : TBufDatasetIndex;
2178 
2179 begin
2180   // Default index
2181   FD:=FIndexes.FindIndex(SDefaultIndex);
2182   if (FD=Nil) then
2183     begin
2184     FD:=InternalAddIndex(SDefaultIndex,'',[],'','');
2185     FD.IndexType:=itDefault;
2186     FD.FDiscardOnClose:=True;
2187     end
2188 // Not sure about this. For the moment we leave it in comment
2189 {  else if FD.BufferIndex=Nil then
2190     InternalCreateIndex(FD)}
2191     ;
2192 
2193   FCurrentIndexDef:=FD;
2194   // Custom index
2195   if not IsUniDirectional then
2196     begin
2197     FC:=Findexes.FindIndex(SCustomIndex);
2198     if (FC=Nil) then
2199       begin
2200       FC:=InternalAddIndex(SCustomIndex,'',[],'','');
2201       FC.IndexType:=itCustom;
2202       FC.FDiscardOnClose:=True;
2203       end
2204     // Not sure about this. For the moment we leave it in comment
2205 {    else if FD.BufferIndex=Nil then
2206       InternalCreateIndex(FD)}
2207       ;
2208     end;
2209   BookmarkSize:=CurrentIndexBuf.BookmarkSize;
2210 end;
2211 
2212 procedure TCustomBufDataset.AddIndex(const AName, AFields : string; AOptions : TIndexOptions; const ADescFields: string = '';
2213   const ACaseInsFields: string = '');
2214 
2215 Var
2216   F : TBufDatasetIndex;
2217 
2218 begin
2219   CheckBiDirectional;
2220   if (AFields='') then
2221     DatabaseError(SNoIndexFieldNameGiven,Self);
2222   if Active and (FIndexes.Count=FMaxIndexesCount) then
2223     DatabaseError(SMaxIndexes,Self);
2224   // If not all packets are fetched, you can not sort properly.
2225   if not Active then
2226     FPacketRecords:=-1;
2227   F:=InternalAddIndex(AName,AFields,AOptions,ADescFields,ACaseInsFields);
2228   F.FDiscardOnClose:=Active;
2229 end;
2230 
TCustomBufDataset.InternalAddIndexnull2231 Function TCustomBufDataset.InternalAddIndex(const AName, AFields : string; AOptions : TIndexOptions; const ADescFields: string;
2232                                          const ACaseInsFields: string) : TBufDatasetIndex;
2233 
2234 Var
2235   F : TBufDatasetIndex;
2236 
2237 begin
2238   F:=FIndexes.AddIndexDef as TBufDatasetIndex;
2239   F.Name:=AName;
2240   F.Fields:=AFields;
2241   F.Options:=AOptions;
2242   F.DescFields:=ADescFields;
2243   F.CaseInsFields:=ACaseInsFields;
2244   InternalCreateIndex(F);
2245   Result:=F;
2246 end;
2247 
2248 procedure TCustomBufDataset.InternalCreateIndex(F : TBufDataSetIndex);
2249 
2250 Var
2251   B : TBufIndex;
2252 begin
2253   if Active and not Refreshing then
2254     FetchAll;
2255   if IsUniDirectional then
2256     B:=TUniDirectionalBufIndex.Create(self)
2257   else
2258     B:=TDoubleLinkedBufIndex.Create(self);
2259   F.FBufferIndex:=B;
2260   with B do
2261     begin
2262     InitialiseIndex;
2263     F.SetIndexProperties;
2264     end;
2265   if Active  then
2266     begin
2267     if not Refreshing then
2268       B.InitialiseSpareRecord(IntAllocRecordBuffer);
2269     if (F.Fields<>'') then
2270       BuildIndex(B);
2271     end
2272   else
2273     if (FIndexes.Count+2>FMaxIndexesCount) then
2274       FMaxIndexesCount:=FIndexes.Count+2; // Custom+Default order
2275 end;
2276 
TCustomBufDataset.DefaultReadFileFormatnull2277 class function TCustomBufDataset.DefaultReadFileFormat: TDataPacketFormat;
2278 begin
2279   Result:=dfAny;
2280 end;
2281 
TCustomBufDataset.DefaultWriteFileFormatnull2282 class function TCustomBufDataset.DefaultWriteFileFormat: TDataPacketFormat;
2283 begin
2284   Result:=dfBinary;
2285 end;
2286 
TCustomBufDataset.DefaultPacketClassnull2287 class function TCustomBufDataset.DefaultPacketClass: TDataPacketReaderClass;
2288 begin
2289   Result:=TFpcBinaryDatapacketReader;
2290 end;
2291 
CreateDefaultPacketReadernull2292 function TCustomBufDataset.CreateDefaultPacketReader(aStream : TStream): TDataPacketReader;
2293 begin
2294   Result:=DefaultPacketClass.Create(Self,aStream);
2295 end;
2296 
2297 
2298 procedure TCustomBufDataset.SetIndexFieldNames(const AValue: String);
2299 
2300 begin
2301   FIndexFieldNames:=AValue;
2302   if (AValue='') then
2303     begin
2304     FCurrentIndexDef:=FIndexes.FindIndex(SDefaultIndex);
2305     Exit;
2306     end;
2307   if Active then
2308     BuildCustomIndex;
2309 end;
2310 
2311 procedure TCustomBufDataset.BuildCustomIndex;
2312 
2313 var
2314   i, p: integer;
2315   s: string;
2316   SortFields, DescFields: string;
2317   F : TBufDatasetIndex;
2318 
2319 begin
2320   F:=FIndexes.FindIndex(SCustomIndex);
2321   if (F=Nil) then
2322     InitDefaultIndexes;
2323   F:=FIndexes.FindIndex(SCustomIndex);
2324   SortFields := '';
2325   DescFields := '';
2326   for i := 1 to WordCount(FIndexFieldNames, [Limiter]) do
2327     begin
2328       s := ExtractDelimited(i, FIndexFieldNames, [Limiter]);
2329       p := Pos(Desc, s);
2330       if p>0 then
2331       begin
2332         system.Delete(s, p, LenDesc);
2333         DescFields := DescFields + Limiter + s;
2334       end;
2335       SortFields := SortFields + Limiter + s;
2336     end;
2337   if (Length(SortFields)>0) and (SortFields[1]=Limiter) then
2338     system.Delete(SortFields,1,1);
2339   if (Length(DescFields)>0) and (DescFields[1]=Limiter) then
2340     system.Delete(DescFields,1,1);
2341   F.Fields:=SortFields;
2342   F.Options:=[];
2343   F.DescFields:=DescFields;
2344   FCurrentIndexDef:=F;
2345   F.SetIndexProperties;
2346   if Active then
2347     begin
2348     FetchAll;
2349     BuildIndex(F.BufferIndex);
2350     Resync([rmCenter]);
2351     end;
2352   FPacketRecords:=-1;
2353 end;
2354 
2355 procedure TCustomBufDataset.SetIndexName(AValue: String);
2356 
2357 var
2358   F : TBufDatasetIndex;
2359   B : TDoubleLinkedBufIndex;
2360   N : String;
2361 
2362 begin
2363   N:=AValue;
2364   If (N='') then
2365     N:=SDefaultIndex;
2366   F:=FIndexes.FindIndex(N);
2367   if (F=Nil) and (AValue<>'') and not (csLoading in ComponentState) then
2368     DatabaseErrorFmt(SIndexNotFound,[AValue],Self);
2369   FIndexName:=AValue;
2370   if Assigned(F) then
2371     begin
2372     B:=F.BufferIndex as TDoubleLinkedBufIndex;
2373     if Assigned(CurrentIndexBuf) then
2374       B.FCurrentRecBuf:=(CurrentIndexBuf as TDoubleLinkedBufIndex).FCurrentRecBuf;
2375     FCurrentIndexDef:=F;
2376     if Active then
2377       Resync([rmCenter]);
2378     end
2379   else
2380     FCurrentIndexDef:=Nil;
2381 end;
2382 
2383 procedure TCustomBufDataset.SetMaxIndexesCount(const AValue: Integer);
2384 begin
2385   CheckInactive;
2386   if AValue > 1 then
2387     FMaxIndexesCount:=AValue
2388   else
2389     DatabaseError(SMinIndexes,Self);
2390 end;
2391 
2392 procedure TCustomBufDataset.InternalSetToRecord(Buffer: TRecordBuffer);
2393 begin
2394   CurrentIndexBuf.GotoBookmark(PBufBookmark(Buffer+FRecordSize));
2395 end;
2396 
2397 procedure TCustomBufDataset.SetBookmarkData(Buffer: TRecordBuffer; Data: Pointer);
2398 begin
2399   PBufBookmark(Buffer + FRecordSize)^ := PBufBookmark(Data)^;
2400 end;
2401 
2402 procedure TCustomBufDataset.SetBookmarkFlag(Buffer: TRecordBuffer; Value: TBookmarkFlag);
2403 begin
2404   PBufBookmark(Buffer + FRecordSize)^.BookmarkFlag := Value;
2405 end;
2406 
2407 procedure TCustomBufDataset.GetBookmarkData(Buffer: TRecordBuffer; Data: Pointer);
2408 begin
2409   PBufBookmark(Data)^ := PBufBookmark(Buffer + FRecordSize)^;
2410 end;
2411 
GetBookmarkFlagnull2412 function TCustomBufDataset.GetBookmarkFlag(Buffer: TRecordBuffer): TBookmarkFlag;
2413 begin
2414   Result := PBufBookmark(Buffer + FRecordSize)^.BookmarkFlag;
2415 end;
2416 
2417 procedure TCustomBufDataset.InternalGotoBookmark(ABookmark: Pointer);
2418 begin
2419   // note that ABookMark should be a PBufBookmark. But this way it can also be
2420   // a pointer to a TBufRecLinkItem
2421   CurrentIndexBuf.GotoBookmark(ABookmark);
2422 end;
2423 
getnextpacketnull2424 function TCustomBufDataset.getnextpacket : integer;
2425 
2426 var i : integer;
2427     pb : TRecordBuffer;
2428     T : TBufIndex;
2429 
2430 begin
2431   if FAllPacketsFetched then
2432     begin
2433     result := 0;
2434     exit;
2435     end;
2436   T:=CurrentIndexBuf;
2437   T.BeginUpdate;
2438 
2439   i := 0;
2440   pb := DefaultBufferIndex.SpareBuffer;
2441   while ((i < FPacketRecords) or (FPacketRecords = -1)) and (LoadBuffer(pb) = grOk) do
2442     begin
2443     with DefaultBufferIndex do
2444       begin
2445       AddRecord;
2446       pb := SpareBuffer;
2447       end;
2448     inc(i);
2449     end;
2450 
2451   T.EndUpdate;
2452   FBRecordCount := FBRecordCount + i;
2453   result := i;
2454 end;
2455 
TCustomBufDataset.GetFieldSizenull2456 function TCustomBufDataset.GetFieldSize(FieldDef : TFieldDef) : longint;
2457 
2458 begin
2459   case FieldDef.DataType of
2460     ftUnknown    : result := 0;
2461     ftString,
2462       ftGuid,
2463       ftFixedChar: result := FieldDef.Size*FieldDef.CharSize + 1;
2464     ftFixedWideChar,
2465       ftWideString:result := (FieldDef.Size + 1)*FieldDef.CharSize;
2466     ftSmallint,
2467       ftInteger,
2468       ftAutoInc,
2469       ftword     : result := sizeof(longint);
2470     ftBoolean    : result := sizeof(wordbool);
2471     ftBCD        : result := sizeof(currency);
2472     ftFmtBCD     : result := sizeof(TBCD);
2473     ftFloat,
2474       ftCurrency : result := sizeof(double);
2475     ftLargeInt   : result := sizeof(largeint);
2476     ftTime,
2477       ftDate,
2478       ftDateTime : result := sizeof(TDateTime);
2479     ftBytes      : result := FieldDef.Size;
2480     ftVarBytes   : result := FieldDef.Size + 2;
2481     ftVariant    : result := sizeof(variant);
2482     ftBlob,
2483       ftMemo,
2484       ftGraphic,
2485       ftFmtMemo,
2486       ftParadoxOle,
2487       ftDBaseOle,
2488       ftTypedBinary,
2489       ftOraBlob,
2490       ftOraClob,
2491       ftWideMemo : result := sizeof(TBufBlobField)
2492   else
2493     DatabaseErrorFmt(SUnsupportedFieldType,[Fieldtypenames[FieldDef.DataType]]);
2494   end;
2495 {$IFDEF FPC_REQUIRES_PROPER_ALIGNMENT}
2496   result:=Align(result,4);
2497 {$ENDIF}
2498 end;
2499 
GetRecordUpdateBuffernull2500 function TCustomBufDataset.GetRecordUpdateBuffer(const ABookmark : TBufBookmark; IncludePrior : boolean = false; AFindNext : boolean = false): boolean;
2501 
2502 var x        : integer;
2503     StartBuf : integer;
2504 
2505 begin
2506   if AFindNext then
2507     StartBuf := FCurrentUpdateBuffer + 1
2508   else
2509     StartBuf := 0;
2510   Result := False;
2511   for x := StartBuf to high(FUpdateBuffer) do
2512    if CurrentIndexBuf.SameBookmarks(@FUpdateBuffer[x].BookmarkData,@ABookmark) or
2513       (IncludePrior and (FUpdateBuffer[x].UpdateKind=ukDelete) and CurrentIndexBuf.SameBookmarks(@FUpdateBuffer[x].NextBookmarkData,@ABookmark)) then
2514     begin
2515     FCurrentUpdateBuffer := x;
2516     Result := True;
2517     break;
2518     end;
2519 end;
2520 
GetRecordUpdateBufferCachednull2521 function TCustomBufDataset.GetRecordUpdateBufferCached(const ABookmark: TBufBookmark;
2522   IncludePrior: boolean): boolean;
2523 begin
2524   // if the current update buffer matches, immediately return true
2525   if (FCurrentUpdateBuffer < length(FUpdateBuffer)) and (
2526       CurrentIndexBuf.SameBookmarks(@FUpdateBuffer[FCurrentUpdateBuffer].BookmarkData,@ABookmark) or
2527       (IncludePrior
2528         and (FUpdateBuffer[FCurrentUpdateBuffer].UpdateKind=ukDelete)
2529         and  CurrentIndexBuf.SameBookmarks(@FUpdateBuffer[FCurrentUpdateBuffer].NextBookmarkData,@ABookmark))) then
2530      begin
2531      Result := True;
2532      end
2533   else
2534     Result := GetRecordUpdateBuffer(ABookmark,IncludePrior);
2535 end;
2536 
TCustomBufDataset.LoadBuffernull2537 function TCustomBufDataset.LoadBuffer(Buffer : TRecordBuffer): TGetResult;
2538 
2539 var NullMask        : pbyte;
2540     x               : longint;
2541     CreateBlobField : boolean;
2542     BufBlob         : PBufBlobField;
2543 
2544 begin
2545   if not Fetch then
2546     begin
2547     Result := grEOF;
2548     FAllPacketsFetched := True;
2549     // This code has to be placed elsewhere. At least it should also run when
2550     // the datapacket is loaded from file ... see IntLoadRecordsFromFile
2551     BuildIndexes;
2552     Exit;
2553     end;
2554 
2555   NullMask := pointer(buffer);
2556   fillchar(Nullmask^,FNullmaskSize,0);
2557   inc(buffer,FNullmaskSize);
2558 
2559   for x := 0 to FieldDefs.Count-1 do
2560     begin
2561     if not LoadField(FieldDefs[x],buffer,CreateBlobField) then
2562       SetFieldIsNull(NullMask,x)
2563     else if CreateBlobField then
2564       begin
2565       BufBlob := PBufBlobField(Buffer);
2566       BufBlob^.BlobBuffer := GetNewBlobBuffer;
2567       LoadBlobIntoBuffer(FieldDefs[x],BufBlob);
2568       end;
2569     inc(buffer,GetFieldSize(FieldDefs[x]));
2570     end;
2571   Result := grOK;
2572 end;
2573 
GetCurrentBuffernull2574 function TCustomBufDataset.GetCurrentBuffer: TRecordBuffer;
2575 begin
2576   case State of
2577     dsFilter:        Result := FFilterBuffer;
2578     dsCalcFields:    Result := CalcBuffer;
2579     dsRefreshFields: Result := CurrentIndexBuf.CurrentBuffer
2580     else             Result := ActiveBuffer;
2581   end;
2582 end;
2583 
2584 
TCustomBufDataset.GetFieldDatanull2585 function TCustomBufDataset.GetFieldData(Field: TField; Buffer: Pointer;
2586   NativeFormat: Boolean): Boolean;
2587 begin
2588   Result := GetFieldData(Field, Buffer);
2589 end;
2590 
TCustomBufDataset.GetFieldDatanull2591 function TCustomBufDataset.GetFieldData(Field: TField; Buffer: Pointer): Boolean;
2592 
2593 var
2594   CurrBuff : TRecordBuffer;
2595 
2596 begin
2597   Result := False;
2598   if State = dsOldValue then
2599   begin
2600     if FSavedState = dsInsert then
2601       CurrBuff := nil // old values = null
2602     else if GetActiveRecordUpdateBuffer then
2603       CurrBuff := FUpdateBuffer[FCurrentUpdateBuffer].OldValuesBuffer
2604     else
2605       // There is no UpdateBuffer for ActiveRecord, so there are no explicit old values available
2606       // then we can assume, that old values = current values
2607       CurrBuff := CurrentIndexBuf.CurrentBuffer;
2608   end
2609   else
2610     CurrBuff := GetCurrentBuffer;
2611 
2612   if not assigned(CurrBuff) then Exit; //Null value
2613 
2614   If Field.FieldNo > 0 then // If =-1, then calculated/lookup field or =0 unbound field
2615     begin
2616     if GetFieldIsNull(pbyte(CurrBuff),Field.FieldNo-1) then
2617       Exit;
2618     if assigned(Buffer) then
2619       begin
2620       inc(CurrBuff,FFieldBufPositions[Field.FieldNo-1]);
2621       if Field.IsBlob then // we need GetFieldSize for BLOB but Field.DataSize for others - #36747
2622         Move(CurrBuff^, Buffer^, GetFieldSize(FieldDefs[Field.FieldNo-1]))
2623       else
2624         Move(CurrBuff^, Buffer^, Field.DataSize);
2625       end;
2626     Result := True;
2627     end
2628   else
2629     begin
2630     Inc(CurrBuff, GetRecordSize + Field.Offset);
2631     Result := Boolean(CurrBuff^);
2632     if Result and assigned(Buffer) then
2633       begin
2634       inc(CurrBuff);
2635       Move(CurrBuff^, Buffer^, Field.DataSize);
2636       end;
2637     end;
2638 end;
2639 
2640 procedure TCustomBufDataset.SetFieldData(Field: TField; Buffer: Pointer;
2641   NativeFormat: Boolean);
2642 begin
2643   SetFieldData(Field,Buffer);
2644 end;
2645 
2646 procedure TCustomBufDataset.SetFieldData(Field: TField; Buffer: Pointer);
2647 
2648 var CurrBuff : pointer;
2649     NullMask : pbyte;
2650 
2651 begin
2652   if not (State in dsWriteModes) then
2653     DatabaseErrorFmt(SNotEditing, [Name], Self);
2654   CurrBuff := GetCurrentBuffer;
2655   If Field.FieldNo > 0 then // If =-1, then calculated/lookup field or =0 unbound field
2656     begin
2657     if Field.ReadOnly and not (State in [dsSetKey, dsFilter, dsRefreshFields]) then
2658       DatabaseErrorFmt(SReadOnlyField, [Field.DisplayName]);
2659     if State in [dsEdit, dsInsert, dsNewValue] then
2660       Field.Validate(Buffer);
2661     NullMask := CurrBuff;
2662 
2663     inc(CurrBuff,FFieldBufPositions[Field.FieldNo-1]);
2664     if assigned(buffer) then
2665       begin
2666       if Field.IsBlob then // we need GetFieldSize for BLOB but Field.DataSize for others - #36747
2667         Move(Buffer^, CurrBuff^, GetFieldSize(FieldDefs[Field.FieldNo-1]))
2668       else
2669         Move(Buffer^, CurrBuff^, Field.DataSize);
2670       unSetFieldIsNull(NullMask,Field.FieldNo-1);
2671       end
2672     else
2673       SetFieldIsNull(NullMask,Field.FieldNo-1);
2674     end
2675   else
2676     begin
2677     Inc(CurrBuff, GetRecordSize + Field.Offset);
2678     Boolean(CurrBuff^) := Buffer <> nil;
2679     inc(CurrBuff);
2680     if assigned(Buffer) then
2681       Move(Buffer^, CurrBuff^, Field.DataSize);
2682     end;
2683   if not (State in [dsCalcFields, dsFilter, dsNewValue]) then
2684     DataEvent(deFieldChange, PtrInt(Field));
2685 end;
2686 
2687 procedure TCustomBufDataset.InternalDelete;
2688 var RemRec : pointer;
2689     RemRecBookmrk : TBufBookmark;
2690 begin
2691   InternalSetToRecord(ActiveBuffer);
2692   // Remove the record from all active indexes
2693   CurrentIndexBuf.StoreCurrentRecIntoBookmark(@RemRecBookmrk);
2694   RemRec := CurrentIndexBuf.CurrentBuffer;
2695   RemoveRecordFromIndexes(RemRecBookmrk);
2696 
2697   if not GetActiveRecordUpdateBuffer then
2698     begin
2699     FCurrentUpdateBuffer := length(FUpdateBuffer);
2700     SetLength(FUpdateBuffer,FCurrentUpdateBuffer+1);
2701     FUpdateBuffer[FCurrentUpdateBuffer].OldValuesBuffer := IntAllocRecordBuffer;
2702     move(RemRec^, FUpdateBuffer[FCurrentUpdateBuffer].OldValuesBuffer^,FRecordSize);
2703     end
2704   else
2705     begin
2706     if FUpdateBuffer[FCurrentUpdateBuffer].UpdateKind <> ukModify then
2707       begin
2708       FUpdateBuffer[FCurrentUpdateBuffer].OldValuesBuffer := nil;  //this 'disables' the updatebuffer
2709       // Do NOT release record buffer (pointed to by RemRecBookmrk.BookmarkData) here
2710       //  - When record is inserted and deleted (and memory released) and again inserted then the same memory block can be returned
2711       //    which leads to confusion, because we get the same BookmarkData for distinct records
2712       //  - In CancelUpdates when records are restored, it is expected that deleted records still exist in memory
2713       // There also could be record(s) in the update buffer that is linked to this record.
2714       end;
2715     end;
2716   CurrentIndexBuf.StoreCurrentRecIntoBookmark(@FUpdateBuffer[FCurrentUpdateBuffer].NextBookmarkData);
2717   FUpdateBuffer[FCurrentUpdateBuffer].BookmarkData := RemRecBookmrk;
2718   FUpdateBuffer[FCurrentUpdateBuffer].UpdateKind := ukDelete;
2719   dec(FBRecordCount);
2720 end;
2721 
2722 
2723 procedure TCustomBufDataset.ApplyRecUpdate(UpdateKind : TUpdateKind);
2724 
2725 begin
2726   raise EDatabaseError.Create(SApplyRecNotSupported);
2727 end;
2728 
2729 procedure TCustomBufDataset.CancelRecordUpdateBuffer(AUpdateBufferIndex: integer; var ABookmark: TBufBookmark);
2730 var
2731   ARecordBuffer: TRecordBuffer;
2732   NBookmark    : TBufBookmark;
2733   i            : integer;
2734 begin
2735   with FUpdateBuffer[AUpdateBufferIndex] do
2736     if Assigned(BookmarkData.BookmarkData) then // this is used to exclude buffers which are already handled
2737       begin
2738       case UpdateKind of
2739         ukModify:
2740           begin
2741           CurrentIndexBuf.GotoBookmark(@BookmarkData);
2742           move(TRecordBuffer(OldValuesBuffer)^, TRecordBuffer(CurrentIndexBuf.CurrentBuffer)^, FRecordSize);
2743           FreeRecordBuffer(OldValuesBuffer);
2744           end;
2745         ukDelete:
2746           if (assigned(OldValuesBuffer)) then
2747             begin
2748             CurrentIndexBuf.GotoBookmark(@NextBookmarkData);
2749             CurrentIndexBuf.InsertRecordBeforeCurrentRecord(TRecordBuffer(BookmarkData.BookmarkData));
2750             CurrentIndexBuf.ScrollBackward;
2751             move(TRecordBuffer(OldValuesBuffer)^, TRecordBuffer(CurrentIndexBuf.CurrentBuffer)^, FRecordSize);
2752             FreeRecordBuffer(OldValuesBuffer);
2753             inc(FBRecordCount);
2754             end;
2755         ukInsert:
2756           begin
2757           CurrentIndexBuf.GotoBookmark(@BookmarkData);
2758           ARecordBuffer := CurrentIndexBuf.CurrentRecord;
2759 
2760           // Find next record's bookmark
2761           CurrentIndexBuf.DoScrollForward;
2762           CurrentIndexBuf.StoreCurrentRecIntoBookmark(@NBookmark);
2763           // Process (re-link) all update buffers linked to this record before this record is removed
2764           //  Modified record #1, which is later deleted can be linked to another inserted record #2. In this case deleted record #1 precedes inserted #2 in update buffer.
2765           //  Deleted records, which are deleted after this record is inserted are in update buffer after this record.
2766           //  if we need revert inserted record which is linked from another deleted records, then we must re-link these records
2767           for i:=0 to high(FUpdateBuffer) do
2768             if (FUpdateBuffer[i].UpdateKind = ukDelete) and
2769                (FUpdateBuffer[i].NextBookmarkData.BookmarkData = BookmarkData.BookmarkData) then
2770               FUpdateBuffer[i].NextBookmarkData := NBookmark;
2771 
2772           // ReSync won't work if the CurrentBuffer is freed ... so in this case move to next/prior record
2773           if CurrentIndexBuf.SameBookmarks(@BookmarkData,@ABookmark) then
2774             with CurrentIndexBuf do
2775               begin
2776               GotoBookmark(@ABookmark);
2777               if ScrollForward = grEOF then
2778                 if ScrollBackward = grBOF then
2779                   ScrollLast;  // last record will be removed from index, so move to spare record
2780               StoreCurrentRecIntoBookmark(@ABookmark);
2781               end;
2782 
2783           RemoveRecordFromIndexes(BookmarkData);
2784           FreeRecordBuffer(ARecordBuffer);
2785           dec(FBRecordCount);
2786           end;
2787       end;
2788       BookmarkData.BookmarkData := nil;
2789       end;
2790 end;
2791 
2792 procedure TCustomBufDataset.RevertRecord;
2793 var
2794   ABookmark : TBufBookmark;
2795 begin
2796   CheckBrowseMode;
2797 
2798   if GetActiveRecordUpdateBuffer then
2799   begin
2800     CurrentIndexBuf.StoreCurrentRecIntoBookmark(@ABookmark);
2801 
2802     CancelRecordUpdateBuffer(FCurrentUpdateBuffer, ABookmark);
2803 
2804     // remove update record of current record from update-buffer array
2805     Move(FUpdateBuffer[FCurrentUpdateBuffer+1], FUpdateBuffer[FCurrentUpdateBuffer], (High(FUpdateBuffer)-FCurrentUpdateBuffer)*SizeOf(TRecUpdateBuffer));
2806     SetLength(FUpdateBuffer, High(FUpdateBuffer));
2807 
2808     CurrentIndexBuf.GotoBookmark(@ABookmark);
2809 
2810     Resync([]);
2811   end;
2812 end;
2813 
2814 procedure TCustomBufDataset.CancelUpdates;
2815 var
2816   ABookmark : TBufBookmark;
2817   r         : Integer;
2818 begin
2819   CheckBrowseMode;
2820 
2821   if Length(FUpdateBuffer) > 0 then
2822     begin
2823     CurrentIndexBuf.StoreCurrentRecIntoBookmark(@ABookmark);
2824 
2825     for r := High(FUpdateBuffer) downto 0 do
2826       CancelRecordUpdateBuffer(r, ABookmark);
2827     SetLength(FUpdateBuffer, 0);
2828 
2829     CurrentIndexBuf.GotoBookmark(@ABookmark);
2830 
2831     Resync([]);
2832     end;
2833 end;
2834 
2835 procedure TCustomBufDataset.SetOnUpdateError(const AValue: TResolverErrorEvent);
2836 
2837 begin
2838   FOnUpdateError := AValue;
2839 end;
2840 
2841 procedure TCustomBufDataset.ApplyUpdates; // For backward compatibility
2842 
2843 begin
2844   ApplyUpdates(0);
2845 end;
2846 
2847 procedure TCustomBufDataset.ApplyUpdates(MaxErrors: Integer);
2848 
2849 var r            : Integer;
2850     FailedCount  : integer;
2851     Response     : TResolverResponse;
2852     StoreCurrRec : TBufBookmark;
2853     AUpdateError : EUpdateError;
2854 
2855 begin
2856   CheckBrowseMode;
2857 
2858   CurrentIndexBuf.StoreCurrentRecIntoBookmark(@StoreCurrRec);
2859 
2860   r := 0;
2861   FailedCount := 0;
2862   Response := rrApply;
2863   DisableControls;
2864   try
2865     while (r < Length(FUpdateBuffer)) and (Response <> rrAbort) do
2866       begin
2867       // If the record is first inserted and afterwards deleted, do nothing
2868       if not ((FUpdateBuffer[r].UpdateKind=ukDelete) and not (assigned(FUpdateBuffer[r].OldValuesBuffer))) then
2869         begin
2870         CurrentIndexBuf.GotoBookmark(@FUpdateBuffer[r].BookmarkData);
2871         // Synchronise the CurrentBuffer to the ActiveBuffer
2872         CurrentRecordToBuffer(ActiveBuffer);
2873         Response := rrApply;
2874         try
2875           ApplyRecUpdate(FUpdateBuffer[r].UpdateKind);
2876         except
2877           on E: EDatabaseError do
2878             begin
2879             Inc(FailedCount);
2880             if FailedCount > word(MaxErrors) then
2881               Response := rrAbort
2882             else
2883               Response := rrSkip;
2884             if assigned(FOnUpdateError) then
2885               begin
2886               AUpdateError := PSGetUpdateException(Exception(AcquireExceptionObject), nil);
2887               FOnUpdateError(Self, Self, AUpdateError, FUpdateBuffer[r].UpdateKind, Response);
2888               AUpdateError.Free;
2889               if Response in [rrApply, rrIgnore] then dec(FailedCount);
2890               if Response = rrApply then dec(r);
2891               end
2892             else if Response = rrAbort then
2893               begin
2894               AUpdateError := PSGetUpdateException(Exception(AcquireExceptionObject), nil);
2895               raise AUpdateError;
2896               end;
2897             end
2898           else
2899             raise;
2900         end;
2901         if Response in [rrApply, rrIgnore] then
2902           begin
2903           FreeRecordBuffer(FUpdateBuffer[r].OldValuesBuffer);
2904           if FUpdateBuffer[r].UpdateKind = ukDelete then
2905             FreeRecordBuffer( TRecordBuffer(FUpdateBuffer[r].BookmarkData.BookmarkData));
2906           FUpdateBuffer[r].BookmarkData.BookmarkData := nil;
2907           end
2908         end;
2909       inc(r);
2910       end;
2911   finally
2912     if (FailedCount=0) and Not ManualMergeChangeLog then
2913       MergeChangeLog;
2914     InternalGotoBookmark(@StoreCurrRec);
2915     Resync([]);
2916     EnableControls;
2917   end;
2918 end;
2919 
2920 procedure TCustomBufDataset.MergeChangeLog;
2921 
2922 var r            : Integer;
2923 
2924 begin
2925   for r:=0 to length(FUpdateBuffer)-1 do
2926     if assigned(FUpdateBuffer[r].OldValuesBuffer) then
2927       FreeMem(FUpdateBuffer[r].OldValuesBuffer);
2928   SetLength(FUpdateBuffer,0);
2929 
2930   if assigned(FUpdateBlobBuffers) then for r:=0 to length(FUpdateBlobBuffers)-1 do
2931     if assigned(FUpdateBlobBuffers[r]) then
2932       begin
2933       // update blob buffer is already referenced from record buffer (see InternalPost)
2934       if FUpdateBlobBuffers[r]^.OrgBufID >= 0 then
2935         begin
2936         FreeBlobBuffer(FBlobBuffers[FUpdateBlobBuffers[r]^.OrgBufID]);
2937         FBlobBuffers[FUpdateBlobBuffers[r]^.OrgBufID] := FUpdateBlobBuffers[r];
2938         end
2939       else
2940         begin
2941         setlength(FBlobBuffers,length(FBlobBuffers)+1);
2942         FUpdateBlobBuffers[r]^.OrgBufID := high(FBlobBuffers);
2943         FBlobBuffers[high(FBlobBuffers)] := FUpdateBlobBuffers[r];
2944         end;
2945       end;
2946   SetLength(FUpdateBlobBuffers,0);
2947 end;
2948 
2949 
2950 procedure TCustomBufDataset.InternalCancel;
2951 
2952 Var i            : integer;
2953 
2954 begin
2955   if assigned(FUpdateBlobBuffers) then for i:=0 to high(FUpdateBlobBuffers) do
2956     if assigned(FUpdateBlobBuffers[i]) and (FUpdateBlobBuffers[i]^.FieldNo>0) then
2957       FreeBlobBuffer(FUpdateBlobBuffers[i]);
2958 end;
2959 
2960 procedure TCustomBufDataset.InternalPost;
2961 
2962 Var ABuff        : TRecordBuffer;
2963     i            : integer;
2964     ABookmark    : PBufBookmark;
2965 
2966 begin
2967   inherited InternalPost;
2968 
2969   if assigned(FUpdateBlobBuffers) then for i:=0 to high(FUpdateBlobBuffers) do
2970    if assigned(FUpdateBlobBuffers[i]) and (FUpdateBlobBuffers[i]^.FieldNo>0) then
2971     FUpdateBlobBuffers[i]^.FieldNo := -1;
2972 
2973   if State = dsInsert then
2974     begin
2975     if assigned(FAutoIncField) then
2976       begin
2977       FAutoIncField.AsInteger := FAutoIncValue;
2978       inc(FAutoIncValue);
2979       end;
2980     // The active buffer is the newly created TDataSet record,
2981     // from which the bookmark is set to the record where the new record should be
2982     // inserted
2983     ABookmark := PBufBookmark(ActiveBuffer + FRecordSize);
2984     // Create the new record buffer
2985     ABuff := IntAllocRecordBuffer;
2986 
2987     // Add new record to all active indexes
2988     for i := 0 to FIndexes.Count-1 do
2989       if BufIndexdefs[i].IsActiveIndex(FCurrentIndexDef) then
2990       begin
2991         if ABookmark^.BookmarkFlag = bfEOF then
2992           // append at end
2993           BufIndexes[i].ScrollLast
2994         else
2995           // insert (before current record)
2996           BufIndexes[i].GotoBookmark(ABookmark);
2997 
2998         // insert new record before current record
2999         BufIndexes[i].InsertRecordBeforeCurrentRecord(ABuff);
3000         // newly inserted record becomes current record
3001         BufIndexes[i].ScrollBackward;
3002       end;
3003 
3004     // Link the newly created record buffer to the newly created TDataSet record
3005     CurrentIndexBuf.StoreCurrentRecIntoBookmark(ABookmark);
3006     ABookmark^.BookmarkFlag := bfInserted;
3007 
3008     inc(FBRecordCount);
3009     end
3010   else
3011     InternalSetToRecord(ActiveBuffer);
3012 
3013   // If there is no updatebuffer already, add one
3014   if not GetActiveRecordUpdateBuffer then
3015     begin
3016     // Add a new updatebuffer
3017     FCurrentUpdateBuffer := length(FUpdateBuffer);
3018     SetLength(FUpdateBuffer,FCurrentUpdateBuffer+1);
3019 
3020     // Store a bookmark of the current record into the updatebuffer's bookmark
3021     CurrentIndexBuf.StoreCurrentRecIntoBookmark(@FUpdateBuffer[FCurrentUpdateBuffer].BookmarkData);
3022 
3023     if State = dsEdit then
3024       begin
3025       // Create an OldValues buffer with the old values of the record
3026       FUpdateBuffer[FCurrentUpdateBuffer].UpdateKind := ukModify;
3027       FUpdateBuffer[FCurrentUpdateBuffer].OldValuesBuffer := IntAllocRecordBuffer;
3028       // Move only the real data
3029       move(CurrentIndexBuf.CurrentBuffer^, FUpdateBuffer[FCurrentUpdateBuffer].OldValuesBuffer^, FRecordSize);
3030       end
3031     else
3032       begin
3033       FUpdateBuffer[FCurrentUpdateBuffer].UpdateKind := ukInsert;
3034       FUpdateBuffer[FCurrentUpdateBuffer].OldValuesBuffer := nil;
3035       end;
3036     end;
3037 
3038   Move(ActiveBuffer^, CurrentIndexBuf.CurrentBuffer^, FRecordSize);
3039 
3040   // new data are now in current record so reorder current record if needed
3041   for i := 0 to FIndexes.Count-1 do
3042     if BufIndexDefs[i].MustBuild(FCurrentIndexDef) then
3043       BufIndexes[i].OrderCurrentRecord;
3044 end;
3045 
3046 procedure TCustomBufDataset.CalcRecordSize;
3047 
3048 var x : longint;
3049 
3050 begin
3051   FNullmaskSize := (FieldDefs.Count+7) div 8;
3052 {$IFDEF FPC_REQUIRES_PROPER_ALIGNMENT}
3053   FNullmaskSize:=Align(FNullmaskSize,4);
3054 {$ENDIF}
3055   FRecordSize := FNullmaskSize;
3056   SetLength(FFieldBufPositions,FieldDefs.count);
3057   for x := 0 to FieldDefs.count-1 do
3058     begin
3059     FFieldBufPositions[x] := FRecordSize;
3060     inc(FRecordSize, GetFieldSize(FieldDefs[x]));
3061     end;
3062 end;
3063 
GetIndexFieldNamesnull3064 function TCustomBufDataset.GetIndexFieldNames: String;
3065 
3066 var
3067   i, p: integer;
3068   s: string;
3069   IndexBuf: TBufIndex;
3070 
3071 begin
3072   Result := FIndexFieldNames;
3073   IndexBuf:=CurrentIndexBuf;
3074   if (IndexBuf=Nil) then
3075     Exit;
3076   Result:='';
3077   for i := 1 to WordCount(IndexBuf.FieldsName, [Limiter]) do
3078   begin
3079     s := ExtractDelimited(i, IndexBuf.FieldsName, [Limiter]);
3080     p := Pos(s, IndexBuf.DescFields);
3081     if p>0 then
3082       s := s + Desc;
3083     Result := Result + Limiter + s;
3084   end;
3085   if (Length(Result)>0) and (Result[1]=Limiter) then
3086     system.Delete(Result, 1, 1);
3087 end;
3088 
GetIndexNamenull3089 function TCustomBufDataset.GetIndexName: String;
3090 begin
3091   if (FIndexes.Count>0) and (CurrentIndexBuf <> nil) then
3092     result := CurrentIndexBuf.Name
3093   else
3094     result := FIndexName;
3095 end;
3096 
GetBufUniDirectionalnull3097 function TCustomBufDataset.GetBufUniDirectional: boolean;
3098 begin
3099   result := IsUniDirectional;
3100 end;
3101 
GetPacketReadernull3102 function TCustomBufDataset.GetPacketReader(const Format: TDataPacketFormat; const AStream: TStream): TDataPacketReader;
3103 
3104 var
3105   APacketReader: TDataPacketReader;
3106   APacketReaderReg: TDatapacketReaderRegistration;
3107   Fmt : TDataPacketFormat;
3108 begin
3109   fmt:=Format;
3110   if (Fmt=dfDefault) then
3111     fmt:=DefaultReadFileFormat;
3112   if fmt=dfDefault then
3113     APacketReader := CreateDefaultPacketReader(AStream)
3114   else if GetRegisterDatapacketReader(AStream, fmt, APacketReaderReg) then
3115     APacketReader := APacketReaderReg.ReaderClass.Create(Self, AStream)
3116   else if TFpcBinaryDatapacketReader.RecognizeStream(AStream) then
3117     begin
3118     AStream.Seek(0, soFromBeginning);
3119     APacketReader := TFpcBinaryDatapacketReader.Create(Self, AStream)
3120     end
3121   else
3122     DatabaseError(SStreamNotRecognised,Self);
3123   Result:=APacketReader;
3124 end;
3125 
GetRecordSizenull3126 function TCustomBufDataset.GetRecordSize : Word;
3127 
3128 begin
3129   result := FRecordSize + BookmarkSize;
3130 end;
3131 
GetChangeCountnull3132 function TCustomBufDataset.GetChangeCount: integer;
3133 
3134 begin
3135   result := length(FUpdateBuffer);
3136 end;
3137 
3138 
3139 procedure TCustomBufDataset.InternalInitRecord(Buffer:  TRecordBuffer);
3140 
3141 begin
3142   FillChar(Buffer^, FRecordSize, #0);
3143 
3144   fillchar(Buffer^,FNullmaskSize,255);
3145 end;
3146 
3147 procedure TCustomBufDataset.SetRecNo(Value: Longint);
3148 
3149 var ABookmark : TBufBookmark;
3150 
3151 begin
3152   CheckBrowseMode;
3153   if Value > RecordCount then
3154     repeat until (getnextpacket < FPacketRecords) or (Value <= RecordCount) or (FPacketRecords = -1);
3155 
3156   if (Value > RecordCount) or (Value < 1) then
3157     begin
3158     DatabaseError(SNoSuchRecord, Self);
3159     exit;
3160     end;
3161 
3162   CurrentIndexBuf.RecNo:=Value;
3163   CurrentIndexBuf.StoreCurrentRecIntoBookmark(@ABookmark);
3164   GotoBookmark(@ABookmark);
3165 end;
3166 
GetRecNonull3167 function TCustomBufDataset.GetRecNo: Longint;
3168 
3169 begin
3170   if IsUniDirectional then
3171     Result := -1
3172   else if (FBRecordCount = 0) or (State = dsInsert) then
3173     Result := 0
3174   else
3175     begin
3176     UpdateCursorPos;
3177     Result := CurrentIndexBuf.RecNo;
3178     end;
3179 end;
3180 
IsCursorOpennull3181 function TCustomBufDataset.IsCursorOpen: Boolean;
3182 
3183 begin
3184   Result := FOpen;
3185 end;
3186 
GetRecordCountnull3187 function TCustomBufDataset.GetRecordCount: Longint;
3188 begin
3189   if Active then
3190     Result := FBRecordCount
3191   else
3192     Result:=0;
3193 end;
3194 
UpdateStatusnull3195 function TCustomBufDataset.UpdateStatus: TUpdateStatus;
3196 
3197 begin
3198   Result:=usUnmodified;
3199   if GetActiveRecordUpdateBuffer then
3200     case FUpdateBuffer[FCurrentUpdateBuffer].UpdateKind of
3201       ukModify : Result := usModified;
3202       ukInsert : Result := usInserted;
3203       ukDelete : Result := usDeleted;
3204     end;
3205 end;
3206 
GetNewBlobBuffernull3207 function TCustomBufDataset.GetNewBlobBuffer : PBlobBuffer;
3208 
3209 var ABlobBuffer : PBlobBuffer;
3210 
3211 begin
3212   setlength(FBlobBuffers,length(FBlobBuffers)+1);
3213   new(ABlobBuffer);
3214   fillbyte(ABlobBuffer^,sizeof(ABlobBuffer^),0);
3215   ABlobBuffer^.OrgBufID := high(FBlobBuffers);
3216   FBlobBuffers[high(FBlobBuffers)] := ABlobBuffer;
3217   result := ABlobBuffer;
3218 end;
3219 
GetNewWriteBlobBuffernull3220 function TCustomBufDataset.GetNewWriteBlobBuffer : PBlobBuffer;
3221 
3222 var ABlobBuffer : PBlobBuffer;
3223 
3224 begin
3225   setlength(FUpdateBlobBuffers,length(FUpdateBlobBuffers)+1);
3226   new(ABlobBuffer);
3227   fillbyte(ABlobBuffer^,sizeof(ABlobBuffer^),0);
3228   FUpdateBlobBuffers[high(FUpdateBlobBuffers)] := ABlobBuffer;
3229   result := ABlobBuffer;
3230 end;
3231 
3232 procedure TCustomBufDataset.FreeBlobBuffer(var ABlobBuffer: PBlobBuffer);
3233 
3234 begin
3235   if not Assigned(ABlobBuffer) then Exit;
3236   FreeMem(ABlobBuffer^.Buffer, ABlobBuffer^.Size);
3237   Dispose(ABlobBuffer);
3238   ABlobBuffer := Nil;
3239 end;
3240 
3241 { TBufBlobStream }
3242 
Seeknull3243 function TBufBlobStream.Seek(Offset: Longint; Origin: Word): Longint;
3244 
3245 begin
3246   Case Origin of
3247     soFromBeginning : FPosition:=Offset;
3248     soFromEnd       : FPosition:=FBlobBuffer^.Size+Offset;
3249     soFromCurrent   : FPosition:=FPosition+Offset;
3250   end;
3251   Result:=FPosition;
3252 end;
3253 
3254 
Readnull3255 function TBufBlobStream.Read(var Buffer; Count: Longint): Longint;
3256 
3257 var ptr : pointer;
3258 
3259 begin
3260   if FPosition + Count > FBlobBuffer^.Size then
3261     Count := FBlobBuffer^.Size-FPosition;
3262   ptr := FBlobBuffer^.Buffer+FPosition;
3263   move(ptr^, Buffer, Count);
3264   inc(FPosition, Count);
3265   result := Count;
3266 end;
3267 
Writenull3268 function TBufBlobStream.Write(const Buffer; Count: Longint): Longint;
3269 
3270 var ptr : pointer;
3271 
3272 begin
3273   ReAllocMem(FBlobBuffer^.Buffer, FPosition+Count);
3274   ptr := FBlobBuffer^.Buffer+FPosition;
3275   move(buffer, ptr^, Count);
3276   inc(FBlobBuffer^.Size, Count);
3277   inc(FPosition, Count);
3278   FModified := True;
3279   Result := Count;
3280 end;
3281 
3282 constructor TBufBlobStream.Create(Field: TBlobField; Mode: TBlobStreamMode);
3283 
3284 var bufblob : TBufBlobField;
3285     CurrBuff : TRecordBuffer;
3286 
3287 begin
3288   FField := Field;
3289   FDataSet := Field.DataSet as TCustomBufDataset;
3290   with FDataSet do
3291     if Mode = bmRead then
3292       begin
3293       if not Field.GetData(@bufblob) then
3294         DatabaseError(SFieldIsNull);
3295       if not assigned(bufblob.BlobBuffer) then
3296         begin
3297         bufblob.BlobBuffer := GetNewBlobBuffer;
3298         LoadBlobIntoBuffer(FieldDefs[Field.FieldNo-1], @bufblob);
3299         end;
3300       FBlobBuffer := bufblob.BlobBuffer;
3301       end
3302     else if Mode=bmWrite then
3303       begin
3304       FBlobBuffer := GetNewWriteBlobBuffer;
3305       FBlobBuffer^.FieldNo := Field.FieldNo;
3306       if Field.GetData(@bufblob) and assigned(bufblob.BlobBuffer) then
3307         FBlobBuffer^.OrgBufID := bufblob.BlobBuffer^.OrgBufID
3308       else
3309         FBlobBuffer^.OrgBufID := -1;
3310       bufblob.BlobBuffer := FBlobBuffer;
3311 
3312       CurrBuff := GetCurrentBuffer;
3313       // unset null flag for blob field
3314       unSetFieldIsNull(PByte(CurrBuff), Field.FieldNo-1);
3315       // redirect pointer in current record buffer to new write blob buffer
3316       inc(CurrBuff, FDataSet.FFieldBufPositions[Field.FieldNo-1]);
3317       Move(bufblob, CurrBuff^, FDataSet.GetFieldSize(FDataSet.FieldDefs[Field.FieldNo-1]));
3318       FModified := True;
3319       end;
3320 end;
3321 
3322 destructor TBufBlobStream.Destroy;
3323 begin
3324   if FModified then
3325     begin
3326     // if TBufBlobStream was requested, but no data was written, then Size = 0;
3327     //  used by TBlobField.Clear, so in this case set Field to null
3328     //FField.Modified := True; // should be set to True, but TBlobField.Modified is never reset
3329 
3330     if not (FDataSet.State in [dsFilter, dsCalcFields, dsNewValue]) then
3331       begin
3332       if FBlobBuffer^.Size = 0 then // empty blob = IsNull
3333         // blob stream should be destroyed while DataSet is in write state
3334         SetFieldIsNull(PByte(FDataSet.GetCurrentBuffer), FField.FieldNo-1);
3335       FDataSet.DataEvent(deFieldChange, PtrInt(FField));
3336       end;
3337     end;
3338   inherited Destroy;
3339 end;
3340 
CreateBlobStreamnull3341 function TCustomBufDataset.CreateBlobStream(Field: TField; Mode: TBlobStreamMode): TStream;
3342 
3343 var bufblob : TBufBlobField;
3344 
3345 begin
3346   Result := nil;
3347   case Mode of
3348     bmRead:
3349       if not Field.GetData(@bufblob) then Exit;
3350     bmWrite:
3351       begin
3352       if not (State in [dsEdit, dsInsert, dsFilter, dsCalcFields]) then
3353         DatabaseErrorFmt(SNotEditing, [Name], Self);
3354       if Field.ReadOnly and not (State in [dsSetKey, dsFilter]) then
3355         DatabaseErrorFmt(SReadOnlyField, [Field.DisplayName]);
3356       end;
3357   end;
3358   Result := TBufBlobStream.Create(Field as TBlobField, Mode);
3359 end;
3360 
3361 procedure TCustomBufDataset.SetDatasetPacket(AReader: TDataPacketReader);
3362 begin
3363   FDatasetReader := AReader;
3364   try
3365     Open;
3366   finally
3367     FDatasetReader := nil;
3368   end;
3369 end;
3370 
3371 procedure TCustomBufDataset.GetDatasetPacket(AWriter: TDataPacketReader);
3372 
3373   procedure StoreUpdateBuffer(AUpdBuffer : TRecUpdateBuffer; var ARowState: TRowState);
3374   var AThisRowState : TRowState;
3375       AStoreUpdBuf  : Integer;
3376   begin
3377     if AUpdBuffer.UpdateKind = ukModify then
3378       begin
3379       AThisRowState := [rsvOriginal];
3380       ARowState:=[rsvUpdated];
3381       end
3382     else if AUpdBuffer.UpdateKind = ukDelete then
3383       begin
3384       AStoreUpdBuf:=FCurrentUpdateBuffer;
3385       if GetRecordUpdateBuffer(AUpdBuffer.BookmarkData,True,False) then
3386         repeat
3387           if CurrentIndexBuf.SameBookmarks(@FUpdateBuffer[FCurrentUpdateBuffer].NextBookmarkData, @AUpdBuffer.BookmarkData) then
3388             StoreUpdateBuffer(FUpdateBuffer[FCurrentUpdateBuffer], ARowState);
3389         until not GetRecordUpdateBuffer(AUpdBuffer.BookmarkData,True,True);
3390       FCurrentUpdateBuffer:=AStoreUpdBuf;
3391       AThisRowState := [rsvDeleted];
3392       end
3393     else // ie: UpdateKind = ukInsert
3394       ARowState := [rsvInserted];
3395 
3396     FFilterBuffer:=AUpdBuffer.OldValuesBuffer;
3397     // OldValuesBuffer is nil if the record is either inserted or inserted and then deleted
3398     if assigned(FFilterBuffer) then
3399       FDatasetReader.StoreRecord(AThisRowState,FCurrentUpdateBuffer);
3400   end;
3401 
3402   procedure HandleUpdateBuffersFromRecord(AFindNext : boolean; ARecBookmark : TBufBookmark; var ARowState: TRowState);
3403   var StoreUpdBuf1,StoreUpdBuf2 : Integer;
3404   begin
3405     if not AFindNext then ARowState:=[];
3406     if GetRecordUpdateBuffer(ARecBookmark,True,AFindNext) then
3407       begin
3408       if FUpdateBuffer[FCurrentUpdateBuffer].UpdateKind=ukDelete then
3409         begin
3410         StoreUpdBuf1:=FCurrentUpdateBuffer;
3411         HandleUpdateBuffersFromRecord(True,ARecBookmark,ARowState);
3412         StoreUpdBuf2:=FCurrentUpdateBuffer;
3413         FCurrentUpdateBuffer:=StoreUpdBuf1;
3414         StoreUpdateBuffer(FUpdateBuffer[StoreUpdBuf1], ARowState);
3415         FCurrentUpdateBuffer:=StoreUpdBuf2;
3416         end
3417       else
3418         begin
3419         StoreUpdateBuffer(FUpdateBuffer[FCurrentUpdateBuffer], ARowState);
3420         HandleUpdateBuffersFromRecord(True,ARecBookmark,ARowState);
3421         end;
3422       end
3423   end;
3424 
3425 var ScrollResult   : TGetResult;
3426     SavedState     : TDataSetState;
3427     ABookMark      : PBufBookmark;
3428     ATBookmark     : TBufBookmark;
3429     RowState       : TRowState;
3430 
3431 begin
3432   FDatasetReader := AWriter;
3433   try
3434     //  CheckActive;
3435     ABookMark:=@ATBookmark;
3436     FDatasetReader.StoreFieldDefs(FAutoIncValue);
3437 
3438     SavedState:=SetTempState(dsFilter);
3439     ScrollResult:=CurrentIndexBuf.ScrollFirst;
3440     while ScrollResult=grOK do
3441       begin
3442       RowState:=[];
3443       CurrentIndexBuf.StoreCurrentRecIntoBookmark(ABookmark);
3444       // updates related to current record are stored first
3445       HandleUpdateBuffersFromRecord(False,ABookmark^,RowState);
3446       // now store current record
3447       FFilterBuffer:=CurrentIndexBuf.CurrentBuffer;
3448       if RowState=[] then
3449         FDatasetReader.StoreRecord([])
3450       else
3451         FDatasetReader.StoreRecord(RowState,FCurrentUpdateBuffer);
3452 
3453       ScrollResult:=CurrentIndexBuf.ScrollForward;
3454       if ScrollResult<>grOK then
3455         begin
3456         if getnextpacket>0 then
3457           ScrollResult := CurrentIndexBuf.ScrollForward;
3458         end;
3459       end;
3460     // There could be an update buffer linked to the last (spare) record
3461     CurrentIndexBuf.StoreSpareRecIntoBookmark(ABookmark);
3462     HandleUpdateBuffersFromRecord(False,ABookmark^,RowState);
3463 
3464     RestoreState(SavedState);
3465 
3466     FDatasetReader.FinalizeStoreRecords;
3467   finally
3468     FDatasetReader := nil;
3469   end;
3470 end;
3471 
3472 procedure TCustomBufDataset.LoadFromStream(AStream: TStream; Format: TDataPacketFormat);
3473 var APacketReader : TDataPacketReader;
3474 begin
3475   CheckBiDirectional;
3476   APacketReader:=GetPacketReader(Format, AStream);
3477   try
3478     SetDatasetPacket(APacketReader);
3479   finally
3480     APacketReader.Free;
3481   end;
3482 end;
3483 
3484 procedure TCustomBufDataset.SaveToStream(AStream: TStream; Format: TDataPacketFormat);
3485 var APacketReaderReg : TDatapacketReaderRegistration;
3486     APacketWriter : TDataPacketReader;
3487     Fmt : TDataPacketFormat;
3488 begin
3489   CheckBiDirectional;
3490   fmt:=Format;
3491   if Fmt=dfDefault then
3492     fmt:=DefaultWriteFileFormat;
3493   if fmt=dfDefault then
3494     APacketWriter := CreateDefaultPacketReader(AStream)
3495   else if GetRegisterDatapacketReader(Nil,fmt,APacketReaderReg) then
3496     APacketWriter := APacketReaderReg.ReaderClass.Create(Self, AStream)
3497   else if fmt = dfBinary then
3498     APacketWriter := TFpcBinaryDatapacketReader.Create(Self, AStream)
3499   else
3500     DatabaseError(SNoReaderClassRegistered,Self);
3501   try
3502     GetDatasetPacket(APacketWriter);
3503   finally
3504     APacketWriter.Free;
3505   end;
3506 end;
3507 
3508 procedure TCustomBufDataset.LoadFromFile(AFileName: string; Format: TDataPacketFormat);
3509 
3510 var
3511   AFileStream : TFileStream;
3512 
3513 begin
3514   if AFileName='' then
3515      AFileName := FFileName;
3516   AFileStream := TFileStream.Create(AFileName,fmOpenRead);
3517   try
3518     LoadFromStream(AFileStream, Format);
3519   finally
3520     AFileStream.Free;
3521   end;
3522 end;
3523 
3524 procedure TCustomBufDataset.SaveToFile(AFileName: string; Format: TDataPacketFormat);
3525 
3526 var
3527   AFileStream : TFileStream;
3528 
3529 begin
3530   if AFileName='' then
3531     AFileName := FFileName;
3532   AFileStream := TFileStream.Create(AFileName,fmCreate);
3533   try
3534     SaveToStream(AFileStream, Format);
3535   finally
3536     AFileStream.Free;
3537   end;
3538 end;
3539 
3540 procedure TCustomBufDataset.CreateDataset;
3541 
3542 var
3543   AStoreFileName: string;
3544 
3545 begin
3546   CheckInactive;
3547   if ((Fields.Count=0) or (FieldDefs.Count=0)) then
3548     begin
3549     if (FieldDefs.Count>0) then
3550       CreateFields
3551     else if (Fields.Count>0) then
3552       begin
3553       InitFieldDefsFromFields;
3554       BindFields(True);
3555       end
3556     else
3557       raise Exception.Create(SErrNoFieldsDefined);
3558     end;
3559   if FAutoIncValue<0 then
3560     FAutoIncValue:=1;
3561   // When a FileName is set, do not read from this file; we want empty dataset
3562   AStoreFileName:=FFileName;
3563   FFileName := '';
3564   try
3565     Open;
3566   finally
3567     FFileName:=AStoreFileName;
3568   end;
3569 end;
3570 
3571 procedure TCustomBufDataset.Clear;
3572 begin
3573   Close;
3574   FieldDefs.Clear;
3575   Fields.Clear;
3576 end;
3577 
BookmarkValidnull3578 function TCustomBufDataset.BookmarkValid(ABookmark: TBookmark): Boolean;
3579 begin
3580   Result:=Assigned(CurrentIndexBuf) and CurrentIndexBuf.BookmarkValid(pointer(ABookmark));
3581 end;
3582 
CompareBookmarksnull3583 function TCustomBufDataset.CompareBookmarks(Bookmark1, Bookmark2: TBookmark): Longint;
3584 begin
3585   if Bookmark1 = Bookmark2 then
3586     Result := 0
3587   else if not assigned(Bookmark1) then
3588     Result := 1
3589   else if not assigned(Bookmark2) then
3590     Result := -1
3591   else if assigned(CurrentIndexBuf) then
3592     Result := CurrentIndexBuf.CompareBookmarks(pointer(Bookmark1),pointer(Bookmark2))
3593   else
3594     Result := -1;
3595 end;
3596 
3597 procedure TCustomBufDataset.IntLoadFieldDefsFromFile;
3598 
3599 begin
3600   FReadFromFile := True;
3601   if not assigned(FDatasetReader) then
3602     begin
3603     FFileStream := TFileStream.Create(FileName, fmOpenRead);
3604     FDatasetReader := GetPacketReader(dfDefault, FFileStream);
3605     end;
3606 
3607   FieldDefs.Clear;
3608   FDatasetReader.LoadFieldDefs(FAutoIncValue);
3609   if DefaultFields then
3610     CreateFields
3611   else
3612     BindFields(true);
3613 end;
3614 
3615 procedure TCustomBufDataset.IntLoadRecordsFromFile;
3616 
3617 var
3618   SavedState      : TDataSetState;
3619   ARowState       : TRowState;
3620   AUpdOrder       : integer;
3621   i               : integer;
3622   DefIdx : TBufIndex;
3623 
3624 begin
3625   CheckBiDirectional;
3626   DefIdx:=DefaultBufferIndex;
3627   FDatasetReader.InitLoadRecords;
3628   SavedState:=SetTempState(dsFilter);
3629 
3630   while FDatasetReader.GetCurrentRecord do
3631     begin
3632     ARowState := FDatasetReader.GetRecordRowState(AUpdOrder);
3633     if rsvOriginal in ARowState then
3634       begin
3635       if length(FUpdateBuffer) < (AUpdOrder+1) then
3636         SetLength(FUpdateBuffer,AUpdOrder+1);
3637 
3638       FCurrentUpdateBuffer:=AUpdOrder;
3639 
3640       FFilterBuffer:=IntAllocRecordBuffer;
3641       fillchar(FFilterBuffer^,FNullmaskSize,0);
3642       FUpdateBuffer[FCurrentUpdateBuffer].OldValuesBuffer := FFilterBuffer;
3643       FDatasetReader.RestoreRecord;
3644 
3645       FDatasetReader.GotoNextRecord;
3646       if not FDatasetReader.GetCurrentRecord then
3647         DatabaseError(SStreamNotRecognised,Self);
3648       ARowState := FDatasetReader.GetRecordRowState(AUpdOrder);
3649       if rsvUpdated in ARowState then
3650         FUpdateBuffer[FCurrentUpdateBuffer].UpdateKind:= ukModify
3651       else
3652         DatabaseError(SStreamNotRecognised,Self);
3653 
3654       FFilterBuffer:=DefIdx.SpareBuffer;
3655       DefIdx.StoreSpareRecIntoBookmark(@FUpdateBuffer[FCurrentUpdateBuffer].BookmarkData);
3656       fillchar(FFilterBuffer^,FNullmaskSize,0);
3657 
3658       FDatasetReader.RestoreRecord;
3659       DefIdx.AddRecord;
3660       inc(FBRecordCount);
3661       end
3662     else if rsvDeleted in ARowState then
3663       begin
3664       if length(FUpdateBuffer) < (AUpdOrder+1) then
3665         SetLength(FUpdateBuffer,AUpdOrder+1);
3666 
3667       FCurrentUpdateBuffer:=AUpdOrder;
3668 
3669       FFilterBuffer:=IntAllocRecordBuffer;
3670       fillchar(FFilterBuffer^,FNullmaskSize,0);
3671 
3672       FUpdateBuffer[FCurrentUpdateBuffer].OldValuesBuffer := FFilterBuffer;
3673       FDatasetReader.RestoreRecord;
3674 
3675       FUpdateBuffer[FCurrentUpdateBuffer].UpdateKind:= ukDelete;
3676       DefIdx.StoreSpareRecIntoBookmark(@FUpdateBuffer[FCurrentUpdateBuffer].BookmarkData);
3677       DefIdx.AddRecord;
3678       DefIdx.RemoveRecordFromIndex(FUpdateBuffer[FCurrentUpdateBuffer].BookmarkData);
3679       DefIdx.StoreSpareRecIntoBookmark(@FUpdateBuffer[FCurrentUpdateBuffer].NextBookmarkData);
3680 
3681       for i := FCurrentUpdateBuffer+1 to high(FUpdateBuffer) do
3682         if DefIdx.SameBookmarks(@FUpdateBuffer[FCurrentUpdateBuffer].BookmarkData, @FUpdateBuffer[i].NextBookmarkData) then
3683           DefIdx.StoreSpareRecIntoBookmark(@FUpdateBuffer[i].NextBookmarkData);
3684       end
3685     else
3686       begin
3687       FFilterBuffer:=DefIdx.SpareBuffer;
3688       fillchar(FFilterBuffer^,FNullmaskSize,0);
3689       FDatasetReader.RestoreRecord;
3690       if rsvInserted in ARowState then
3691         begin
3692         if length(FUpdateBuffer) < (AUpdOrder+1) then
3693           SetLength(FUpdateBuffer,AUpdOrder+1);
3694         FCurrentUpdateBuffer:=AUpdOrder;
3695         FUpdateBuffer[FCurrentUpdateBuffer].UpdateKind:= ukInsert;
3696         DefIdx.StoreSpareRecIntoBookmark(@FUpdateBuffer[FCurrentUpdateBuffer].BookmarkData);
3697         end;
3698 
3699       DefIdx.AddRecord;
3700       inc(FBRecordCount);
3701       end;
3702 
3703     FDatasetReader.GotoNextRecord;
3704     end;
3705 
3706   RestoreState(SavedState);
3707   DefIdx.SetToFirstRecord;
3708   FAllPacketsFetched:=True;
3709   if assigned(FFileStream) then
3710     begin
3711     FreeAndNil(FFileStream);
3712     FreeAndNil(FDatasetReader);
3713     end;
3714 
3715   // rebuild indexes
3716   BuildIndexes;
3717 end;
3718 
3719 procedure TCustomBufDataset.DoFilterRecord(out Acceptable: Boolean);
3720 begin
3721   Acceptable := true;
3722   // check user filter
3723   if Assigned(OnFilterRecord) then
3724     OnFilterRecord(Self, Acceptable);
3725 
3726   // check filtertext
3727   if Acceptable and (Length(Filter) > 0) then
3728     Acceptable := Boolean((FParser.ExtractFromBuffer(GetCurrentBuffer))^);
3729 end;
3730 
3731 procedure TCustomBufDataset.SetFilterText(const Value: String);
3732 begin
3733   if Value = Filter then
3734     exit;
3735 
3736   // parse
3737   ParseFilter(Value);
3738 
3739   // call dataset method
3740   inherited;
3741 
3742   // refilter dataset if filtered
3743   if IsCursorOpen and Filtered then Resync([]);
3744 end;
3745 
3746 procedure TCustomBufDataset.SetFiltered(Value: Boolean); {override;}
3747 begin
3748   if Value = Filtered then
3749     exit;
3750 
3751   // pass on to ancestor
3752   inherited;
3753 
3754   // only refresh if active
3755   if IsCursorOpen then
3756     Resync([]);
3757 end;
3758 
3759 procedure TCustomBufDataset.InternalRefresh;
3760 
3761 var
3762   StoreDefaultFields: boolean;
3763 
3764 begin
3765   if length(FUpdateBuffer)>0 then
3766     DatabaseError(SErrApplyUpdBeforeRefresh,Self);
3767   FRefreshing:=True;
3768   try
3769     StoreDefaultFields:=DefaultFields;
3770     SetDefaultFields(False);
3771     FreeFieldBuffers;
3772     ClearBuffers;
3773     InternalClose;
3774     BeforeRefreshOpenCursor;
3775     InternalOpen;
3776     SetDefaultFields(StoreDefaultFields);
3777   Finally
3778     FRefreshing:=False;
3779   end;
3780 end;
3781 
3782 procedure TCustomBufDataset.BeforeRefreshOpenCursor;
3783 begin
3784   // Do nothing
3785 end;
3786 
3787 procedure TCustomBufDataset.DataEvent(Event: TDataEvent; Info: PtrInt);
3788 begin
3789   if Event = deUpdateState then
3790     // Save DataSet.State set by DataSet.SetState (filter out State set by DataSet.SetTempState)
3791     FSavedState := State;
3792   inherited;
3793 end;
3794 
Fetchnull3795 function TCustomBufDataset.Fetch: boolean;
3796 begin
3797   // Empty procedure to make it possible to use TCustomBufDataset as a memory dataset
3798   Result := False;
3799 end;
3800 
LoadFieldnull3801 function TCustomBufDataset.LoadField(FieldDef: TFieldDef; buffer: pointer; out
3802   CreateBlob: boolean): boolean;
3803 begin
3804   // Empty procedure to make it possible to use TCustomBufDataset as a memory dataset
3805   CreateBlob := False;
3806   Result := False;
3807 end;
3808 
IsReadFromPacketnull3809 function TCustomBufDataset.IsReadFromPacket: Boolean;
3810 begin
3811   Result := (FDatasetReader<>nil) or (FFileName<>'') or FReadFromFile;
3812 end;
3813 
3814 procedure TCustomBufDataset.ParseFilter(const AFilter: string);
3815 begin
3816   // parser created?
3817   if Length(AFilter) > 0 then
3818   begin
3819     if (FParser = nil) and IsCursorOpen then
3820     begin
3821       FParser := TBufDatasetParser.Create(Self);
3822     end;
3823     // is there a parser now?
3824     if FParser <> nil then
3825     begin
3826       // set options
3827       FParser.PartialMatch := not (foNoPartialCompare in FilterOptions);
3828       FParser.CaseInsensitive := foCaseInsensitive in FilterOptions;
3829       // parse expression
3830       FParser.ParseExpression(AFilter);
3831     end;
3832   end;
3833 end;
3834 
Locatenull3835 function TCustomBufDataset.Locate(const KeyFields: string; const KeyValues: Variant; Options: TLocateOptions): boolean;
3836 
3837 begin
3838   Result:=DoLocate(keyfields,KeyValues,Options,True);
3839 end;
3840 
DoLocatenull3841 function TCustomBufDataset.DoLocate(const KeyFields: string; const KeyValues: Variant; Options: TLocateOptions; DoEvents : Boolean) : boolean;
3842 
3843 
3844 var SearchFields    : TList;
3845     DBCompareStruct : TDBCompareStruct;
3846     ABookmark       : TBufBookmark;
3847     SavedState      : TDataSetState;
3848     FilterRecord    : TRecordBuffer;
3849     FilterAcceptable: boolean;
3850 
3851 begin
3852   // Call inherited to make sure the dataset is bi-directional
3853   Result := inherited Locate(KeyFields,KeyValues,Options);
3854   CheckActive;
3855   if IsEmpty then exit;
3856 
3857   // Build the DBCompare structure
3858   SearchFields := TList.Create;
3859   try
3860     GetFieldList(SearchFields,KeyFields);
3861     if SearchFields.Count=0 then exit;
3862     ProcessFieldsToCompareStruct(SearchFields, nil, nil, [], Options, DBCompareStruct);
3863   finally
3864     SearchFields.Free;
3865   end;
3866 
3867   // Set the filter buffer
3868   SavedState:=SetTempState(dsFilter);
3869   FilterRecord:=IntAllocRecordBuffer;
3870   FFilterBuffer:=FilterRecord + BufferOffset;
3871   SetFieldValues(KeyFields,KeyValues);
3872 
3873   // Iterate through the records until a match is found
3874   ABookmark.BookmarkData:=nil;
3875   while true do
3876     begin
3877     // try get next record
3878     if CurrentIndexBuf.GetRecord(@ABookmark, gmNext) <> grOK then
3879       // for grEOF ABookmark points to SpareRecord, which is used for storing next record(s)
3880       if getnextpacket = 0 then
3881         break;
3882     if IndexCompareRecords(FilterRecord, ABookmark.BookmarkData, DBCompareStruct) = 0 then
3883       begin
3884       if Filtered then
3885         begin
3886         FFilterBuffer:=pointer(ABookmark.BookmarkData) + BufferOffset;
3887         // The dataset state is still dsFilter at this point, so we don't have to set it.
3888         DoFilterRecord(FilterAcceptable);
3889         if FilterAcceptable then
3890           begin
3891           Result := True;
3892           break;
3893           end;
3894         end
3895       else
3896         begin
3897         Result := True;
3898         break;
3899         end;
3900       end;
3901     end;
3902 
3903   RestoreState(SavedState);
3904   FreeRecordBuffer(FilterRecord);
3905 
3906   // If a match is found, jump to the found record
3907   if Result then
3908     begin
3909     ABookmark.BookmarkFlag := bfCurrent;
3910     if DoEvents then
3911       GotoBookmark(@ABookmark)
3912     else
3913       begin
3914       InternalGotoBookMark(@ABookmark);
3915       Resync([rmExact,rmCenter]);
3916       end;
3917     end;
3918 end;
3919 
Lookupnull3920 function TCustomBufDataset.Lookup(const KeyFields: string;
3921   const KeyValues: Variant; const ResultFields: string): Variant;
3922 var
3923   bm:TBookmark;
3924 begin
3925   result:=Null;
3926   if IsEmpty then
3927     exit;
3928   bm:=GetBookmark;
3929   DisableControls;
3930   try
3931     if DoLocate(KeyFields,KeyValues,[],False) then
3932       begin
3933       //  CalculateFields(ActiveBuffer); // not needed, done by Locate more than once
3934       result:=FieldValues[ResultFields];
3935       end;
3936     InternalGotoBookMark(pointer(bm));
3937     Resync([rmExact,rmCenter]);
3938     FreeBookmark(bm);
3939   finally
3940     EnableControls;
3941   end;
3942 end;
3943 
3944 { TArrayBufIndex }
3945 
TArrayBufIndex.GetBookmarkSizenull3946 function TArrayBufIndex.GetBookmarkSize: integer;
3947 begin
3948   Result:=Sizeof(TBufBookmark);
3949 end;
3950 
GetCurrentBuffernull3951 function TArrayBufIndex.GetCurrentBuffer: Pointer;
3952 begin
3953   Result:=TRecordBuffer(FRecordArray[FCurrentRecInd]);
3954 end;
3955 
TArrayBufIndex.GetCurrentRecordnull3956 function TArrayBufIndex.GetCurrentRecord:  TRecordBuffer;
3957 begin
3958   Result:=GetCurrentBuffer;
3959 end;
3960 
GetIsInitializednull3961 function TArrayBufIndex.GetIsInitialized: boolean;
3962 begin
3963   Result:=Length(FRecordArray)>0;
3964 end;
3965 
GetSpareBuffernull3966 function TArrayBufIndex.GetSpareBuffer:  TRecordBuffer;
3967 begin
3968   if FLastRecInd>-1 then
3969     Result:= TRecordBuffer(FRecordArray[FLastRecInd])
3970   else
3971     Result := nil;
3972 end;
3973 
TArrayBufIndex.GetSpareRecordnull3974 function TArrayBufIndex.GetSpareRecord:  TRecordBuffer;
3975 begin
3976   Result := GetSpareBuffer;
3977 end;
3978 
3979 constructor TArrayBufIndex.Create(const ADataset: TCustomBufDataset);
3980 begin
3981   Inherited create(ADataset);
3982   FInitialBuffers:=10000;
3983   FGrowBuffer:=1000;
3984 end;
3985 
ScrollBackwardnull3986 function TArrayBufIndex.ScrollBackward: TGetResult;
3987 begin
3988   if FCurrentRecInd>0 then
3989     begin
3990     dec(FCurrentRecInd);
3991     Result := grOK;
3992     end
3993   else
3994     Result := grBOF;
3995 end;
3996 
TArrayBufIndex.ScrollForwardnull3997 function TArrayBufIndex.ScrollForward: TGetResult;
3998 begin
3999   if FCurrentRecInd = FLastRecInd-1 then
4000     result := grEOF
4001   else
4002     begin
4003     Result:=grOK;
4004     inc(FCurrentRecInd);
4005     end;
4006 end;
4007 
TArrayBufIndex.GetCurrentnull4008 function TArrayBufIndex.GetCurrent: TGetResult;
4009 begin
4010   if FLastRecInd=0 then
4011     Result := grError
4012   else
4013     begin
4014     Result := grOK;
4015     if FCurrentRecInd = FLastRecInd then
4016       dec(FCurrentRecInd);
4017     end;
4018 end;
4019 
ScrollFirstnull4020 function TArrayBufIndex.ScrollFirst: TGetResult;
4021 begin
4022   FCurrentRecInd:=0;
4023   if (FCurrentRecInd = FLastRecInd) then
4024     result := grEOF
4025   else
4026     result := grOk;
4027 end;
4028 
4029 procedure TArrayBufIndex.ScrollLast;
4030 begin
4031   FCurrentRecInd:=FLastRecInd;
4032 end;
4033 
4034 procedure TArrayBufIndex.SetToFirstRecord;
4035 begin
4036   // if FCurrentRecBuf = FLastRecBuf then the dataset is just opened and empty
4037   // in which case InternalFirst should do nothing (bug 7211)
4038   if FCurrentRecInd <> FLastRecInd then
4039     FCurrentRecInd := -1;
4040 end;
4041 
4042 procedure TArrayBufIndex.SetToLastRecord;
4043 begin
4044   if FLastRecInd <> 0 then FCurrentRecInd := FLastRecInd;
4045 end;
4046 
4047 procedure TArrayBufIndex.StoreCurrentRecord;
4048 begin
4049   FStoredRecBuf := FCurrentRecInd;
4050 end;
4051 
4052 procedure TArrayBufIndex.RestoreCurrentRecord;
4053 begin
4054   FCurrentRecInd := FStoredRecBuf;
4055 end;
4056 
TArrayBufIndex.CanScrollForwardnull4057 function TArrayBufIndex.CanScrollForward: Boolean;
4058 begin
4059   Result := (FCurrentRecInd < FLastRecInd-1);
4060 end;
4061 
4062 procedure TArrayBufIndex.DoScrollForward;
4063 begin
4064   inc(FCurrentRecInd);
4065 end;
4066 
4067 procedure TArrayBufIndex.StoreCurrentRecIntoBookmark(const ABookmark: PBufBookmark);
4068 begin
4069   with ABookmark^ do
4070     begin
4071     BookmarkInt := FCurrentRecInd;
4072     BookmarkData := FRecordArray[FCurrentRecInd];
4073     end;
4074 end;
4075 
4076 procedure TArrayBufIndex.StoreSpareRecIntoBookmark(const ABookmark: PBufBookmark
4077   );
4078 begin
4079   with ABookmark^ do
4080     begin
4081     BookmarkInt := FLastRecInd;
4082     BookmarkData := FRecordArray[FLastRecInd];
4083     end;
4084 end;
4085 
TArrayBufIndex.GetRecordFromBookmarknull4086 function TArrayBufIndex.GetRecordFromBookmark(ABookmark: TBufBookmark): integer;
4087 begin
4088   // ABookmark.BookMarkBuf is nil if SetRecNo calls GotoBookmark
4089   if (ABookmark.BookmarkData<>nil) and (FRecordArray[ABookmark.BookmarkInt]<>ABookmark.BookmarkData) then
4090     begin
4091     // Start searching two records before the expected record
4092     if ABookmark.BookmarkInt > 2 then
4093       Result := ABookmark.BookmarkInt-2
4094     else
4095       Result := 0;
4096 
4097     while (Result<FLastRecInd) do
4098       begin
4099       if (FRecordArray[Result] = ABookmark.BookmarkData) then exit;
4100       inc(Result);
4101       end;
4102 
4103     Result:=0;
4104     while (Result<ABookmark.BookmarkInt) do
4105       begin
4106       if (FRecordArray[Result] = ABookmark.BookmarkData) then exit;
4107       inc(Result);
4108       end;
4109 
4110     DatabaseError(SInvalidBookmark,Self.FDataset)
4111     end
4112   else
4113     Result := ABookmark.BookmarkInt;
4114 end;
4115 
4116 procedure TArrayBufIndex.GotoBookmark(const ABookmark : PBufBookmark);
4117 begin
4118   FCurrentRecInd:=GetRecordFromBookmark(ABookmark^);
4119 end;
4120 
4121 procedure TArrayBufIndex.InitialiseIndex;
4122 begin
4123   //  FRecordArray:=nil;
4124   setlength(FRecordArray,FInitialBuffers);
4125   FCurrentRecInd:=-1;
4126   FLastRecInd:=-1;
4127 end;
4128 
4129 procedure TArrayBufIndex.InitialiseSpareRecord(const ASpareRecord:  TRecordBuffer);
4130 begin
4131   FLastRecInd := 0;
4132   // FCurrentRecInd := 0;
4133   FRecordArray[0] := ASpareRecord;
4134 end;
4135 
4136 procedure TArrayBufIndex.ReleaseSpareRecord;
4137 begin
4138   SetLength(FRecordArray,FInitialBuffers);
4139 end;
4140 
GetRecNonull4141 function TArrayBufIndex.GetRecNo: integer;
4142 begin
4143   Result := FCurrentRecInd+1;
4144 end;
4145 
4146 procedure TArrayBufIndex.SetRecNo(ARecNo: Longint);
4147 begin
4148   FCurrentRecInd := ARecNo-1;
4149 end;
4150 
4151 procedure TArrayBufIndex.InsertRecordBeforeCurrentRecord(const ARecord:  TRecordBuffer);
4152 begin
4153   inc(FLastRecInd);
4154   if FLastRecInd >= length(FRecordArray) then
4155     SetLength(FRecordArray,length(FRecordArray)+FGrowBuffer);
4156 
4157   Move(FRecordArray[FCurrentRecInd],FRecordArray[FCurrentRecInd+1],sizeof(Pointer)*(FLastRecInd-FCurrentRecInd));
4158   FRecordArray[FCurrentRecInd]:=ARecord;
4159   inc(FCurrentRecInd);
4160 end;
4161 
4162 procedure TArrayBufIndex.RemoveRecordFromIndex(const ABookmark : TBufBookmark);
4163 var ARecordInd : integer;
4164 begin
4165   ARecordInd:=GetRecordFromBookmark(ABookmark);
4166   Move(FRecordArray[ARecordInd+1],FRecordArray[ARecordInd],sizeof(Pointer)*(FLastRecInd-ARecordInd));
4167   dec(FLastRecInd);
4168 end;
4169 
4170 procedure TArrayBufIndex.BeginUpdate;
4171 begin
4172   //  inherited BeginUpdate;
4173 end;
4174 
4175 procedure TArrayBufIndex.AddRecord;
4176 var ARecord:  TRecordBuffer;
4177 begin
4178   ARecord := FDataset.IntAllocRecordBuffer;
4179   inc(FLastRecInd);
4180   if FLastRecInd >= length(FRecordArray) then
4181     SetLength(FRecordArray,length(FRecordArray)+FGrowBuffer);
4182   FRecordArray[FLastRecInd]:=ARecord;
4183 end;
4184 
4185 procedure TArrayBufIndex.EndUpdate;
4186 begin
4187   //  inherited EndUpdate;
4188 end;
4189 
4190 
4191 { TDataPacketReader }
4192 
TDataPacketReader.RowStateToBytenull4193 class function TDataPacketReader.RowStateToByte(const ARowState: TRowState
4194   ): byte;
4195 var RowStateInt : Byte;
4196 begin
4197   RowStateInt:=0;
4198   if rsvOriginal in ARowState then RowStateInt := RowStateInt+1;
4199   if rsvDeleted in ARowState then RowStateInt := RowStateInt+2;
4200   if rsvInserted in ARowState then RowStateInt := RowStateInt+4;
4201   if rsvUpdated in ARowState then RowStateInt := RowStateInt+8;
4202   Result := RowStateInt;
4203 end;
4204 
TDataPacketReader.ByteToRowStatenull4205 class function TDataPacketReader.ByteToRowState(const AByte: Byte): TRowState;
4206 begin
4207   result := [];
4208   if (AByte and 1)=1 then Result := Result+[rsvOriginal];
4209   if (AByte and 2)=2 then Result := Result+[rsvDeleted];
4210   if (AByte and 4)=4 then Result := Result+[rsvInserted];
4211   if (AByte and 8)=8 then Result := Result+[rsvUpdated];
4212 end;
4213 
4214 procedure TDataPacketReader.RestoreBlobField(AField: TField; ASource: pointer; ASize: integer);
4215 var
4216   ABufBlobField: TBufBlobField;
4217 begin
4218   ABufBlobField.BlobBuffer:=FDataSet.GetNewBlobBuffer;
4219   ABufBlobField.BlobBuffer^.Size:=ASize;
4220   ReAllocMem(ABufBlobField.BlobBuffer^.Buffer, ASize);
4221   move(ASource^, ABufBlobField.BlobBuffer^.Buffer^, ASize);
4222   AField.SetData(@ABufBlobField);
4223 end;
4224 
4225 constructor TDataPacketReader.Create(ADataSet: TCustomBufDataset; AStream: TStream);
4226 begin
4227   FDataSet := ADataSet;
4228   FStream := AStream;
4229 end;
4230 
4231 
4232 { TFpcBinaryDatapacketReader }
4233 
4234 constructor TFpcBinaryDatapacketReader.Create(ADataSet: TCustomBufDataset; AStream: TStream);
4235 begin
4236   inherited;
4237   FVersion := 20; // default version 2.0
4238 end;
4239 
4240 procedure TFpcBinaryDatapacketReader.LoadFieldDefs(var AnAutoIncValue: integer);
4241 
4242 var FldCount : word;
4243     i        : integer;
4244     s        : string;
4245 
4246 begin
4247   // Identify version
4248   SetLength(s, 13);
4249   if (Stream.Read(s[1], 13) = 13) then
4250     case s of
4251       FpcBinaryIdent1:
4252         FVersion := 10;
4253       FpcBinaryIdent2:
4254         FVersion := Stream.ReadByte;
4255       else
4256         DatabaseError(SStreamNotRecognised,Self.FDataset);
4257     end;
4258 
4259   // Read FieldDefs
4260   FldCount := Stream.ReadWord;
4261   DataSet.FieldDefs.Clear;
4262   for i := 0 to FldCount - 1 do with DataSet.FieldDefs.AddFieldDef do
4263     begin
4264     Name := Stream.ReadAnsiString;
4265     Displayname := Stream.ReadAnsiString;
4266     Size := Stream.ReadWord;
4267     DataType := TFieldType(Stream.ReadWord);
4268 
4269     if Stream.ReadByte = 1 then
4270       Attributes := Attributes + [faReadonly];
4271     end;
4272   Stream.ReadBuffer(i,sizeof(i));
4273   AnAutoIncValue := i;
4274 
4275   FNullBitmapSize := (FldCount + 7) div 8;
4276   SetLength(FNullBitmap, FNullBitmapSize);
4277 end;
4278 
4279 procedure TFpcBinaryDatapacketReader.StoreFieldDefs(AnAutoIncValue: integer);
4280 var i : integer;
4281 begin
4282   Stream.Write(FpcBinaryIdent2[1], length(FpcBinaryIdent2));
4283   Stream.WriteByte(FVersion);
4284 
4285   Stream.WriteWord(DataSet.FieldDefs.Count);
4286   for i := 0 to DataSet.FieldDefs.Count - 1 do with DataSet.FieldDefs[i] do
4287     begin
4288     Stream.WriteAnsiString(Name);
4289     Stream.WriteAnsiString(DisplayName);
4290     Stream.WriteWord(Size);
4291     Stream.WriteWord(ord(DataType));
4292 
4293     if faReadonly in Attributes then
4294       Stream.WriteByte(1)
4295     else
4296       Stream.WriteByte(0);
4297     end;
4298   i := AnAutoIncValue;
4299   Stream.WriteBuffer(i,sizeof(i));
4300 
4301   FNullBitmapSize := (DataSet.FieldDefs.Count + 7) div 8;
4302   SetLength(FNullBitmap, FNullBitmapSize);
4303 end;
4304 
4305 procedure TFpcBinaryDatapacketReader.InitLoadRecords;
4306 begin
4307   //  Do nothing
4308 end;
4309 
TFpcBinaryDatapacketReader.GetCurrentRecordnull4310 function TFpcBinaryDatapacketReader.GetCurrentRecord: boolean;
4311 var Buf : byte;
4312 begin
4313   Result := (Stream.Read(Buf,1)=1) and (Buf=$fe);
4314 end;
4315 
TFpcBinaryDatapacketReader.GetRecordRowStatenull4316 function TFpcBinaryDatapacketReader.GetRecordRowState(out AUpdOrder : Integer) : TRowState;
4317 var Buf : byte;
4318 begin
4319   Stream.Read(Buf,1);
4320   Result := ByteToRowState(Buf);
4321   if Result<>[] then
4322     Stream.ReadBuffer(AUpdOrder,sizeof(integer))
4323   else
4324     AUpdOrder := 0;
4325 end;
4326 
4327 procedure TFpcBinaryDatapacketReader.GotoNextRecord;
4328 begin
4329   //  Do Nothing
4330 end;
4331 
4332 procedure TFpcBinaryDatapacketReader.RestoreRecord;
4333 var
4334   AField: TField;
4335   i: integer;
4336   L: cardinal;
4337   B: TBytes;
4338 begin
4339   with DataSet do
4340     case FVersion of
4341       10:
4342         Stream.ReadBuffer(GetCurrentBuffer^, FRecordSize);  // Ugly because private members of ADataset are used...
4343       20:
4344         begin
4345         // Restore field's Null bitmap
4346         Stream.ReadBuffer(FNullBitmap[0], FNullBitmapSize);
4347         // Restore field's data
4348         for i:=0 to FieldDefs.Count-1 do
4349           begin
4350           AField := Fields.FieldByNumber(FieldDefs[i].FieldNo);
4351           if AField=nil then continue;
4352           if GetFieldIsNull(PByte(FNullBitmap), i) then
4353             AField.SetData(nil)
4354           else if AField.DataType in StringFieldTypes then
4355             AField.AsString := Stream.ReadAnsiString
4356           else
4357             begin
4358             if AField.DataType in VarLenFieldTypes then
4359               L := Stream.ReadDWord
4360             else
4361               L := AField.DataSize;
4362             SetLength(B, L);
4363             if L > 0 then
4364               Stream.ReadBuffer(B[0], L);
4365             if AField.DataType in BlobFieldTypes then
4366               RestoreBlobField(AField, @B[0], L)
4367             else
4368               AField.SetData(@B[0], False);  // set it to the FilterBuffer
4369             end;
4370           end;
4371         end;
4372     end;
4373 end;
4374 
4375 procedure TFpcBinaryDatapacketReader.StoreRecord(ARowState: TRowState; AUpdOrder : integer);
4376 var
4377   AField: TField;
4378   i: integer;
4379   L: cardinal;
4380   B: TBytes;
4381 begin
4382   // Record header
4383   Stream.WriteByte($fe);
4384   Stream.WriteByte(RowStateToByte(ARowState));
4385   if ARowState<>[] then
4386     Stream.WriteBuffer(AUpdOrder,sizeof(integer));
4387 
4388   // Record data
4389   with DataSet do
4390     case FVersion of
4391       10:
4392         Stream.WriteBuffer(GetCurrentBuffer^, FRecordSize); // Old 1.0 version
4393       20:
4394         begin
4395         // store fields Null bitmap
4396         FillByte(FNullBitmap[0], FNullBitmapSize, 0);
4397         for i:=0 to FieldDefs.Count-1 do
4398           begin
4399           AField := Fields.FieldByNumber(FieldDefs[i].FieldNo);
4400           if assigned(AField) and AField.IsNull then
4401             SetFieldIsNull(PByte(FNullBitmap), i);
4402           end;
4403         Stream.WriteBuffer(FNullBitmap[0], FNullBitmapSize);
4404 
4405         for i:=0 to FieldDefs.Count-1 do
4406           begin
4407           AField := Fields.FieldByNumber(FieldDefs[i].FieldNo);
4408           if not assigned(AField) or AField.IsNull then continue;
4409           if AField.DataType in StringFieldTypes then
4410             Stream.WriteAnsiString(AField.AsString)
4411           else
4412             begin
4413             B := AField.AsBytes;
4414             L := length(B);
4415             if AField.DataType in VarLenFieldTypes then
4416               Stream.WriteDWord(L);
4417             if L > 0 then
4418               Stream.WriteBuffer(B[0], L);
4419             end;
4420           end;
4421         end;
4422     end;
4423 end;
4424 
4425 procedure TFpcBinaryDatapacketReader.FinalizeStoreRecords;
4426 begin
4427   //  Do nothing
4428 end;
4429 
TFpcBinaryDatapacketReader.RecognizeStreamnull4430 class function TFpcBinaryDatapacketReader.RecognizeStream(AStream: TStream): boolean;
4431 var s : string;
4432 begin
4433   SetLength(s, 13);
4434   if (AStream.Read(s[1], 13) = 13) then
4435     case s of
4436       FpcBinaryIdent1,
4437       FpcBinaryIdent2:
4438         Result := True;
4439       else
4440         Result := False;
4441     end;
4442 end;
4443 
4444 { TUniDirectionalBufIndex }
4445 
TUniDirectionalBufIndex.GetBookmarkSizenull4446 function TUniDirectionalBufIndex.GetBookmarkSize: integer;
4447 begin
4448   // In principle there are no bookmarks, and the size should be 0.
4449   // But there is quite some code in TCustomBufDataset that relies on
4450   // an existing bookmark of the TBufBookmark type.
4451   // This code could be moved to the TBufIndex but that would make things
4452   // more complicated and probably slower. So use a 'fake' bookmark of
4453   // size TBufBookmark.
4454   // When there are other TBufIndexes which also need special bookmark code
4455   // this can be adapted.
4456   Result:=sizeof(TBufBookmark);
4457 end;
4458 
GetCurrentBuffernull4459 function TUniDirectionalBufIndex.GetCurrentBuffer: Pointer;
4460 begin
4461   result := FSPareBuffer;
4462 end;
4463 
TUniDirectionalBufIndex.GetCurrentRecordnull4464 function TUniDirectionalBufIndex.GetCurrentRecord:  TRecordBuffer;
4465 begin
4466   Result:=Nil;
4467   //  Result:=inherited GetCurrentRecord;
4468 end;
4469 
GetIsInitializednull4470 function TUniDirectionalBufIndex.GetIsInitialized: boolean;
4471 begin
4472   Result := Assigned(FSPareBuffer);
4473 end;
4474 
TUniDirectionalBufIndex.GetSpareBuffernull4475 function TUniDirectionalBufIndex.GetSpareBuffer:  TRecordBuffer;
4476 begin
4477   result := FSPareBuffer;
4478 end;
4479 
TUniDirectionalBufIndex.GetSpareRecordnull4480 function TUniDirectionalBufIndex.GetSpareRecord:  TRecordBuffer;
4481 begin
4482   result := FSPareBuffer;
4483 end;
4484 
TUniDirectionalBufIndex.ScrollBackwardnull4485 function TUniDirectionalBufIndex.ScrollBackward: TGetResult;
4486 begin
4487   result := grError;
4488 end;
4489 
TUniDirectionalBufIndex.ScrollForwardnull4490 function TUniDirectionalBufIndex.ScrollForward: TGetResult;
4491 begin
4492   result := grOk;
4493 end;
4494 
TUniDirectionalBufIndex.GetCurrentnull4495 function TUniDirectionalBufIndex.GetCurrent: TGetResult;
4496 begin
4497   result := grOk;
4498 end;
4499 
ScrollFirstnull4500 function TUniDirectionalBufIndex.ScrollFirst: TGetResult;
4501 begin
4502   Result:=grError;
4503 end;
4504 
4505 procedure TUniDirectionalBufIndex.ScrollLast;
4506 begin
4507   DatabaseError(SUniDirectional);
4508 end;
4509 
4510 procedure TUniDirectionalBufIndex.SetToFirstRecord;
4511 begin
4512   // for UniDirectional datasets should be [Internal]First valid method call
4513   // do nothing
4514 end;
4515 
4516 procedure TUniDirectionalBufIndex.SetToLastRecord;
4517 begin
4518   DatabaseError(SUniDirectional);
4519 end;
4520 
4521 procedure TUniDirectionalBufIndex.StoreCurrentRecord;
4522 begin
4523   DatabaseError(SUniDirectional);
4524 end;
4525 
4526 procedure TUniDirectionalBufIndex.RestoreCurrentRecord;
4527 begin
4528   DatabaseError(SUniDirectional);
4529 end;
4530 
CanScrollForwardnull4531 function TUniDirectionalBufIndex.CanScrollForward: Boolean;
4532 begin
4533   // should return true if next record is already fetched
4534   result := false;
4535 end;
4536 
4537 procedure TUniDirectionalBufIndex.DoScrollForward;
4538 begin
4539   // do nothing
4540 end;
4541 
4542 procedure TUniDirectionalBufIndex.StoreCurrentRecIntoBookmark(const ABookmark: PBufBookmark);
4543 begin
4544   // do nothing
4545 end;
4546 
4547 procedure TUniDirectionalBufIndex.StoreSpareRecIntoBookmark(const ABookmark: PBufBookmark);
4548 begin
4549   // do nothing
4550 end;
4551 
4552 procedure TUniDirectionalBufIndex.GotoBookmark(const ABookmark: PBufBookmark);
4553 begin
4554   DatabaseError(SUniDirectional);
4555 end;
4556 
4557 procedure TUniDirectionalBufIndex.InitialiseIndex;
4558 begin
4559   // do nothing
4560 end;
4561 
4562 procedure TUniDirectionalBufIndex.InitialiseSpareRecord(const ASpareRecord:  TRecordBuffer);
4563 begin
4564   FSPareBuffer:=ASpareRecord;
4565 end;
4566 
4567 procedure TUniDirectionalBufIndex.ReleaseSpareRecord;
4568 begin
4569   FSPareBuffer:=nil;
4570 end;
4571 
TUniDirectionalBufIndex.GetRecNonull4572 function TUniDirectionalBufIndex.GetRecNo: Longint;
4573 begin
4574   Result := -1;
4575 end;
4576 
4577 procedure TUniDirectionalBufIndex.SetRecNo(ARecNo: Longint);
4578 begin
4579   DatabaseError(SUniDirectional);
4580 end;
4581 
4582 procedure TUniDirectionalBufIndex.BeginUpdate;
4583 begin
4584   // Do nothing
4585 end;
4586 
4587 procedure TUniDirectionalBufIndex.AddRecord;
4588 var
4589   h,i: integer;
4590 begin
4591   // Release unneeded blob buffers, in order to save memory
4592   // TDataSet has own buffer of records, so do not release blobs until they can be referenced
4593   with FDataSet do
4594     begin
4595     h := high(FBlobBuffers) - BufferCount*BlobFieldCount;
4596     if h > 10 then //Free in batches, starting with oldest (at beginning)
4597       begin
4598       for i := 0 to h do
4599         FreeBlobBuffer(FBlobBuffers[i]);
4600       FBlobBuffers := Copy(FBlobBuffers, h+1, high(FBlobBuffers)-h);
4601       end;
4602     end;
4603 end;
4604 
4605 procedure TUniDirectionalBufIndex.InsertRecordBeforeCurrentRecord(const ARecord:  TRecordBuffer);
4606 begin
4607   // Do nothing
4608 end;
4609 
4610 procedure TUniDirectionalBufIndex.RemoveRecordFromIndex(const ABookmark: TBufBookmark);
4611 begin
4612   DatabaseError(SUniDirectional);
4613 end;
4614 
4615 procedure TUniDirectionalBufIndex.OrderCurrentRecord;
4616 begin
4617   // Do nothing
4618 end;
4619 
4620 procedure TUniDirectionalBufIndex.EndUpdate;
4621 begin
4622   // Do nothing
4623 end;
4624 
4625 
4626 initialization
4627   setlength(RegisteredDatapacketReaders,0);
4628 finalization
4629   setlength(RegisteredDatapacketReaders,0);
4630 end.
4631