ViewVC Help
View File | Revision Log | Show Annotations | Download File | View Changeset | Root Listing
root/public/ibx/trunk/runtime/nongui/IBBufferedCursors.pas
Revision: 416
Committed: Mon Jul 24 08:32:01 2023 UTC (15 months, 4 weeks ago) by tony
Content type: text/x-pascal
File size: 110659 byte(s)
Log Message:
Fixed repeated type keyword in IBBufferCursors

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

Properties

Name Value
svn:eol-style native