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

# Content
1 (*
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