ViewVC Help
View File | Revision Log | Show Annotations | Download File | View Changeset | Root Listing
root/public/ibx/trunk/runtime/nongui/IBBufferedCursors.pas
Revision: 410
Committed: Thu Jun 22 13:52:39 2023 UTC (17 months ago) by tony
Content type: text/x-pascal
File size: 110668 byte(s)
Log Message:
Release 2.6.0 beta

File Contents

# User Rev Content
1 tony 410 (*
2     * IBX For Lazarus (Firebird Express)
3     *
4     * The contents of this file are subject to the Initial Developer's
5     * Public License Version 1.0 (the "License"); you may not use this
6     * file except in compliance with the License. You may obtain a copy
7     * of the License here:
8     *
9     * http://www.firebirdsql.org/index.php?op=doc&id=idpl
10     *
11     * Software distributed under the License is distributed on an "AS
12     * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
13     * implied. See the License for the specific language governing rights
14     * and limitations under the License.
15     *
16     * The Initial Developer of the Original Code is Tony Whyman.
17     *
18     * The Original Code is (C) 2023 Tony Whyman, MWA Software
19     * (http://www.mwasoftware.co.uk).
20     *
21     * All Rights Reserved.
22     *
23     * Contributor(s): ______________________________________.
24     *
25     *)
26     unit IBBufferedCursors;
27    
28     {$mode Delphi}
29     {$codepage UTF8}
30    
31     { $define PrintBuf}
32    
33     interface
34    
35     uses
36     Classes, SysUtils, DB, IB, IBBlob, IBExternals, FmtBCD;
37    
38     type
39     { The TIBSimpleBufferPool provides basic buffer management for IBX. The pool consists
40     of one or more memory blocks organised as a bi-directional linked list. The blocks do
41     not have to be the same size and the first is typically a small block with
42     subsequent blocks larger in size. This allows for efficient memory allocation
43     for small datasets whilst efficiently extending the pool for larger datasets.
44    
45     Each block comprises a "start header", one or more fixed size "buffers" and a trailing end
46     header. Each type is distinguished by the first byte value. Additionally, the
47     first byte of the first buffer in a block is separately identified. This
48     allows for a block to be parsed in either direction with the end header terminating
49     forward parsing while the first buffer identifier terminates parsing in the
50     backwards direction.
51    
52     The end header consists of no more than a pointer back to the start header, while
53     the start header includes the previous and next block pointers, the number of buffers
54     in the block and the number of buffers in use.
55    
56     Each buffer is allocated a sequential record number starting from one. A simple
57     TList based index allows for random access to the block containing a buffer
58     identifies by record number with the requested buffer then located as an
59     offset from the start header.
60     }
61    
62     PIBRecordNumber = ^TIBRecordNumber;
63    
64     TIBRecordNumber = cardinal;
65    
66     { TIBSimpleBufferPool }
67    
68     TIBSimpleBufferPool = class
69     private type
70     THeaderTypes = (htEmptySlot=0, htStart, htEnd, htFirstBuffer, htBuffer);
71     PStartHeader = ^TStartHeader;
72     TStartHeader = record
73     HeaderType: THeaderTypes;
74     PreviousBlock: PByte;
75     NextBlock: PByte;
76     MaxBuffers: integer;
77     BuffersInUse: integer;
78     FirstRecNo: TIBRecordNumber; {1- based}
79     end;
80    
81     PEndHeader = ^TEndHeader;
82     TEndHeader = record
83     HeaderType: THeaderTypes;
84     StartHeader: PByte;
85     end;
86    
87     PBufferHeader = ^TBufferHeader;
88     TBufferHeader = record
89     HeaderType: THeaderTypes;
90     RecNo: TIBRecordNumber;
91     end;
92    
93     strict private
94     FFirstBlock: PByte;
95     FLastBlock: PByte;
96     FBufferSize: integer; {user buffer size i.e. not including header}
97     FBuffersPerBlock: integer;
98     FFirstBlockBuffers: integer;
99     FBufferIndex: TList;
100     FLastBuffer: PByte;
101     FCurrent: PByte;
102     FName: string;
103     FRecordCount: TIBRecordNumber;
104     function AllocBlock(buffers: integer): PByte;
105     procedure CheckBuffersAvailable;
106     procedure InternalCheckValidBuffer(P:PByte); inline;
107     protected
108     procedure CheckValidBuffer(P:PByte); virtual;
109     function AddBuffer: PByte; virtual;
110     procedure Clear; virtual;
111     function GetFirst: PByte; virtual;
112     function GetLast: PByte; virtual;
113     function GetBuffer(RecNo: TIBRecordNumber): PByte; virtual;
114     function GetNextBuffer(aBuffer: PByte): PByte; virtual;
115     function GetPriorBuffer(aBuffer: PByte): PByte; virtual;
116     public
117     constructor Create(aName: string; bufSize, aBuffersPerBlock, firstBlockBuffers: integer);
118     destructor Destroy; override;
119     function Append: PByte;
120     function GetRecNo(aBuffer: PByte): TIBRecordNumber; virtual;
121     function GetRecordCount: TIBRecordNumber;
122     function Empty: boolean;
123     property BuffersPerBlock: integer read FBuffersPerBlock write FBuffersPerBlock;
124     property Name: string read FName;
125     property RecordCount: TIBRecordNumber read FRecordCount;
126     property BufferSize: integer read FBufferSize;
127     end;
128    
129     PIBDBKey = ^TIBDBKey;
130     TIBDBKey = record
131     DBKey: array[0..7] of Byte;
132     end;
133    
134    
135     {
136     The TIBBufferPool builds on TIBSimpleBufferPool and adds the ability to
137     insert buffers before and after a given buffer and to mark a buffer as
138     deleted.
139    
140     In order to avoid large memory to memory copies, a previousBuffer pointer
141     is added to each buffer header. InsertBefore is thus simply achieved by
142     adding a buffer to the pool and inserting it into the backwards chain.
143     GetPriorBuffer is then amended to follow the previous pointer.
144    
145     InsertAfter is implemented similarly. However, those appended to the pool
146     have to be identified as appended instead of simply inserted. This is because
147     GetNextBuffer is implemented using the inherited method while skipping
148     inserted and deleted buffers. That is the inherited method is called repeatedly
149     until an appended buffer is returned or EOF is reached.
150    
151     Deletion is implemetned by marking a buffer as deleted and adjusting the previous
152     pointer of the next buffer in sequence
153     }
154     { TIBBufferPool }
155    
156     TIBBufferPool = class(TIBSimpleBufferPool)
157     private type
158     TRecordStatus = (rsAppended,rsInserted,rsInsertDeleted,rsAppendDeleted);
159     PRecordData = ^TRecordData;
160     TRecordData = record
161     rdStatus: TRecordStatus;
162     rdPreviousBuffer: PByte;
163     end;
164     strict private
165     FFirstRecord: PByte;
166     FLastRecord: PByte;
167     FInsertedRecords: integer;
168     FDeletedRecords: integer;
169     function InternalGetNextBuffer(aBuffer: PByte; IncludeDeleted: boolean): PByte;
170     public
171     constructor Create(aName: string; bufSize, aBuffersPerBlock, firstBlockBuffers: integer);
172     procedure Clear; override;
173     function GetFirst: PByte; override;
174     function GetLast: PByte; override;
175     function GetBuffer(RecNo: TIBRecordNumber): PByte; override;
176     function GetNextBuffer(aBuffer: PByte): PByte; override; overload;
177     function GetNextBuffer(aBuffer: PByte; IncludeDeleted: boolean): PByte; overload;
178     function GetPriorBuffer(aBuffer: PByte): PByte; override;
179     function GetRecNo(aBuffer: PByte): TIBRecordNumber; override;
180     function InsertBefore(aBuffer: PByte): PByte; virtual;
181     function InsertAfter(aBuffer: PByte): PByte; virtual;
182     function LocatePreviousBuffer(aBuffer: PByte): PByte;
183     function Append: PByte;
184     function Delete(aBuffer: PByte): PByte;
185     procedure UnDelete(aBuffer: PByte);
186     function GetUpdateStatus(aBuffer: PByte): TUpdateStatus;
187     function GetRecordStatus(aBuffer: PByte): TRecordStatus;
188     property InsertedRecords: integer read FInsertedRecords;
189     property DeletedRecords: integer read FDeletedRecords;
190     {$ifdef PrintBuf}
191     public
192     procedure PrintBufferList;
193     {$endif}
194     end;
195    
196     {
197     TIIBOldBufferPool is used to support cached updates. When a record is updated
198     a new buffer is allocated from this pool. The caller may then copy the
199     record data into this buffer. The buffer header includes the cached update
200     status and a pointer back to the buffer containing the updated data.
201    
202     Insertions and Deletions do not need a data copy. This only applies to updated
203     records.
204    
205     Forward and backwards iterators are provided. The Forward iterator may be used
206     to apply updates while the backwards iterator may be used to cancel updates. In
207     each case the update status is used to determine the actual and the pointer to
208     the data buffer is used to access the updated data.
209     }
210    
211     TCachedUpdateStatus = (
212     cusUnmodified=0, cusModified, cusInserted,
213     cusDeleted, cusUninserted
214     );
215    
216     TIBUpdateRecordTypes = set of TCachedUpdateStatus;
217    
218     { TIBOldBufferPool }
219    
220     TIBOldBufferPool = class(TIBSimpleBufferPool)
221     private type
222     PRecordData = ^TRecordData;
223     TRecordData = record
224     rdStatus: TCachedUpdateStatus;
225     rdRecordNumber: TIBRecordNumber;
226     rdDataBuffer: PByte;
227     end;
228    
229     private
230     FModifiedRecords: integer;
231    
232     protected
233     procedure CheckValidBuffer(P:PByte); override;
234    
235     public type
236     TIterator = procedure(status: TCachedUpdateStatus; DataBuffer, OldBuffer: PByte) of object;
237    
238     public
239     constructor Create(aName: string; bufSize, aBuffersPerBlock, firstBlockBuffers: integer);
240     function Append(RecNo: TIBRecordNumber; DataBuffer: PByte): PByte;
241     procedure Clear; override;
242     function FindOldBufferFor(RecNo: TIBRecordNumber): PByte;
243     function GetBuffer(RecNo: TIBRecordNumber): PByte; override;
244     function GetRecNo(aBuffer: PByte): TIBRecordNumber; override;
245     function GetStatus(aBuffer: PByte): TCachedUpdateStatus; overload;
246     function GetStatus(RecNo: TIBRecordNumber): TCachedUpdateStatus; overload;
247     procedure SetStatus(aBuffer: PByte; status: TCachedUpdateStatus);
248     procedure SetDataBuffer(aBuffer: PByte; aDataBuffer: PByte);
249     procedure ForwardIterator(iterator: TIterator);
250     procedure BackwardsIterator(iterator: TIterator);
251     property ModifiedRecords: integer read FModifiedRecords;
252     end;
253    
254     PIBBufferedDateTimeWithTimeZone = ^TIBBufferedDateTimeWithTimeZone;
255     TIBBufferedDateTimeWithTimeZone = packed record
256     Timestamp: TDateTime;
257     dstOffset: smallint;
258     TimeZoneID: ISC_USHORT;
259     end;
260    
261     { TIBDSBlobStream }
262    
263     TIBDSBlobStream = class(TStream)
264     private
265     FHasWritten: boolean;
266     procedure FieldChanged;
267     protected
268     FField: TField;
269     FBlobStream: TIBBlobStream;
270     function GetSize: Int64; override;
271     public
272     constructor Create(AField: TField; ABlobStream: TIBBlobStream;
273     Mode: TBlobStreamMode);
274     destructor Destroy; override;
275     function Read(var Buffer; Count: Longint): Longint; override;
276     function Seek(Offset: Longint; Origin: Word): Longint; override;
277     procedure SetSize(NewSize: Longint); override;
278     function Write(const Buffer; Count: Longint): Longint; override;
279     property BlobStream: TIBBlobStream read FBlobStream;
280     end;
281    
282     TUpdatesIterator = procedure(status: TCachedUpdateStatus; aBufID: TRecordBuffer;
283     var RecordSkipped: boolean) of object;
284    
285     TOnValuesReturned = procedure(qryResults: IResults) of object;
286    
287     TRegisteredQueryTypes = (rqInsert,rqModify,rqDelete,rqRefresh);
288    
289     IIBCursor = interface
290     ['{909621f7-e7fe-4b39-a8c5-f25c40a71c12}']
291     function AllocRecordBuffer: TRecordBuffer;
292     procedure FreeRecordBuffer(var Buffer: TRecordBuffer);
293     function CreateBlobStream(aBufID: TRecordBuffer; Field: TField; Mode: TBlobStreamMode): TStream;
294     function GetArray(aBufID: TRecordBuffer; Field: TField): IArray;
295     procedure SetArrayIntf(aBufID: TRecordBuffer; AnArray: IArray; Field: TField);
296     function GetRecDBkey(aBufID: TRecordBuffer): TIBDBKey;
297     function GetFieldData(aBufID: TRecordBuffer; field: TField; outBuffer: PByte): boolean;
298     procedure SetFieldData(aBufID: TRecordBuffer; field: TField; inBuffer: PByte);
299     procedure SetSQLParams(aBufID: TRecordBuffer; params: ISQLParams);
300     procedure UpdateRecordFromQuery(aBufID: TRecordBuffer; QryResults: IResults);
301     function NeedRefresh(aBufID: TRecordBuffer): boolean;
302     function GetBookmarkFlag(aBufID: TRecordBuffer): TBookmarkFlag;
303     procedure SetBookmarkFlag(aBufID: TRecordBuffer; aBookmarkFlag: TBookmarkFlag);
304     procedure GetBookmarkData(Buffer: TRecordBuffer; Data: Pointer);
305     procedure SetBookmarkData(Buffer: TRecordBuffer; Data: Pointer);
306     function GetBookmarkSize: integer;
307     function GetRecord(aBufID: TRecordBuffer; GetMode: TGetMode;
308     DoCheck: Boolean): TGetResult;
309     procedure GotoFirst;
310     procedure GotoLast;
311     function GotoRecordNumber(RecNo: TIBRecordNumber): boolean;
312     function GetRecNo(aBufID: TRecordBuffer): TIBRecordNumber;
313     function GetCurrentRecNo: TIBRecordNumber;
314     function GetRecordCount: TIBRecordNumber;
315     function GetRecordSize: word;
316     procedure SetCurrentRecord(aBufID: TRecordBuffer);
317     procedure EditBuffer(aBufID: TRecordBuffer);
318     procedure CancelChanges(aBufID: TRecordBuffer);
319     procedure EditingDone(aBufID: TRecordBuffer; UpdateStatus: TCachedUpdateStatus);
320     procedure InsertBefore(aBufID: TRecordBuffer);
321     procedure Append(aBufID: TRecordBuffer);
322     procedure Delete(aBufID: TRecordBuffer);
323     procedure UnDelete(aBufID: TRecordBuffer);
324     function GetCachedUpdateStatus(aBufID: TRecordBuffer): TCachedUpdateStatus;
325     function GetUpdateStatus(aBufID: TRecordBuffer): TUpdateStatus;
326    
327     function GetInsertedRecords: integer;
328     function GetDeletedRecords: integer;
329     procedure ApplyUpdates(iterator: TUpdatesIterator);
330     procedure CancelUpdates;
331     function UpdatesPending: boolean;
332     function GetCachedUpdatesEnabled:boolean;
333     procedure SwapDataBuffer(buf1, buf2: TRecordBuffer);
334     function GetAliasName(FieldNo: integer): AnsiString;
335     procedure InitRecord(aBufID: TRecordBuffer);
336     function AtBOF: boolean;
337     function AtEOF: boolean;
338     procedure ClearCalcFields(aBufID: TRecordBuffer);
339     procedure SetCursor(aCursor: IResultSet);
340     procedure RegisterQuery(qryType : TRegisteredQueryTypes; qry : IStatement;
341     OnValuesReturnedProc : TOnValuesReturned);
342     procedure ExecRegisteredQuery(qryType : TRegisteredQueryTypes; aBufID: TRecordBuffer;
343     var SelectCount, InsertCount, UpdateCount, DeleteCount: integer);
344    
345     function HasRegisteredQuery(qryType : TRegisteredQueryTypes): boolean;
346     end;
347    
348     {
349     TIBSelectCursor provides common functions for uni-directional, bi-directional
350     and bi-directional with cached updates cursors.
351    
352     The IB Cursor classes support a common interface that is used to satisfy the
353     TDataset abstract methods used in buffer management including AllocRecordBuffer
354     and GetRecord.
355    
356     The opaque pointer to a buffer returned to TDataset is a pointer to an internal
357     data structure including a pointer to the actual buffer and a pointer to a separate
358     buffer for cached updates. This approach is used to avoid in memory copies every
359     time the dataset is scolled. It also avoids the cached rows buffers having to
360     include space for the calculated fields.
361     }
362    
363     TIBSelectCursor = class(TInterfacedObject)
364     private type
365     type
366     { TIBArray }
367    
368     TIBArray = class {Wrapper class to support array cache and event handling}
369     private
370     FArray: IArray;
371     FRecNo: integer;
372     FField: TField;
373     procedure EventHandler(Sender: IArray; Reason: TArrayEventReason);
374     public
375     constructor Create(aField: TField; anArray: IArray);
376     destructor Destroy; override;
377     property ArrayIntf: IArray read FArray;
378     end;
379    
380     PIBArray = ^TIBArray;
381     PIBBlobStream = ^TIBBlobStream;
382    
383     TColumnMetadata = record
384     fdSQLColIndex: Integer; {Corresponding element index in ISQLData}
385     fdDataType: Short;
386     fdDataScale: Short;
387     fdNullable: Boolean;
388     fdDataSize: Short;
389     fdDataOfs: Integer;
390     fdCodePage: TSystemCodePage;
391     fdRefreshOnInsert: boolean;
392     fdRefreshOnUpdate: boolean;
393     fdObjOffset: Integer; {used for Blob and Array columns}
394     fdAliasName: AnsiString;
395     end;
396    
397     PDisplayBuffer = ^TDisplaybuffer;
398     TDisplayBuffer = record
399     dbBookmarkFlag: TBookmarkFlag;
400     dbBookmarkData: array [1..sizeof(TIBRecordNumber)] of Byte;
401     dbBuffer: PByte;
402     dbCalcFields: PByte;
403     end;
404    
405     PRecordHeader = ^TRecordHeader;
406     TRecordHeader = record
407     rhUpdateStatus: TUpdateStatus;
408     end;
409    
410     TColumnMetadataArray = array of TColumnMetadata;
411    
412     TRegisteredQuery = record
413     stmt: IStatement;
414     ParamMap: array of integer;
415     UseOldValue: array of boolean;
416     ColMap: array of integer; {return values}
417     OnValuesReturned: TOnValuesReturned;
418     end;
419    
420     strict private
421     FRecordBufferSize: Integer; {Calculated size in bytes for each record buffer}
422     FRecordCount: Integer; {No. of records held in buffer pool. Total in dataset with Cursor.IsEof}
423     FColumnMetaData: TColumnMetadataArray; {Metadata extracted from cursor + per column info.
424     Note: only includes columns required i.e. there is
425     a corresponding field for the column or is the DBKey}
426     FColumnCount: integer; {size of metadata array}
427     FCalcFieldsSize: integer; {size in bytes of calculated fields buffer}
428     FBlobFieldCount: Longint; {Number of blob fields in each record}
429     FBlobStreamList: TList; {Keeps track of TIBBlobStream objects created}
430     FArrayList: TList; {Keeps track of TIBArry objects created}
431     FArrayFieldCount: integer; {Number of array fields in each record}
432     FDBKeyFieldColumn: integer; {FColumnMetadata index of DBKey with alias sDBKeyAIias}
433     FFieldNo2ColumnMap: array of integer; {TField.FieldNo to FColumnMetadata index map}
434     FNullColBitmapOffset: integer; {start of NullColumn Bitmap in each record buffer}
435     FRefreshRequiredBitmapOffset: integer; {start of Refresh Requried Bitmap in each record buffer}
436     FRefreshRequiredSize: integer; {number of bytes in RefreshRequired bitmap}
437     FCursor: IResultSet; {The Cursor}
438     FDefaultTZDate: TDateTime; {Default Time Zone time}
439     FName: string; {Local cursor name - set by creator}
440     FRegisteredQueries: array[TRegisteredQueryTypes] of TRegisteredQuery; {cached query info}
441     FDataset: TDataSet;
442    
443     function GetSQLParams : ISQLParams;
444     procedure SetupBufferStructure(metadata: IMetadata; aFields: TFields);
445     procedure ClearBlobCache;
446     procedure ClearArrayCache;
447     procedure CopyCursorDataToBuffer(QryResults: IResults; QryIndex, ColIndex: integer;
448     destBuff: PByte);
449     function InternalGetIsNull(Buff: PByte; ColIndex: integer): boolean;
450     procedure InternalSetIsNull(Buff: PByte; ColIndex: integer; IsNull: boolean);
451     procedure SaveBlobsAndArrays(Buff: PByte);
452     function NormaliseParamName(aName: AnsiString; var UseOldValue: boolean): AnsiString;
453     procedure ClearRegisteredQueries;
454     procedure SetParamValue(Buff: PByte; colIndex: integer; Param: ISQLParam);
455     protected
456     FCurrentRecord: PByte;
457     FCurrentRecordStatus: (csBOF, csRowBuffer, csEOF);
458     FSaveBufferSize: integer;
459     FEditState: (esBrowse, esEdit, esInsert);
460     function CalcRecordHdrSize: integer; virtual;
461     procedure ClearRowCache(aBuffer: PByte);
462     function ColIndexByName(aName: AnsiString; caseSensitive: boolean=false): integer;
463     procedure FetchCurrentRecord(destBuffer: PByte);
464     procedure FieldChanged(aBuffer: PByte; aField: TField); virtual;
465     function GetBuffer(aBufID: TRecordBuffer): PByte; inline;
466     procedure SetBuffer(aBufID: TRecordBuffer; aBuffer: PByte); inline;
467     function GetCalcFields(aBufID: TRecordBuffer): PByte; inline;
468     function GetOldBufferFor(aBuffer: PByte): PByte; virtual; abstract;
469     function FieldNo2ColumnIndex(aField: TField): integer; inline;
470     function InternalAllocRecordBuffer: PByte; virtual; abstract;
471     procedure InternalFreeRecordBuffer(aBuffer: PByte); virtual; abstract;
472     function InternalGetRecNo(aBuffer: PByte): TIBRecordNumber; virtual; abstract;
473     procedure InternalDelete(aBufID: TRecordBuffer); virtual; abstract;
474     procedure InternalUnDelete(aBuffer: PByte); virtual; abstract;
475     function InternalGetUpdateStatus(aBuffer: PByte): TUpdateStatus; inline;
476     procedure InternalSetUpdateStatus(aBuffer: PByte; status: TUpdateStatus); inline;
477     procedure SetRefreshRequired(Buff: PByte; ColIndex: integer; RefreshRequired: boolean);
478     procedure SetUpdateStatus(aBufID: TRecordBuffer; status: TUpdateStatus);
479     procedure Reset; virtual;
480     function FetchNext: boolean;
481     protected
482     property Buffers[index: TRecordBuffer]:PByte read GetBuffer;
483     property Cursor: IResultSet read FCursor;
484     property ColumnMetaData: TColumnMetadataArray read FColumnMetaData;
485     public
486     constructor Create(aDataset: TDataset; aName: string; aCursor: IResultSet; aFields: TFields;
487     aCalcFieldsSize: integer; aDefaultTZDate: TDateTime);
488     destructor Destroy; override;
489    
490     {TDataset Interface}
491     function AllocRecordBuffer: TRecordBuffer;
492     procedure FreeRecordBuffer(var Buffer: TRecordBuffer);
493     procedure SetCurrentRecord(aBufID: TRecordBuffer);
494     function CreateBlobStream(aBufID: TRecordBuffer; Field: TField; Mode: TBlobStreamMode): TStream; virtual;
495     function GetArray(aBufID: TRecordBuffer; Field: TField): IArray;
496     procedure SetArrayIntf(aBufID: TRecordBuffer; AnArray: IArray; Field: TField);
497     function GetRecDBkey(aBufID: TRecordBuffer): TIBDBKey;
498     function GetFieldData(aBufID: TRecordBuffer; field: TField; outBuffer: PByte): boolean;
499     procedure SetFieldData(aBufID: TRecordBuffer; field: TField; inBuffer: PByte);
500     procedure SetSQLParams(aBufID: TRecordBuffer; params: ISQLParams);
501     procedure UpdateRecordFromQuery(aBufID: TRecordBuffer; QryResults: IResults);
502     function NeedRefresh(aBufID: TRecordBuffer): boolean;
503     function GetBookmarkFlag(aBufID: TRecordBuffer): TBookmarkFlag;
504     procedure SetBookmarkFlag(aBufID: TRecordBuffer; aBookmarkFlag: TBookmarkFlag);
505     procedure SetBookmarkData(aBufID: TRecordBuffer; RecNo: TIBRecordNumber); overload;
506     procedure GetBookmarkData(Buffer: TRecordBuffer; Data: Pointer);
507     procedure SetBookmarkData(Buffer: TRecordBuffer; Data: Pointer); overload;
508     function GetBookmarkSize: integer;
509     function GetRecordSize: word;
510     function GetCurrentRecNo: TIBRecordNumber;
511     procedure SwapDataBuffer(buf1, buf2: TRecordBuffer);
512     function GetAliasName(FieldNo: integer): AnsiString;
513     procedure InitRecord(aBufID: TRecordBuffer); virtual;
514     function AtBOF: boolean;
515     function AtEOF: boolean;
516     function CursorAtEOF: boolean;
517     procedure Delete(aBufID: TRecordBuffer);
518     procedure UnDelete(aBufID: TRecordBuffer);
519     function GetUpdateStatus(aBufID: TRecordBuffer): TUpdateStatus;
520     procedure ClearCalcFields(aBufID: TRecordBuffer);
521     procedure SetCursor(aCursor: IResultSet);
522     procedure RegisterQuery(qryType : TRegisteredQueryTypes; qry : IStatement;
523     OnValuesReturnedProc : TOnValuesReturned);
524     procedure ExecRegisteredQuery(qryType : TRegisteredQueryTypes; aBufID: TRecordBuffer;
525     var SelectCount, InsertCount, UpdateCount, DeleteCount: integer);
526     function HasRegisteredQuery(qryType : TRegisteredQueryTypes): boolean;
527     public
528     property RecordBufferSize: integer read FRecordBufferSize;
529     property ColumnCount: integer read FColumnCount;
530     property RecordCount: integer read FRecordCount;
531     property CalcFieldsSize: integer read FCalcFieldsSize;
532     property BlobFieldCount: integer read FBlobFieldCount;
533     property ArrayFieldCount: integer read FArrayFieldCount;
534     property Name: string read FName;
535     property Dataset: TDataset read FDataset;
536     property Params: ISQLParams read GetSQLParams;
537     end;
538    
539     { TIBEditableCursor adds the means the save a copy of the current buffer during
540     a row edit and to restore it later. This may either be a single save buffer
541     or, if cached updates are enabled, a save buffer allocated from the old buffer pool.}
542    
543     TIBEditableCursor = class(TIBSelectCursor)
544     private
545     const OldBuffersPerBlock = 1024;
546     type
547     PLocalHeader = ^TLocalHeader;
548     TLocalHeader = record
549     lhOldBuffer: PByte;
550     end;
551     strict private
552     FApplyUpdates: TUpdatesIterator;
553     FSaveBuffer: PByte;
554     FOldBufferCache: TIBOldBufferPool;
555     FCachedUpdatesEnabled: boolean;
556     FLocalHdrOffset: integer;
557     procedure ApplyUpdatesIterator(status: TCachedUpdateStatus; DataBuffer, OldBuffer: PByte);
558     protected
559     FSavedRecNo: TIBRecordNumber; {record number of saved buffer if any}
560     function CalcRecordHdrSize: integer override;
561     procedure CancelBlobAndArrayChanges(aBuffer: PByte);
562     procedure CopyBuffers(src, dest: PByte); inline;
563     procedure DoCancelUpdates; virtual; abstract;
564     procedure DoOnInserted(aBuffer: PByte);
565     procedure FieldChanged(aBuffer: PByte; aField: TField); override;
566     function GetOldBufferFor(aBuffer: PByte): PByte; override;
567     procedure InitCachedUpdates; virtual;
568     procedure ReleaseSaveBuffer(aBufID: TRecordBuffer; UpdateStatus: TCachedUpdateStatus);
569     property OldBufferCache: TIBOldBufferPool read FOldBufferCache;
570     procedure InternalDelete(aBufID: TRecordBuffer); override;
571     procedure InternalUnDelete(aBuffer: PByte); override;
572     procedure InternalSetOldBuffer(aBuffer, OldBuffer: PByte); inline;
573     procedure Reset; override;
574     public
575     constructor Create(aDataset: TDataset; aName: string; aCursor: IResultSet; aFields: TFields;
576     aCalcFieldsSize: integer; aDefaultTZDate: TDateTime; CachedUpdates: boolean);
577     destructor Destroy; override;
578     procedure EditingDone(aBufID: TRecordBuffer; UpdateStatus: TCachedUpdateStatus); virtual;
579     procedure EditBuffer(aBufID: TRecordBuffer);
580     function GetRecNo(aBufID: TRecordBuffer): TIBRecordNumber; virtual; abstract;
581     procedure CancelChanges(aBufID: TRecordBuffer);
582     procedure ApplyUpdates(iterator: TUpdatesIterator);
583     procedure CancelUpdates;
584     function UpdatesPending: boolean;
585     function GetCachedUpdatesEnabled:boolean;
586     function GetCachedUpdateStatus(aBufID: TRecordBuffer): TCachedUpdateStatus;
587     end;
588    
589     {
590     TIBUniDirectionalCursor provides a simple forwards only cursor with edit support.
591    
592     }
593    
594     TIBUniDirectionalCursor = class(TIBEditableCursor, IIBCursor)
595     private const
596     DataBuffersPerBlock = 1024;
597     private type
598     PUniDirRecordHdr = ^TUniDirRecordHdr;
599     TUniDirRecordHdr = record
600     rdRecNo: TIBRecordNumber;
601     end;
602    
603     private
604     FRecordCount: TIBRecordNumber;
605     FInserting: boolean;
606     FInsertedRecords: integer;
607     FDeletedRecords: integer;
608     FDataBufferCache: TIBSimpleBufferPool;
609     FUniDirHdrOffset: integer;
610     procedure InternalSetRecNo(aBuffer: PByte; recno: TIBRecordNumber); inline;
611     protected
612     function CalcRecordHdrSize: integer; override;
613     procedure DoCancelUpdates; override;
614     function InternalAllocRecordBuffer: PByte; override;
615     procedure InternalFreeRecordBuffer(aBuffer: PByte); override;
616     function InternalGetRecNo(aBuffer : PByte) : TIBRecordNumber; override;
617     procedure InitCachedUpdates; override;
618     procedure InternalDelete(aBufID: TRecordBuffer); override;
619     procedure InternalUnDelete(aBuffer: PByte); override;
620     procedure Reset; override;
621     public
622     destructor Destroy; override;
623     function GetRecord(aBufID: TRecordBuffer; GetMode: TGetMode;
624     DoCheck: Boolean): TGetResult;
625     function GetRecNo(aBufID: TRecordBuffer): TIBRecordNumber; override;
626     function GetRecordCount: TIBRecordNumber;
627     procedure GotoFirst;
628     procedure GotoLast;
629     function GotoRecordNumber(RecNo: TIBRecordNumber): boolean;
630     procedure EditingDone(aBufID: TRecordBuffer; UpdateStatus: TCachedUpdateStatus); override;
631     procedure InsertBefore(aBufID: TRecordBuffer);
632     procedure Append(aBufID: TRecordBuffer);
633     function GetInsertedRecords: integer;
634     function GetDeletedRecords: integer;
635     procedure InitRecord(aBufID: TRecordBuffer); override;
636     end;
637    
638     {TIBBiDirectionalCursor provides a buffered dataset that can be scrolled in either direction}
639    
640     TIBBiDirectionalCursor = class(TIBEditableCursor,IIBCursor)
641     private
642     FBufferPool: TIBBufferPool;
643     procedure CancelUpdatesIterator(status: TCachedUpdateStatus; DataBuffer, OldBuffer: PByte);
644     function NewBuffer: PByte; inline;
645     protected
646     procedure DoCancelUpdates; override;
647     function InternalAllocRecordBuffer: PByte; override;
648     procedure InternalFreeRecordBuffer(aBuffer: PByte); override;
649     function InternalGetRecNo(aBuffer: PByte): TIBRecordNumber; override;
650     procedure InternalDelete(aBufID: TRecordBuffer); override;
651     procedure InternalUnDelete(aBuffer: PByte); override;
652     procedure Reset; override;
653     public
654     constructor Create(aDataset: TDataset; aName: string; aCursor: IResultSet; aFields: TFields;
655     aCalcFieldsSize: integer;
656     aDefaultTZDate: TDateTime; CachedUpdates: boolean;
657     aBuffersPerBlock, aFirstBlockBuffers: integer);
658     destructor Destroy; override;
659     function GetRecord(aBufID: TRecordBuffer; GetMode: TGetMode;
660     DoCheck: Boolean): TGetResult;
661     function GetRecNo(aBufID: TRecordBuffer): TIBRecordNumber; override;
662     procedure GotoFirst;
663     procedure GotoLast;
664     function GotoRecordNumber(RecNo: TIBRecordNumber): boolean;
665     function GetRecordCount: TIBRecordNumber;
666     procedure InsertBefore(aBufID: TRecordBuffer);
667     procedure Append(aBufID: TRecordBuffer);
668     function GetInsertedRecords: integer;
669     function GetDeletedRecords: integer;
670     procedure InitRecord(aBufID: TRecordBuffer); override;
671     end;
672    
673     implementation
674    
675     uses IBMessages, IBCustomDataSet, IBInternals, IBSQLMonitor;
676    
677     type
678     THackedField = class(TField); {Used to access to protected method TField.DataChange}
679    
680     { TIBEditableCursor }
681    
682     procedure TIBEditableCursor.ApplyUpdatesIterator(status: TCachedUpdateStatus;
683     DataBuffer, OldBuffer: PByte);
684     var DisplayBuffer: TDisplayBuffer;
685     RecordSkipped: boolean;
686     curOldBuffer: PByte;
687     begin
688     FSavedRecNo := InternalGetRecNo(DataBuffer);
689     DisplayBuffer.dbBookmarkFlag := bfCurrent;
690     DisplayBuffer.dbBuffer := DataBuffer;
691     DisplayBuffer.dbCalcFields := nil;
692     curOldBuffer := GetOldBufferFor(DataBuffer);
693     InternalSetOldBuffer(DataBuffer,OldBuffer); {May be different if more than one edit}
694     try
695     RecordSkipped := false;
696     FApplyUpdates(status,TRecordBuffer(@DisplayBuffer),RecordSkipped);
697     if not RecordSkipped then
698     FOldBufferCache.SetStatus(OldBuffer,cusUnModified);
699     finally
700     FSavedRecNo := 0;
701     InternalSetOldBuffer(DataBuffer,curOldBuffer);
702     end;
703     end;
704    
705     function TIBEditableCursor.CalcRecordHdrSize: integer;
706     begin
707     Result := inherited CalcRecordHdrSize;
708     if FCachedUpdatesEnabled then
709     begin
710     FLocalHdrOffset := Result;
711     Inc(Result,sizeof(TLocalHeader));
712     end;
713     end;
714    
715     procedure TIBEditableCursor.CancelBlobAndArrayChanges(aBuffer: PByte);
716     var i: integer;
717     pda: PIBArray;
718     begin
719     for i := 0 to ColumnCount - 1 do
720     with ColumnMetaData[i] do
721     case fdDataType of
722     SQL_BLOB:
723     PIBBlobStream(aBuffer + fdObjOffset)^ := nil;
724     SQL_ARRAY:
725     begin
726     pda := PIBArray(aBuffer + fdObjOffset);
727     if pda^ <> nil then
728     begin
729     pda^.ArrayIntf.CancelChanges;
730     pda^ := nil;
731     end;
732     end;
733     end;
734     end;
735    
736     procedure TIBEditableCursor.CopyBuffers(src, dest: PByte);
737     begin
738     Move(src^,dest^,FSaveBufferSize);
739     end;
740    
741     procedure TIBEditableCursor.DoOnInserted(aBuffer: PByte);
742     var OldBuffer: PByte;
743     i: integer;
744     begin
745     if FCachedUpdatesEnabled then
746     begin
747     OldBuffer := FOldBufferCache.Append(InternalGetRecNo(aBuffer),aBuffer);
748     FOldBufferCache.SetStatus(OldBuffer,cusInserted);
749     end;
750     FEditState := esInsert;
751     for i := 0 to ColumnCount - 1 do
752     with ColumnMetaData[i] do
753     if fdRefreshOnInsert then
754     SetRefreshRequired(aBuffer,i,true);
755     end;
756    
757     procedure TIBEditableCursor.FieldChanged(aBuffer : PByte; aField : TField);
758     var I: integer;
759     begin
760     inherited FieldChanged(aBuffer, aField);
761    
762     if InternalGetUpdateStatus(aBuffer) = usUnModified then
763     InternalSetUpdateStatus(aBuffer,usModified);
764     end;
765    
766     function TIBEditableCursor.GetOldBufferFor(aBuffer: PByte): PByte;
767     begin
768     if InternalGetRecNo(aBuffer) = FSavedRecNo then
769     if FCachedUpdatesEnabled then
770     Result := PLocalHeader(aBuffer + FLocalHdrOffset)^.lhOldBuffer
771     else
772     Result := FSaveBuffer
773     else
774     Result := nil;
775     end;
776    
777     procedure TIBEditableCursor.InitCachedUpdates;
778     begin
779     if FOldBufferCache = nil then
780     FOldBufferCache := TIBOldBufferPool.Create(Name + ': Old Buffer Cache', FSaveBufferSize,
781     OldBuffersPerBlock, OldBuffersPerBlock);
782     end;
783    
784     constructor TIBEditableCursor.Create(aDataset : TDataset; aName : string;
785     aCursor : IResultSet; aFields : TFields; aCalcFieldsSize : integer;
786     aDefaultTZDate : TDateTime; CachedUpdates : boolean);
787     begin
788     FCachedUpdatesEnabled := CachedUpdates;
789     inherited Create(aDataset, aName, aCursor,aFields, aCalcFieldsSize, aDefaultTZDate);
790     if FCachedUpdatesEnabled then
791     InitCachedUpdates
792     else
793     begin
794     FSaveBuffer := GetMem(FSaveBufferSize);
795     if FSaveBuffer = nil then
796     OutOfMemoryError;
797     end;
798     FEditState := esBrowse;
799     end;
800    
801     destructor TIBEditableCursor.Destroy;
802     begin
803     if FSaveBuffer <> nil then
804     FreeMem(FSaveBuffer);
805     if FOldBufferCache <> nil then
806     FOldBufferCache.Free;
807     inherited Destroy;
808     end;
809    
810     procedure TIBEditableCursor.EditingDone(aBufID: TRecordBuffer;
811     UpdateStatus: TCachedUpdateStatus);
812     begin
813     if FSavedRecNo <> 0 then
814     ReleaseSaveBuffer(aBufID, UpdateStatus);
815     FEditState := esBrowse;
816     end;
817    
818     procedure TIBEditableCursor.EditBuffer(aBufID: TRecordBuffer);
819     var Buff: PByte;
820     OldBuffer: PByte;
821     i: integer;
822     begin
823     Buff := GetBuffer(aBufID);
824     if Buff = nil then
825     IBError(ibxeBufferNotSet, [nil]);
826    
827     if FSavedRecNo <> 0 then
828     IBError(ibxeSaveBufferNotReleased,[InternalGetRecNo(Buff),FSavedRecNo]);
829    
830     if FCachedUpdatesEnabled then
831     OldBuffer := FOldBufferCache.Append(InternalGetRecNo(Buff),Buff)
832     else
833     OldBuffer := FSaveBuffer;
834    
835     InternalSetOldBuffer(Buff,OldBuffer);
836     CopyBuffers(Buff,OldBuffer);
837     FSavedRecNo := InternalGetRecNo(Buff);
838     FEditState := esEdit;
839     for i := 0 to ColumnCount - 1 do
840     with ColumnMetaData[i] do
841     if fdRefreshOnUpdate then
842     SetRefreshRequired(Buff,i,true);
843     end;
844    
845     procedure TIBEditableCursor.CancelChanges(aBufID: TRecordBuffer);
846     var Buff: PByte;
847     OldBuffer: PByte;
848     begin
849     Buff := GetBuffer(aBufID);
850     if Buff = nil then
851     IBError(ibxeBufferNotSet, [nil]);
852    
853     case FEditState of
854     esInsert:
855     InternalDelete(aBufID);
856    
857     esEdit:
858     begin
859     if InternalGetRecNo(Buff) <> FSavedRecNo then
860     IBError(ibxeUnableToRestore,[InternalGetRecNo(Buff),FSavedRecNo]);
861    
862     CancelBlobAndArrayChanges(Buff);
863     OldBuffer := GetOldBufferFor(Buff);
864     CopyBuffers(OldBuffer,Buff);
865     if FCachedUpdatesEnabled then
866     FOldBufferCache.SetStatus(OldBuffer,cusUnModified);
867     InternalSetOldBuffer(Buff,nil);
868     FSavedRecNo := 0;
869     end;
870     end;
871     FEditState := esBrowse;
872     end;
873    
874     procedure TIBEditableCursor.ReleaseSaveBuffer(aBufID: TRecordBuffer;
875     UpdateStatus: TCachedUpdateStatus);
876     var Buff: PByte;
877     begin
878     Buff := GetBuffer(aBufID);
879     if Buff = nil then
880     IBError(ibxeBufferNotSet, [nil]);
881    
882     if FSavedRecNo <> InternalGetRecNo(Buff) then
883     IBError(ibxeUnableToReleaseSaveBuffer,[InternalGetRecNo(Buff),FSavedRecNo]);
884    
885     if FCachedUpdatesEnabled then
886     FOldBufferCache.SetStatus(GetOldBufferFor(Buff),UpdateStatus);
887     InternalSetOldBuffer(Buff,nil);
888     FSavedRecNo := 0;
889     end;
890    
891     procedure TIBEditableCursor.InternalDelete(aBufID: TRecordBuffer);
892     begin
893     SetUpdateStatus(aBufID,usDeleted);
894     if FCachedUpdatesEnabled then
895     begin
896     if FSavedRecNo = 0 then
897     EditBuffer(aBufID);
898     if GetUpdateStatus(aBufID) = usInserted then
899     EditingDone(aBufID,cusUninserted)
900     else
901     EditingDone(aBufID,cusDeleted);
902     end;
903     end;
904    
905     procedure TIBEditableCursor.InternalUnDelete(aBuffer: PByte);
906     var OldBuffer: PByte;
907     begin
908     if not FCachedUpdatesEnabled then
909     IBError(ibxeCannotUnDelete,[]);
910    
911     OldBuffer := GetOldBufferFor(aBuffer);
912    
913     case FOldBufferCache.GetStatus(OldBuffer) of
914     cusUninserted:
915     FOldBufferCache.SetStatus(OldBuffer,cusInserted);
916    
917     cusDeleted:
918     if InternalGetUpdateStatus(aBuffer) = usDeleted then
919     begin
920     FOldBufferCache.SetStatus(OldBuffer,cusUnmodified);
921     InternalSetUpdateStatus(aBuffer,usUnmodified);
922     end;
923     end;
924     end;
925    
926     procedure TIBEditableCursor.InternalSetOldBuffer(aBuffer, OldBuffer: PByte);
927     begin
928     if FCachedUpdatesEnabled then
929     PLocalHeader(aBuffer + FLocalHdrOffset)^.lhOldBuffer := OldBuffer;
930     end;
931    
932     procedure TIBEditableCursor.Reset;
933     begin
934     inherited Reset;
935     if FOldBufferCache <> nil then
936     FOldBufferCache.Clear;
937     FSavedRecNo := 0;
938     end;
939    
940     procedure TIBEditableCursor.ApplyUpdates(iterator: TUpdatesIterator);
941     begin
942     if FSavedRecNo <> 0 then
943     IBError(ibxeSaveBufferNotReleased,[0,FSavedRecNo]);
944    
945     if not FCachedUpdatesEnabled or not UpdatesPending then Exit;
946    
947     FCachedUpdatesEnabled := false;
948     try
949     FApplyUpdates := iterator;
950     FSavedRecNo := 0;
951     FOldBufferCache.ForwardIterator(ApplyUpdatesIterator);
952     if FOldBufferCache.ModifiedRecords = 0 then
953     FOldBufferCache.Clear;
954     finally
955     FCachedUpdatesEnabled := true;
956     end;
957     end;
958    
959     procedure TIBEditableCursor.CancelUpdates;
960     begin
961     if FSavedRecNo <> 0 then
962     IBError(ibxeSaveBufferNotReleased,[0,FSavedRecNo]);
963    
964     if not FCachedUpdatesEnabled or not UpdatesPending then Exit;
965    
966     FCachedUpdatesEnabled := false;
967     try
968     FSavedRecNo := 0;
969     DoCancelUpdates;
970     FOldBufferCache.Clear;
971     finally
972     FCachedUpdatesEnabled := true;
973     end;
974     end;
975    
976     function TIBEditableCursor.UpdatesPending: boolean;
977     begin
978     Result := FOldBufferCache.RecordCount > 0;
979     end;
980    
981     function TIBEditableCursor.GetCachedUpdatesEnabled: boolean;
982     begin
983     Result := FCachedUpdatesEnabled;
984     end;
985    
986     function TIBEditableCursor.GetCachedUpdateStatus(aBufID: TRecordBuffer
987     ): TCachedUpdateStatus;
988     begin
989     if GetCachedUpdatesEnabled then
990     Result := FOldBufferCache.GetStatus(GetRecNo(aBufID))
991     else
992     Result := cusUnModified;
993     end;
994    
995     { TIBBiDirectionalCursor }
996    
997     procedure TIBBiDirectionalCursor.CancelUpdatesIterator(
998     status: TCachedUpdateStatus; DataBuffer, OldBuffer: PByte);
999     var curOldBuffer: PByte;
1000     begin
1001     curOldBuffer := GetOldBufferFor(DataBuffer);
1002     InternalSetOldBuffer(DataBuffer,OldBuffer);
1003     FSavedRecNo := InternalGetRecNo(DataBuffer);
1004     try
1005     case status of
1006     cusInserted:
1007     FBufferPool.Delete(DataBuffer);
1008    
1009     cusDeleted:
1010     FBufferPool.UnDelete(DataBuffer);
1011    
1012     cusModified:
1013     CopyBuffers(OldBuffer,DataBuffer);
1014     end;
1015     finally
1016     InternalSetOldBuffer(DataBuffer,curOldBuffer);
1017     FSavedRecNo := 0;
1018     end;
1019     end;
1020    
1021     function TIBBiDirectionalCursor.NewBuffer: PByte;
1022     begin
1023     Result := FBufferPool.Append;
1024     end;
1025    
1026     procedure TIBBiDirectionalCursor.DoCancelUpdates;
1027     begin
1028     OldBufferCache.BackwardsIterator(CancelUpdatesIterator);
1029     GotoLast;
1030     end;
1031    
1032     function TIBBiDirectionalCursor.InternalGetRecNo(aBuffer: PByte): TIBRecordNumber;
1033     begin
1034     Result := FBufferPool.GetRecNo(aBuffer);
1035     end;
1036    
1037     procedure TIBBiDirectionalCursor.InternalDelete(aBufID: TRecordBuffer);
1038     begin
1039     inherited InternalDelete(aBufID);
1040     SetCurrentRecord(aBufID);
1041     case FCurrentRecordStatus of
1042     csBOF:
1043     IBError(ibxeDeleteAtBOF,[]);
1044     csEOF:
1045     IBError(ibxeDeleteBeyondEOF,[]);
1046     csRowBuffer:
1047     begin
1048     {$ifdef PrintBuf}
1049     writeln('Delete RecNo ',GetRecNo(aBufID));
1050     {$endif}
1051     FCurrentRecord := FBufferPool.Delete(FCurrentRecord);
1052     {$ifdef PrintBuf}
1053     FBufferPool.PrintBufferList;
1054     {$endif}
1055     if FCurrentRecord = nil then
1056     FCurrentRecordStatus := csBOF;
1057     end;
1058     end;
1059     end;
1060    
1061     procedure TIBBiDirectionalCursor.InternalUnDelete(aBuffer: PByte);
1062     begin
1063     inherited InternalUnDelete(aBuffer);
1064     FBufferPool.UnDelete(aBuffer);
1065     end;
1066    
1067     procedure TIBBiDirectionalCursor.Reset;
1068     begin
1069     inherited Reset;
1070     if FBufferPool <> nil then
1071     FBufferPool.Clear;
1072     end;
1073    
1074     function TIBBiDirectionalCursor.InternalAllocRecordBuffer: PByte;
1075     begin
1076     Result := nil; {see GetRecord}
1077     end;
1078    
1079     procedure TIBBiDirectionalCursor.InternalFreeRecordBuffer(aBuffer: PByte);
1080     begin
1081     // Do nothing
1082     end;
1083    
1084     constructor TIBBiDirectionalCursor.Create(aDataset : TDataset; aName : string;
1085     aCursor : IResultSet; aFields : TFields; aCalcFieldsSize : integer;
1086     aDefaultTZDate : TDateTime; CachedUpdates : boolean; aBuffersPerBlock,
1087     aFirstBlockBuffers : integer);
1088     begin
1089     inherited Create(aDataset,aName,aCursor,aFields, aCalcFieldsSize, aDefaultTZDate,CachedUpdates);
1090     FBufferPool := TIBBufferPool.Create(aName+ ': BiDirectional record cache',
1091     RecordBufferSize,aBuffersPerBlock, aFirstBlockBuffers);
1092     end;
1093    
1094     destructor TIBBiDirectionalCursor.Destroy;
1095     begin
1096     if FBufferPool <> nil then
1097     FBufferPool.Free;
1098     inherited Destroy;
1099     end;
1100    
1101     function TIBBiDirectionalCursor.GetRecord(aBufID: TRecordBuffer;
1102     GetMode: TGetMode; DoCheck: Boolean): TGetResult;
1103    
1104     function ReadNext: TGetResult;
1105     begin
1106     if not Cursor.IsEof and FetchNext then
1107     begin
1108     FCurrentRecord := NewBuffer;
1109     FetchCurrentRecord(FCurrentRecord);
1110     FCurrentRecordStatus := csRowBuffer;
1111     Result := grOK;
1112     end
1113     else
1114     begin
1115     FCurrentRecordStatus := csEOF;
1116     Result := grEOF;
1117     end;
1118     end;
1119    
1120     begin
1121     Result := grError;
1122     case GetMode of
1123     gmPrior:
1124     case FCurrentRecordStatus of
1125     csBOF:
1126     Result := grBOF;
1127     csRowBuffer:
1128     begin
1129     FCurrentRecord := FBufferPool.GetPriorBuffer(FCurrentRecord);
1130     if FCurrentRecord = nil then
1131     begin
1132     FCurrentRecordStatus := csBOF;
1133     Result := grBOF
1134     end
1135     else
1136     Result := grOK;
1137     end;
1138     csEOF:
1139     begin
1140     FCurrentRecord := FBufferPool.GetLast;
1141     if FCurrentRecord = nil then
1142     Result := grEOF
1143     else
1144     begin
1145     FCurrentRecordStatus := csRowBuffer;
1146     Result := grOK;
1147     end;
1148     end;
1149     end;
1150    
1151     gmCurrent:
1152     case FCurrentRecordStatus of
1153     csBOF:
1154     Result := grBOF;
1155     csRowBuffer:
1156     Result := grOK;
1157     csEOF:
1158     Result := grEOF;
1159     end;
1160    
1161     gmNext:
1162     case FCurrentRecordStatus of
1163     csBOF:
1164     begin
1165     FCurrentRecord := FBufferPool.GetFirst;
1166     if (FCurrentRecord = nil) then
1167     Result := ReadNext
1168     else
1169     begin
1170     FCurrentRecordStatus := csRowBuffer;
1171     Result := grOK;
1172     end;
1173     end;
1174     csRowBuffer:
1175     begin
1176     FCurrentRecord := FBufferPool.GetNextBuffer(FCurrentRecord);
1177     if (FCurrentRecord = nil) then
1178     Result := ReadNext
1179     else
1180     Result := grOK;
1181     end;
1182     csEOF:
1183     Result := grEOF;
1184     end;
1185     end;
1186    
1187     case Result of
1188     grOK:
1189     begin
1190     SetBuffer(aBufID,FCurrentRecord);
1191     SetBookmarkFlag(aBufID,bfCurrent);
1192     end;
1193     grBOF:
1194     begin
1195     SetBuffer(aBufID,nil);
1196     SetBookmarkFlag(aBufID,bfBOF);
1197     end;
1198     grEOF:
1199     begin
1200     SetBuffer(aBufID,nil);
1201     SetBookmarkFlag(aBufID,bfEOF);
1202     end;
1203     end;
1204     { if FCurrentRecord <> nil then
1205     writeln('Get Record request ',GetMode,' Returns Rec No ',InternalGetRecNo(FCurrentRecord),' Status = ',
1206     FBufferPool.GetRecordStatus(FCurrentRecord))
1207     else
1208     writeln('Get Record request ',GetMode,' Returns ',Result); }
1209     SetBookmarkData(aBufID,InternalGetRecNo(FCurrentRecord));
1210     end;
1211    
1212     function TIBBiDirectionalCursor.GetRecNo(aBufID: TRecordBuffer): TIBRecordNumber;
1213     var Buff: PByte;
1214     begin
1215     Buff := GetBuffer(aBufID);
1216     if Buff = nil then
1217     Result := 0
1218     else
1219     Result := InternalGetRecNo(Buff);
1220     end;
1221    
1222     procedure TIBBiDirectionalCursor.GotoFirst;
1223     begin
1224     FCurrentRecord := nil;
1225     FCurrentRecordStatus := csBOF;
1226     end;
1227    
1228     procedure TIBBiDirectionalCursor.GotoLast;
1229     begin
1230     FCurrentRecord := FBufferPool.GetLast;
1231     if (FCurrentRecord <> nil) or not Cursor.IsEOF then
1232     begin
1233     FCurrentRecordStatus := csRowBuffer;
1234     if not Cursor.IsEof then
1235     while FetchNext do
1236     begin
1237     FCurrentRecord := NewBuffer;
1238     FetchCurrentRecord(FCurrentRecord);
1239     end;
1240     end;
1241     FCurrentRecord := nil;
1242     FCurrentRecordStatus := csEOF;
1243     end;
1244    
1245     function TIBBiDirectionalCursor.GotoRecordNumber(RecNo: TIBRecordNumber): boolean;
1246     begin
1247     if FBufferPool.GetRecordCount >= RecNo then
1248     FCurrentRecord := FBufferPool.GetBuffer(RecNo)
1249     else
1250     begin
1251     FCurrentRecord := FBufferPool.GetLast;
1252     if not Cursor.IsEOF then
1253     while (FBufferPool.GetRecNo(FCurrentRecord) < RecNo) and FetchNext do
1254     begin
1255     FCurrentRecord := NewBuffer;
1256     FetchCurrentRecord(FCurrentRecord);
1257     end;
1258     end;
1259     Result := (FBufferPool.GetRecNo(FCurrentRecord) = RecNo);
1260     end;
1261    
1262     function TIBBiDirectionalCursor.GetRecordCount: TIBRecordNumber;
1263     begin
1264     Result := FBufferPool.GetRecordCount - FBufferPool.DeletedRecords;
1265     end;
1266    
1267    
1268     procedure TIBBiDirectionalCursor.InsertBefore(aBufID: TRecordBuffer);
1269     var RecNo: TIBRecordNumber;
1270     begin
1271     GetBookmarkData(aBufID,@RecNo);
1272     if RecNo = 0 then
1273     GotoFirst
1274     else
1275     GotoRecordNumber(RecNo);
1276     {$ifdef PrintBuf}
1277     writeln('Insert Before Record = ',RecNo);
1278     {$endif}
1279     case FCurrentRecordStatus of
1280     csBOF:
1281     FCurrentRecord := FBufferPool.InsertBefore(FBufferPool.GetFirst);
1282     csRowBuffer:
1283     FCurrentRecord := FBufferPool.InsertBefore(FCurrentRecord);
1284     csEOF:
1285     FCurrentRecord := NewBuffer;
1286     end;
1287     InternalSetUpdateStatus(FCurrentRecord,usInserted);
1288     DoOnInserted(FCurrentRecord);
1289     SetBuffer(aBufID, FCurrentRecord);
1290     FCurrentRecordStatus := csRowBuffer;
1291     {$ifdef PrintBuf}
1292     FBufferPool.PrintBufferList;
1293     {$endif}
1294     end;
1295    
1296     procedure TIBBiDirectionalCursor.Append(aBufID: TRecordBuffer);
1297     begin
1298     GotoLast;
1299     {$ifdef PrintBuf}
1300     writeln('Append After Record = ',GetRecNo(aBufID));
1301     {$endif}
1302     if FCurrentRecordStatus = csEOF then
1303     FCurrentRecord := FBufferPool.Append
1304     else
1305     FCurrentRecord := FBufferPool.InsertAfter(FCurrentRecord);
1306     InternalSetUpdateStatus(FCurrentRecord,usInserted);
1307     DoOnInserted(FCurrentRecord);
1308     SetBuffer(aBufID,FCurrentRecord);
1309     FCurrentRecordStatus := csRowBuffer;
1310     {$ifdef PrintBuf}
1311     FBufferPool.PrintBufferList;
1312     {$endif}
1313     end;
1314    
1315     function TIBBiDirectionalCursor.GetInsertedRecords: integer;
1316     begin
1317     Result := FBufferPool.InsertedRecords;
1318     end;
1319    
1320     function TIBBiDirectionalCursor.GetDeletedRecords: integer;
1321     begin
1322     Result := FBufferPool.DeletedRecords;
1323     end;
1324    
1325     procedure TIBBiDirectionalCursor.InitRecord(aBufID: TRecordBuffer);
1326     begin
1327     inherited InitRecord(aBufID);
1328     if aBufID <> nil then
1329     PDisplayBuffer(aBufID)^.dbBuffer := nil;
1330     end;
1331    
1332     { TIBUniDirectionalCursor }
1333    
1334     function TIBUniDirectionalCursor.InternalGetRecNo(aBuffer: PByte) : TIBRecordNumber;
1335     begin
1336     if aBuffer = nil then
1337     Result := 0
1338     else
1339     Result := PUniDirRecordHdr(aBuffer+ FUniDirHdrOffset)^.rdRecNo;
1340     end;
1341    
1342     procedure TIBUniDirectionalCursor.InitCachedUpdates;
1343     begin
1344     inherited InitCachedUpdates;
1345     if FDataBufferCache = nil then
1346     FDataBufferCache := TIBSimpleBufferPool.Create(Name + ': Data Buffer Cacne',
1347     FSaveBufferSize,
1348     DataBuffersPerBlock,DataBuffersPerBlock);
1349     end;
1350    
1351     destructor TIBUniDirectionalCursor.Destroy;
1352     begin
1353     if FDataBufferCache <> nil then
1354     FDataBufferCache.Free;
1355     inherited Destroy;
1356     end;
1357    
1358     procedure TIBUniDirectionalCursor.InternalSetRecNo(aBuffer: PByte;
1359     recno: TIBRecordNumber);
1360     begin
1361     PUniDirRecordHdr(aBuffer + FUniDirHdrOffset)^.rdRecNo := recno;
1362     end;
1363    
1364     function TIBUniDirectionalCursor.CalcRecordHdrSize: integer;
1365     begin
1366     Result := inherited CalcRecordHdrSize;
1367     FUniDirHdrOffset := Result;
1368     Inc(Result,sizeof(TUniDirRecordHdr));
1369     end;
1370    
1371     procedure TIBUniDirectionalCursor.DoCancelUpdates;
1372     begin
1373     if FDataBufferCache <> nil then
1374     FDataBufferCache.Clear;
1375     end;
1376    
1377     function TIBUniDirectionalCursor.InternalAllocRecordBuffer: PByte;
1378     begin
1379     Result := GetMem(RecordBufferSize);
1380     if Result = nil then
1381     OutOfMemoryError;
1382     FillChar(Result^,RecordBufferSize,0);
1383     end;
1384    
1385     procedure TIBUniDirectionalCursor.InternalFreeRecordBuffer(aBuffer: PByte);
1386     begin
1387     if aBuffer <> nil then
1388     FreeMem(aBuffer);
1389     end;
1390    
1391     function TIBUniDirectionalCursor.GetRecord(aBufID: TRecordBuffer;
1392     GetMode: TGetMode; DoCheck: Boolean): TGetResult;
1393     var Buff: PByte;
1394    
1395     function GetNext: TGetResult;
1396     begin
1397     if Cursor.IsEOF or not FetchNext then
1398     Result := grEOF
1399     else
1400     begin
1401     FetchCurrentRecord(Buff);
1402     Inc(FRecordCount);
1403     InternalSetRecNo(Buff,FRecordCount);
1404     FCurrentRecord := Buff;
1405     FCurrentRecordStatus := csRowBuffer;
1406     Result := grOK;
1407     end;
1408     end;
1409    
1410     begin
1411     Result := grError;
1412     Buff := GetBuffer(aBufID);
1413     if Buff = nil then Exit;
1414    
1415     ClearRowCache(Buff);
1416     case GetMode of
1417     gmCurrent:
1418     begin
1419     case FCurrentRecordStatus of
1420     csBOF: ; {do nothing - returns grError}
1421     csEOF:
1422     Result := grEOF;
1423     csRowBuffer:
1424     begin
1425     { if InternalGetDeleted(FCurrentRecord) then
1426     Result := GetNext
1427     else}
1428     if Buff <> FCurrentRecord then
1429     begin
1430     FetchCurrentRecord(Buff);
1431     InternalSetRecNo(Buff,FRecordCount);
1432     FCurrentRecord := Buff;
1433     end;
1434     Result := grOK;
1435     end;
1436     end;
1437     end;
1438    
1439     gmNext:
1440     begin
1441     case FCurrentRecordStatus of
1442     csEOF:
1443     Result := grEOF;
1444     csBOF, csRowBuffer:
1445     Result := GetNext;
1446     end;
1447     end;
1448     end;
1449    
1450     case Result of
1451     grOK:
1452     SetBookmarkFlag(aBufID,bfCurrent);
1453     grBOF:
1454     SetBookmarkFlag(aBufID,bfBOF);
1455     grEOF:
1456     SetBookmarkFlag(aBufID,bfEOF);
1457     end;
1458    
1459     SetBookmarkData(aBufID,InternalGetRecNo(FCurrentRecord));
1460     end;
1461    
1462     function TIBUniDirectionalCursor.GetRecNo(aBufID : TRecordBuffer) : TIBRecordNumber;
1463     var Buff: PByte;
1464     begin
1465     Result := 0;
1466     Buff := GetBuffer(aBufID);
1467     if Buff = nil then
1468     IBError(ibxeBufferNotSet, [nil]);
1469     Result := InternalGetRecNo(Buff);
1470     end ;
1471    
1472     function TIBUniDirectionalCursor.GetRecordCount: TIBRecordNumber;
1473     begin
1474     Result := FRecordCount - FDeletedRecords;
1475     end;
1476    
1477     procedure TIBUniDirectionalCursor.GotoFirst;
1478     begin
1479     if FCurrentRecordStatus <> csBOF then
1480     IBError(ibxeDataSetUniDirectional,[]);
1481     end;
1482    
1483     procedure TIBUniDirectionalCursor.GotoLast;
1484     begin
1485     if not Cursor.IsEOF then
1486     while FetchNext do
1487     Inc(FRecordCount);
1488     FCurrentRecord := nil;
1489     FCurrentRecordStatus := csRowBuffer;
1490     end;
1491    
1492     function TIBUniDirectionalCursor.GotoRecordNumber(RecNo: TIBRecordNumber
1493     ): boolean;
1494     begin
1495     if not Cursor.IsEOF then
1496     begin
1497     while (FRecordCount < RecNo) and FetchNext do
1498     Inc(FRecordCount);
1499     FCurrentRecord := nil;
1500     FCurrentRecordStatus := csRowBuffer;
1501     end;
1502     Result := FRecordCount = RecNo;
1503     end;
1504    
1505     procedure TIBUniDirectionalCursor.EditingDone(aBufID: TRecordBuffer;
1506     UpdateStatus: TCachedUpdateStatus);
1507     var Buff: PByte;
1508     DataBuffer: PByte;
1509     OldBuffer: PByte;
1510     begin
1511     if GetCachedUpdatesEnabled and (FSavedRecNo <> 0) then
1512     begin
1513     Buff := GetBuffer(aBufID);
1514     if Buff = nil then
1515     IBError(ibxeBufferNotSet, [nil]);
1516    
1517     OldBuffer := GetOldBufferFor(Buff);
1518     DataBuffer := FDataBufferCache.Append;
1519     CopyBuffers(Buff,DataBuffer); {cache current data buffer}
1520     OldBufferCache.SetDataBuffer(OldBuffer,DataBuffer);
1521     end;
1522     inherited EditingDone(aBufID, UpdateStatus);
1523     FInserting := false;
1524     end;
1525    
1526     procedure TIBUniDirectionalCursor.InsertBefore(aBufID: TRecordBuffer);
1527     var Buff: PByte;
1528     begin
1529     SetCurrentRecord(aBufID);
1530    
1531     Buff := GetBuffer(aBufID);
1532     if Buff = nil then
1533     IBError(ibxeBufferNotSet, [nil]);
1534     Inc(FRecordCount);
1535     InternalSetRecNo(Buff,FRecordCount);
1536     FCurrentRecord := Buff;
1537     FCurrentRecordStatus := csRowBuffer;
1538     FInserting := true;
1539     Inc(FInsertedRecords);
1540     InternalSetUpdateStatus(FCurrentRecord,usInserted);
1541     DoOnInserted(FCurrentRecord);
1542     end;
1543    
1544     procedure TIBUniDirectionalCursor.Append(aBufID: TRecordBuffer);
1545     var Buff: PByte;
1546     begin
1547     Buff := GetBuffer(aBufID);
1548     if Buff = nil then
1549     IBError(ibxeBufferNotSet, [nil]);
1550    
1551     GotoLast;
1552     Inc(FRecordCount);
1553     InternalSetRecNo(Buff,FRecordCount);
1554     FCurrentRecord := Buff;
1555     FCurrentRecordStatus := csRowBuffer;
1556     FInserting := true;
1557     Inc(FInsertedRecords);
1558     InternalSetUpdateStatus(FCurrentRecord,usInserted);
1559     DoOnInserted(FCurrentRecord);
1560     end;
1561    
1562     procedure TIBUniDirectionalCursor.InternalDelete(aBufID: TRecordBuffer);
1563     begin
1564     inherited InternalDelete(aBufID);
1565     SetCurrentRecord(aBufID);
1566    
1567     GetRecord(aBufID,gmNext,false);
1568     Inc(FDeletedRecords);
1569     end;
1570    
1571     procedure TIBUniDirectionalCursor.InternalUnDelete(aBuffer: PByte);
1572     begin
1573     inherited InternalUnDelete(aBuffer);
1574     Dec(FDeletedRecords);
1575     end;
1576    
1577     procedure TIBUniDirectionalCursor.Reset;
1578     begin
1579     inherited Reset;
1580     FRecordCount := 0;
1581     FInserting := false;
1582     FInsertedRecords := 0;
1583     FDeletedRecords := 0;
1584     if FDataBufferCache <> nil then
1585     FDataBufferCache.Clear;
1586     end;
1587    
1588     function TIBUniDirectionalCursor.GetInsertedRecords: integer;
1589     begin
1590     Result := FInsertedRecords
1591     end;
1592    
1593     function TIBUniDirectionalCursor.GetDeletedRecords: integer;
1594     begin
1595     Result := FDeletedRecords;
1596     end;
1597    
1598     procedure TIBUniDirectionalCursor.InitRecord(aBufID: TRecordBuffer);
1599     var Buff: PByte;
1600     begin
1601     inherited InitRecord(aBufID);
1602     Buff := GetBuffer(aBufID);
1603     if Buff = nil then
1604     IBError(ibxeBufferNotSet, [nil]);
1605     ClearRowCache(Buff);
1606     Fillchar(Buff^,RecordBufferSize,0);
1607     InternalSetUpdateStatus(Buff,usInserted);
1608     end;
1609    
1610    
1611     { TIBSelectCursor.TIBArray }
1612    
1613     procedure TIBSelectCursor.TIBArray.EventHandler(Sender: IArray;
1614     Reason: TArrayEventReason);
1615     begin
1616     case Reason of
1617     arChanging:
1618     if FRecNo <> FField.Dataset.RecNo then
1619     IBError(ibxeNotCurrentArray,[nil]);
1620    
1621     arChanged:
1622     THackedField(FField).DataChanged;
1623     end;
1624     end;
1625    
1626     constructor TIBSelectCursor.TIBArray.Create(aField: TField; anArray: IArray);
1627     begin
1628     inherited Create;
1629     FField := aField;
1630     FArray := anArray;
1631     FRecNo := FField.Dataset.RecNo;
1632     FArray.AddEventHandler(EventHandler);
1633     end;
1634    
1635     destructor TIBSelectCursor.TIBArray.Destroy;
1636     begin
1637     FArray.RemoveEventHandler(EventHandler);
1638     inherited Destroy;
1639     end;
1640    
1641     { TIBSelectCursor }
1642    
1643     {
1644     A record buffer is structured into
1645    
1646     1. Space for each field's column data determined from the metadate datasize.
1647     Note: for VarChar the column data is sizeof(short) longer to allow for a length
1648     indicator.
1649     2. Buffer Header for per record local data
1650     3. a little endian bitmap giving each column's null status. 0 => null, 1 => not null
1651     4. Another bitmap to keep track of each colmn's refresh required status.
1652     5. Additional space for the blob and array caches,
1653    
1654     The Column Metadata and Field No. mapping is also established. The interesting
1655     metadata is copied from the IStatement into the FColumnMetadata dynamic array, and which
1656     also includes the SQL Column Index. This latter mapping is necessary because an
1657     IStatement column is ignored if it is not reference from the list of fields, or
1658     it's aliasname is not the IBX specical field sDBkeyAlias (used by TIBTable).
1659    
1660     Likewise a mapping between the Field.FieldNo (a 1 -based sequence nu. and the
1661     corresponding index into the 0 -based FColumnMetadata dynamic array. This is
1662     not just because of the different base, but because the use of IBX special field can
1663     result in and FColumnMetadata that is not linked to a TField.
1664     }
1665    
1666     procedure TIBSelectCursor.SetupBufferStructure(metadata : IMetadata;
1667     aFields : TFields);
1668     var i: integer;
1669     colMetadata: IColumnMetaData;
1670     field: TField;
1671     ColUsed: boolean;
1672     ColMetaDataIndex: integer;
1673     RecordSize: integer;
1674     begin
1675     FArrayFieldCount := 0;
1676     FBlobFieldCount := 0;
1677     FDBKeyFieldColumn := -1;
1678     ColMetaDataIndex := 0;
1679     SetLength(FFieldNo2ColumnMap,aFields.Count+1); {Note: FieldNos are 1-based - index 0 is not used}
1680    
1681     { Initialize offsets, buffer sizes, etc... }
1682     FColumnCount := metadata.Count;
1683     SetLength(FColumnMetadata,FColumnCount);
1684     RecordSize := CalcRecordHdrSize;
1685    
1686     {Now determine how much space needs to be reserved for each column and column metadata}
1687     for i := 0 to FColumnCount - 1 do
1688     with FColumnMetadata[ColMetaDataIndex] do
1689     begin
1690     fdSQLColIndex := i;
1691     colMetadata := metadata[i];
1692     ColUsed := false;
1693    
1694     if colMetadata.GetAliasName = sDBkeyAlias then {special case for TIBTable support}
1695     begin
1696     FDBKeyFieldColumn := ColMetaDataIndex;
1697     ColUsed := true;
1698     end;
1699    
1700     field := aFields.FindField(colMetadata.GetAliasName);
1701     if field <> nil then
1702     begin
1703     FFieldNo2ColumnMap[field.FieldNo] := ColMetaDataIndex;
1704     Colused := true;
1705     fdRefreshOnInsert := (pfRefreshOnInsert in field.ProviderFlags) or
1706     (TIBFieldDef(field.FieldDef).IdentityColumn) or
1707     field.FieldDef.InternalCalcField;
1708     fdRefreshOnUpdate := (pfRefreshOnUpdate in field.ProviderFlags) or
1709     field.FieldDef.InternalCalcField;
1710     end;
1711    
1712     if not Colused then continue;
1713    
1714     fdDataType := colMetadata.GetSQLType;
1715     if fdDataType = SQL_BLOB then
1716     fdDataScale := 0
1717     else
1718     fdDataScale := colMetadata.getScale;
1719     fdNullable := colMetadata.getIsNullable;
1720     fdDataSize := colMetadata.GetSize;
1721     fdCodePage := CP_NONE;
1722     fdAliasName := colMetadata.GetAliasName;
1723    
1724     case fdDataType of
1725     SQL_TIMESTAMP,
1726     SQL_TYPE_DATE,
1727     SQL_TYPE_TIME:
1728     fdDataSize := SizeOf(TDateTime);
1729     SQL_TIMESTAMP_TZ,
1730     SQL_TIMESTAMP_TZ_EX,
1731     SQL_TIME_TZ,
1732     SQL_TIME_TZ_EX:
1733     fdDataSize := SizeOf(TIBBufferedDateTimeWithTimeZone);
1734     SQL_SHORT:
1735     begin
1736     if (fdDataScale = 0) then
1737     fdDataSize := SizeOf(short)
1738     else
1739     if (fdDataScale >= (-4)) then
1740     fdDataSize := SizeOf(Currency)
1741     else
1742     fdDataSize := SizeOf(Double);
1743     end;
1744     SQL_LONG:
1745     begin
1746     if (fdDataScale = 0) then
1747     fdDataSize := SizeOf(Integer)
1748     else
1749     if (fdDataScale >= (-4)) then
1750     fdDataSize := SizeOf(Currency)
1751     else
1752     fdDataSize := SizeOf(Double);
1753     end;
1754     SQL_INT64:
1755     begin
1756     if (fdDataScale = 0) then
1757     fdDataSize := SizeOf(Int64)
1758     else
1759     if (fdDataScale >= (-4)) then
1760     fdDataSize := SizeOf(Currency)
1761     else
1762     fdDataSize := SizeOf(Double);
1763     end;
1764     SQL_DOUBLE, SQL_FLOAT, SQL_D_FLOAT:
1765     fdDataSize := SizeOf(Double);
1766     SQL_BOOLEAN:
1767     fdDataSize := SizeOf(wordBool);
1768     SQL_VARYING,
1769     SQL_TEXT:
1770     fdCodePage := colMetadata.getCodePage;
1771     SQL_BLOB:
1772     begin
1773     Inc(FBlobFieldCount);
1774     fdCodePage := colMetadata.getCodePage;
1775     end;
1776     SQL_DEC16,
1777     SQL_DEC34,
1778     SQL_DEC_FIXED,
1779     SQL_INT128:
1780     fdDataSize := sizeof(tBCD);
1781     SQL_Array:
1782     Inc(FArrayFieldCount);
1783     end;
1784     fdDataOfs := RecordSize;
1785     if fdDataType = SQL_VARYING then
1786     Inc(RecordSize, fdDataSize + sizeof(short))
1787     else
1788     Inc(RecordSize, fdDataSize);
1789     Inc(ColMetaDataIndex);
1790     end;
1791    
1792     FColumnCount := ColMetaDataIndex; {set to number of columns in use}
1793    
1794     {Reserve space for null column bitmap}
1795     FNullColBitmapOffset := RecordSize;
1796     Inc(RecordSize, ((FColumnCount - 1) div 8) + 1);
1797    
1798     {Reserve space for Refresh Required bit map}
1799     FRefreshRequiredBitmapOffset := RecordSize;
1800     FRefreshRequiredSize := ((FColumnCount - 1) div 8) + 1;
1801     Inc(RecordSize, FRefreshRequiredSize);
1802     FSaveBufferSize := RecordSize;
1803    
1804     {Reserve space for Blob Objects}
1805     if FBlobFieldCount > 0 then
1806     for i := 0 to FColumnCount - 1 do
1807     with FColumnMetadata[i] do
1808     begin
1809     if fdDataType = SQL_BLOB then
1810     begin
1811     fdObjOffset := RecordSize;
1812     Inc(RecordSize,sizeof(TIBBlobStream));
1813     end;
1814     end;
1815    
1816     {Reserve space for array objects}
1817     if FArrayFieldCount > 0 then
1818     for i := 0 to FColumnCount - 1 do
1819     with FColumnMetadata[i] do
1820     begin
1821     if fdDataType = SQL_ARRAY then
1822     begin
1823     fdObjOffset := RecordSize;
1824     Inc(RecordSize,sizeof(TIBArray));
1825     end;
1826     end;
1827    
1828     {FRecordBufferSize is how much space needs to be reserved}
1829     FRecordBufferSize := RecordSize;
1830     end;
1831    
1832     function TIBSelectCursor.GetSQLParams : ISQLParams;
1833     begin
1834     Result := FCursor.GetStatement.SQLParams;
1835     end;
1836    
1837     function TIBSelectCursor.GetBuffer(aBufID: TRecordBuffer): PByte;
1838     begin
1839     Result := PDisplayBuffer(aBufID)^.dbBuffer;
1840     end;
1841    
1842     procedure TIBSelectCursor.SetBuffer(aBufID: TRecordBuffer; aBuffer: PByte);
1843     begin
1844     PDisplayBuffer(aBufID)^.dbBuffer := aBuffer;
1845     end;
1846    
1847     function TIBSelectCursor.GetCalcFields(aBufID: TRecordBuffer): PByte;
1848     begin
1849     Result := PDisplayBuffer(aBufID)^.dbCalcFields;
1850     end;
1851    
1852     function TIBSelectCursor.FieldNo2ColumnIndex(aField: TField): integer;
1853     begin
1854     if (aField.FieldNo < 1) or (aField.FieldNo > Length(FFieldNo2ColumnMap)) then
1855     IBError(ibxeBadFieldNo,[aField.FieldNo, Length(FFieldNo2ColumnMap)-1]);
1856    
1857     Result := FFieldNo2ColumnMap[aField.FieldNo];
1858     end;
1859    
1860     function TIBSelectCursor.InternalGetUpdateStatus(aBuffer: PByte): TUpdateStatus;
1861     begin
1862     Result := PRecordHeader(aBuffer)^.rhUpdateStatus;
1863     end;
1864    
1865     procedure TIBSelectCursor.InternalSetUpdateStatus(aBuffer: PByte;
1866     status: TUpdateStatus);
1867     begin
1868     PRecordHeader(aBuffer)^.rhUpdateStatus:= status;
1869     end;
1870    
1871     procedure TIBSelectCursor.SetUpdateStatus(aBufID: TRecordBuffer;
1872     status: TUpdateStatus);
1873     var Buff: PByte;
1874     begin
1875     Buff := GetBuffer(aBufID);
1876     if Buff = nil then
1877     IBError(ibxeBufferNotSet, [nil]);
1878    
1879     InternalSetUpdateStatus(Buff,status);
1880     end;
1881    
1882     procedure TIBSelectCursor.Reset;
1883     begin
1884     FRecordCount := 0;
1885     ClearBlobCache;
1886     ClearArrayCache;
1887     FCursor := nil;
1888     FCurrentRecord := nil;
1889     FCurrentRecordStatus := csBOF;
1890     end;
1891    
1892     function TIBSelectCursor.FetchNext: boolean;
1893     begin
1894     Result := Cursor.FetchNext;
1895     if not (csDesigning in Dataset.ComponentState) then
1896     MonitorHook.SQLFetch(self,Cursor.GetStatement.GetSQLText);
1897     end;
1898    
1899     function TIBSelectCursor.NormaliseParamName(aName : AnsiString;
1900     var UseOldValue : boolean) : AnsiString;
1901     const
1902     sOldPrefix = 'OLD_';
1903     sNewPrefix = 'NEW';
1904     begin
1905     UseOldValue := false;
1906     Result := aName;
1907     if pos(sOldPrefix,Result) = 1 then
1908     begin
1909     system.Delete(Result,1,length(sOldPrefix));
1910     UseOldValue := true;
1911     end
1912     else
1913     begin
1914     if pos(sNewPrefix,Result) = 1 then
1915     system.Delete(Result,1,length(sNewPrefix));
1916     end;
1917     end ;
1918    
1919     procedure TIBSelectCursor.ClearRegisteredQueries;
1920     var i: TRegisteredQueryTypes;
1921     begin
1922     for i := low(FRegisteredQueries) to high(FRegisteredQueries) do
1923     with FRegisteredQueries[i] do
1924     begin
1925     stmt := nil;
1926     SetLength(ParamMap,0);
1927     SetLength(UseOldValue,0);
1928     SetLength(ColMap,0);
1929     end;
1930     end ;
1931    
1932     procedure TIBSelectCursor.ClearBlobCache;
1933     var i: Integer;
1934     begin
1935     for i := 0 to FBlobStreamList.Count - 1 do
1936     begin
1937     TIBBlobStream(FBlobStreamList[i]).Free;
1938     FBlobStreamList[i] := nil;
1939     end;
1940     FBlobStreamList.Pack;
1941     end;
1942    
1943     procedure TIBSelectCursor.ClearArrayCache;
1944     var i: Integer;
1945     begin
1946     for i := 0 to FArrayList.Count - 1 do
1947     begin
1948     TIBArray(FArrayList[i]).Free;
1949     FArrayList[i] := nil;
1950     end;
1951     FArrayList.Pack;
1952     end;
1953    
1954     procedure TIBSelectCursor.ClearRowCache(aBuffer: PByte);
1955     var i: Integer;
1956     begin
1957     for i := 0 to FColumnCount - 1 do
1958     with FColumnMetaData[i] do
1959     case fdDataType of
1960     SQL_BLOB:
1961     PIBBlobStream(aBuffer + fdObjOffset)^ := nil;
1962     SQL_ARRAY:
1963     PIBArray(aBuffer + fdObjOffset)^ := nil;
1964     end;
1965     end;
1966    
1967     procedure TIBSelectCursor.CopyCursorDataToBuffer(QryResults: IResults; QryIndex,
1968     ColIndex: integer; destBuff: PByte);
1969     var LocalData: PByte;
1970     ColData: ISQLData;
1971     IsNull: boolean;
1972     DataLength: Short;
1973     BufPtr: PByte;
1974     begin
1975     QryResults.GetData(QryIndex,IsNull,DataLength,LocalData);
1976     with FColumnMetaData[ColIndex] do
1977     begin
1978     InternalSetIsNull(destBuff,ColIndex,IsNull);
1979     BufPtr := destBuff + fdDataOfs;
1980     if IsNull then
1981     FillChar(BufPtr^,fdDataSize,0)
1982     else
1983     begin
1984     ColData := QryResults[QryIndex];
1985     case fdDataType of {Get Formatted data for column types that need formatting}
1986     SQL_TYPE_DATE,
1987     SQL_TYPE_TIME,
1988     SQL_TIMESTAMP:
1989     {This is an IBX native format and not the TDataset approach. See also GetFieldData}
1990     PDateTime(BufPtr)^ := ColData.AsDateTime;
1991    
1992     SQL_TIMESTAMP_TZ,
1993     SQL_TIMESTAMP_TZ_EX:
1994     begin
1995     with PIBBufferedDateTimeWithTimeZone(Bufptr)^ do
1996     ColData.GetAsDateTime(Timestamp,dstOffset,TimeZoneID);
1997     end;
1998    
1999     SQL_TIME_TZ,
2000     SQL_TIME_TZ_EX:
2001     begin
2002     with PIBBufferedDateTimeWithTimeZone(Bufptr)^ do
2003     ColData.GetAsTime(Timestamp, dstOffset,TimeZoneID, FDefaultTZDate);
2004     end;
2005     SQL_SHORT:
2006     begin
2007     if (fdDataScale = 0) then
2008     PShort(BufPtr)^ := ColData.AsShort
2009     else
2010     if (fdDataScale >= (-4)) then
2011     PCurrency(BufPtr)^ := ColData.AsCurrency
2012     else
2013     PDouble(BufPtr)^ := ColData.AsDouble;
2014     end;
2015     SQL_LONG:
2016     begin
2017     if (fdDataScale = 0) then
2018     PLong(BufPtr)^ := ColData.AsLong
2019     else
2020     if (fdDataScale >= (-4)) then
2021     PCurrency(BufPtr)^ := ColData.AsCurrency
2022     else
2023     PDouble(BufPtr)^ := ColData.AsDouble;
2024     end;
2025     SQL_INT64:
2026     begin
2027     if (fdDataScale = 0) then
2028     PInt64(BufPtr)^ := ColData.AsInt64
2029     else
2030     if (fdDataScale >= (-4)) then
2031     PCurrency(BufPtr)^ := ColData.AsCurrency
2032     else
2033     PDouble(BufPtr)^ := ColData.AsDouble;
2034     end;
2035    
2036     SQL_DOUBLE, SQL_FLOAT, SQL_D_FLOAT:
2037     PDouble(BufPtr)^ := ColData.AsDouble;
2038    
2039     SQL_BOOLEAN:
2040     system.PBoolean(BufPtr)^ := ColData.AsBoolean;
2041    
2042     SQL_DEC16,
2043     SQL_DEC34,
2044     SQL_DEC_FIXED,
2045     SQL_INT128:
2046     pBCD(BufPtr)^ := ColData.GetAsBCD;
2047    
2048     else
2049     begin
2050     if fdDataType = SQL_VARYING then
2051     begin
2052     PShort(BufPtr)^ := DataLength;
2053     Move(LocalData^, (BufPtr + sizeof(Short))^, DataLength);
2054     end
2055     else
2056     Move(LocalData^, BufPtr^, fdDataSize)
2057     end;
2058     end; {case}
2059     end;
2060     end;
2061     end;
2062    
2063     function TIBSelectCursor.InternalGetIsNull(Buff: PByte; ColIndex: integer
2064     ): boolean;
2065     var pBitmap: PByte;
2066     mask: byte;
2067     begin
2068     pBitmap := Buff + FNullColBitmapOffset + ColIndex div 8;
2069     mask := $01 shl (ColIndex mod 8);
2070     Result := (pBitmap^ and mask) = 0; {bit is 0 => null}
2071     end;
2072    
2073     procedure TIBSelectCursor.InternalSetIsNull(Buff: PByte; ColIndex: integer;
2074     IsNull: boolean);
2075     var pBitmap: PByte;
2076     mask: byte;
2077     begin
2078     pBitmap := Buff + FNullColBitmapOffset + ColIndex div 8;
2079     mask := $01 shl (ColIndex mod 8);
2080     if IsNull then
2081     pBitmap^ := pBitmap^ and not mask {unset bit}
2082     else
2083     pBitmap^ := pBitmap^ or mask; {set bit}
2084     end;
2085    
2086     procedure TIBSelectCursor.SetRefreshRequired(Buff: PByte; ColIndex: integer;
2087     RefreshRequired: boolean);
2088     var pBitmap: PByte;
2089     mask: byte;
2090     begin
2091     pBitmap := Buff + FRefreshRequiredBitmapOffset + ColIndex div 8;
2092     mask := $01 shl (ColIndex mod 8);
2093     if RefreshRequired then
2094     pBitmap^ := pBitmap^ or mask {set bit}
2095     else
2096     pBitmap^ := pBitmap^ and not mask; {unset bit}
2097     end;
2098    
2099     procedure TIBSelectCursor.SaveBlobsAndArrays(Buff: PByte);
2100     var pdb: PIBBlobStream;
2101     pda: PIBArray;
2102     i: integer;
2103     begin
2104     for i := 0 to FColumnCount - 1 do
2105     with FColumnMetadata[i] do
2106     begin
2107     case fdDataType of
2108     SQL_BLOB:
2109     begin
2110     pdb := PIBBlobStream(Buff + fdObjOffset);
2111     if pdb^ <> nil then
2112     begin
2113     pdb^.Finalize;
2114     PISC_QUAD(Buff + fdDataOfs)^ := pdb^.BlobID;
2115     InternalSetIsNull(Buff,i, pdb^.Size = 0);
2116     SetRefreshRequired(Buff,i,true);
2117     end
2118     end;
2119    
2120     SQL_ARRAY:
2121     begin
2122     pda := PIBArray(Buff + fdObjOffset);
2123     if pda^ <> nil then
2124     begin
2125     PISC_QUAD(Buff + fdDataOfs)^ := pda^.ArrayIntf.GetArrayID;
2126     InternalSetIsNull(Buff,i, pda^.ArrayIntf.IsEmpty);
2127     SetRefreshRequired(Buff,i,true);
2128     end
2129     end;
2130     end;
2131     end;
2132     end;
2133    
2134     function TIBSelectCursor.CalcRecordHdrSize: integer;
2135     begin
2136     Result := sizeof(TRecordHeader);
2137     end;
2138    
2139     function TIBSelectCursor.ColIndexByName(aName: AnsiString; caseSensitive: boolean
2140     ): integer;
2141     var i: integer;
2142     begin
2143     Result := -1;
2144     if caseSensitive then
2145     for i := 0 to FColumnCount - 1 do
2146     begin
2147     if FColumnMetaData[i].fdAliasName = aName then
2148     begin
2149     Result := i;
2150     Exit;
2151     end
2152     end
2153     else
2154     begin
2155     aName := AnsiUpperCase(aName);
2156     for i := 0 to FColumnCount - 1 do
2157     if AnsiUpperCase(FColumnMetaData[i].fdAliasName) = aName then
2158     begin
2159     Result := i;
2160     Exit;
2161     end
2162     end;
2163     end;
2164    
2165     procedure TIBSelectCursor.FetchCurrentRecord(destBuffer: PByte);
2166     var i: Integer;
2167     begin
2168     { Make sure blob and array caches are empty }
2169     ClearRowCache(destBuffer);
2170    
2171     if Cursor.IsEOF then
2172     IBError(ibxeCursorAtEOF,[]);
2173    
2174     for i := 0 to FColumnCount - 1 do
2175     CopyCursorDataToBuffer(FCursor,FColumnMetaData[i].fdSQLColIndex,i,destBuffer);
2176     InternalSetUpdateStatus(destBuffer,usUnModified);
2177     end;
2178    
2179     procedure TIBSelectCursor.FieldChanged(aBuffer: PByte; aField: TField);
2180     begin
2181     THackedField(aField).DataChanged;
2182     end;
2183    
2184     constructor TIBSelectCursor.Create(aDataset : TDataset; aName : string;
2185     aCursor : IResultSet; aFields : TFields; aCalcFieldsSize : integer;
2186     aDefaultTZDate : TDateTime);
2187     begin
2188     inherited Create;
2189     FBlobStreamList := TList.Create;
2190     FArrayList := TList.Create;
2191     FDataset := aDataset;
2192     FName := aName;
2193     FCursor := aCursor;
2194     FCalcFieldsSize := aCalcFieldsSize;
2195     FDefaultTZDate := aDefaultTZDate;
2196     SetupBufferStructure(cursor.GetStatement.MetaData,aFields);
2197     FCurrentRecord := nil;
2198     FCurrentRecordStatus := csBOF;
2199     ClearRegisteredQueries;
2200     end;
2201    
2202     destructor TIBSelectCursor.Destroy;
2203     begin
2204     ClearBlobCache;
2205     ClearArrayCache;
2206     FBlobStreamList.Free;
2207     FArrayList.Free;
2208     SetLength(FColumnMetadata,0);
2209     ClearRegisteredQueries;
2210     inherited Destroy;
2211     end;
2212    
2213     {Note bufferindex starts at one to avoid confusion with a nil pointer.
2214     The "buffer" is an opaque pointer - actually an integer index to the
2215     FBuffers array.}
2216    
2217     function TIBSelectCursor.AllocRecordBuffer: TRecordBuffer;
2218     begin
2219     Result := GetMem(sizeof(TDisplayBuffer));;
2220     if Result = nil then
2221     OutofMemoryError;
2222     with PDisplayBuffer(Result)^ do
2223     begin
2224     dbBookmarkFlag := bfCurrent;
2225     FillChar(dbBookmarkData,sizeof(dbBookmarkData),0);
2226     dbBuffer := InternalAllocRecordBuffer;
2227     dbCalcFields := GetMem(FCalcFieldsSize);
2228     if dbCalcFields = nil then
2229     OutOfMemoryError;
2230     end;
2231     end;
2232    
2233     procedure TIBSelectCursor.FreeRecordBuffer(var Buffer: TRecordBuffer);
2234     begin
2235     if Buffer <> nil then
2236     begin
2237     with PDisplayBuffer(Buffer)^ do
2238     begin
2239     InternalFreeRecordBuffer(dbBuffer);
2240     FreeMem(dbCalcFields);
2241     end;
2242     FreeMem(Buffer);
2243     end;
2244     Buffer := nil;
2245     end;
2246    
2247     procedure TIBSelectCursor.SetCurrentRecord(aBufID: TRecordBuffer);
2248     var Buff: PByte;
2249    
2250     procedure SetBuffer;
2251     begin
2252     Buff := GetBuffer(aBufID);
2253     if Buff = nil then
2254     IBError(ibxeBufferNotSet, [nil]);
2255     FCurrentRecord := Buff;
2256     FCurrentRecordStatus := csRowBuffer;
2257     end;
2258    
2259     begin
2260     case GetBookmarkFlag(aBufID) of
2261     bfBOF:
2262     FCurrentRecordStatus := csBOF;
2263     bfEOF:
2264     if FEditState = esInsert then
2265     SetBuffer
2266     else
2267     FCurrentRecordStatus := csEOF;
2268     else
2269     SetBuffer;
2270     end;
2271     end;
2272    
2273     {Field.offset is a zero based integer indexing the blob field
2274     Field.FieldNo is a one based field index accross all of a record's fields}
2275    
2276     function TIBSelectCursor.CreateBlobStream(aBufID: TRecordBuffer; Field: TField;
2277     Mode: TBlobStreamMode): TStream;
2278     var pdb: PIBBlobStream;
2279     fs: TIBBlobStream;
2280     Buff: PByte;
2281     ColMetadata: TColumnMetadata;
2282     begin
2283     Buff := GetBuffer(aBufID);
2284     if Buff = nil then
2285     begin
2286     fs := TIBBlobStream.Create;
2287     fs.Mode := bmReadWrite;
2288     fs.Database := (Field.Dataset as TIBCustomDataset).Database;
2289     fs.Transaction := (Field.Dataset as TIBCustomDataset).Transaction;
2290     fs.SetField(Field);
2291     FBlobStreamList.Add(Pointer(fs));
2292     end
2293     else
2294     begin
2295     ColMetadata := FColumnMetaData[FieldNo2ColumnIndex(Field)];
2296     pdb := PIBBlobStream(Buff + ColMetaData.fdObjOffset);
2297     if pdb^ = nil then {not yet assigned}
2298     begin
2299     fs := TIBBlobStream.Create;;
2300     fs.Mode := bmReadWrite;
2301     fs.Database := (Field.Dataset as TIBCustomDataset).Database;
2302     fs.Transaction := (Field.Dataset as TIBCustomDataset).Transaction;
2303     fs.SetField(Field);
2304     fs.BlobID := PISC_QUAD(Buff + ColMetaData.fdDataOfs)^;
2305     pdb^ := fs;
2306     FBlobStreamList.Add(Pointer(fs));
2307     end
2308     else
2309     fs := pdb^;
2310     end;
2311     Result := TIBDSBlobStream.Create(Field, fs, Mode);
2312     end;
2313    
2314     function TIBSelectCursor.GetArray(aBufID: TRecordBuffer; Field: TField
2315     ): IArray;
2316     var Buff: PByte;
2317     pda: PIBArray;
2318     ColIndex: integer;
2319     ColMetadata: TColumnMetadata;
2320     ar: TIBArray;
2321     begin
2322     Buff := GetBuffer(aBufID);
2323     with Field.Dataset as TIBCustomDataset do
2324     begin
2325     if Buff = nil then
2326     Result := Database.Attachment.CreateArray(Transaction.TransactionIntf,
2327     (Field as TIBArrayField).RelationName,Field.FieldName)
2328     else
2329     begin
2330     ColIndex := FieldNo2ColumnIndex(Field);
2331     ColMetadata := FColumnMetaData[ColIndex];
2332     pda := PIBArray(Buff + ColMetadata.fdObjOffset);
2333     if pda^ = nil then
2334     begin
2335     if InternalGetIsNull(Buff,ColIndex) then
2336     Result := Database.Attachment.CreateArray(Transaction.TransactionIntf,
2337     (Field as TIBArrayField).RelationName,Field.FieldName)
2338     else
2339     Result := Database.Attachment.OpenArray(Transaction.TransactionIntf,
2340     (Field as TIBArrayField).RelationName,Field.FieldName,
2341     PISC_QUAD(Buff + ColMetaData.fdDataOfs)^);
2342     ar := TIBArray.Create(Field,Result);
2343     pda^ := ar;
2344     FArrayList.Add(ar);
2345     end
2346     else
2347     Result := pda^.ArrayIntf;
2348     end;
2349     end;
2350     end;
2351    
2352     procedure TIBSelectCursor.SetArrayIntf(aBufID: TRecordBuffer; AnArray: IArray;
2353     Field: TField);
2354     var Buff: PByte;
2355     pda: PIBArray;
2356     ColIndex: integer;
2357     ColMetadata: TColumnMetadata;
2358     ar: TIBArray;
2359     IsNull: boolean;
2360     begin
2361     Buff := GetBuffer(aBufID);
2362     if Buff <> nil then
2363     begin
2364     ColIndex := FieldNo2ColumnIndex(Field);
2365     ColMetadata := FColumnMetaData[ColIndex];
2366     IsNull := AnArray = nil;
2367     InternalSetIsNull(Buff,ColIndex,IsNull);
2368     pda := PIBArray(Buff + ColMetaData.fdObjOffset);
2369     if pda^ = nil then
2370     begin
2371     if not IsNull then
2372     begin
2373     ar := TIBArray.Create(Field,AnArray);
2374     pda^ := ar;
2375     FArrayList.Add(ar);
2376     end
2377     else
2378     pda^.FArray := AnArray;
2379     end;
2380     FieldChanged(Buff,Field);
2381     end;
2382     end;
2383    
2384     function TIBSelectCursor.GetRecDBkey(aBufID: TRecordBuffer): TIBDBKey;
2385     var Buff: PByte;
2386     begin
2387     FillChar(Result,sizeof(TIBDBKey),0);
2388     if FDBKeyFieldColumn >= 0 then
2389     with FColumnMetaData[FDBKeyFieldColumn] do
2390     begin
2391     Buff := GetBuffer(aBufID);
2392     if (Buff <> nil) and (fdDataSize <= 8) then
2393     Result := PIBDBKEY(Buff + fdDataOfs)^;
2394     end;
2395     end;
2396    
2397     function TIBSelectCursor.GetFieldData(aBufID: TRecordBuffer; field: TField;
2398     outBuffer: PByte): boolean;
2399     var Buff: PByte;
2400     ColIndex: integer;
2401     Data: PByte;
2402     len: Short;
2403     begin
2404     Result := false;
2405     if aBufID = nil then Exit;
2406     if field.FieldNo < 0 then {Calculated Field}
2407     begin
2408     Buff := GetCalcFields(aBufID);
2409     if Buff = nil then Exit;
2410    
2411     Inc(Buff, field.Offset); {For CalcFields, TField.offset is the buffer offset}
2412     Result := not Boolean(Buff[0]);
2413    
2414     if Result and (outBuffer <> nil) then
2415     Move(Buff[1], outBuffer^, field.DataSize);
2416     end
2417     else
2418     begin
2419     Buff := GetBuffer(aBufID);
2420     if Buff = nil then Exit;
2421    
2422     ColIndex := FieldNo2ColumnIndex(field);
2423     Result := not InternalGetIsNull(Buff,ColIndex);
2424     if Result and (outBuffer <> nil) then
2425     with FColumnMetaData[ColIndex] do
2426     begin
2427     Data := Buff + fdDataOfs;
2428     if fdDataType = SQL_VARYING then
2429     begin
2430     len := PShort(Data)^;
2431     if len <= field.DataSize then
2432     begin
2433     Inc(Data,sizeof(short));
2434     Move(Data^, outBuffer^, len);
2435     PAnsiChar(outBuffer)[len] := #0;
2436     end
2437     else
2438     IBError(ibxeFieldSizeError,[field.FieldName,field.DataSize,len])
2439     end
2440     else
2441     if fdDataSize <= Field.DataSize then
2442     Move(Data^, outBuffer^, fdDataSize)
2443     else
2444     IBError(ibxeFieldSizeError,[field.FieldName,field.DataSize,fdDataSize])
2445     end;
2446     end;
2447     end;
2448    
2449     procedure TIBSelectCursor.SetFieldData(aBufID: TRecordBuffer; field: TField;
2450     inBuffer: PByte);
2451     var Buff: PByte;
2452     ColIndex: integer;
2453     Data: PByte;
2454     DataSize: Short;
2455     len: Short;
2456     IsNull: boolean;
2457     begin
2458     if field.FieldNo < 0 then {calaculated field}
2459     begin
2460     Buff := GetCalcFields(aBufID);
2461     if Buff = nil then Exit;
2462    
2463     Inc(Buff, field.Offset); {For CalcFields, TField.offset is the buffer offset}
2464     IsNull := inBuffer = nil;
2465     Boolean(Buff[0]) := IsNull;
2466     if not IsNull then
2467     Move(inBuffer^, Buff[1], Field.DataSize);
2468     end
2469     else
2470     begin
2471     Buff := GetBuffer(aBufID);
2472     if Buff = nil then Exit;
2473    
2474     ColIndex := FieldNo2ColumnIndex(field);
2475     Field.Validate(inBuffer);
2476     IsNull := (inBuffer = nil) or
2477     (Field is TStringField) and (PAnsiChar(inBuffer)^ = #0);
2478     InternalSetIsNull(Buff,Colindex,IsNull);
2479     if not IsNull then
2480     with FColumnMetadata[ColIndex] do
2481     begin
2482     Data := Buff + fdDataOfs;
2483     DataSize := fdDataSize;
2484     FillChar(Data^,DataSize,0);
2485     if fdDataType = SQL_VARYING then
2486     begin
2487     len := StrLen(PAnsiChar(inBuffer));
2488     PShort(Data)^ := len;
2489     Inc(Data,sizeof(Short));
2490     end;
2491    
2492     if DataSize >= field.DataSize then
2493     Move(inBuffer^, Data^,DataSize)
2494     else
2495     IBError(ibxeDBBufferTooSmall,[DataSize,field.FieldName,field.DataSize]);
2496    
2497     FieldChanged(Buff,field);
2498     end;
2499     end;
2500     end;
2501    
2502     procedure TIBSelectCursor.SetSQLParams(aBufID: TRecordBuffer; params: ISQLParams);
2503     var Buff: PByte;
2504     OldBuffer: PByte;
2505     i: integer;
2506     Param: ISQLParam;
2507     ParamName: AnsiString;
2508     srcBuffer: PByte;
2509     ColIndex: integer;
2510     UseOldValue: boolean;
2511     begin
2512     Buff := GetBuffer(aBufID);
2513     if Buff = nil then
2514     IBError(ibxeBufferNotSet, [nil]);
2515    
2516     SaveBlobsAndArrays(Buff);
2517    
2518     OldBuffer := GetOldBufferFor(Buff);
2519     for i := 0 to Params.GetCount - 1 do
2520     begin
2521     Param := params[i];
2522     ParamName := NormaliseParamName(Param.Name,UseOldValue);
2523    
2524     {Determine source buffer}
2525     if UseOldValue and (OldBuffer <> nil) then
2526     srcBuffer := OldBuffer
2527     else
2528     srcBuffer := Buff;
2529    
2530     ColIndex := ColIndexByName(ParamName,params.GetHasCaseSensitiveParams);
2531     if ColIndex = -1 then
2532     continue;
2533    
2534     SetParamValue(srcBuffer,ColIndex,Param);
2535     end;
2536     end;
2537    
2538     {This method is called either using a Row Refresh query or an update/insert query
2539     with a returning clause}
2540    
2541     procedure TIBSelectCursor.UpdateRecordFromQuery(aBufID: TRecordBuffer;
2542     QryResults: IResults);
2543     var Buff: PByte;
2544     ColIndex: integer;
2545     i: integer;
2546     begin
2547     Buff := GetBuffer(aBufID);
2548     if Buff = nil then
2549     IBError(ibxeBufferNotSet, [nil]);
2550     ClearRowCache(Buff);
2551    
2552     for i := 0 to QryResults.Count - 1 do
2553     begin
2554     ColIndex := ColIndexByName(QryResults[i].GetAliasName);
2555     if ColIndex >= 0 then
2556     begin
2557     CopyCursorDataToBuffer(QryResults,i,ColIndex,Buff);
2558     SetRefreshRequired(Buff,ColIndex,false);
2559     end ;
2560     end;
2561     end;
2562    
2563     function TIBSelectCursor.NeedRefresh(aBufID : TRecordBuffer) : boolean;
2564     var i: integer;
2565     Buff: PByte;
2566     begin
2567     Buff := GetBuffer(aBufID);
2568     if Buff = nil then
2569     IBError(ibxeBufferNotSet, [nil]);
2570    
2571     Result := false;
2572     for i := 0 to FRefreshRequiredSize - 1 do
2573     if PByte(Buff + FRefreshRequiredBitmapOffset + i)^ <> 0 then
2574     begin
2575     Result := true;
2576     Exit;
2577     end;
2578     end;
2579    
2580     function TIBSelectCursor.GetBookmarkFlag(aBufID: TRecordBuffer): TBookmarkFlag;
2581     begin
2582     Result := PDisplayBuffer(aBufID)^.dbBookmarkFlag;;
2583     end;
2584    
2585     procedure TIBSelectCursor.SetBookmarkFlag(aBufID: TRecordBuffer;
2586     aBookmarkFlag: TBookmarkFlag);
2587     begin
2588     PDisplayBuffer(aBufID)^.dbBookmarkFlag := aBookmarkFlag;
2589     end;
2590    
2591     procedure TIBSelectCursor.SetBookmarkData(aBufID: TRecordBuffer;
2592     RecNo: TIBRecordNumber);
2593     begin
2594     Move(RecNo,PDisplayBuffer(aBufID)^.dbBookmarkData,GetBookmarkSize);
2595     end;
2596    
2597     procedure TIBSelectCursor.GetBookmarkData(Buffer: TRecordBuffer; Data: Pointer);
2598     begin
2599     Move(PDisplayBuffer(Buffer)^.dbBookmarkData,Data^,GetBookmarkSize);
2600     end;
2601    
2602     procedure TIBSelectCursor.SetBookmarkData(Buffer: TRecordBuffer; Data: Pointer);
2603     begin
2604     if Data <> nil then
2605     Move(Data^,PDisplayBuffer(Buffer)^.dbBookmarkData, GetBookmarkSize);
2606     end;
2607    
2608     function TIBSelectCursor.GetBookmarkSize: integer;
2609     begin
2610     Result := sizeof(TIBRecordNumber);
2611     end;
2612    
2613     function TIBSelectCursor.GetRecordSize: word;
2614     begin
2615     Result := sizeof(TDisplayBuffer);
2616     end;
2617    
2618     function TIBSelectCursor.GetCurrentRecNo: TIBRecordNumber;
2619     begin
2620     case FCurrentRecordStatus of
2621     csBOF:
2622     Result := 0;
2623     csEOF:
2624     Result := FRecordCount;
2625     csRowBuffer:
2626     Result := InternalGetRecNo(FCurrentRecord);
2627     end;
2628     end;
2629    
2630     procedure TIBSelectCursor.SwapDataBuffer(buf1, buf2: TRecordBuffer);
2631     var TmpBuf: PByte;
2632     TmpBookmarkFlag: TBookmarkFlag;
2633     TmpBookmarkData1: TBytes;
2634     TmpBookmarkData2: TBytes;
2635     begin
2636     TmpBuf := PDisplayBuffer(Buf1)^.dbBuffer;
2637     TmpBookmarkFlag := GetBookmarkFlag(buf1);
2638     SetLength(TmpBookmarkData1,GetBookmarkSize);
2639     GetBookmarkData(buf1,pointer(TmpBookmarkData1));
2640     SetLength(TmpBookmarkData2,GetBookmarkSize);
2641     GetBookmarkData(buf1,pointer(TmpBookmarkData2));
2642     PDisplayBuffer(Buf1)^.dbBuffer := PDisplayBuffer(Buf2)^.dbBuffer;
2643     SetBookmarkFlag(buf1,GetBookmarkFlag(buf2));
2644     SetBookmarkData(buf1,pointer(TmpBookmarkData2));
2645     PDisplayBuffer(buf2)^.dbBuffer := TmpBuf;
2646     SetBookmarkFlag(buf2,TmpBookmarkFlag);
2647     SetBookmarkData(buf2,pointer(TmpBookmarkData1));
2648     end;
2649    
2650     function TIBSelectCursor.GetAliasName(FieldNo: integer): AnsiString;
2651     begin
2652     if (FieldNo < 1) or (FieldNo > Length(FFieldNo2ColumnMap)) then
2653     IBError(ibxeBadFieldNo,[FieldNo, Length(FFieldNo2ColumnMap)-1]);
2654    
2655     Result := FColumnMetaData[ FFieldNo2ColumnMap[FieldNo] ].fdAliasName;
2656     end;
2657    
2658     procedure TIBSelectCursor.InitRecord(aBufID: TRecordBuffer);
2659     begin
2660     with PDisplayBuffer(aBufID)^ do
2661     begin
2662     dbBookmarkFlag := bfInserted;
2663     Fillchar(dbCalcFields^,CalcFieldsSize,0);
2664     FillChar(dbBookmarkData,GetBookmarkSize,0);
2665     if dbBuffer <> nil then
2666     InternalSetUpdateStatus(dbBuffer,usInserted);
2667     end;
2668     end;
2669    
2670     function TIBSelectCursor.AtBOF: boolean;
2671     begin
2672     Result := FCurrentRecordStatus = csBOF;
2673     end;
2674    
2675     function TIBSelectCursor.AtEOF: boolean;
2676     begin
2677     Result := (FCurrentRecordStatus = csEOF) and Cursor.IsEof;
2678     end;
2679    
2680     function TIBSelectCursor.CursorAtEOF : boolean;
2681     begin
2682     Result := FCursor.IsEof;
2683     end;
2684    
2685     procedure TIBSelectCursor.Delete(aBufID: TRecordBuffer);
2686     var Buff: PByte;
2687     begin
2688     Buff := GetBuffer(aBufID);
2689     if Buff = nil then
2690     IBError(ibxeBufferNotSet, [nil]);
2691    
2692     InternalDelete(aBufID);
2693     end;
2694    
2695     procedure TIBSelectCursor.UnDelete(aBufID: TRecordBuffer);
2696     var Buff: PByte;
2697     begin
2698     Buff := GetBuffer(aBufID);
2699     if Buff = nil then
2700     IBError(ibxeBufferNotSet, [nil]);
2701    
2702     InternalUnDelete(Buff);
2703     end;
2704    
2705     function TIBSelectCursor.GetUpdateStatus(aBufID: TRecordBuffer): TUpdateStatus;
2706     var Buff: PByte;
2707     begin
2708     Buff := GetBuffer(aBufID);
2709     if Buff = nil then
2710     IBError(ibxeBufferNotSet, [nil]);
2711     Result := InternalGetUpdateStatus(Buff);
2712     end;
2713    
2714     procedure TIBSelectCursor.ClearCalcFields(aBufID: TRecordBuffer);
2715     var Buff: PByte;
2716     begin
2717     Buff := GetCalcFields(aBufID);
2718     FillChar(Buff^,FCalcFieldsSize,0);
2719     end;
2720    
2721     procedure TIBSelectCursor.SetCursor(aCursor: IResultSet);
2722     begin
2723     if (FCursor <> nil) and (FCursor.GetStatement <> aCursor.GetStatement) then
2724     IBError(ibxeDifferentStatement,[]);
2725     Reset;
2726     FCursor := aCursor;
2727     end;
2728    
2729     procedure TIBSelectCursor.RegisterQuery(qryType : TRegisteredQueryTypes;
2730     qry : IStatement; OnValuesReturnedProc : TOnValuesReturned);
2731     var i: integer;
2732     ParamName: AnsiString;
2733     begin
2734     with FRegisteredQueries[qryType] do
2735     begin
2736     stmt := qry;
2737     OnValuesReturned := OnValuesReturnedProc;
2738     SetLength(ParamMap,Qry.SQLParams.count);
2739     SetLength(UseOldValue, Qry.SQLParams.count);
2740     for i := 0 to Qry.SQLParams.Count - 1 do
2741     begin
2742     ParamName := NormaliseParamName(Qry.SQLParams[i].Name,UseOldValue[i]);
2743     ParamMap[i] := ColIndexByName(ParamName,Qry.SQLParams.GetHasCaseSensitiveParams);
2744     end;
2745    
2746     SetLength(ColMap,qry.MetaData.Count);
2747     for i := 0 to qry.MetaData.Count - 1 do
2748     ColMap[i] := ColIndexByName(qry.MetaData[i].getAliasName);
2749     end;
2750     end;
2751    
2752     procedure TIBSelectCursor.ExecRegisteredQuery(qryType : TRegisteredQueryTypes;
2753     aBufID : TRecordBuffer; var SelectCount, InsertCount, UpdateCount,
2754     DeleteCount : integer);
2755     var Buff: PByte;
2756     i: integer;
2757     OldBuffer: PByte;
2758     qryResults: IResults;
2759     qryResultSet: IResultSet;
2760     begin
2761     Buff := GetBuffer(aBufID);
2762     if Buff = nil then
2763     IBError(ibxeBufferNotSet, [nil]);
2764    
2765     SaveBlobsAndArrays(Buff);
2766     OldBuffer := GetOldBufferFor(Buff);
2767    
2768     with FRegisteredQueries[qryType] do
2769     begin
2770     {set param values}
2771     for i := 0 to Length(ParamMap) - 1 do
2772     if ParamMap[i] <> -1 then
2773     begin
2774     if UseOldValue[i] and (OldBuffer <> nil) then
2775     SetParamValue(OldBuffer,ParamMap[i],stmt.SQLParams[i])
2776     else
2777     SetParamValue(Buff,ParamMap[i],stmt.SQLParams[i]);
2778     end;
2779    
2780     {execute query}
2781     if stmt.SQLStatementType = SQLSelect then
2782     begin
2783     qryResultSet := stmt.OpenCursor;
2784     qryResultSet.FetchNext;
2785     qryResults := qryResultSet; {Only single results expected}
2786     end
2787     else
2788     qryResults := stmt.Execute;
2789     stmt.GetRowsAffected(SelectCount, InsertCount, UpdateCount, DeleteCount);
2790     if not (csDesigning in Dataset.ComponentState) then
2791     MonitorHook.SQLExecute(self,stmt.GetSQLText);
2792    
2793     {process any return values}
2794     if qryType = rqDelete then
2795     InternalDelete(aBufID)
2796     else
2797     begin
2798     ClearRowCache(Buff);
2799     for i := 0 to Length(ColMap) - 1 do
2800     if ColMap[i] <> -1 then
2801     begin
2802     CopyCursorDataToBuffer(qryResults,i,ColMap[i],Buff);
2803     SetRefreshRequired(Buff,ColMap[i],false);
2804     end;
2805     end;
2806     if (qryResults <> nil) and assigned(OnValuesReturned) then
2807     OnValuesReturned(qryResults);
2808     end;
2809     end;
2810    
2811     function TIBSelectCursor.HasRegisteredQuery(qryType : TRegisteredQueryTypes
2812     ) : boolean;
2813     begin
2814     Result := FRegisteredQueries[qryType].stmt <> nil;
2815     end;
2816    
2817     procedure TIBSelectCursor.SetParamValue(Buff : PByte; colIndex : integer;
2818     Param : ISQLParam);
2819     var Data: PByte;
2820     DataLength: Short;
2821     st: RawByteString;
2822     pda: PIBArray;
2823     begin
2824     if InternalGetIsNull(Buff,ColIndex) then
2825     Param.IsNull := true
2826     else
2827     with FColumnMetaData[ColIndex] do
2828     begin
2829     Data := Buff + fdDataOfs;
2830     case fdDataType of
2831     SQL_TEXT:
2832     if Param.getColMetadata.getCharSetID <= 1 {NONE or OCTETS} then
2833     Param.SetAsPointer(Data)
2834     else
2835     begin
2836     DataLength := strlen(PAnsiChar(Data));
2837     if DataLength > fdDataSize then
2838     DataLength := fdDataSize;
2839     SetString(st, PAnsiChar(Data), DataLength);
2840     SetCodePage(st,fdCodePage,false);
2841     Param.AsString := st;
2842     end;
2843     SQL_VARYING:
2844     begin
2845     DataLength := PShort(Data)^;
2846     Inc(Data,sizeof(Short));
2847     SetString(st, PAnsiChar(Data), DataLength);
2848     SetCodePage(st,fdCodePage,false);
2849     Param.AsString := st;
2850     end;
2851     SQL_FLOAT, SQL_DOUBLE, SQL_D_FLOAT:
2852     Param.AsDouble := PDouble(Data)^;
2853     SQL_SHORT:
2854     begin
2855     if fdDataScale = 0 then
2856     Param.AsShort := PShort(Data)^
2857     else
2858     if fdDataScale >= (-4) then
2859     Param.AsCurrency := PCurrency(Data)^
2860     else
2861     Param.AsDouble := PDouble(Data)^;
2862     end;
2863     SQL_LONG:
2864     begin
2865     if fdDataScale = 0 then
2866     Param.AsLong := PLong(Data)^
2867     else
2868     if fdDataScale >= (-4) then
2869     Param.AsCurrency := PCurrency(Data)^
2870     else
2871     Param.AsDouble := PDouble(Data)^;
2872     end;
2873     SQL_INT64:
2874     begin
2875     if fdDataScale = 0 then
2876     Param.AsInt64 := PInt64(Data)^
2877     else
2878     if fdDataScale >= (-4) then
2879     Param.AsCurrency := PCurrency(Data)^
2880     else
2881     Param.AsDouble := PDouble(Data)^;
2882     end;
2883     SQL_BLOB, SQL_QUAD:
2884     Param.AsQuad := PISC_QUAD(Data)^;
2885     SQL_ARRAY:
2886     begin
2887     pda := PIBArray(Buff + fdObjOffset);
2888     if pda^ = nil then
2889     Param.AsQuad := PISC_QUAD(Data)^
2890     else
2891     Param.AsArray := pda^.ArrayIntf;
2892     end;
2893     SQL_TYPE_DATE,
2894     SQL_TYPE_TIME,
2895     SQL_TIMESTAMP:
2896     {This is an IBX native format and not the TDataset approach. See also SetFieldData}
2897     Param.AsDateTime := PDateTime(Data)^;
2898     SQL_TIMESTAMP_TZ_EX,
2899     SQL_TIMESTAMP_TZ:
2900     with PIBBufferedDateTimeWithTimeZone(Data)^ do
2901     Param.SetAsDateTime(Timestamp,TimeZoneID);
2902     SQL_TIME_TZ_EX,
2903     SQL_TIME_TZ:
2904     with PIBBufferedDateTimeWithTimeZone(Data)^ do
2905     Param.SetAsTime(Timestamp,FDefaultTZDate,TimeZoneID);
2906     SQL_BOOLEAN:
2907     Param.AsBoolean := PWordBool(Data)^;
2908     SQL_DEC16,
2909     SQL_DEC34,
2910     SQL_DEC_FIXED,
2911     SQL_INT128:
2912     Param.AsBCD := pBCD(Data)^;
2913     else
2914     IBError(ibxeUnknownSQLType,[fdDataType]);
2915     end;
2916     end;
2917     end;
2918    
2919     { TIBSimpleBufferPool }
2920    
2921     function TIBSimpleBufferPool.AllocBlock(buffers: integer): PByte;
2922     var blockSize: integer;
2923     userBufferAreaSize: integer;
2924     begin
2925     userBufferAreaSize := buffers * (BufferSize + sizeof(TBufferHeader));
2926     blockSize := sizeof(TStartHeader) + userBufferAreaSize + sizeof(TEndHeader);
2927     Result := GetMem(blockSize);
2928     if Result <> nil then
2929     begin
2930     FillChar(Result^,blockSize,0);
2931     with PStartHeader(Result)^ do
2932     begin
2933     HeaderType := htStart;
2934     {add to end of list}
2935     PreviousBlock := FLastBlock;
2936     NextBlock := nil;
2937     MaxBuffers := buffers;
2938     BuffersInUse := 0;
2939     FirstRecNo := 1;
2940     if PreviousBlock <> nil then
2941     begin
2942     FirstRecNo := PStartHeader(PreviousBlock)^.FirstRecNo + PStartHeader(PreviousBlock)^.BuffersInUse;
2943     PStartHeader(PreviousBlock)^.NextBlock := Result;
2944     end;
2945     end;
2946     with PEndHeader(Result + sizeof(TStartHeader) + userBufferAreaSize)^ do
2947     begin
2948     HeaderType := htEnd;
2949     StartHeader := Result;
2950     end;
2951     FLastBlock := Result;
2952     FBufferIndex.Add(Result);
2953     end
2954     else
2955     OutofMemoryError;
2956     end;
2957    
2958     procedure TIBSimpleBufferPool.CheckValidBuffer(P: PByte);
2959     begin
2960     Dec(P,sizeof(TBufferHeader));
2961     InternalCheckValidBuffer(P);
2962     end;
2963    
2964     procedure TIBSimpleBufferPool.CheckBuffersAvailable;
2965     begin
2966     if Empty then
2967     IBError(ibxeEmptyBufferPool,[FName]);
2968     end;
2969    
2970     procedure TIBSimpleBufferPool.InternalCheckValidBuffer(P: PByte);
2971     begin
2972     if not (PBufferHeader(P)^.HeaderType in [htFirstBuffer,htBuffer]) then
2973     IBError(ibxeNotABuffer,[FName]);
2974     end;
2975    
2976     constructor TIBSimpleBufferPool.Create(aName: string; bufSize,
2977     aBuffersPerBlock, firstBlockBuffers: integer);
2978     begin
2979     inherited Create;
2980     FName := aName;
2981     FBufferSize := bufSize;
2982     if (aBuffersPerBlock <= 1) or (firstBlockBuffers <= 1) then
2983     IBError(ibxeNotEnoughBuffers,[FName]);
2984     FBuffersPerBlock := aBuffersPerBlock;
2985     FFirstBlockBuffers := firstBlockBuffers;
2986     FBufferIndex := TList.Create;
2987     end;
2988    
2989     destructor TIBSimpleBufferPool.Destroy;
2990     begin
2991     Clear;
2992     if FBufferIndex <> nil then FBufferIndex.Free;
2993     inherited Destroy;
2994     end;
2995    
2996     function TIBSimpleBufferPool.Append: PByte;
2997     begin
2998     Result := AddBuffer;
2999     end;
3000    
3001     function TIBSimpleBufferPool.AddBuffer: PByte;
3002     begin
3003     Result := nil;
3004     if FFirstBlock = nil then
3005     FFirstBlock := AllocBlock(FFirstBlockBuffers);
3006    
3007     with PStartHeader(FLastBlock)^ do
3008     if BuffersInUse >= MaxBuffers then
3009     AllocBlock(FBuffersPerBlock); {Add a Block and set FLastBlock to newly added block}
3010    
3011     with PStartHeader(FLastBlock)^ do
3012     begin
3013     Result := FLastBlock + sizeof(TStartHeader) + BuffersInUse * (FBufferSize + sizeof(TBufferHeader));
3014     with PBufferHeader(Result)^ do
3015     begin
3016     if BuffersInUse = 0 then
3017     HeaderType := htFirstBuffer
3018     else
3019     HeaderType := htBuffer;
3020     RecNo := FirstRecNo + BuffersInUse;
3021     Inc(BuffersInUse);
3022     end;
3023     end;
3024     FLastBuffer := Result;
3025     Inc(Result,sizeof(TBufferHeader)); {start of user data}
3026     Inc(FRecordCount);
3027     end;
3028    
3029     procedure TIBSimpleBufferPool.Clear;
3030     var P, P1: PByte;
3031     begin
3032     P := FFirstBlock;
3033     while P <> nil do
3034     begin
3035     P1 := PStartHeader(P)^.NextBlock;
3036     FreeMem(P);
3037     P := P1;
3038     end;
3039     FFirstBlock := nil;
3040     FLastBlock := nil;
3041     FLastBuffer := nil;
3042     FCurrent := nil;
3043     FBufferIndex.Clear;
3044     FRecordCount := 0;
3045     end;
3046    
3047     function TIBSimpleBufferPool.GetFirst: PByte;
3048     begin
3049     CheckBuffersAvailable;
3050     Result := FFirstBlock + sizeof(TStartHeader);
3051     InternalCheckValidBuffer(Result);
3052     FCurrent := Result;
3053     Inc(Result,sizeof(TBufferHeader))
3054     end;
3055    
3056     function TIBSimpleBufferPool.GetLast: PByte;
3057     begin
3058     CheckBuffersAvailable;
3059     Result := FLastBuffer;
3060     InternalCheckValidBuffer(Result);
3061     FCurrent := Result;
3062     Inc(Result,sizeof(TBufferHeader))
3063     end;
3064    
3065     function TIBSimpleBufferPool.GetBuffer(RecNo: TIBRecordNumber): PByte;
3066     var i: integer;
3067     begin
3068     Result := nil;
3069     CheckBuffersAvailable;
3070    
3071     for i := 0 to FBufferIndex.Count - 1 do
3072     begin
3073     with PStartHeader(FBufferIndex[i]) ^ do
3074     if (BuffersInUse > 0 ) and (RecNo < FirstRecNo + BuffersInUse) then
3075     begin
3076     Result := FBufferIndex[i] + sizeof(TStartHeader) +
3077     (RecNo - FirstRecNo) * (sizeof(TBufferHeader) + FBufferSize);
3078     break;
3079     end;
3080     end;
3081    
3082     if Result <> nil then
3083     begin
3084     InternalCheckValidBuffer(Result);
3085     FCurrent := Result;
3086     Inc(Result, sizeof(TBufferHeader));
3087     end;
3088     end;
3089    
3090     {Returns either pointer to next user buffer or nil if EOF}
3091    
3092     function TIBSimpleBufferPool.GetNextBuffer(aBuffer: PByte): PByte;
3093     var P: PByte;
3094     begin
3095     Result := nil;
3096     CheckBuffersAvailable;
3097    
3098     if aBuffer = nil then {Implicit request for current buffer}
3099     Result := FCurrent
3100     else
3101     begin
3102     P := aBuffer - sizeof(TBufferHeader);
3103     InternalCheckValidBuffer(P);
3104     Inc(P,sizeof(TBufferHeader)+FBufferSize);
3105     case PBufferHeader(P)^.HeaderType of
3106     htFirstBuffer,htBuffer:
3107     Result := P ;
3108    
3109     htEmptyslot:
3110     ; {No more buffers}
3111    
3112     htEnd:
3113     {get first buffer in next block if available}
3114     begin
3115     P := PStartHeader(PEndHeader(P)^.StartHeader)^.NextBlock;
3116     if (P <> nil) and (PStartHeader(P)^.BuffersInUse <> 0) then
3117     Result := P + sizeof(TStartHeader);
3118     end;
3119    
3120     else
3121     IBError(ibxeUnrecognisedHeaderType,[ord(PBufferHeader(P)^.HeaderType)]);
3122     end;
3123     end;
3124     if Result <> nil then
3125     begin
3126     InternalCheckValidBuffer(Result);
3127     FCurrent := Result;
3128     Inc(Result, sizeof(TBufferHeader));
3129     end;
3130     end;
3131    
3132     {returns either pointer to previous user buffer or nil if BOF}
3133    
3134     function TIBSimpleBufferPool.GetPriorBuffer(aBuffer: PByte): PByte;
3135     var P: PByte;
3136     begin
3137     Result := nil;
3138     CheckBuffersAvailable;
3139    
3140     if aBuffer = nil then {Implicit request for current buffer}
3141     Result := FCurrent
3142     else
3143     begin
3144     P := aBuffer - sizeof(TBufferHeader);
3145     InternalCheckValidBuffer(P);
3146     if PBufferHeader(P)^.HeaderType = htFirstBuffer then
3147     begin
3148     P := PStartHeader(P- sizeof(TStartHeader))^.PreviousBlock;
3149     if (P <> nil) and (PStartHeader(P)^.BuffersInUse <> 0) then
3150     Result := P + sizeof(TStartHeader) +
3151     (PStartHeader(P)^.BuffersInUse - 1)*(sizeof(TBufferHeader) + FBufferSize);
3152     end
3153     else
3154     Result := P - FBufferSize - sizeof(TBufferHeader);
3155     end;
3156    
3157     if Result <> nil then
3158     begin
3159     InternalCheckValidBuffer(Result);
3160     FCurrent := Result;
3161     Inc(Result, sizeof(TBufferHeader));
3162     end;
3163     end;
3164    
3165     function TIBSimpleBufferPool.GetRecNo(aBuffer: PByte): TIBRecordNumber;
3166     var P: PByte;
3167     begin
3168     P := aBuffer - sizeof(TBufferHeader);
3169     InternalCheckValidBuffer(P);
3170     Result := PBufferHeader(P)^.RecNo;
3171     end;
3172    
3173     function TIBSimpleBufferPool.GetRecordCount: TIBRecordNumber;
3174     begin
3175     Result := FRecordCount;
3176     end;
3177    
3178     function TIBSimpleBufferPool.Empty: boolean;
3179     begin
3180     Result := FFirstBlock = nil;
3181     end;
3182    
3183     { TIBBufferPool }
3184    
3185     constructor TIBBufferPool.Create(aName: string; bufSize, aBuffersPerBlock,
3186     firstBlockBuffers: integer);
3187     begin
3188     inherited Create(aName,bufSize + sizeof(TRecordData), aBuffersPerBlock, firstBlockBuffers);
3189     end;
3190    
3191     procedure TIBBufferPool.Clear;
3192     begin
3193     inherited Clear;
3194     FFirstRecord := nil;
3195     FLastRecord := nil;
3196     FInsertedRecords := 0;
3197     FDeletedRecords := 0;
3198     end;
3199    
3200     function TIBBufferPool.GetFirst: PByte;
3201     begin
3202     Result := FFirstRecord;
3203     if (Result <> nil) and (PRecordData(Result)^.rdStatus in [rsAppendDeleted,rsInsertDeleted]) then
3204     Result := InternalGetNextBuffer(Result,false);
3205     if Result <> nil then
3206     Inc(Result,sizeof(TRecordData));
3207     end;
3208    
3209     function TIBBufferPool.GetLast: PByte;
3210     begin
3211     Result := FLastRecord;
3212     while (Result <> nil) and (PRecordData(Result)^.rdStatus in [rsAppendDeleted,rsInsertDeleted]) do
3213     Result := PRecordData(Result)^.rdPreviousBuffer;
3214     if Result <> nil then
3215     Inc(Result,sizeof(TRecordData));
3216     end;
3217    
3218     {InternalGetNextBuffer skips over deleted records and tries to find the next
3219     record that points back to aBuffer}
3220    
3221     function TIBBufferPool.InternalGetNextBuffer(aBuffer: PByte;
3222     IncludeDeleted: boolean): PByte;
3223     {aBuffer points to TRecordData}
3224     var CurBuffer:PByte;
3225     temp: PByte;
3226     begin
3227     Result := aBuffer;
3228     repeat
3229     CurBuffer := Result;
3230     case PRecordData(CurBuffer)^.rdStatus of
3231    
3232     {records are always appended in sequence but the sequence may be interrupted by
3233     inserted out of sequence records. These should generally be ignored but could
3234     be the next in sequence - so have to check them}
3235    
3236     rsAppended, rsAppendDeleted:
3237     repeat {look for the next undeleted buffer with a previous pointer to this buffer}
3238     Result := inherited GetNextBuffer(Result);
3239     until (Result = nil) or (PRecordData(Result)^.rdPreviousBuffer = CurBuffer);
3240    
3241     {Inserted records are typically out of sequence, but can be part of a local
3242     sequence of inserted buffers.
3243    
3244     1. lookahead until either the next in sequence is found or the local sequence
3245     breaks.
3246     2. If not found then work backwards to current sequence insertion point
3247     3. Then walk forwards to find the buffer
3248     }
3249    
3250     rsInserted, rsInsertDeleted:
3251     begin
3252     Result := inherited GetNextBuffer(CurBuffer);
3253     if Result = nil then
3254     begin
3255     {go back to insertion point and walk forwards}
3256     Result := CurBuffer;
3257     temp := PRecordData(Result)^.rdPreviousBuffer;
3258     while (temp <> nil) and
3259     (PRecordData(temp)^.rdStatus in [rsInsertDeleted, rsAppendDeleted]) do
3260     begin
3261     Result := PRecordData(temp)^.rdPreviousBuffer;
3262     temp := PRecordData(Result)^.rdPreviousBuffer;
3263     end;
3264    
3265     if PRecordData(Result)^.rdPreviousBuffer <> nil then
3266     Result := inherited GetNextBuffer(PRecordData(Result)^.rdPreviousBuffer)
3267     else
3268     {inserted at start. Have to walk the pool to find the next buffer}
3269     Result := LocatePreviousBuffer(CurBuffer);
3270     end
3271     else
3272     if (Result <> nil) and (PRecordData(Result)^.rdPreviousBuffer <> CurBuffer) then {otherwise found it}
3273     begin
3274     Result := CurBuffer;
3275     {Go back to insertion point}
3276     repeat
3277     Result := PRecordData(Result)^.rdPreviousBuffer;
3278     until (Result = nil) or (PRecordData(Result)^.rdStatus in [rsAppended, rsAppendDeleted]);
3279    
3280     if Result <> nil then {now back at the point where the buffer(s) were
3281     inserted.}
3282     begin
3283     {find the next appended buffer}
3284     repeat
3285     Result := inherited GetNextBuffer(Result);
3286     until (Result = nil) or (PRecordData(Result)^.rdStatus in [rsAppended, rsAppendDeleted]);
3287    
3288     {now work backwards to find the next buffer in the sequence}
3289     while (Result <> nil) and (PRecordData(Result)^.rdPreviousBuffer <> CurBuffer) do
3290     {look backwards for the next buffer with a previous pointer to this buffer}
3291     Result := PRecordData(Result)^.rdPreviousBuffer;
3292     end;
3293     end;
3294     end;
3295     end;
3296     until (Result = nil) or (PRecordData(Result)^.rdStatus in [rsAppended,rsInserted])
3297     or (IncludeDeleted and (PRecordData(Result)^.rdStatus in [rsAppendDeleted,rsInsertDeleted]));
3298     end;
3299    
3300     function TIBBufferPool.GetBuffer(RecNo: TIBRecordNumber): PByte;
3301     begin
3302     Result := inherited GetBuffer(RecNo);
3303     if PRecordData(Result)^.rdStatus in [rsInsertDeleted, rsAppendDeleted] then
3304     IBError(ibxeRecordisDeleted,[RecNo]);
3305     if Result <> nil then
3306     Inc(Result,sizeof(TRecordData));
3307     end;
3308    
3309     function TIBBufferPool.GetNextBuffer(aBuffer: PByte): PByte;
3310     begin
3311     Dec(aBuffer,sizeof(TRecordData));
3312     CheckValidBuffer(aBuffer);
3313     Result := InternalGetNextBuffer(aBuffer,false);
3314     if Result <> nil then
3315     Inc(Result,sizeof(TRecordData));
3316     end;
3317    
3318     function TIBBufferPool.GetNextBuffer(aBuffer : PByte; IncludeDeleted : boolean
3319     ) : PByte;
3320     begin
3321     Dec(aBuffer,sizeof(TRecordData));
3322     CheckValidBuffer(aBuffer);
3323     Result := InternalGetNextBuffer(aBuffer,IncludeDeleted);
3324     if Result <> nil then
3325     Inc(Result,sizeof(TRecordData));
3326     end;
3327    
3328     function TIBBufferPool.GetPriorBuffer(aBuffer: PByte): PByte;
3329     begin
3330     Dec(aBuffer,sizeof(TRecordData));
3331     CheckValidBuffer(aBuffer);
3332     Result := aBuffer;
3333     repeat
3334     Result := PRecordData(Result)^.rdPreviousBuffer
3335     until (Result = nil) or (PRecordData(Result)^.rdStatus in [rsAppended,rsInserted]);
3336     if Result <> nil then
3337     Inc(Result,sizeof(TRecordData));
3338     end;
3339    
3340     function TIBBufferPool.GetRecNo(aBuffer: PByte): TIBRecordNumber;
3341     begin
3342     if aBuffer = nil then
3343     Result := 0
3344     else
3345     begin
3346     Dec(aBuffer,sizeof(TRecordData));
3347     CheckValidBuffer(aBuffer);
3348     if PRecordData(aBuffer)^.rdStatus in [rsInsertDeleted, rsAppendDeleted] then
3349     Result := 0;
3350     Result := inherited GetRecNo(aBuffer);
3351     end;
3352     end;
3353    
3354     {Locate by walking bufferpool from start to finish}
3355    
3356     function TIBBufferPool.LocatePreviousBuffer(aBuffer : PByte) : PByte;
3357     begin
3358     Result := inherited GetFirst;
3359     while (Result <> nil) and (PRecordData(Result)^.rdPreviousBuffer <> aBuffer) do
3360     Result := inherited GetNextBuffer(Result);
3361     end;
3362    
3363     function TIBBufferPool.InsertBefore(aBuffer: PByte): PByte;
3364     begin
3365     if Empty then
3366     Result := Append
3367     else
3368     begin
3369     Dec(aBuffer,sizeof(TRecordData));
3370     CheckValidBuffer(aBuffer);
3371     Result := AddBuffer;
3372     with PRecordData(Result)^ do
3373     begin
3374     rdStatus := rsInserted;
3375     rdPreviousBuffer := PRecordData(aBuffer)^.rdPreviousBuffer;
3376     end;
3377     PRecordData(aBuffer)^.rdPreviousBuffer := Result;
3378     if aBuffer = FFirstRecord then
3379     FFirstRecord := Result;
3380     end;
3381     Inc(Result,sizeof(TRecordData));
3382     Inc(FInsertedRecords);
3383     end;
3384    
3385     function TIBBufferPool.InsertAfter(aBuffer: PByte): PByte;
3386     begin
3387     if Empty then
3388     Result := Append
3389     else
3390     begin
3391     Dec(aBuffer,sizeof(TRecordData));
3392     CheckValidBuffer(aBuffer);
3393     Result := AddBuffer;
3394     with PRecordData(Result)^ do
3395     begin
3396     rdPreviousBuffer := aBuffer;
3397     if aBuffer = FLastRecord then
3398     begin
3399     rdStatus := rsAppended;
3400     FLastRecord := Result;
3401     end
3402     else
3403     begin
3404     rdStatus := rsInserted;
3405     {assumes InternalGetNextBuffer can never return nil given aBuffer is not last}
3406     PRecordData(InternalGetNextBuffer(aBuffer,true))^.rdPreviousBuffer := Result;
3407     end;
3408     end;
3409     end;
3410     Inc(Result,sizeof(TRecordData));
3411     Inc(FInsertedRecords);
3412     end;
3413    
3414     function TIBBufferPool.Append: PByte;
3415     begin
3416     Result := AddBuffer;
3417     with PRecordData(Result)^ do
3418     begin
3419     rdPreviousBuffer := FLastRecord;
3420     rdStatus := rsAppended;
3421     FLastRecord := Result;
3422     if FFirstRecord = nil then
3423     FFirstRecord := Result;
3424     end;
3425     Inc(Result,sizeof(TRecordData));
3426     Inc(FInsertedRecords);
3427     end;
3428    
3429     function TIBBufferPool.Delete(aBuffer: PByte): PByte;
3430     begin
3431     Result := GetPriorBuffer(aBuffer);
3432     Dec(aBuffer,sizeof(TRecordData));
3433     CheckValidBuffer(aBuffer);
3434     case PRecordData(aBuffer)^.rdStatus of
3435     rsInserted:
3436     begin
3437     if FFirstRecord = aBuffer then
3438     begin
3439     FFirstRecord := InternalGetNextBuffer(aBuffer,true);
3440     if FFirstRecord <> nil then
3441     PRecordData(FFirstRecord)^.rdPreviousBuffer := nil;
3442     end;
3443     PRecordData(aBuffer)^.rdStatus := rsInsertDeleted;
3444     end;
3445     rsAppended:
3446     PRecordData(aBuffer)^.rdStatus := rsAppendDeleted;
3447     end;
3448     // writeln('Rec No = ',inherited GetRecNo(aBuffer),' status = ', PRecordData(aBuffer)^.rdStatus);
3449     Inc(FDeletedRecords);
3450     end;
3451    
3452     procedure TIBBufferPool.UnDelete(aBuffer: PByte);
3453     begin
3454     Dec(aBuffer,sizeof(TRecordData));
3455     CheckValidBuffer(aBuffer);
3456     case PRecordData(aBuffer)^.rdStatus of
3457     rsInsertDeleted:
3458     begin
3459     PRecordData(aBuffer)^.rdStatus := rsInserted;
3460     if PRecordData(aBuffer)^.rdPreviousBuffer = nil then
3461     {restore as first record}
3462     begin
3463     PrecordData(FFirstRecord)^.rdPreviousBuffer := aBuffer;
3464     FFirstRecord := aBuffer;
3465     end;
3466     end;
3467     rsAppendDeleted:
3468     PRecordData(aBuffer)^.rdStatus := rsAppended;
3469     end;
3470     Dec(FDeletedRecords);
3471     end;
3472    
3473     function TIBBufferPool.GetUpdateStatus(aBuffer: PByte): TUpdateStatus;
3474     begin
3475     Dec(aBuffer,sizeof(TRecordData));
3476     CheckValidBuffer(aBuffer);
3477     case PRecordData(aBuffer)^.rdStatus of
3478     rsInsertDeleted,
3479     rsAppendDeleted:
3480     Result := usDeleted;
3481    
3482     rsInserted,
3483     rsAppended:
3484     Result := usInserted;
3485    
3486     else
3487     Result := usUnmodified;
3488     end;
3489     end;
3490    
3491     function TIBBufferPool.GetRecordStatus(aBuffer: PByte): TRecordStatus;
3492     begin
3493     Dec(aBuffer,sizeof(TRecordData));
3494     CheckValidBuffer(aBuffer);
3495     Result := PRecordData(aBuffer)^.rdStatus;
3496     end;
3497    
3498     {$ifdef PrintBuf}
3499     procedure TIBBufferPool.PrintBufferList;
3500     var buff: PByte;
3501     begin
3502     writeln('Print Buffer List for ',Name);
3503     writeln('Record Count = ',RecordCount);
3504     buff := GetFirst;
3505     while buff <> nil do
3506     begin
3507     writeln('Rec No = ',GetRecNo(buff),' status = ',GetRecordStatus(buff),' previous = ',
3508     GetRecNo(GetPriorBuffer(buff)));
3509     buff := GetNextBuffer(buff);
3510     end;
3511     writeln('Include Deleted');
3512     buff := GetFirst;
3513     while buff <> nil do
3514     begin
3515     writeln('Rec No = ',GetRecNo(buff),' status = ',GetRecordStatus(buff));
3516     buff := GetNextBuffer(buff,true);
3517     end;
3518     end;
3519     {$endif}
3520    
3521     { TIBOldBufferPool }
3522    
3523     procedure TIBOldBufferPool.CheckValidBuffer(P: PByte);
3524     begin
3525     Dec(P,sizeof(TRecordData));
3526     inherited CheckValidBuffer(P);
3527     end;
3528    
3529     constructor TIBOldBufferPool.Create(aName: string; bufSize, aBuffersPerBlock,
3530     firstBlockBuffers: integer);
3531     begin
3532     inherited Create(aName,bufSize + sizeof(TRecordData),aBuffersPerBlock,
3533     firstBlockBuffers);
3534     end;
3535    
3536     function TIBOldBufferPool.Append(RecNo: TIBRecordNumber; DataBuffer: PByte
3537     ): PByte;
3538     begin
3539     Result := AddBuffer;
3540     with PRecordData(Result)^ do
3541     begin
3542     rdStatus := cusUnModified;
3543     rdRecordNumber := RecNo;
3544     rdDataBuffer := DataBuffer;
3545     end;
3546     Inc(Result,sizeof(TRecordData));
3547     end;
3548    
3549     procedure TIBOldBufferPool.Clear;
3550     begin
3551     inherited Clear;
3552     FModifiedRecords := 0;
3553     end;
3554    
3555     function TIBOldBufferPool.FindOldBufferFor(RecNo: TIBRecordNumber): PByte;
3556     var buffer: PByte;
3557     begin
3558     Result := nil;
3559     buffer := GetFirst;
3560     while (buffer <> nil) do
3561     begin
3562     if PRecordData(buffer)^.rdRecordNumber = RecNo then
3563     begin
3564     Result := buffer;
3565     break;
3566     end;
3567     buffer := GetNextBuffer(buffer);
3568     end;
3569     end;
3570    
3571     function TIBOldBufferPool.GetBuffer(RecNo: TIBRecordNumber): PByte;
3572     begin
3573     Result := inherited GetBuffer(RecNo);
3574     if Result <> nil then
3575     Inc(Result,sizeof(TRecordData));
3576     end;
3577    
3578     function TIBOldBufferPool.GetRecNo(aBuffer: PByte): TIBRecordNumber;
3579     begin
3580     Result := inherited GetRecNo(aBuffer - sizeof(TRecordData));
3581     end;
3582    
3583     function TIBOldBufferPool.GetStatus(aBuffer: PByte): TCachedUpdateStatus;
3584     begin
3585     Dec(aBuffer,sizeof(TRecordData));
3586     CheckValidBuffer(aBuffer);
3587     Result := PRecordData(aBuffer)^.rdStatus;
3588     end;
3589    
3590     function TIBOldBufferPool.GetStatus(RecNo: TIBRecordNumber
3591     ): TCachedUpdateStatus;
3592     var buffer: PByte;
3593     begin
3594     buffer := GetFirst;
3595     while (buffer <> nil) do
3596     begin
3597     if PRecordData(buffer)^.rdRecordNumber = RecNo then
3598     break;
3599     buffer := GetNextBuffer(buffer);
3600     end;
3601     if Buffer = nil then
3602     Result := cusUnmodified
3603     else
3604     Result := PRecordData(buffer)^.rdStatus
3605     end;
3606    
3607     procedure TIBOldBufferPool.SetStatus(aBuffer: PByte;
3608     status: TCachedUpdateStatus);
3609     begin
3610     CheckValidBuffer(aBuffer);
3611     Dec(aBuffer,sizeof(TRecordData));
3612     if PRecordData(aBuffer)^.rdStatus <> status then
3613     begin
3614     PRecordData(aBuffer)^.rdStatus := status;
3615     case status of
3616     cusUnmodified:
3617     Inc(FModifiedRecords);
3618     else
3619     Dec(FModifiedRecords);
3620     end;
3621     end;
3622     end;
3623    
3624     procedure TIBOldBufferPool.SetDataBuffer(aBuffer: PByte; aDataBuffer: PByte);
3625     begin
3626     Dec(aBuffer,sizeof(TRecordData));
3627     CheckValidBuffer(aBuffer);
3628     PRecordData(aBuffer)^.rdDataBuffer := aDataBuffer;
3629     end;
3630    
3631     procedure TIBOldBufferPool.ForwardIterator(iterator: TIterator);
3632     var buf: PByte;
3633     begin
3634     buf := GetFirst;
3635     while (buf <> nil) do
3636     with PRecordData(buf)^ do
3637     begin
3638     if rdStatus <> cusUnModified then
3639     iterator(rdStatus,rdDataBuffer,buf + sizeof(TRecordData));
3640     buf := GetNextBuffer(buf);
3641     end;
3642     end;
3643    
3644     procedure TIBOldBufferPool.BackwardsIterator(iterator: TIterator);
3645     var buf: PByte;
3646     begin
3647     buf := GetLast;
3648     while (buf <> nil) do
3649     with PRecordData(buf)^ do
3650     begin
3651     if rdStatus <> cusUnModified then
3652     iterator(rdStatus,rdDataBuffer,buf + sizeof(TRecordData));
3653     buf := GetPriorBuffer(buf);
3654     end;
3655     end;
3656    
3657     { TIBDSBlobStream }
3658    
3659     procedure TIBDSBlobStream.FieldChanged;
3660     begin
3661     TBlobField(FField).Modified := true;
3662     THackedField(FField).DataChanged;
3663     end;
3664    
3665     function TIBDSBlobStream.GetSize: Int64;
3666     begin
3667     Result := FBlobStream.BlobSize;
3668     end;
3669    
3670     constructor TIBDSBlobStream.Create(AField: TField; ABlobStream: TIBBlobStream;
3671     Mode: TBlobStreamMode);
3672     begin
3673     FField := AField;
3674     FBlobStream := ABlobStream;
3675     FBlobStream.Seek(0, soFromBeginning);
3676     if (Mode = bmWrite) then
3677     begin
3678     FBlobStream.Truncate;
3679     FieldChanged;
3680     FHasWritten := true;
3681     end;
3682     end;
3683    
3684     destructor TIBDSBlobStream.Destroy;
3685     begin
3686     if FHasWritten then
3687     FieldChanged;
3688     inherited Destroy;
3689     end;
3690    
3691     function TIBDSBlobStream.Read(var Buffer; Count: Longint): Longint;
3692     begin
3693     result := FBlobStream.Read(Buffer, Count);
3694     end;
3695    
3696     function TIBDSBlobStream.Seek(Offset: Longint; Origin: Word): Longint;
3697     begin
3698     result := FBlobStream.Seek(Offset, Origin);
3699     end;
3700    
3701     procedure TIBDSBlobStream.SetSize(NewSize: Longint);
3702     begin
3703     FBlobStream.SetSize(NewSize);
3704     end;
3705    
3706     function TIBDSBlobStream.Write(const Buffer; Count: Longint): Longint;
3707     begin
3708     if not (FField.DataSet.State in [dsEdit, dsInsert]) then
3709     IBError(ibxeNotEditing, [nil]);
3710     FieldChanged;
3711     result := FBlobStream.Write(Buffer, Count);
3712     FHasWritten := true;
3713     end;
3714    
3715     end.
3716    

Properties

Name Value
svn:eol-style native