ViewVC Help
View File | Revision Log | Show Annotations | Download File | View Changeset | Root Listing
root/public/ibx/trunk/runtime/IBCustomDataSet.pas
Revision: 27
Committed: Tue Apr 14 13:10:23 2015 UTC (9 years ago) by tony
Content type: text/x-pascal
File size: 131294 byte(s)
Log Message:
Committing updates for Release R1-2-3

File Contents

# User Rev Content
1 tony 17 {************************************************************************}
2     { }
3     { Borland Delphi Visual Component Library }
4     { InterBase Express core components }
5     { }
6     { Copyright (c) 1998-2000 Inprise Corporation }
7     { }
8     { InterBase Express is based in part on the product }
9     { Free IB Components, written by Gregory H. Deatz for }
10     { Hoagland, Longo, Moran, Dunst & Doukas Company. }
11     { Free IB Components is used under license. }
12     { }
13     { The contents of this file are subject to the InterBase }
14     { Public License Version 1.0 (the "License"); you may not }
15     { use this file except in compliance with the License. You }
16     { may obtain a copy of the License at http://www.Inprise.com/IPL.html }
17     { Software distributed under the License is distributed on }
18     { an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either }
19     { express or implied. See the License for the specific language }
20     { governing rights and limitations under the License. }
21     { The Original Code was created by InterBase Software Corporation }
22     { and its successors. }
23     { Portions created by Inprise Corporation are Copyright (C) Inprise }
24     { Corporation. All Rights Reserved. }
25     { Contributor(s): Jeff Overcash }
26     { }
27     { IBX For Lazarus (Firebird Express) }
28     { Contributor: Tony Whyman, MWA Software http://www.mwasoftware.co.uk }
29     { Portions created by MWA Software are copyright McCallum Whyman }
30 tony 23 { Associates Ltd 2011 - 2015 }
31 tony 17 { }
32     {************************************************************************}
33    
34     unit IBCustomDataSet;
35    
36 tony 21 {$R-}
37    
38 tony 17 {$Mode Delphi}
39    
40     {$IFDEF DELPHI}
41     {$DEFINE TDBDFIELD_IS_BCD}
42     {$ENDIF}
43    
44     interface
45    
46     uses
47     {$IFDEF WINDOWS }
48     Windows,
49     {$ELSE}
50     unix,
51     {$ENDIF}
52     SysUtils, Classes, Forms, Controls, IBDatabase,
53     IBExternals, IB, IBHeader, IBSQL, Db,
54 tony 21 IBUtils, IBBlob, IBSQLParser;
55 tony 17
56     const
57     BufferCacheSize = 1000; { Allocate cache in this many record chunks}
58     UniCache = 2; { Uni-directional cache is 2 records big }
59    
60     type
61     TIBCustomDataSet = class;
62     TIBDataSet = class;
63    
64     { TIBDataSetUpdateObject }
65    
66     TIBDataSetUpdateObject = class(TComponent)
67     private
68     FRefreshSQL: TStrings;
69     procedure SetRefreshSQL(value: TStrings);
70     protected
71     function GetDataSet: TIBCustomDataSet; virtual; abstract;
72     procedure SetDataSet(ADataSet: TIBCustomDataSet); virtual; abstract;
73     procedure Apply(UpdateKind: TUpdateKind; buff: PChar); virtual; abstract;
74     function GetSQL(UpdateKind: TUpdateKind): TStrings; virtual; abstract;
75     procedure InternalSetParams(Query: TIBSQL; buff: PChar);
76     property DataSet: TIBCustomDataSet read GetDataSet write SetDataSet;
77     public
78     constructor Create(AOwner: TComponent); override;
79     destructor Destroy; override;
80     published
81     property RefreshSQL: TStrings read FRefreshSQL write SetRefreshSQL;
82     end;
83    
84     TBlobDataArray = array[0..0] of TIBBlobStream;
85     PBlobDataArray = ^TBlobDataArray;
86    
87     { TIBCustomDataSet }
88     TFieldData = record
89     fdDataType: Short;
90     fdDataScale: Short;
91     fdNullable: Boolean;
92     fdIsNull: Boolean;
93     fdDataSize: Short;
94     fdDataLength: Short;
95     fdDataOfs: Integer;
96     end;
97     PFieldData = ^TFieldData;
98    
99     TCachedUpdateStatus = (
100     cusUnmodified, cusModified, cusInserted,
101     cusDeleted, cusUninserted
102     );
103     TIBDBKey = record
104     DBKey: array[0..7] of Byte;
105     end;
106     PIBDBKey = ^TIBDBKey;
107    
108     TRecordData = record
109     rdBookmarkFlag: TBookmarkFlag;
110     rdFieldCount: Short;
111     rdRecordNumber: Integer;
112     rdCachedUpdateStatus: TCachedUpdateStatus;
113     rdUpdateStatus: TUpdateStatus;
114     rdSavedOffset: DWORD;
115     rdDBKey: TIBDBKey;
116     rdFields: array[1..1] of TFieldData;
117     end;
118     PRecordData = ^TRecordData;
119    
120     { TIBStringField allows us to have strings longer than 8196 }
121    
122     TIBStringField = class(TStringField)
123 tony 27 private
124     FInitialised: boolean;
125     protected
126     procedure SetSize(AValue: Integer); override;
127 tony 17 public
128     constructor create(AOwner: TComponent); override;
129     class procedure CheckTypeSize(Value: Integer); override;
130     function GetAsString: string; override;
131     function GetAsVariant: Variant; override;
132     function GetValue(var Value: string): Boolean;
133     procedure SetAsString(const Value: string); override;
134     end;
135    
136     { TIBBCDField }
137     { Actually, there is no BCD involved in this type,
138     instead it deals with currency types.
139     In IB, this is an encapsulation of Numeric (x, y)
140     where x < 18 and y <= 4.
141     Note: y > 4 will default to Floats
142     }
143     TIBBCDField = class(TBCDField)
144     protected
145     class procedure CheckTypeSize(Value: Integer); override;
146     function GetAsCurrency: Currency; override;
147     function GetAsString: string; override;
148     function GetAsVariant: Variant; override;
149     function GetDataSize: Integer; override;
150     public
151     constructor Create(AOwner: TComponent); override;
152     published
153     property Size default 8;
154     end;
155    
156     TIBDataLink = class(TDetailDataLink)
157     private
158     FDataSet: TIBCustomDataSet;
159     protected
160     procedure ActiveChanged; override;
161     procedure RecordChanged(Field: TField); override;
162     function GetDetailDataSet: TDataSet; override;
163     procedure CheckBrowseMode; override;
164     public
165     constructor Create(ADataSet: TIBCustomDataSet);
166     destructor Destroy; override;
167     end;
168    
169     TIBGeneratorApplyOnEvent = (gaeOnNewRecord,gaeOnPostRecord);
170    
171     { TIBGenerator }
172    
173     TIBGenerator = class(TPersistent)
174     private
175     FOwner: TIBCustomDataSet;
176     FApplyOnEvent: TIBGeneratorApplyOnEvent;
177     FFieldName: string;
178     FGeneratorName: string;
179     FIncrement: integer;
180     procedure SetIncrement(const AValue: integer);
181     protected
182     function GetNextValue(ADatabase: TIBDatabase; ATransaction: TIBTransaction): integer;
183     public
184     constructor Create(Owner: TIBCustomDataSet);
185     procedure Apply;
186     property Owner: TIBCustomDataSet read FOwner;
187     published
188     property Generator: string read FGeneratorName write FGeneratorName;
189     property Field: string read FFieldName write FFieldName;
190     property Increment: integer read FIncrement write SetIncrement default 1;
191     property ApplyOnEvent: TIBGeneratorApplyOnEvent read FApplyOnEvent write FApplyOnEvent;
192     end;
193    
194 tony 27 {TIBControlLink - Allows IB Aware controls to react to dataset state changes}
195    
196     TIBControlLink = class
197     private
198     FTIBDataSet: TIBCustomDataSet;
199     procedure SetIBDataSet(AValue: TIBCustomDataSet);
200     protected
201     procedure UpdateSQL(Sender: TObject); virtual;
202     procedure UpdateParams(Sender: TObject); virtual;
203     public
204     destructor Destroy; override;
205     property IBDataSet: TIBCustomDataSet read FTIBDataSet write SetIBDataSet;
206     end;
207    
208 tony 23 TIBAutoCommit = (acDisabled, acCommitRetaining);
209    
210 tony 17 { TIBCustomDataSet }
211     TIBUpdateAction = (uaFail, uaAbort, uaSkip, uaRetry, uaApply, uaApplied);
212    
213     TIBUpdateErrorEvent = procedure(DataSet: TDataSet; E: EDatabaseError;
214 tony 27 UpdateKind: TUpdateKind; var TheUpdateAction: TIBUpdateAction)
215 tony 17 of object;
216     TIBUpdateRecordEvent = procedure(DataSet: TDataSet; UpdateKind: TUpdateKind;
217     var UpdateAction: TIBUpdateAction) of object;
218    
219     TIBUpdateRecordTypes = set of TCachedUpdateStatus;
220    
221 tony 27 TDataSetCloseAction = (dcDiscardChanges, dcSaveChanges);
222    
223     TOnValidatePost = procedure (Sender: TObject; var CancelPost: boolean) of object;
224    
225 tony 17 TIBCustomDataSet = class(TDataset)
226     private
227 tony 23 FAutoCommit: TIBAutoCommit;
228 tony 19 FGenerateParamNames: Boolean;
229 tony 17 FGeneratorField: TIBGenerator;
230     FNeedsRefresh: Boolean;
231     FForcedRefresh: Boolean;
232     FDidActivate: Boolean;
233     FIBLoaded: Boolean;
234     FBase: TIBBase;
235     FBlobCacheOffset: Integer;
236     FBlobStreamList: TList;
237     FBufferChunks: Integer;
238     FBufferCache,
239     FOldBufferCache: PChar;
240     FBufferChunkSize,
241     FCacheSize,
242     FOldCacheSize: Integer;
243     FFilterBuffer: PChar;
244     FBPos,
245     FOBPos,
246     FBEnd,
247     FOBEnd: DWord;
248     FCachedUpdates: Boolean;
249     FCalcFieldsOffset: Integer;
250     FCurrentRecord: Long;
251     FDeletedRecords: Long;
252     FModelBuffer,
253     FOldBuffer: PChar;
254 tony 27 FOnValidatePost: TOnValidatePost;
255 tony 17 FOpen: Boolean;
256     FInternalPrepared: Boolean;
257     FQDelete,
258     FQInsert,
259     FQRefresh,
260     FQSelect,
261     FQModify: TIBSQL;
262     FRecordBufferSize: Integer;
263     FRecordCount: Integer;
264     FRecordSize: Integer;
265 tony 27 FDataSetCloseAction: TDataSetCloseAction;
266 tony 17 FUniDirectional: Boolean;
267     FUpdateMode: TUpdateMode;
268     FUpdateObject: TIBDataSetUpdateObject;
269     FParamCheck: Boolean;
270     FUpdatesPending: Boolean;
271     FUpdateRecordTypes: TIBUpdateRecordTypes;
272     FMappedFieldPosition: array of Integer;
273     FDataLink: TIBDataLink;
274    
275     FBeforeDatabaseDisconnect,
276     FAfterDatabaseDisconnect,
277     FDatabaseFree: TNotifyEvent;
278     FOnUpdateError: TIBUpdateErrorEvent;
279     FOnUpdateRecord: TIBUpdateRecordEvent;
280     FBeforeTransactionEnd,
281     FAfterTransactionEnd,
282     FTransactionFree: TNotifyEvent;
283 tony 19 FAliasNameMap: array of string;
284 tony 21 FAliasNameList: array of string;
285     FBaseSQLSelect: TStrings;
286     FParser: TSelectSQLParser;
287 tony 27 FCloseAction: TTransactionAction;
288     FInTransactionEnd: boolean;
289     FIBLinks: TList;
290 tony 17 function GetSelectStmtHandle: TISC_STMT_HANDLE;
291     procedure SetUpdateMode(const Value: TUpdateMode);
292     procedure SetUpdateObject(Value: TIBDataSetUpdateObject);
293    
294     function AdjustCurrentRecord(Buffer: Pointer; GetMode: TGetMode): TGetResult;
295     procedure AdjustRecordOnInsert(Buffer: Pointer);
296     function CanEdit: Boolean;
297     function CanInsert: Boolean;
298     function CanDelete: Boolean;
299     function CanRefresh: Boolean;
300     procedure CheckEditState;
301     procedure ClearBlobCache;
302 tony 27 procedure ClearIBLinks;
303 tony 17 procedure CopyRecordBuffer(Source, Dest: Pointer);
304     procedure DoBeforeDatabaseDisconnect(Sender: TObject);
305     procedure DoAfterDatabaseDisconnect(Sender: TObject);
306     procedure DoDatabaseFree(Sender: TObject);
307 tony 27 procedure DoBeforeTransactionEnd(Sender: TObject; Action: TTransactionAction);
308 tony 17 procedure DoAfterTransactionEnd(Sender: TObject);
309     procedure DoTransactionFree(Sender: TObject);
310     procedure FetchCurrentRecordToBuffer(Qry: TIBSQL; RecordNumber: Integer;
311     Buffer: PChar);
312     function GetDatabase: TIBDatabase;
313     function GetDBHandle: PISC_DB_HANDLE;
314     function GetDeleteSQL: TStrings;
315     function GetInsertSQL: TStrings;
316     function GetSQLParams: TIBXSQLDA;
317     function GetRefreshSQL: TStrings;
318     function GetSelectSQL: TStrings;
319     function GetStatementType: TIBSQLTypes;
320     function GetModifySQL: TStrings;
321     function GetTransaction: TIBTransaction;
322     function GetTRHandle: PISC_TR_HANDLE;
323 tony 21 function GetParser: TSelectSQLParser;
324 tony 17 procedure InternalDeleteRecord(Qry: TIBSQL; Buff: Pointer); virtual;
325     function InternalLocate(const KeyFields: string; const KeyValues: Variant;
326     Options: TLocateOptions): Boolean; virtual;
327     procedure InternalPostRecord(Qry: TIBSQL; Buff: Pointer); virtual;
328     procedure InternalRevertRecord(RecordNumber: Integer); virtual;
329     function IsVisible(Buffer: PChar): Boolean;
330 tony 27 procedure RegisterIBLink(Sender: TIBControlLink);
331     procedure UnRegisterIBLink(Sender: TIBControlLink);
332 tony 17 procedure SaveOldBuffer(Buffer: PChar);
333     procedure SetBufferChunks(Value: Integer);
334     procedure SetDatabase(Value: TIBDatabase);
335     procedure SetDeleteSQL(Value: TStrings);
336     procedure SetInsertSQL(Value: TStrings);
337     procedure SetInternalSQLParams(Qry: TIBSQL; Buffer: Pointer);
338     procedure SetRefreshSQL(Value: TStrings);
339     procedure SetSelectSQL(Value: TStrings);
340     procedure SetModifySQL(Value: TStrings);
341     procedure SetTransaction(Value: TIBTransaction);
342     procedure SetUpdateRecordTypes(Value: TIBUpdateRecordTypes);
343     procedure SetUniDirectional(Value: Boolean);
344     procedure RefreshParams;
345     procedure SQLChanging(Sender: TObject); virtual;
346     function AdjustPosition(FCache: PChar; Offset: DWORD;
347     Origin: Integer): DWORD;
348     procedure ReadCache(FCache: PChar; Offset: DWORD; Origin: Integer;
349     Buffer: PChar);
350     procedure ReadRecordCache(RecordNumber: Integer; Buffer: PChar;
351     ReadOldBuffer: Boolean);
352     procedure WriteCache(FCache: PChar; Offset: DWORD; Origin: Integer;
353     Buffer: PChar);
354     procedure WriteRecordCache(RecordNumber: Integer; Buffer: PChar);
355     function InternalGetRecord(Buffer: PChar; GetMode: TGetMode;
356     DoCheck: Boolean): TGetResult; virtual;
357    
358     protected
359     procedure ActivateConnection;
360     function ActivateTransaction: Boolean;
361     procedure DeactivateTransaction;
362     procedure CheckDatasetClosed;
363     procedure CheckDatasetOpen;
364 tony 21 function CreateParser: TSelectSQLParser; virtual;
365 tony 19 procedure FieldDefsFromQuery(SourceQuery: TIBSQL);
366 tony 17 function GetActiveBuf: PChar;
367     procedure InternalBatchInput(InputObject: TIBBatchInput); virtual;
368     procedure InternalBatchOutput(OutputObject: TIBBatchOutput); virtual;
369     procedure InternalPrepare; virtual;
370     procedure InternalUnPrepare; virtual;
371     procedure InternalExecQuery; virtual;
372     procedure InternalRefreshRow; virtual;
373     procedure InternalSetParamsFromCursor; virtual;
374     procedure CheckNotUniDirectional;
375    
376     (* { IProviderSupport }
377     procedure PSEndTransaction(Commit: Boolean); override;
378     function PSExecuteStatement(const ASQL: string; AParams: TParams;
379     ResultSet: Pointer = nil): Integer; override;
380     function PsGetTableName: string; override;
381     function PSGetQuoteChar: string; override;
382     function PSGetUpdateException(E: Exception; Prev: EUpdateError): EUpdateError; override;
383     function PSInTransaction: Boolean; override;
384     function PSIsSQLBased: Boolean; override;
385     function PSIsSQLSupported: Boolean; override;
386     procedure PSStartTransaction; override;
387     procedure PSReset; override;
388     function PSUpdateRecord(UpdateKind: TUpdateKind; Delta: TDataSet): Boolean; override;
389     *)
390     { TDataSet support }
391     procedure InternalInsert; override;
392     procedure InitRecord(Buffer: PChar); override;
393     procedure Disconnect; virtual;
394     function ConstraintsStored: Boolean;
395     procedure ClearCalcFields(Buffer: PChar); override;
396     function AllocRecordBuffer: PChar; override;
397     procedure DoBeforeDelete; override;
398 tony 23 procedure DoAfterDelete; override;
399 tony 17 procedure DoBeforeEdit; override;
400 tony 27 procedure DoAfterEdit; override;
401 tony 17 procedure DoBeforeInsert; override;
402     procedure DoAfterInsert; override;
403 tony 27 procedure DoBeforeClose; override;
404 tony 21 procedure DoBeforeOpen; override;
405 tony 17 procedure DoBeforePost; override;
406 tony 23 procedure DoAfterPost; override;
407 tony 17 procedure FreeRecordBuffer(var Buffer: PChar); override;
408     procedure GetBookmarkData(Buffer: PChar; Data: Pointer); override;
409     function GetBookmarkFlag(Buffer: PChar): TBookmarkFlag; override;
410     function GetCanModify: Boolean; override;
411     function GetDataSource: TDataSource; override;
412 tony 19 function GetDBAliasName(FieldNo: integer): string;
413     function GetFieldDefFromAlias(aliasName: string): TFieldDef;
414 tony 17 function GetFieldClass(FieldType: TFieldType): TFieldClass; override;
415     function GetRecNo: Integer; override;
416     function GetRecord(Buffer: PChar; GetMode: TGetMode;
417     DoCheck: Boolean): TGetResult; override;
418     function GetRecordCount: Integer; override;
419     function GetRecordSize: Word; override;
420 tony 23 procedure InternalAutoCommit;
421 tony 17 procedure InternalAddRecord(Buffer: Pointer; Append: Boolean); override;
422     procedure InternalCancel; override;
423     procedure InternalClose; override;
424     procedure InternalDelete; override;
425     procedure InternalFirst; override;
426     function InternalGetFieldData(Field: TField; Buffer: Pointer): Boolean; virtual;
427     procedure InternalGotoBookmark(Bookmark: Pointer); override;
428     procedure InternalHandleException; override;
429     procedure InternalInitFieldDefs; override;
430     procedure InternalInitRecord(Buffer: PChar); override;
431     procedure InternalLast; override;
432     procedure InternalOpen; override;
433     procedure InternalPost; override;
434     procedure InternalRefresh; override;
435     procedure InternalSetFieldData(Field: TField; Buffer: Pointer); virtual;
436     procedure InternalSetToRecord(Buffer: PChar); override;
437     function IsCursorOpen: Boolean; override;
438 tony 21 procedure Loaded; override;
439 tony 17 procedure ReQuery;
440     procedure SetBookmarkFlag(Buffer: PChar; Value: TBookmarkFlag); override;
441     procedure SetBookmarkData(Buffer: PChar; Data: Pointer); override;
442     procedure SetCachedUpdates(Value: Boolean);
443     procedure SetDataSource(Value: TDataSource);
444 tony 19 procedure SetGenerateParamNames(AValue: Boolean); virtual;
445 tony 17 procedure SetFieldData(Field : TField; Buffer : Pointer); override;
446     procedure SetFieldData(Field : TField; Buffer : Pointer;
447     NativeFormat : Boolean); overload; override;
448     procedure SetRecNo(Value: Integer); override;
449    
450     protected
451     {Likely to be made public by descendant classes}
452 tony 23 property AutoCommit: TIBAutoCommit read FAutoCommit write FAutoCommit default acDisabled;
453 tony 17 property SQLParams: TIBXSQLDA read GetSQLParams;
454     property Params: TIBXSQLDA read GetSQLParams;
455     property InternalPrepared: Boolean read FInternalPrepared;
456     property QDelete: TIBSQL read FQDelete;
457     property QInsert: TIBSQL read FQInsert;
458     property QRefresh: TIBSQL read FQRefresh;
459     property QSelect: TIBSQL read FQSelect;
460     property QModify: TIBSQL read FQModify;
461     property StatementType: TIBSQLTypes read GetStatementType;
462     property SelectStmtHandle: TISC_STMT_HANDLE read GetSelectStmtHandle;
463    
464     {Likely to be made published by descendant classes}
465     property BufferChunks: Integer read FBufferChunks write SetBufferChunks;
466     property CachedUpdates: Boolean read FCachedUpdates write SetCachedUpdates;
467     property UniDirectional: Boolean read FUniDirectional write SetUniDirectional default False;
468     property GeneratorField: TIBGenerator read FGeneratorField write FGeneratorField;
469     property DeleteSQL: TStrings read GetDeleteSQL write SetDeleteSQL;
470     property InsertSQL: TStrings read GetInsertSQL write SetInsertSQL;
471     property RefreshSQL: TStrings read GetRefreshSQL write SetRefreshSQL;
472     property SelectSQL: TStrings read GetSelectSQL write SetSelectSQL;
473     property ModifySQL: TStrings read GetModifySQL write SetModifySQL;
474     property UpdateMode: TUpdateMode read FUpdateMode write SetUpdateMode default upWhereAll;
475     property ParamCheck: Boolean read FParamCheck write FParamCheck default True;
476 tony 21 property Parser: TSelectSQLParser read GetParser;
477     property BaseSQLSelect: TStrings read FBaseSQLSelect;
478 tony 17
479     property BeforeDatabaseDisconnect: TNotifyEvent read FBeforeDatabaseDisconnect
480     write FBeforeDatabaseDisconnect;
481     property AfterDatabaseDisconnect: TNotifyEvent read FAfterDatabaseDisconnect
482     write FAfterDatabaseDisconnect;
483     property DatabaseFree: TNotifyEvent read FDatabaseFree
484     write FDatabaseFree;
485     property BeforeTransactionEnd: TNotifyEvent read FBeforeTransactionEnd
486     write FBeforeTransactionEnd;
487     property AfterTransactionEnd: TNotifyEvent read FAfterTransactionEnd
488     write FAfterTransactionEnd;
489     property TransactionFree: TNotifyEvent read FTransactionFree
490     write FTransactionFree;
491 tony 27 property OnValidatePost: TOnValidatePost read FOnValidatePost write FOnValidatePost;
492 tony 17
493     public
494     constructor Create(AOwner: TComponent); override;
495     destructor Destroy; override;
496     procedure ApplyUpdates;
497     function CachedUpdateStatus: TCachedUpdateStatus;
498     procedure CancelUpdates;
499 tony 21 function GetFieldPosition(AliasName: string): integer;
500 tony 17 procedure FetchAll;
501     function LocateNext(const KeyFields: string; const KeyValues: Variant;
502     Options: TLocateOptions): Boolean;
503     procedure RecordModified(Value: Boolean);
504     procedure RevertRecord;
505     procedure Undelete;
506 tony 21 procedure ResetParser;
507     function HasParser: boolean;
508 tony 17
509     { TDataSet support methods }
510     function BookmarkValid(Bookmark: TBookmark): Boolean; override;
511     function CompareBookmarks(Bookmark1, Bookmark2: TBookmark): Integer; override;
512     function CreateBlobStream(Field: TField; Mode: TBlobStreamMode): TStream; override;
513     function GetCurrentRecord(Buffer: PChar): Boolean; override;
514     function GetFieldData(Field : TField; Buffer : Pointer) : Boolean; overload; override;
515     function GetFieldData(FieldNo: Integer; Buffer: Pointer): Boolean; overload; (*override;*)
516     function GetFieldData(Field : TField; Buffer : Pointer;
517     NativeFormat : Boolean) : Boolean; overload; override;
518 tony 19 property GenerateParamNames: Boolean read FGenerateParamNames write SetGenerateParamNames;
519 tony 17 function Locate(const KeyFields: string; const KeyValues: Variant;
520     Options: TLocateOptions): Boolean; override;
521     function Lookup(const KeyFields: string; const KeyValues: Variant;
522     const ResultFields: string): Variant; override;
523     function UpdateStatus: TUpdateStatus; override;
524     function IsSequenced: Boolean; override;
525 tony 27 procedure Post; override;
526 tony 17 function ParamByName(ParamName: String): TIBXSQLVAR;
527     property DBHandle: PISC_DB_HANDLE read GetDBHandle;
528     property TRHandle: PISC_TR_HANDLE read GetTRHandle;
529     property UpdateObject: TIBDataSetUpdateObject read FUpdateObject write SetUpdateObject;
530     property UpdatesPending: Boolean read FUpdatesPending;
531     property UpdateRecordTypes: TIBUpdateRecordTypes read FUpdateRecordTypes
532     write SetUpdateRecordTypes;
533 tony 27 property DataSetCloseAction: TDataSetCloseAction
534     read FDataSetCloseAction write FDataSetCloseAction;
535 tony 17
536     published
537     property Database: TIBDatabase read GetDatabase write SetDatabase;
538     property Transaction: TIBTransaction read GetTransaction
539     write SetTransaction;
540     property ForcedRefresh: Boolean read FForcedRefresh
541     write FForcedRefresh default False;
542     property AutoCalcFields;
543    
544     property AfterCancel;
545     property AfterClose;
546     property AfterDelete;
547     property AfterEdit;
548     property AfterInsert;
549     property AfterOpen;
550     property AfterPost;
551     property AfterRefresh;
552     property AfterScroll;
553     property BeforeCancel;
554     property BeforeClose;
555     property BeforeDelete;
556     property BeforeEdit;
557     property BeforeInsert;
558     property BeforeOpen;
559     property BeforePost;
560     property BeforeRefresh;
561     property BeforeScroll;
562     property OnCalcFields;
563     property OnDeleteError;
564     property OnEditError;
565     property OnNewRecord;
566     property OnPostError;
567     property OnUpdateError: TIBUpdateErrorEvent read FOnUpdateError
568     write FOnUpdateError;
569     property OnUpdateRecord: TIBUpdateRecordEvent read FOnUpdateRecord
570     write FOnUpdateRecord;
571     end;
572    
573 tony 21 TIBParserDataSet = class(TIBCustomDataSet)
574     public
575     property Parser;
576     end;
577    
578     TIBDataSet = class(TIBParserDataSet)
579 tony 17 private
580     function GetPrepared: Boolean;
581    
582     protected
583     procedure SetFiltered(Value: Boolean); override;
584     procedure InternalOpen; override;
585    
586     public
587     procedure Prepare;
588     procedure UnPrepare;
589     procedure BatchInput(InputObject: TIBBatchInput);
590     procedure BatchOutput(OutputObject: TIBBatchOutput);
591     procedure ExecSQL;
592    
593     public
594     property Params;
595     property Prepared : Boolean read GetPrepared;
596     property QDelete;
597     property QInsert;
598     property QRefresh;
599     property QSelect;
600     property QModify;
601     property StatementType;
602     property SelectStmtHandle;
603 tony 21 property BaseSQLSelect;
604 tony 17
605     published
606     { TIBCustomDataSet }
607 tony 23 property AutoCommit;
608 tony 17 property BufferChunks;
609     property CachedUpdates;
610     property DeleteSQL;
611     property InsertSQL;
612     property RefreshSQL;
613     property SelectSQL;
614     property ModifySQL;
615     property GeneratorField;
616 tony 19 property GenerateParamNames;
617 tony 17 property ParamCheck;
618     property UniDirectional;
619     property Filtered;
620 tony 27 property DataSetCloseAction;
621 tony 17
622     property BeforeDatabaseDisconnect;
623     property AfterDatabaseDisconnect;
624     property DatabaseFree;
625     property BeforeTransactionEnd;
626     property AfterTransactionEnd;
627     property TransactionFree;
628    
629     { TIBDataSet }
630     property Active;
631     property AutoCalcFields;
632     property DataSource read GetDataSource write SetDataSource;
633    
634     property AfterCancel;
635     property AfterClose;
636     property AfterDelete;
637     property AfterEdit;
638     property AfterInsert;
639     property AfterOpen;
640     property AfterPost;
641     property AfterScroll;
642     property BeforeCancel;
643     property BeforeClose;
644     property BeforeDelete;
645     property BeforeEdit;
646     property BeforeInsert;
647     property BeforeOpen;
648     property BeforePost;
649     property BeforeScroll;
650     property OnCalcFields;
651     property OnDeleteError;
652     property OnEditError;
653     property OnFilterRecord;
654     property OnNewRecord;
655     property OnPostError;
656 tony 27 property OnValidatePost;
657 tony 17 end;
658    
659     { TIBDSBlobStream }
660     TIBDSBlobStream = class(TStream)
661     protected
662     FField: TField;
663     FBlobStream: TIBBlobStream;
664     public
665     constructor Create(AField: TField; ABlobStream: TIBBlobStream;
666     Mode: TBlobStreamMode);
667     function Read(var Buffer; Count: Longint): Longint; override;
668     function Seek(Offset: Longint; Origin: Word): Longint; override;
669     procedure SetSize(NewSize: Longint); override;
670     function Write(const Buffer; Count: Longint): Longint; override;
671     end;
672    
673     const
674     DefaultFieldClasses: array[TFieldType] of TFieldClass = (
675     nil, { ftUnknown }
676     TIBStringField, { ftString }
677     TSmallintField, { ftSmallint }
678     TIntegerField, { ftInteger }
679     TWordField, { ftWord }
680     TBooleanField, { ftBoolean }
681     TFloatField, { ftFloat }
682     TCurrencyField, { ftCurrency }
683     TIBBCDField, { ftBCD }
684     TDateField, { ftDate }
685     TTimeField, { ftTime }
686     TDateTimeField, { ftDateTime }
687     TBytesField, { ftBytes }
688     TVarBytesField, { ftVarBytes }
689     TAutoIncField, { ftAutoInc }
690     TBlobField, { ftBlob }
691     TMemoField, { ftMemo }
692     TGraphicField, { ftGraphic }
693     TBlobField, { ftFmtMemo }
694     TBlobField, { ftParadoxOle }
695     TBlobField, { ftDBaseOle }
696     TBlobField, { ftTypedBinary }
697     nil, { ftCursor }
698     TStringField, { ftFixedChar }
699     TWideStringField, { ftWideString }
700     TLargeIntField, { ftLargeInt }
701     nil, { ftADT }
702     nil, { ftArray }
703     nil, { ftReference }
704     nil, { ftDataSet }
705     TBlobField, { ftOraBlob }
706     TMemoField, { ftOraClob }
707     TVariantField, { ftVariant }
708     nil, { ftInterface }
709     nil, { ftIDispatch }
710     TGuidField, { ftGuid }
711     TDateTimeField, {ftTimestamp}
712     TIBBCDField, {ftFMTBcd}
713     nil, {ftFixedWideChar}
714     TWideMemoField); {ftWideMemo}
715     (*
716     TADTField, { ftADT }
717     TArrayField, { ftArray }
718     TReferenceField, { ftReference }
719     TDataSetField, { ftDataSet }
720     TBlobField, { ftOraBlob }
721     TMemoField, { ftOraClob }
722     TVariantField, { ftVariant }
723     TInterfaceField, { ftInterface }
724     TIDispatchField, { ftIDispatch }
725     TGuidField); { ftGuid } *)
726     (*var
727     CreateProviderProc: function(DataSet: TIBCustomDataSet): IProvider = nil;*)
728    
729     implementation
730    
731     uses IBIntf, Variants, FmtBCD;
732    
733     const FILE_BEGIN = 0;
734     FILE_CURRENT = 1;
735     FILE_END = 2;
736    
737     type
738    
739     TFieldNode = class(TObject)
740     protected
741     FieldName : String;
742     COMPUTED_BLR : Boolean;
743     DEFAULT_VALUE : boolean;
744     NextField : TFieldNode;
745     end;
746    
747     TRelationNode = class(TObject)
748     protected
749     RelationName : String;
750     FieldNodes : TFieldNode;
751     NextRelation : TRelationNode;
752     end;
753    
754 tony 27 { TIBControlLink }
755 tony 17
756 tony 27 destructor TIBControlLink.Destroy;
757     begin
758     IBDataSet := nil;
759     inherited Destroy;
760     end;
761    
762     procedure TIBControlLink.UpdateParams(Sender: TObject);
763     begin
764    
765     end;
766    
767     procedure TIBControlLink.UpdateSQL(Sender: TObject);
768     begin
769    
770     end;
771    
772     procedure TIBControlLink.SetIBDataSet(AValue: TIBCustomDataSet);
773     begin
774     if FTIBDataSet = AValue then Exit;
775     if IBDataSet <> nil then
776     IBDataSet.UnRegisterIBLink(self);
777     FTIBDataSet := AValue;
778     if IBDataSet <> nil then
779     IBDataSet.RegisterIBLink(self);
780     end;
781    
782    
783 tony 17 { TIBStringField}
784    
785 tony 27 constructor TIBStringField.create(AOwner: TComponent);
786 tony 17 begin
787     inherited Create(AOwner);
788     end;
789    
790     class procedure TIBStringField.CheckTypeSize(Value: Integer);
791     begin
792     { don't check string size. all sizes valid }
793     end;
794    
795     function TIBStringField.GetAsString: string;
796     begin
797     if not GetValue(Result) then Result := '';
798     end;
799    
800     function TIBStringField.GetAsVariant: Variant;
801     var
802     S: string;
803     begin
804     if GetValue(S) then Result := S else Result := Null;
805     end;
806    
807     function TIBStringField.GetValue(var Value: string): Boolean;
808     var
809     Buffer: PChar;
810     begin
811     Buffer := nil;
812     IBAlloc(Buffer, 0, Size + 1);
813     try
814     Result := GetData(Buffer);
815     if Result then
816     begin
817     Value := string(Buffer);
818     if Transliterate and (Value <> '') then
819     DataSet.Translate(PChar(Value), PChar(Value), False);
820     end
821     finally
822     FreeMem(Buffer);
823     end;
824     end;
825    
826     procedure TIBStringField.SetAsString(const Value: string);
827     var
828     Buffer: PChar;
829     begin
830     Buffer := nil;
831     IBAlloc(Buffer, 0, Size + 1);
832     try
833     StrLCopy(Buffer, PChar(Value), Size);
834     if Transliterate then
835     DataSet.Translate(Buffer, Buffer, True);
836     SetData(Buffer);
837     finally
838     FreeMem(Buffer);
839     end;
840     end;
841    
842 tony 27 procedure TIBStringField.SetSize(AValue: Integer);
843     var FieldSize: integer;
844     begin
845     if csLoading in ComponentState then
846     FInitialised := true;
847     if FInitialised then
848     inherited SetSize(AValue)
849     else
850     begin
851     {IBCustomDataSet encodes the CharWidth size in the size}
852     FieldSize := AValue div 4;
853     inherited SetSize(FieldSize);
854     DisplayWidth := FieldSize div ((AValue mod 4) + 1);
855     FInitialised := true;
856     end;
857     end;
858    
859 tony 17 { TIBBCDField }
860    
861     constructor TIBBCDField.Create(AOwner: TComponent);
862     begin
863     inherited Create(AOwner);
864     SetDataType(ftBCD);
865     Size := 8;
866     end;
867    
868     class procedure TIBBCDField.CheckTypeSize(Value: Integer);
869     begin
870     { No need to check as the base type is currency, not BCD }
871     end;
872    
873     function TIBBCDField.GetAsCurrency: Currency;
874     begin
875     if not GetValue(Result) then
876     Result := 0;
877     end;
878    
879     function TIBBCDField.GetAsString: string;
880     var
881     C: System.Currency;
882     begin
883     if GetValue(C) then
884     Result := CurrToStr(C)
885     else
886     Result := '';
887     end;
888    
889     function TIBBCDField.GetAsVariant: Variant;
890     var
891     C: System.Currency;
892     begin
893     if GetValue(C) then
894     Result := C
895     else
896     Result := Null;
897     end;
898    
899     function TIBBCDField.GetDataSize: Integer;
900     begin
901     {$IFDEF TBCDFIELD_IS_BCD}
902     Result := 8;
903     {$ELSE}
904     Result := inherited GetDataSize
905     {$ENDIF}
906     end;
907    
908     { TIBDataLink }
909    
910     constructor TIBDataLink.Create(ADataSet: TIBCustomDataSet);
911     begin
912     inherited Create;
913     FDataSet := ADataSet;
914     end;
915    
916     destructor TIBDataLink.Destroy;
917     begin
918     FDataSet.FDataLink := nil;
919     inherited Destroy;
920     end;
921    
922    
923     procedure TIBDataLink.ActiveChanged;
924     begin
925     if FDataSet.Active then
926     FDataSet.RefreshParams;
927     end;
928    
929    
930     function TIBDataLink.GetDetailDataSet: TDataSet;
931     begin
932     Result := FDataSet;
933     end;
934    
935     procedure TIBDataLink.RecordChanged(Field: TField);
936     begin
937     if (Field = nil) and FDataSet.Active then
938     FDataSet.RefreshParams;
939     end;
940    
941     procedure TIBDataLink.CheckBrowseMode;
942     begin
943     if FDataSet.Active then
944     FDataSet.CheckBrowseMode;
945     end;
946    
947     { TIBCustomDataSet }
948    
949     constructor TIBCustomDataSet.Create(AOwner: TComponent);
950     begin
951     inherited Create(AOwner);
952     FIBLoaded := False;
953     CheckIBLoaded;
954     FIBLoaded := True;
955     FBase := TIBBase.Create(Self);
956 tony 27 FIBLinks := TList.Create;
957 tony 17 FCurrentRecord := -1;
958     FDeletedRecords := 0;
959     FUniDirectional := False;
960     FBufferChunks := BufferCacheSize;
961     FBlobStreamList := TList.Create;
962     FGeneratorField := TIBGenerator.Create(self);
963     FDataLink := TIBDataLink.Create(Self);
964     FQDelete := TIBSQL.Create(Self);
965     FQDelete.OnSQLChanging := SQLChanging;
966     FQDelete.GoToFirstRecordOnExecute := False;
967     FQInsert := TIBSQL.Create(Self);
968     FQInsert.OnSQLChanging := SQLChanging;
969     FQInsert.GoToFirstRecordOnExecute := False;
970     FQRefresh := TIBSQL.Create(Self);
971     FQRefresh.OnSQLChanging := SQLChanging;
972     FQRefresh.GoToFirstRecordOnExecute := False;
973     FQSelect := TIBSQL.Create(Self);
974     FQSelect.OnSQLChanging := SQLChanging;
975     FQSelect.GoToFirstRecordOnExecute := False;
976     FQModify := TIBSQL.Create(Self);
977     FQModify.OnSQLChanging := SQLChanging;
978     FQModify.GoToFirstRecordOnExecute := False;
979     FUpdateRecordTypes := [cusUnmodified, cusModified, cusInserted];
980     FParamCheck := True;
981 tony 19 FGenerateParamNames := False;
982 tony 17 FForcedRefresh := False;
983 tony 23 FAutoCommit:= acDisabled;
984 tony 27 FDataSetCloseAction := dcDiscardChanges;
985 tony 17 {Bookmark Size is Integer for IBX}
986     BookmarkSize := SizeOf(Integer);
987     FBase.BeforeDatabaseDisconnect := DoBeforeDatabaseDisconnect;
988     FBase.AfterDatabaseDisconnect := DoAfterDatabaseDisconnect;
989     FBase.OnDatabaseFree := DoDatabaseFree;
990     FBase.BeforeTransactionEnd := DoBeforeTransactionEnd;
991     FBase.AfterTransactionEnd := DoAfterTransactionEnd;
992     FBase.OnTransactionFree := DoTransactionFree;
993     if AOwner is TIBDatabase then
994     Database := TIBDatabase(AOwner)
995     else
996     if AOwner is TIBTransaction then
997     Transaction := TIBTransaction(AOwner);
998 tony 21 FBaseSQLSelect := TStringList.Create;
999 tony 17 end;
1000    
1001     destructor TIBCustomDataSet.Destroy;
1002     begin
1003     if Active then Active := false;
1004     if FIBLoaded then
1005     begin
1006     if assigned(FGeneratorField) then FGeneratorField.Free;
1007     FDataLink.Free;
1008     FBase.Free;
1009     ClearBlobCache;
1010 tony 27 ClearIBLinks;
1011     FIBLinks.Free;
1012 tony 17 FBlobStreamList.Free;
1013     FreeMem(FBufferCache);
1014     FBufferCache := nil;
1015     FreeMem(FOldBufferCache);
1016     FOldBufferCache := nil;
1017     FCacheSize := 0;
1018     FOldCacheSize := 0;
1019     FMappedFieldPosition := nil;
1020     end;
1021 tony 21 if assigned(FBaseSQLSelect) then FBaseSQLSelect.Free;
1022     if assigned(FParser) then FParser.Free;
1023 tony 17 inherited Destroy;
1024     end;
1025    
1026     function TIBCustomDataSet.AdjustCurrentRecord(Buffer: Pointer; GetMode: TGetMode):
1027     TGetResult;
1028     begin
1029     while not IsVisible(Buffer) do
1030     begin
1031     if GetMode = gmPrior then
1032     begin
1033     Dec(FCurrentRecord);
1034     if FCurrentRecord = -1 then
1035     begin
1036     result := grBOF;
1037     exit;
1038     end;
1039     ReadRecordCache(FCurrentRecord, Buffer, False);
1040     end
1041     else begin
1042     Inc(FCurrentRecord);
1043     if (FCurrentRecord = FRecordCount) then
1044     begin
1045     if (not FQSelect.EOF) and (FQSelect.Next <> nil) then
1046     begin
1047     FetchCurrentRecordToBuffer(FQSelect, FCurrentRecord, Buffer);
1048     Inc(FRecordCount);
1049     end
1050     else begin
1051     result := grEOF;
1052     exit;
1053     end;
1054     end
1055     else
1056     ReadRecordCache(FCurrentRecord, Buffer, False);
1057     end;
1058     end;
1059     result := grOK;
1060     end;
1061    
1062     procedure TIBCustomDataSet.ApplyUpdates;
1063     var
1064     {$IF FPC_FULLVERSION >= 20700 }
1065     CurBookmark: TBookmark;
1066     {$ELSE}
1067     CurBookmark: string;
1068     {$ENDIF}
1069     Buffer: PRecordData;
1070     CurUpdateTypes: TIBUpdateRecordTypes;
1071     UpdateAction: TIBUpdateAction;
1072     UpdateKind: TUpdateKind;
1073     bRecordsSkipped: Boolean;
1074    
1075     procedure GetUpdateKind;
1076     begin
1077     case Buffer^.rdCachedUpdateStatus of
1078     cusModified:
1079     UpdateKind := ukModify;
1080     cusInserted:
1081     UpdateKind := ukInsert;
1082     else
1083     UpdateKind := ukDelete;
1084     end;
1085     end;
1086    
1087     procedure ResetBufferUpdateStatus;
1088     begin
1089     case Buffer^.rdCachedUpdateStatus of
1090     cusModified:
1091     begin
1092     PRecordData(Buffer)^.rdUpdateStatus := usUnmodified;
1093     PRecordData(Buffer)^.rdCachedUpdateStatus := cusUnmodified;
1094     end;
1095     cusInserted:
1096     begin
1097     PRecordData(Buffer)^.rdUpdateStatus := usUnmodified;
1098     PRecordData(Buffer)^.rdCachedUpdateStatus := cusUnmodified;
1099     end;
1100     cusDeleted:
1101     begin
1102     PRecordData(Buffer)^.rdUpdateStatus := usDeleted;
1103     PRecordData(Buffer)^.rdCachedUpdateStatus := cusUnmodified;
1104     end;
1105     end;
1106     WriteRecordCache(PRecordData(Buffer)^.rdRecordNumber, Pointer(Buffer));
1107     end;
1108    
1109     procedure UpdateUsingOnUpdateRecord;
1110     begin
1111     UpdateAction := uaFail;
1112     try
1113     FOnUpdateRecord(Self, UpdateKind, UpdateAction);
1114     except
1115     on E: Exception do
1116     begin
1117     if (E is EDatabaseError) and Assigned(FOnUpdateError) then
1118     FOnUpdateError(Self, EIBError(E), UpdateKind, UpdateAction);
1119     if UpdateAction = uaFail then
1120     raise;
1121     end;
1122     end;
1123     end;
1124    
1125     procedure UpdateUsingUpdateObject;
1126     begin
1127     try
1128     FUpdateObject.Apply(UpdateKind,PChar(Buffer));
1129     ResetBufferUpdateStatus;
1130     except
1131     on E: Exception do
1132     if (E is EDatabaseError) and Assigned(FOnUpdateError) then
1133     FOnUpdateError(Self, EIBError(E), UpdateKind, UpdateAction);
1134     end;
1135     end;
1136    
1137     procedure UpdateUsingInternalquery;
1138     begin
1139     try
1140     case Buffer^.rdCachedUpdateStatus of
1141     cusModified:
1142     InternalPostRecord(FQModify, Buffer);
1143     cusInserted:
1144     InternalPostRecord(FQInsert, Buffer);
1145     cusDeleted:
1146     InternalDeleteRecord(FQDelete, Buffer);
1147     end;
1148     except
1149     on E: EIBError do begin
1150     UpdateAction := uaFail;
1151     if Assigned(FOnUpdateError) then
1152     FOnUpdateError(Self, E, UpdateKind, UpdateAction);
1153     case UpdateAction of
1154     uaFail: raise;
1155     uaAbort: SysUtils.Abort;
1156     uaSkip: bRecordsSkipped := True;
1157     end;
1158     end;
1159     end;
1160     end;
1161    
1162     begin
1163     if State in [dsEdit, dsInsert] then
1164     Post;
1165     FBase.CheckDatabase;
1166     FBase.CheckTransaction;
1167     DisableControls;
1168     CurBookmark := Bookmark;
1169     CurUpdateTypes := FUpdateRecordTypes;
1170     FUpdateRecordTypes := [cusModified, cusInserted, cusDeleted];
1171     try
1172     First;
1173     bRecordsSkipped := False;
1174     while not EOF do
1175     begin
1176     Buffer := PRecordData(GetActiveBuf);
1177     GetUpdateKind;
1178     UpdateAction := uaApply;
1179     if Assigned(FUpdateObject) or Assigned(FOnUpdateRecord) then
1180     begin
1181     if (Assigned(FOnUpdateRecord)) then
1182     UpdateUsingOnUpdateRecord
1183     else
1184     if Assigned(FUpdateObject) then
1185     UpdateUsingUpdateObject;
1186     case UpdateAction of
1187     uaFail:
1188     IBError(ibxeUserAbort, [nil]);
1189     uaAbort:
1190     SysUtils.Abort;
1191     uaApplied:
1192     ResetBufferUpdateStatus;
1193     uaSkip:
1194     bRecordsSkipped := True;
1195     uaRetry:
1196     Continue;
1197     end;
1198     end;
1199     if (not Assigned(FUpdateObject)) and (UpdateAction = UaApply) then
1200     begin
1201     UpdateUsingInternalquery;
1202     UpdateAction := uaApplied;
1203     end;
1204     Next;
1205     end;
1206     FUpdatesPending := bRecordsSkipped;
1207     finally
1208     FUpdateRecordTypes := CurUpdateTypes;
1209     Bookmark := CurBookmark;
1210     EnableControls;
1211     end;
1212     end;
1213    
1214     procedure TIBCustomDataSet.InternalBatchInput(InputObject: TIBBatchInput);
1215     begin
1216     FQSelect.BatchInput(InputObject);
1217     end;
1218    
1219     procedure TIBCustomDataSet.InternalBatchOutput(OutputObject: TIBBatchOutput);
1220     var
1221     Qry: TIBSQL;
1222     begin
1223     Qry := TIBSQL.Create(Self);
1224     try
1225     Qry.Database := FBase.Database;
1226     Qry.Transaction := FBase.Transaction;
1227     Qry.SQL.Assign(FQSelect.SQL);
1228     Qry.BatchOutput(OutputObject);
1229     finally
1230     Qry.Free;
1231     end;
1232     end;
1233    
1234     procedure TIBCustomDataSet.CancelUpdates;
1235     var
1236     CurUpdateTypes: TIBUpdateRecordTypes;
1237     begin
1238     if State in [dsEdit, dsInsert] then
1239     Post;
1240     if FCachedUpdates and FUpdatesPending then
1241     begin
1242     DisableControls;
1243     CurUpdateTypes := UpdateRecordTypes;
1244     UpdateRecordTypes := [cusModified, cusInserted, cusDeleted];
1245     try
1246     First;
1247     while not EOF do
1248     begin
1249     if UpdateStatus = usInserted then
1250     RevertRecord
1251     else
1252     begin
1253     RevertRecord;
1254     Next;
1255     end;
1256     end;
1257     finally
1258     UpdateRecordTypes := CurUpdateTypes;
1259     First;
1260     FUpdatesPending := False;
1261     EnableControls;
1262     end;
1263     end;
1264     end;
1265    
1266 tony 21 function TIBCustomDataSet.GetFieldPosition(AliasName: string): integer;
1267     var i: integer;
1268     Prepared: boolean;
1269     begin
1270     Result := 0;
1271     Prepared := FInternalPrepared;
1272     if not Prepared then
1273     InternalPrepare;
1274     try
1275     for i := 0 to Length(FAliasNameList) - 1 do
1276     if FAliasNameList[i] = AliasName then
1277     begin
1278     Result := i + 1;
1279     Exit
1280     end;
1281     finally
1282     if not Prepared then
1283     InternalUnPrepare;
1284     end;
1285     end;
1286    
1287 tony 17 procedure TIBCustomDataSet.ActivateConnection;
1288     begin
1289     if not Assigned(Database) then
1290     IBError(ibxeDatabaseNotAssigned, [nil]);
1291     if not Assigned(Transaction) then
1292     IBError(ibxeTransactionNotAssigned, [nil]);
1293     if not Database.Connected then Database.Open;
1294     end;
1295    
1296     function TIBCustomDataSet.ActivateTransaction: Boolean;
1297     begin
1298     Result := False;
1299     if not Assigned(Transaction) then
1300     IBError(ibxeTransactionNotAssigned, [nil]);
1301     if not Transaction.Active then
1302     begin
1303     Result := True;
1304     Transaction.StartTransaction;
1305     FDidActivate := True;
1306     end;
1307     end;
1308    
1309     procedure TIBCustomDataSet.DeactivateTransaction;
1310     var
1311     i: Integer;
1312     begin
1313     if not Assigned(Transaction) then
1314     IBError(ibxeTransactionNotAssigned, [nil]);
1315     with Transaction do
1316     begin
1317     for i := 0 to SQLObjectCount - 1 do
1318     begin
1319     if (SQLObjects[i] <> nil) and ((SQLObjects[i]).owner is TDataSet) then
1320     begin
1321     if TDataSet(SQLObjects[i].owner).Active then
1322     begin
1323     FDidActivate := False;
1324     exit;
1325     end;
1326     end;
1327     end;
1328     end;
1329     FInternalPrepared := False;
1330     if Transaction.InTransaction then
1331     Transaction.Commit;
1332     FDidActivate := False;
1333     end;
1334    
1335     procedure TIBCustomDataSet.CheckDatasetClosed;
1336     begin
1337     if FOpen then
1338     IBError(ibxeDatasetOpen, [nil]);
1339     end;
1340    
1341     procedure TIBCustomDataSet.CheckDatasetOpen;
1342     begin
1343     if not FOpen then
1344     IBError(ibxeDatasetClosed, [nil]);
1345     end;
1346    
1347 tony 21 function TIBCustomDataSet.CreateParser: TSelectSQLParser;
1348     begin
1349     Result := TSelectSQLParser.Create(self,FBaseSQLSelect);
1350     Result.OnSQLChanging := SQLChanging
1351     end;
1352    
1353 tony 17 procedure TIBCustomDataSet.CheckNotUniDirectional;
1354     begin
1355     if UniDirectional then
1356     IBError(ibxeDataSetUniDirectional, [nil]);
1357     end;
1358    
1359     procedure TIBCustomDataSet.AdjustRecordOnInsert(Buffer: Pointer);
1360     begin
1361     with PRecordData(Buffer)^ do
1362     if (State = dsInsert) and (not Modified) then
1363     begin
1364     rdRecordNumber := FRecordCount;
1365     FCurrentRecord := FRecordCount;
1366     end;
1367     end;
1368    
1369     function TIBCustomDataSet.CanEdit: Boolean;
1370     var
1371     Buff: PRecordData;
1372     begin
1373     Buff := PRecordData(GetActiveBuf);
1374     result := (FQModify.SQL.Text <> '') or
1375     (Assigned(FUpdateObject) and (FUpdateObject.GetSQL(ukModify).Text <> '')) or
1376     ((Buff <> nil) and (Buff^.rdCachedUpdateStatus = cusInserted) and
1377     (FCachedUpdates));
1378     end;
1379    
1380     function TIBCustomDataSet.CanInsert: Boolean;
1381     begin
1382     result := (FQInsert.SQL.Text <> '') or
1383     (Assigned(FUpdateObject) and (FUpdateObject.GetSQL(ukInsert).Text <> ''));
1384     end;
1385    
1386     function TIBCustomDataSet.CanDelete: Boolean;
1387     begin
1388     if (FQDelete.SQL.Text <> '') or
1389     (Assigned(FUpdateObject) and (FUpdateObject.GetSQL(ukDelete).Text <> '')) then
1390     result := True
1391     else
1392     result := False;
1393     end;
1394    
1395     function TIBCustomDataSet.CanRefresh: Boolean;
1396     begin
1397     result := (FQRefresh.SQL.Text <> '') or
1398     (Assigned(FUpdateObject) and (FUpdateObject.RefreshSQL.Text <> ''));
1399     end;
1400    
1401     procedure TIBCustomDataSet.CheckEditState;
1402     begin
1403     case State of
1404     { Check all the wsEditMode types }
1405     dsEdit, dsInsert, dsSetKey, dsCalcFields, dsFilter,
1406     dsNewValue, dsInternalCalc :
1407     begin
1408     if (State in [dsEdit]) and (not CanEdit) then
1409     IBError(ibxeCannotUpdate, [nil]);
1410     if (State in [dsInsert]) and (not CanInsert) then
1411     IBError(ibxeCannotInsert, [nil]);
1412     end;
1413     else
1414     IBError(ibxeNotEditing, [])
1415     end;
1416     end;
1417    
1418     procedure TIBCustomDataSet.ClearBlobCache;
1419     var
1420     i: Integer;
1421     begin
1422     for i := 0 to FBlobStreamList.Count - 1 do
1423     begin
1424     TIBBlobStream(FBlobStreamList[i]).Free;
1425     FBlobStreamList[i] := nil;
1426     end;
1427     FBlobStreamList.Pack;
1428     end;
1429    
1430     procedure TIBCustomDataSet.CopyRecordBuffer(Source, Dest: Pointer);
1431     begin
1432     Move(Source^, Dest^, FRecordBufferSize);
1433     end;
1434    
1435     procedure TIBCustomDataSet.DoBeforeDatabaseDisconnect(Sender: TObject);
1436     begin
1437     if Active then
1438     Active := False;
1439     FInternalPrepared := False;
1440     if Assigned(FBeforeDatabaseDisconnect) then
1441     FBeforeDatabaseDisconnect(Sender);
1442     end;
1443    
1444     procedure TIBCustomDataSet.DoAfterDatabaseDisconnect(Sender: TObject);
1445     begin
1446     if Assigned(FAfterDatabaseDisconnect) then
1447     FAfterDatabaseDisconnect(Sender);
1448     end;
1449    
1450     procedure TIBCustomDataSet.DoDatabaseFree(Sender: TObject);
1451     begin
1452     if Assigned(FDatabaseFree) then
1453     FDatabaseFree(Sender);
1454     end;
1455    
1456 tony 27 procedure TIBCustomDataSet.DoBeforeTransactionEnd(Sender: TObject;
1457     Action: TTransactionAction);
1458 tony 17 begin
1459 tony 27 FCloseAction := Action;
1460     FInTransactionEnd := true;
1461     try
1462     if Active then
1463     Active := False;
1464     finally
1465     FInTransactionEnd := false;
1466     end;
1467 tony 17 if FQSelect <> nil then
1468     FQSelect.FreeHandle;
1469     if FQDelete <> nil then
1470     FQDelete.FreeHandle;
1471     if FQInsert <> nil then
1472     FQInsert.FreeHandle;
1473     if FQModify <> nil then
1474     FQModify.FreeHandle;
1475     if FQRefresh <> nil then
1476     FQRefresh.FreeHandle;
1477     if Assigned(FBeforeTransactionEnd) then
1478     FBeforeTransactionEnd(Sender);
1479     end;
1480    
1481     procedure TIBCustomDataSet.DoAfterTransactionEnd(Sender: TObject);
1482     begin
1483     if Assigned(FAfterTransactionEnd) then
1484     FAfterTransactionEnd(Sender);
1485     end;
1486    
1487     procedure TIBCustomDataSet.DoTransactionFree(Sender: TObject);
1488     begin
1489     if Assigned(FTransactionFree) then
1490     FTransactionFree(Sender);
1491     end;
1492    
1493     { Read the record from FQSelect.Current into the record buffer
1494     Then write the buffer to in memory cache }
1495     procedure TIBCustomDataSet.FetchCurrentRecordToBuffer(Qry: TIBSQL;
1496     RecordNumber: Integer; Buffer: PChar);
1497     var
1498     p: PRecordData;
1499     pbd: PBlobDataArray;
1500     i, j: Integer;
1501     LocalData: Pointer;
1502     LocalDate, LocalDouble: Double;
1503     LocalInt: Integer;
1504 tony 23 LocalBool: wordBool;
1505 tony 17 LocalInt64: Int64;
1506     LocalCurrency: Currency;
1507     FieldsLoaded: Integer;
1508     temp: TIBXSQLVAR;
1509     begin
1510     p := PRecordData(Buffer);
1511     { Make sure blob cache is empty }
1512     pbd := PBlobDataArray(Buffer + FBlobCacheOffset);
1513     if RecordNumber > -1 then
1514     for i := 0 to BlobFieldCount - 1 do
1515     pbd^[i] := nil;
1516     { Get record information }
1517     p^.rdBookmarkFlag := bfCurrent;
1518     p^.rdFieldCount := Qry.Current.Count;
1519     p^.rdRecordNumber := RecordNumber;
1520     p^.rdUpdateStatus := usUnmodified;
1521     p^.rdCachedUpdateStatus := cusUnmodified;
1522     p^.rdSavedOffset := $FFFFFFFF;
1523    
1524     { Load up the fields }
1525     FieldsLoaded := FQSelect.Current.Count;
1526     j := 1;
1527     for i := 0 to Qry.Current.Count - 1 do
1528     begin
1529     if (Qry = FQSelect) then
1530     j := i + 1
1531     else begin
1532     if FieldsLoaded = 0 then
1533     break;
1534     j := FQSelect.FieldIndex[Qry.Current[i].Name] + 1;
1535     if j < 1 then
1536     continue
1537     else
1538     Dec(FieldsLoaded);
1539     end;
1540     with FQSelect.Current[j - 1].Data^ do
1541     if aliasname = 'IBX_INTERNAL_DBKEY' then {do not localize}
1542     begin
1543     if sqllen <= 8 then
1544     p^.rdDBKey := PIBDBKEY(Qry.Current[i].AsPointer)^;
1545     continue;
1546     end;
1547     if j > 0 then with p^ do
1548     begin
1549     rdFields[j].fdDataType :=
1550     Qry.Current[i].Data^.sqltype and (not 1);
1551     rdFields[j].fdDataScale :=
1552     Qry.Current[i].Data^.sqlscale;
1553     rdFields[j].fdNullable :=
1554     (Qry.Current[i].Data^.sqltype and 1 = 1);
1555     rdFields[j].fdIsNull :=
1556     (rdFields[j].fdNullable and (Qry.Current[i].Data^.sqlind^ = -1));
1557     LocalData := Qry.Current[i].Data^.sqldata;
1558     case rdFields[j].fdDataType of
1559     SQL_TIMESTAMP:
1560     begin
1561     rdFields[j].fdDataSize := SizeOf(TDateTime);
1562     if RecordNumber >= 0 then
1563     LocalDate := TimeStampToMSecs(DateTimeToTimeStamp(Qry.Current[i].AsDateTime));
1564     LocalData := PChar(@LocalDate);
1565     end;
1566     SQL_TYPE_DATE:
1567     begin
1568     rdFields[j].fdDataSize := SizeOf(TDateTime);
1569     if RecordNumber >= 0 then
1570     LocalInt := DateTimeToTimeStamp(Qry.Current[i].AsDateTime).Date;
1571     LocalData := PChar(@LocalInt);
1572     end;
1573     SQL_TYPE_TIME:
1574     begin
1575     rdFields[j].fdDataSize := SizeOf(TDateTime);
1576     if RecordNumber >= 0 then
1577     LocalInt := DateTimeToTimeStamp(Qry.Current[i].AsDateTime).Time;
1578     LocalData := PChar(@LocalInt);
1579     end;
1580     SQL_SHORT, SQL_LONG:
1581     begin
1582     if (rdFields[j].fdDataScale = 0) then
1583     begin
1584     rdFields[j].fdDataSize := SizeOf(Integer);
1585     if RecordNumber >= 0 then
1586     LocalInt := Qry.Current[i].AsLong;
1587     LocalData := PChar(@LocalInt);
1588     end
1589     else if (rdFields[j].fdDataScale >= (-4)) then
1590     begin
1591     rdFields[j].fdDataSize := SizeOf(Currency);
1592     if RecordNumber >= 0 then
1593     LocalCurrency := Qry.Current[i].AsCurrency;
1594     LocalData := PChar(@LocalCurrency);
1595     end
1596     else begin
1597     rdFields[j].fdDataSize := SizeOf(Double);
1598     if RecordNumber >= 0 then
1599     LocalDouble := Qry.Current[i].AsDouble;
1600     LocalData := PChar(@LocalDouble);
1601     end;
1602     end;
1603     SQL_INT64:
1604     begin
1605     if (rdFields[j].fdDataScale = 0) then
1606     begin
1607     rdFields[j].fdDataSize := SizeOf(Int64);
1608     if RecordNumber >= 0 then
1609     LocalInt64 := Qry.Current[i].AsInt64;
1610     LocalData := PChar(@LocalInt64);
1611     end
1612     else if (rdFields[j].fdDataScale >= (-4)) then
1613     begin
1614     rdFields[j].fdDataSize := SizeOf(Currency);
1615     if RecordNumber >= 0 then
1616     LocalCurrency := Qry.Current[i].AsCurrency;
1617     LocalData := PChar(@LocalCurrency);
1618     end
1619     else begin
1620     rdFields[j].fdDataSize := SizeOf(Double);
1621     if RecordNumber >= 0 then
1622     LocalDouble := Qry.Current[i].AsDouble;
1623     LocalData := PChar(@LocalDouble);
1624     end
1625     end;
1626     SQL_DOUBLE, SQL_FLOAT, SQL_D_FLOAT:
1627     begin
1628     rdFields[j].fdDataSize := SizeOf(Double);
1629     if RecordNumber >= 0 then
1630     LocalDouble := Qry.Current[i].AsDouble;
1631     LocalData := PChar(@LocalDouble);
1632     end;
1633     SQL_VARYING:
1634     begin
1635     rdFields[j].fdDataSize := Qry.Current[i].Data^.sqllen;
1636     rdFields[j].fdDataLength := isc_vax_integer(Qry.Current[i].Data^.sqldata, 2);
1637     if RecordNumber >= 0 then
1638     begin
1639     if (rdFields[j].fdDataLength = 0) then
1640     LocalData := nil
1641     else
1642     begin
1643     temp := Qry.Current[i];
1644     LocalData := @temp.Data^.sqldata[2];
1645     (* LocalData := @Qry.Current[i].Data^.sqldata[2];*)
1646     end;
1647     end;
1648     end;
1649 tony 23 SQL_BOOLEAN:
1650     begin
1651     LocalBool:= false;
1652     rdFields[j].fdDataSize := SizeOf(wordBool);
1653     if RecordNumber >= 0 then
1654     LocalBool := Qry.Current[i].AsBoolean;
1655     LocalData := PChar(@LocalBool);
1656     end;
1657 tony 17 else { SQL_TEXT, SQL_BLOB, SQL_ARRAY, SQL_QUAD }
1658     begin
1659     rdFields[j].fdDataSize := Qry.Current[i].Data^.sqllen;
1660     if (rdFields[j].fdDataType = SQL_TEXT) then
1661     rdFields[j].fdDataLength := rdFields[j].fdDataSize;
1662     end;
1663     end;
1664     if RecordNumber < 0 then
1665     begin
1666     rdFields[j].fdIsNull := True;
1667     rdFields[j].fdDataOfs := FRecordSize;
1668     Inc(FRecordSize, rdFields[j].fdDataSize);
1669     end
1670     else begin
1671     if rdFields[j].fdDataType = SQL_VARYING then
1672     begin
1673     if LocalData <> nil then
1674     Move(LocalData^, Buffer[rdFields[j].fdDataOfs], rdFields[j].fdDataLength)
1675     end
1676     else
1677     Move(LocalData^, Buffer[rdFields[j].fdDataOfs], rdFields[j].fdDataSize)
1678     end;
1679     end;
1680     end;
1681     WriteRecordCache(RecordNumber, PChar(p));
1682     end;
1683    
1684     function TIBCustomDataSet.GetActiveBuf: PChar;
1685     begin
1686     case State of
1687     dsBrowse:
1688     if IsEmpty then
1689     result := nil
1690     else
1691     result := ActiveBuffer;
1692     dsEdit, dsInsert:
1693     result := ActiveBuffer;
1694     dsCalcFields:
1695     result := CalcBuffer;
1696     dsFilter:
1697     result := FFilterBuffer;
1698     dsNewValue:
1699     result := ActiveBuffer;
1700     dsOldValue:
1701     if (PRecordData(ActiveBuffer)^.rdRecordNumber =
1702     PRecordData(FOldBuffer)^.rdRecordNumber) then
1703     result := FOldBuffer
1704     else
1705     result := ActiveBuffer;
1706     else if not FOpen then
1707     result := nil
1708     else
1709     result := ActiveBuffer;
1710     end;
1711     end;
1712    
1713     function TIBCustomDataSet.CachedUpdateStatus: TCachedUpdateStatus;
1714     begin
1715     if Active then
1716     result := PRecordData(GetActiveBuf)^.rdCachedUpdateStatus
1717     else
1718     result := cusUnmodified;
1719     end;
1720    
1721     function TIBCustomDataSet.GetDatabase: TIBDatabase;
1722     begin
1723     result := FBase.Database;
1724     end;
1725    
1726     function TIBCustomDataSet.GetDBHandle: PISC_DB_HANDLE;
1727     begin
1728     result := FBase.DBHandle;
1729     end;
1730    
1731     function TIBCustomDataSet.GetDeleteSQL: TStrings;
1732     begin
1733     result := FQDelete.SQL;
1734     end;
1735    
1736     function TIBCustomDataSet.GetInsertSQL: TStrings;
1737     begin
1738     result := FQInsert.SQL;
1739     end;
1740    
1741     function TIBCustomDataSet.GetSQLParams: TIBXSQLDA;
1742     begin
1743     if not FInternalPrepared then
1744     InternalPrepare;
1745     result := FQSelect.Params;
1746     end;
1747    
1748     function TIBCustomDataSet.GetRefreshSQL: TStrings;
1749     begin
1750     result := FQRefresh.SQL;
1751     end;
1752    
1753     function TIBCustomDataSet.GetSelectSQL: TStrings;
1754     begin
1755     result := FQSelect.SQL;
1756     end;
1757    
1758     function TIBCustomDataSet.GetStatementType: TIBSQLTypes;
1759     begin
1760     result := FQSelect.SQLType;
1761     end;
1762    
1763     function TIBCustomDataSet.GetModifySQL: TStrings;
1764     begin
1765     result := FQModify.SQL;
1766     end;
1767    
1768     function TIBCustomDataSet.GetTransaction: TIBTransaction;
1769     begin
1770     result := FBase.Transaction;
1771     end;
1772    
1773     function TIBCustomDataSet.GetTRHandle: PISC_TR_HANDLE;
1774     begin
1775     result := FBase.TRHandle;
1776     end;
1777    
1778     procedure TIBCustomDataSet.InternalDeleteRecord(Qry: TIBSQL; Buff: Pointer);
1779     begin
1780     if (Assigned(FUpdateObject) and (FUpdateObject.GetSQL(ukDelete).Text <> '')) then
1781     FUpdateObject.Apply(ukDelete,Buff)
1782     else
1783     begin
1784     SetInternalSQLParams(FQDelete, Buff);
1785     FQDelete.ExecQuery;
1786     end;
1787     with PRecordData(Buff)^ do
1788     begin
1789     rdUpdateStatus := usDeleted;
1790     rdCachedUpdateStatus := cusUnmodified;
1791     end;
1792     WriteRecordCache(PRecordData(Buff)^.rdRecordNumber, Buff);
1793     end;
1794    
1795     function TIBCustomDataSet.InternalLocate(const KeyFields: string;
1796     const KeyValues: Variant; Options: TLocateOptions): Boolean;
1797     var
1798     keyFieldList: TList;
1799     {$IF FPC_FULLVERSION >= 20700 }
1800     CurBookmark: TBookmark;
1801     {$ELSE}
1802     CurBookmark: string;
1803     {$ENDIF}
1804     fieldValue: Variant;
1805     lookupValues: array of variant;
1806     i, fieldCount: Integer;
1807     fieldValueAsString: string;
1808     lookupValueAsString: string;
1809     begin
1810     keyFieldList := TList.Create;
1811     try
1812     GetFieldList(keyFieldList, KeyFields);
1813     fieldCount := keyFieldList.Count;
1814     CurBookmark := Bookmark;
1815     result := false;
1816     SetLength(lookupValues, fieldCount);
1817     if not EOF then
1818     begin
1819     for i := 0 to fieldCount - 1 do {expand key values into lookupValues array}
1820     begin
1821     if VarIsArray(KeyValues) then
1822     lookupValues[i] := KeyValues[i]
1823     else
1824     if i > 0 then
1825     lookupValues[i] := NULL
1826     else
1827     lookupValues[0] := KeyValues;
1828    
1829     {convert to upper case is case insensitive search}
1830     if (TField(keyFieldList[i]).DataType = ftString) and
1831     not VarIsNull(lookupValues[i]) and (loCaseInsensitive in Options) then
1832     lookupValues[i] := UpperCase(lookupValues[i]);
1833     end;
1834     end;
1835     while not result and not EOF do {search for a matching record}
1836     begin
1837     i := 0;
1838     result := true;
1839     while result and (i < fieldCount) do
1840     {see if all of the key fields matches}
1841     begin
1842     fieldValue := TField(keyFieldList[i]).Value;
1843     result := not (VarIsNull(fieldValue) xor VarIsNull(lookupValues[i]));
1844     if result and not VarIsNull(fieldValue) then
1845     begin
1846     try
1847     if TField(keyFieldList[i]).DataType = ftString then
1848     begin
1849     {strings need special handling because of the locate options that
1850     apply to them}
1851     fieldValueAsString := TField(keyFieldList[i]).AsString;
1852     lookupValueAsString := lookupValues[i];
1853     if (loCaseInsensitive in Options) then
1854     fieldValueAsString := UpperCase(fieldValueAsString);
1855    
1856     if (loPartialKey in Options) then
1857     result := result and (Pos(lookupValueAsString, fieldValueAsString) = 1)
1858     else
1859     result := result and (fieldValueAsString = lookupValueAsString);
1860     end
1861     else
1862     result := result and (lookupValues[i] =
1863     VarAsType(fieldValue, VarType(lookupValues[i])));
1864     except on EVariantError do
1865     result := False;
1866     end;
1867     end;
1868     Inc(i);
1869     end;
1870     if not result then
1871     Next;
1872     end;
1873     if not result then
1874     Bookmark := CurBookmark
1875     else
1876     CursorPosChanged;
1877     finally
1878     keyFieldList.Free;
1879     SetLength(lookupValues,0)
1880     end;
1881     end;
1882    
1883     procedure TIBCustomDataSet.InternalPostRecord(Qry: TIBSQL; Buff: Pointer);
1884     var
1885     i, j, k: Integer;
1886     pbd: PBlobDataArray;
1887     begin
1888     pbd := PBlobDataArray(PChar(Buff) + FBlobCacheOffset);
1889     j := 0;
1890     for i := 0 to FieldCount - 1 do
1891     if Fields[i].IsBlob then
1892     begin
1893     k := FMappedFieldPosition[Fields[i].FieldNo -1];
1894     if pbd^[j] <> nil then
1895     begin
1896     pbd^[j].Finalize;
1897     PISC_QUAD(
1898     PChar(Buff) + PRecordData(Buff)^.rdFields[k].fdDataOfs)^ :=
1899     pbd^[j].BlobID;
1900     PRecordData(Buff)^.rdFields[k].fdIsNull := pbd^[j].Size = 0;
1901     end;
1902     Inc(j);
1903     end;
1904     if Assigned(FUpdateObject) then
1905     begin
1906     if (Qry = FQDelete) then
1907     FUpdateObject.Apply(ukDelete,Buff)
1908     else if (Qry = FQInsert) then
1909     FUpdateObject.Apply(ukInsert,Buff)
1910     else
1911     FUpdateObject.Apply(ukModify,Buff);
1912     end
1913     else begin
1914     SetInternalSQLParams(Qry, Buff);
1915     Qry.ExecQuery;
1916     end;
1917     PRecordData(Buff)^.rdUpdateStatus := usUnmodified;
1918     PRecordData(Buff)^.rdCachedUpdateStatus := cusUnmodified;
1919     SetModified(False);
1920     WriteRecordCache(PRecordData(Buff)^.rdRecordNumber, Buff);
1921     if (FForcedRefresh or FNeedsRefresh) and CanRefresh then
1922     InternalRefreshRow;
1923     end;
1924    
1925     procedure TIBCustomDataSet.InternalRefreshRow;
1926     var
1927     Buff: PChar;
1928     SetCursor: Boolean;
1929     ofs: DWORD;
1930     Qry: TIBSQL;
1931     begin
1932 tony 23 if Assigned(Database) and not Database.SQLHourGlass then
1933     SetCursor := False
1934     else
1935     SetCursor := (GetCurrentThreadID = MainThreadID) and (Screen.Cursor = crDefault);
1936 tony 17 if SetCursor then
1937     Screen.Cursor := crHourGlass;
1938     try
1939     Buff := GetActiveBuf;
1940     if CanRefresh then
1941     begin
1942     if Buff <> nil then
1943     begin
1944     if (Assigned(FUpdateObject) and (FUpdateObject.RefreshSQL.Text <> '')) then
1945     begin
1946     Qry := TIBSQL.Create(self);
1947     Qry.Database := Database;
1948     Qry.Transaction := Transaction;
1949     Qry.GoToFirstRecordOnExecute := False;
1950     Qry.SQL.Text := FUpdateObject.RefreshSQL.Text;
1951     end
1952     else
1953     Qry := FQRefresh;
1954     SetInternalSQLParams(Qry, Buff);
1955     Qry.ExecQuery;
1956     try
1957     if (Qry.SQLType = SQLExecProcedure) or
1958     (Qry.Next <> nil) then
1959     begin
1960     ofs := PRecordData(Buff)^.rdSavedOffset;
1961     FetchCurrentRecordToBuffer(Qry,
1962     PRecordData(Buff)^.rdRecordNumber,
1963     Buff);
1964     if FCachedUpdates and (ofs <> $FFFFFFFF) then
1965     begin
1966     PRecordData(Buff)^.rdSavedOffset := ofs;
1967     WriteRecordCache(PRecordData(Buff)^.rdRecordNumber, Buff);
1968     SaveOldBuffer(Buff);
1969     end;
1970     end;
1971     finally
1972     Qry.Close;
1973     end;
1974     if Qry <> FQRefresh then
1975     Qry.Free;
1976     end
1977     end
1978     else
1979     IBError(ibxeCannotRefresh, [nil]);
1980     finally
1981     if SetCursor and (Screen.Cursor = crHourGlass) then
1982     Screen.Cursor := crDefault;
1983     end;
1984     end;
1985    
1986     procedure TIBCustomDataSet.InternalRevertRecord(RecordNumber: Integer);
1987     var
1988     NewBuffer, OldBuffer: PRecordData;
1989    
1990     begin
1991     NewBuffer := nil;
1992     OldBuffer := nil;
1993     NewBuffer := PRecordData(AllocRecordBuffer);
1994     OldBuffer := PRecordData(AllocRecordBuffer);
1995     try
1996     ReadRecordCache(RecordNumber, PChar(NewBuffer), False);
1997     ReadRecordCache(RecordNumber, PChar(OldBuffer), True);
1998     case NewBuffer^.rdCachedUpdateStatus of
1999     cusInserted:
2000     begin
2001     NewBuffer^.rdCachedUpdateStatus := cusUninserted;
2002     Inc(FDeletedRecords);
2003     end;
2004     cusModified,
2005     cusDeleted:
2006     begin
2007     if (NewBuffer^.rdCachedUpdateStatus = cusDeleted) then
2008     Dec(FDeletedRecords);
2009     CopyRecordBuffer(OldBuffer, NewBuffer);
2010     end;
2011     end;
2012    
2013     if State in dsEditModes then
2014     Cancel;
2015    
2016     WriteRecordCache(RecordNumber, PChar(NewBuffer));
2017    
2018     if (NewBuffer^.rdCachedUpdateStatus = cusUninserted ) then
2019     ReSync([]);
2020     finally
2021     FreeRecordBuffer(PChar(NewBuffer));
2022     FreeRecordBuffer(PChar(OldBuffer));
2023     end;
2024     end;
2025    
2026     { A visible record is one that is not truly deleted,
2027     and it is also listed in the FUpdateRecordTypes set }
2028    
2029     function TIBCustomDataSet.IsVisible(Buffer: PChar): Boolean;
2030     begin
2031     result := True;
2032     if not (State = dsOldValue) then
2033     result :=
2034     (PRecordData(Buffer)^.rdCachedUpdateStatus in FUpdateRecordTypes) and
2035     (not ((PRecordData(Buffer)^.rdCachedUpdateStatus = cusUnmodified) and
2036     (PRecordData(Buffer)^.rdUpdateStatus = usDeleted)));
2037     end;
2038    
2039    
2040     function TIBCustomDataSet.LocateNext(const KeyFields: string;
2041     const KeyValues: Variant; Options: TLocateOptions): Boolean;
2042     begin
2043     DisableControls;
2044     try
2045     result := InternalLocate(KeyFields, KeyValues, Options);
2046     finally
2047     EnableControls;
2048     end;
2049     end;
2050    
2051     procedure TIBCustomDataSet.InternalPrepare;
2052     var
2053     SetCursor: Boolean;
2054     DidActivate: Boolean;
2055     begin
2056     if FInternalPrepared then
2057     Exit;
2058     DidActivate := False;
2059 tony 23 if Assigned(Database) and not Database.SQLHourGlass then
2060     SetCursor := False
2061     else
2062     SetCursor := (GetCurrentThreadID = MainThreadID) and (Screen.Cursor = crDefault);
2063 tony 17 if SetCursor then
2064     Screen.Cursor := crHourGlass;
2065     try
2066     ActivateConnection;
2067     DidActivate := ActivateTransaction;
2068     FBase.CheckDatabase;
2069     FBase.CheckTransaction;
2070 tony 21 if HasParser and (FParser.SQLText <> FQSelect.SQL.Text) then
2071     FQSelect.SQL.Text := FParser.SQLText;
2072     // writeln( FQSelect.SQL.Text);
2073 tony 17 if FQSelect.SQL.Text <> '' then
2074     begin
2075     if not FQSelect.Prepared then
2076     begin
2077 tony 19 FQSelect.GenerateParamNames := FGenerateParamNames;
2078 tony 17 FQSelect.ParamCheck := ParamCheck;
2079     FQSelect.Prepare;
2080     end;
2081 tony 19 FQDelete.GenerateParamNames := FGenerateParamNames;
2082 tony 17 if (Trim(FQDelete.SQL.Text) <> '') and (not FQDelete.Prepared) then
2083     FQDelete.Prepare;
2084 tony 19 FQInsert.GenerateParamNames := FGenerateParamNames;
2085 tony 17 if (Trim(FQInsert.SQL.Text) <> '') and (not FQInsert.Prepared) then
2086     FQInsert.Prepare;
2087 tony 19 FQRefresh.GenerateParamNames := FGenerateParamNames;
2088 tony 17 if (Trim(FQRefresh.SQL.Text) <> '') and (not FQRefresh.Prepared) then
2089     FQRefresh.Prepare;
2090 tony 19 FQModify.GenerateParamNames := FGenerateParamNames;
2091 tony 17 if (Trim(FQModify.SQL.Text) <> '') and (not FQModify.Prepared) then
2092     FQModify.Prepare;
2093     FInternalPrepared := True;
2094     InternalInitFieldDefs;
2095     end else
2096     IBError(ibxeEmptyQuery, [nil]);
2097     finally
2098     if DidActivate then
2099     DeactivateTransaction;
2100     if SetCursor and (Screen.Cursor = crHourGlass) then
2101     Screen.Cursor := crDefault;
2102     end;
2103     end;
2104    
2105     procedure TIBCustomDataSet.RecordModified(Value: Boolean);
2106     begin
2107     SetModified(Value);
2108     end;
2109    
2110     procedure TIBCustomDataSet.RevertRecord;
2111     var
2112     Buff: PRecordData;
2113     begin
2114     if FCachedUpdates and FUpdatesPending then
2115     begin
2116     Buff := PRecordData(GetActiveBuf);
2117     InternalRevertRecord(Buff^.rdRecordNumber);
2118     ReadRecordCache(Buff^.rdRecordNumber, PChar(Buff), False);
2119     DataEvent(deRecordChange, 0);
2120     end;
2121     end;
2122    
2123     procedure TIBCustomDataSet.SaveOldBuffer(Buffer: PChar);
2124     var
2125     OldBuffer: Pointer;
2126     procedure CopyOldBuffer;
2127     begin
2128     CopyRecordBuffer(Buffer, OldBuffer);
2129     if BlobFieldCount > 0 then
2130     FillChar(PChar(OldBuffer)[FBlobCacheOffset], BlobFieldCount * SizeOf(TIBBlobStream),
2131     0);
2132     end;
2133    
2134     begin
2135     if (Buffer <> nil) and (PRecordData(Buffer)^.rdRecordNumber >= 0) then
2136     begin
2137     OldBuffer := AllocRecordBuffer;
2138     try
2139     if (PRecordData(Buffer)^.rdSavedOffset = $FFFFFFFF) then
2140     begin
2141     PRecordData(Buffer)^.rdSavedOffset := AdjustPosition(FOldBufferCache, 0,
2142     FILE_END);
2143     CopyOldBuffer;
2144     WriteCache(FOldBufferCache, 0, FILE_CURRENT, OldBuffer);
2145     WriteCache(FBufferCache, PRecordData(Buffer)^.rdRecordNumber * FRecordBufferSize,
2146     FILE_BEGIN, Buffer);
2147     end
2148     else begin
2149     CopyOldBuffer;
2150     WriteCache(FOldBufferCache, PRecordData(Buffer)^.rdSavedOffset, FILE_BEGIN,
2151     OldBuffer);
2152     end;
2153     finally
2154     FreeRecordBuffer(PChar(OldBuffer));
2155     end;
2156     end;
2157     end;
2158    
2159     procedure TIBCustomDataSet.SetBufferChunks(Value: Integer);
2160     begin
2161     if (Value <= 0) then
2162     FBufferChunks := BufferCacheSize
2163     else
2164     FBufferChunks := Value;
2165     end;
2166    
2167     procedure TIBCustomDataSet.SetDatabase(Value: TIBDatabase);
2168     begin
2169     if (FBase.Database <> Value) then
2170     begin
2171     CheckDatasetClosed;
2172     FBase.Database := Value;
2173     FQDelete.Database := Value;
2174     FQInsert.Database := Value;
2175     FQRefresh.Database := Value;
2176     FQSelect.Database := Value;
2177     FQModify.Database := Value;
2178     end;
2179     end;
2180    
2181     procedure TIBCustomDataSet.SetDeleteSQL(Value: TStrings);
2182     begin
2183     if FQDelete.SQL.Text <> Value.Text then
2184     begin
2185     Disconnect;
2186     FQDelete.SQL.Assign(Value);
2187     end;
2188     end;
2189    
2190     procedure TIBCustomDataSet.SetInsertSQL(Value: TStrings);
2191     begin
2192     if FQInsert.SQL.Text <> Value.Text then
2193     begin
2194     Disconnect;
2195     FQInsert.SQL.Assign(Value);
2196     end;
2197     end;
2198    
2199     procedure TIBCustomDataSet.SetInternalSQLParams(Qry: TIBSQL; Buffer: Pointer);
2200     var
2201     i, j: Integer;
2202     cr, data: PChar;
2203     fn, st: string;
2204     OldBuffer: Pointer;
2205     ts: TTimeStamp;
2206     begin
2207     if (Buffer = nil) then
2208     IBError(ibxeBufferNotSet, [nil]);
2209     if (not FInternalPrepared) then
2210     InternalPrepare;
2211     OldBuffer := nil;
2212     try
2213     for i := 0 to Qry.Params.Count - 1 do
2214     begin
2215     fn := Qry.Params[i].Name;
2216     if (Pos('OLD_', fn) = 1) then {mbcs ok}
2217     begin
2218     fn := Copy(fn, 5, Length(fn));
2219     if not Assigned(OldBuffer) then
2220     begin
2221     OldBuffer := AllocRecordBuffer;
2222     ReadRecordCache(PRecordData(Buffer)^.rdRecordNumber, OldBuffer, True);
2223     end;
2224     cr := OldBuffer;
2225     end
2226     else if (Pos('NEW_', fn) = 1) then {mbcs ok}
2227     begin
2228     fn := Copy(fn, 5, Length(fn));
2229     cr := Buffer;
2230     end
2231     else
2232     cr := Buffer;
2233     j := FQSelect.FieldIndex[fn] + 1;
2234     if (j > 0) then
2235     with PRecordData(cr)^ do
2236     begin
2237     if Qry.Params[i].name = 'IBX_INTERNAL_DBKEY' then {do not localize}
2238     begin
2239     PIBDBKey(Qry.Params[i].AsPointer)^ := rdDBKey;
2240     continue;
2241     end;
2242     if rdFields[j].fdIsNull then
2243     Qry.Params[i].IsNull := True
2244     else begin
2245     Qry.Params[i].IsNull := False;
2246     data := cr + rdFields[j].fdDataOfs;
2247     case rdFields[j].fdDataType of
2248     SQL_TEXT, SQL_VARYING:
2249     begin
2250     SetString(st, data, rdFields[j].fdDataLength);
2251     Qry.Params[i].AsString := st;
2252     end;
2253     SQL_FLOAT, SQL_DOUBLE, SQL_D_FLOAT:
2254     Qry.Params[i].AsDouble := PDouble(data)^;
2255     SQL_SHORT, SQL_LONG:
2256     begin
2257     if rdFields[j].fdDataScale = 0 then
2258     Qry.Params[i].AsLong := PLong(data)^
2259     else if rdFields[j].fdDataScale >= (-4) then
2260     Qry.Params[i].AsCurrency := PCurrency(data)^
2261     else
2262     Qry.Params[i].AsDouble := PDouble(data)^;
2263     end;
2264     SQL_INT64:
2265     begin
2266     if rdFields[j].fdDataScale = 0 then
2267     Qry.Params[i].AsInt64 := PInt64(data)^
2268     else if rdFields[j].fdDataScale >= (-4) then
2269     Qry.Params[i].AsCurrency := PCurrency(data)^
2270     else
2271     Qry.Params[i].AsDouble := PDouble(data)^;
2272     end;
2273     SQL_BLOB, SQL_ARRAY, SQL_QUAD:
2274     Qry.Params[i].AsQuad := PISC_QUAD(data)^;
2275     SQL_TYPE_DATE:
2276     begin
2277     ts.Date := PInt(data)^;
2278     ts.Time := 0;
2279     Qry.Params[i].AsDate :=
2280     TimeStampToDateTime(ts);
2281     end;
2282     SQL_TYPE_TIME:
2283     begin
2284     ts.Date := 0;
2285     ts.Time := PInt(data)^;
2286     Qry.Params[i].AsTime :=
2287     TimeStampToDateTime(ts);
2288     end;
2289     SQL_TIMESTAMP:
2290     Qry.Params[i].AsDateTime :=
2291     TimeStampToDateTime(MSecsToTimeStamp(trunc(PDouble(data)^)));
2292 tony 23 SQL_BOOLEAN:
2293     Qry.Params[i].AsBoolean := PWordBool(data)^;
2294 tony 17 end;
2295     end;
2296     end;
2297     end;
2298     finally
2299     if (OldBuffer <> nil) then
2300     FreeRecordBuffer(PChar(OldBuffer));
2301     end;
2302     end;
2303    
2304     procedure TIBCustomDataSet.SetRefreshSQL(Value: TStrings);
2305     begin
2306     if FQRefresh.SQL.Text <> Value.Text then
2307     begin
2308     Disconnect;
2309     FQRefresh.SQL.Assign(Value);
2310     end;
2311     end;
2312    
2313     procedure TIBCustomDataSet.SetSelectSQL(Value: TStrings);
2314     begin
2315     if FQSelect.SQL.Text <> Value.Text then
2316     begin
2317     Disconnect;
2318     FQSelect.SQL.Assign(Value);
2319 tony 21 FBaseSQLSelect.assign(Value);
2320 tony 17 end;
2321     end;
2322    
2323     procedure TIBCustomDataSet.SetModifySQL(Value: TStrings);
2324     begin
2325     if FQModify.SQL.Text <> Value.Text then
2326     begin
2327     Disconnect;
2328     FQModify.SQL.Assign(Value);
2329     end;
2330     end;
2331    
2332     procedure TIBCustomDataSet.SetTransaction(Value: TIBTransaction);
2333     begin
2334     if (FBase.Transaction <> Value) then
2335     begin
2336     CheckDatasetClosed;
2337     FBase.Transaction := Value;
2338     FQDelete.Transaction := Value;
2339     FQInsert.Transaction := Value;
2340     FQRefresh.Transaction := Value;
2341     FQSelect.Transaction := Value;
2342     FQModify.Transaction := Value;
2343     end;
2344     end;
2345    
2346     procedure TIBCustomDataSet.SetUniDirectional(Value: Boolean);
2347     begin
2348     CheckDatasetClosed;
2349     FUniDirectional := Value;
2350     end;
2351    
2352     procedure TIBCustomDataSet.SetUpdateRecordTypes(Value: TIBUpdateRecordTypes);
2353     begin
2354     FUpdateRecordTypes := Value;
2355     if Active then
2356     First;
2357     end;
2358    
2359     procedure TIBCustomDataSet.RefreshParams;
2360     var
2361     DataSet: TDataSet;
2362     begin
2363     DisableControls;
2364     try
2365     if FDataLink.DataSource <> nil then
2366     begin
2367     DataSet := FDataLink.DataSource.DataSet;
2368     if DataSet <> nil then
2369     if DataSet.Active and (DataSet.State <> dsSetKey) then
2370     begin
2371     Close;
2372     Open;
2373     end;
2374     end;
2375     finally
2376     EnableControls;
2377     end;
2378     end;
2379    
2380 tony 27 procedure TIBCustomDataSet.RegisterIBLink(Sender: TIBControlLink);
2381     begin
2382     if FIBLinks.IndexOf(Sender) = -1 then
2383     FIBLinks.Add(Sender);
2384     end;
2385 tony 17
2386 tony 27
2387 tony 17 procedure TIBCustomDataSet.SQLChanging(Sender: TObject);
2388     begin
2389 tony 21 Active := false;
2390     { if FOpen then
2391     InternalClose;}
2392 tony 17 if FInternalPrepared then
2393     InternalUnPrepare;
2394 tony 19 FieldDefs.Clear;
2395 tony 21 FieldDefs.Updated := false;
2396 tony 17 end;
2397    
2398     { I can "undelete" uninserted records (make them "inserted" again).
2399     I can "undelete" cached deleted (the deletion hasn't yet occurred) }
2400     procedure TIBCustomDataSet.Undelete;
2401     var
2402     Buff: PRecordData;
2403     begin
2404     CheckActive;
2405     Buff := PRecordData(GetActiveBuf);
2406     with Buff^ do
2407     begin
2408     if rdCachedUpdateStatus = cusUninserted then
2409     begin
2410     rdCachedUpdateStatus := cusInserted;
2411     Dec(FDeletedRecords);
2412     end
2413     else if (rdUpdateStatus = usDeleted) and
2414     (rdCachedUpdateStatus = cusDeleted) then
2415     begin
2416     rdCachedUpdateStatus := cusUnmodified;
2417     rdUpdateStatus := usUnmodified;
2418     Dec(FDeletedRecords);
2419     end;
2420     WriteRecordCache(rdRecordNumber, PChar(Buff));
2421     end;
2422     end;
2423    
2424 tony 27 procedure TIBCustomDataSet.UnRegisterIBLink(Sender: TIBControlLink);
2425     begin
2426     FIBLinks.Remove(Sender);
2427     end;
2428    
2429 tony 17 function TIBCustomDataSet.UpdateStatus: TUpdateStatus;
2430     begin
2431     if Active then
2432     if GetActiveBuf <> nil then
2433     result := PRecordData(GetActiveBuf)^.rdUpdateStatus
2434     else
2435     result := usUnmodified
2436     else
2437     result := usUnmodified;
2438     end;
2439    
2440     function TIBCustomDataSet.IsSequenced: Boolean;
2441     begin
2442     Result := Assigned( FQSelect ) and FQSelect.EOF;
2443     end;
2444    
2445     function TIBCustomDataSet.ParamByName(ParamName: String): TIBXSQLVAR;
2446     begin
2447     ActivateConnection;
2448     ActivateTransaction;
2449     if not FInternalPrepared then
2450     InternalPrepare;
2451     Result := Params.ByName(ParamName);
2452     end;
2453    
2454     {Beware: the parameter FCache is used as an identifier to determine which
2455     cache is being operated on and is not referenced in the computation.
2456     The result is an adjusted offset into the identified cache, either the
2457     Buffer Cache or the old Buffer Cache.}
2458    
2459     function TIBCustomDataSet.AdjustPosition(FCache: PChar; Offset: DWORD;
2460     Origin: Integer): DWORD;
2461     var
2462     OldCacheSize: Integer;
2463     begin
2464     if (FCache = FBufferCache) then
2465     begin
2466     case Origin of
2467     FILE_BEGIN: FBPos := Offset;
2468     FILE_CURRENT: FBPos := FBPos + Offset;
2469     FILE_END: FBPos := DWORD(FBEnd) + Offset;
2470     end;
2471     OldCacheSize := FCacheSize;
2472     while (FBPos >= DWORD(FCacheSize)) do
2473     Inc(FCacheSize, FBufferChunkSize);
2474     if FCacheSize > OldCacheSize then
2475     IBAlloc(FBufferCache, FCacheSize, FCacheSize);
2476     result := FBPos;
2477     end
2478     else begin
2479     case Origin of
2480     FILE_BEGIN: FOBPos := Offset;
2481     FILE_CURRENT: FOBPos := FOBPos + Offset;
2482     FILE_END: FOBPos := DWORD(FOBEnd) + Offset;
2483     end;
2484     OldCacheSize := FOldCacheSize;
2485     while (FBPos >= DWORD(FOldCacheSize)) do
2486     Inc(FOldCacheSize, FBufferChunkSize);
2487     if FOldCacheSize > OldCacheSize then
2488     IBAlloc(FOldBufferCache, FOldCacheSize, FOldCacheSize);
2489     result := FOBPos;
2490     end;
2491     end;
2492    
2493     procedure TIBCustomDataSet.ReadCache(FCache: PChar; Offset: DWORD; Origin: Integer;
2494     Buffer: PChar);
2495     var
2496     pCache: PChar;
2497     AdjustedOffset: DWORD;
2498     bOld: Boolean;
2499     begin
2500     bOld := (FCache = FOldBufferCache);
2501     AdjustedOffset := AdjustPosition(FCache, Offset, Origin);
2502     if not bOld then
2503     pCache := FBufferCache + AdjustedOffset
2504     else
2505     pCache := FOldBufferCache + AdjustedOffset;
2506     Move(pCache^, Buffer^, DWORD(FRecordBufferSize));
2507     AdjustPosition(FCache, FRecordBufferSize, FILE_CURRENT);
2508     end;
2509    
2510     procedure TIBCustomDataSet.ReadRecordCache(RecordNumber: Integer; Buffer: PChar;
2511     ReadOldBuffer: Boolean);
2512     begin
2513     if FUniDirectional then
2514     RecordNumber := RecordNumber mod UniCache;
2515     if (ReadOldBuffer) then
2516     begin
2517     ReadRecordCache(RecordNumber, Buffer, False);
2518     if FCachedUpdates and
2519     (PRecordData(Buffer)^.rdSavedOffset <> $FFFFFFFF) then
2520     ReadCache(FOldBufferCache, PRecordData(Buffer)^.rdSavedOffset, FILE_BEGIN,
2521     Buffer)
2522     else
2523     if ReadOldBuffer and
2524     (PRecordData(FOldBuffer)^.rdRecordNumber = RecordNumber) then
2525     CopyRecordBuffer( FOldBuffer, Buffer )
2526     end
2527     else
2528     ReadCache(FBufferCache, RecordNumber * FRecordBufferSize, FILE_BEGIN, Buffer);
2529     end;
2530    
2531     procedure TIBCustomDataSet.WriteCache(FCache: PChar; Offset: DWORD; Origin: Integer;
2532     Buffer: PChar);
2533     var
2534     pCache: PChar;
2535     AdjustedOffset: DWORD;
2536     bOld: Boolean;
2537     dwEnd: DWORD;
2538     begin
2539     bOld := (FCache = FOldBufferCache);
2540     AdjustedOffset := AdjustPosition(FCache, Offset, Origin);
2541     if not bOld then
2542     pCache := FBufferCache + AdjustedOffset
2543     else
2544     pCache := FOldBufferCache + AdjustedOffset;
2545     Move(Buffer^, pCache^, FRecordBufferSize);
2546     dwEnd := AdjustPosition(FCache, FRecordBufferSize, FILE_CURRENT);
2547     if not bOld then
2548     begin
2549     if (dwEnd > FBEnd) then
2550     FBEnd := dwEnd;
2551     end
2552     else begin
2553     if (dwEnd > FOBEnd) then
2554     FOBEnd := dwEnd;
2555     end;
2556     end;
2557    
2558     procedure TIBCustomDataSet.WriteRecordCache(RecordNumber: Integer; Buffer: PChar);
2559     begin
2560     if RecordNumber >= 0 then
2561     begin
2562     if FUniDirectional then
2563     RecordNumber := RecordNumber mod UniCache;
2564     WriteCache(FBufferCache, RecordNumber * FRecordBufferSize, FILE_BEGIN, Buffer);
2565     end;
2566     end;
2567    
2568     function TIBCustomDataSet.AllocRecordBuffer: PChar;
2569     begin
2570     result := nil;
2571     IBAlloc(result, FRecordBufferSize, FRecordBufferSize);
2572     Move(FModelBuffer^, result^, FRecordBufferSize);
2573     end;
2574    
2575     function TIBCustomDataSet.CreateBlobStream(Field: TField; Mode: TBlobStreamMode): TStream;
2576     var
2577     pb: PBlobDataArray;
2578     fs: TIBBlobStream;
2579     Buff: PChar;
2580     bTr, bDB: Boolean;
2581     begin
2582     Buff := GetActiveBuf;
2583     if Buff = nil then
2584     begin
2585     fs := TIBBlobStream.Create;
2586     fs.Mode := bmReadWrite;
2587     FBlobStreamList.Add(Pointer(fs));
2588     result := TIBDSBlobStream.Create(Field, fs, Mode);
2589     exit;
2590     end;
2591     pb := PBlobDataArray(Buff + FBlobCacheOffset);
2592     if pb^[Field.Offset] = nil then
2593     begin
2594     AdjustRecordOnInsert(Buff);
2595     pb^[Field.Offset] := TIBBlobStream.Create;
2596     fs := pb^[Field.Offset];
2597     FBlobStreamList.Add(Pointer(fs));
2598     fs.Mode := bmReadWrite;
2599     fs.Database := Database;
2600     fs.Transaction := Transaction;
2601     fs.BlobID :=
2602     PISC_QUAD(@Buff[PRecordData(Buff)^.rdFields[FMappedFieldPosition[Field.FieldNo - 1]].fdDataOfs])^;
2603     if (CachedUpdates) then
2604     begin
2605     bTr := not Transaction.InTransaction;
2606     bDB := not Database.Connected;
2607     if bDB then
2608     Database.Open;
2609     if bTr then
2610     Transaction.StartTransaction;
2611     fs.Seek(0, soFromBeginning);
2612     if bTr then
2613     Transaction.Commit;
2614     if bDB then
2615     Database.Close;
2616     end;
2617     WriteRecordCache(PRecordData(Buff)^.rdRecordNumber, Pointer(Buff));
2618     end else
2619     fs := pb^[Field.Offset];
2620     result := TIBDSBlobStream.Create(Field, fs, Mode);
2621     end;
2622    
2623     function TIBCustomDataSet.CompareBookmarks(Bookmark1, Bookmark2: TBookmark): Integer;
2624     const
2625     CMPLess = -1;
2626     CMPEql = 0;
2627     CMPGtr = 1;
2628     RetCodes: array[Boolean, Boolean] of ShortInt = ((2, CMPLess),
2629     (CMPGtr, CMPEql));
2630     begin
2631     result := RetCodes[Bookmark1 = nil, Bookmark2 = nil];
2632    
2633     if Result = 2 then
2634     begin
2635     if PInteger(Bookmark1)^ < PInteger(Bookmark2)^ then
2636     Result := CMPLess
2637     else
2638     if PInteger(Bookmark1)^ > PInteger(Bookmark2)^ then
2639     Result := CMPGtr
2640     else
2641     Result := CMPEql;
2642     end;
2643     end;
2644    
2645     procedure TIBCustomDataSet.DoBeforeDelete;
2646     var
2647     Buff: PRecordData;
2648     begin
2649     if not CanDelete then
2650     IBError(ibxeCannotDelete, [nil]);
2651     Buff := PRecordData(GetActiveBuf);
2652     if FCachedUpdates and
2653     (Buff^.rdCachedUpdateStatus in [cusUnmodified]) then
2654     SaveOldBuffer(PChar(Buff));
2655     inherited DoBeforeDelete;
2656     end;
2657    
2658 tony 23 procedure TIBCustomDataSet.DoAfterDelete;
2659     begin
2660     inherited DoAfterDelete;
2661 tony 27 FBase.DoAfterDelete(self);
2662 tony 23 InternalAutoCommit;
2663     end;
2664    
2665 tony 17 procedure TIBCustomDataSet.DoBeforeEdit;
2666     var
2667     Buff: PRecordData;
2668     begin
2669     Buff := PRecordData(GetActiveBuf);
2670     if not(CanEdit or (FQModify.SQL.Count <> 0) or
2671     (FCachedUpdates and Assigned(FOnUpdateRecord))) then
2672     IBError(ibxeCannotUpdate, [nil]);
2673     if FCachedUpdates and (Buff^.rdCachedUpdateStatus in [cusUnmodified, cusInserted]) then
2674     SaveOldBuffer(PChar(Buff));
2675     CopyRecordBuffer(GetActiveBuf, FOldBuffer);
2676     inherited DoBeforeEdit;
2677     end;
2678    
2679 tony 27 procedure TIBCustomDataSet.DoAfterEdit;
2680     begin
2681     inherited DoAfterEdit;
2682     FBase.DoAfterEdit(self);
2683     end;
2684    
2685 tony 17 procedure TIBCustomDataSet.DoBeforeInsert;
2686     begin
2687     if not CanInsert then
2688     IBError(ibxeCannotInsert, [nil]);
2689     inherited DoBeforeInsert;
2690     end;
2691    
2692     procedure TIBCustomDataSet.DoAfterInsert;
2693     begin
2694     if GeneratorField.ApplyOnEvent = gaeOnNewRecord then
2695     GeneratorField.Apply;
2696     inherited DoAfterInsert;
2697 tony 27 FBase.DoAfterInsert(self);
2698 tony 17 end;
2699    
2700 tony 27 procedure TIBCustomDataSet.DoBeforeClose;
2701     begin
2702     inherited DoBeforeClose;
2703     if State in [dsInsert,dsEdit] then
2704     begin
2705     if FInTransactionEnd and (FCloseAction = TARollback) then
2706     Exit;
2707    
2708     if DataSetCloseAction = dcSaveChanges then
2709     Post;
2710     {Note this can fail with an exception e.g. due to
2711     database validation error. In which case the dataset remains open }
2712     end;
2713     end;
2714    
2715 tony 21 procedure TIBCustomDataSet.DoBeforeOpen;
2716 tony 27 var i: integer;
2717 tony 21 begin
2718     if assigned(FParser) then
2719     FParser.Reset;
2720 tony 27 for i := 0 to FIBLinks.Count - 1 do
2721     TIBControlLink(FIBLinks[i]).UpdateSQL(self);
2722 tony 21 inherited DoBeforeOpen;
2723 tony 27 for i := 0 to FIBLinks.Count - 1 do
2724     TIBControlLink(FIBLinks[i]).UpdateParams(self);
2725 tony 21 end;
2726    
2727 tony 17 procedure TIBCustomDataSet.DoBeforePost;
2728     begin
2729     inherited DoBeforePost;
2730     if (State = dsInsert) and
2731     (GeneratorField.ApplyOnEvent = gaeOnPostRecord) then
2732     GeneratorField.Apply
2733     end;
2734    
2735 tony 23 procedure TIBCustomDataSet.DoAfterPost;
2736     begin
2737     inherited DoAfterPost;
2738 tony 27 FBase.DoAfterPost(self);
2739 tony 23 InternalAutoCommit;
2740     end;
2741    
2742 tony 17 procedure TIBCustomDataSet.FetchAll;
2743     var
2744     SetCursor: Boolean;
2745     {$IF FPC_FULLVERSION >= 20700 }
2746     CurBookmark: TBookmark;
2747     {$ELSE}
2748     CurBookmark: string;
2749     {$ENDIF}
2750     begin
2751 tony 23 if Assigned(Database) and not Database.SQLHourGlass then
2752     SetCursor := False
2753     else
2754     SetCursor := (GetCurrentThreadID = MainThreadID) and (Screen.Cursor = crDefault);
2755 tony 17 if SetCursor then
2756     Screen.Cursor := crHourGlass;
2757     try
2758     if FQSelect.EOF or not FQSelect.Open then
2759     exit;
2760     DisableControls;
2761     try
2762     CurBookmark := Bookmark;
2763     Last;
2764     Bookmark := CurBookmark;
2765     finally
2766     EnableControls;
2767     end;
2768     finally
2769     if SetCursor and (Screen.Cursor = crHourGlass) then
2770     Screen.Cursor := crDefault;
2771     end;
2772     end;
2773    
2774     procedure TIBCustomDataSet.FreeRecordBuffer(var Buffer: PChar);
2775     begin
2776     FreeMem(Buffer);
2777     Buffer := nil;
2778     end;
2779    
2780     procedure TIBCustomDataSet.GetBookmarkData(Buffer: PChar; Data: Pointer);
2781     begin
2782     Move(PRecordData(Buffer)^.rdRecordNumber, Data^, BookmarkSize);
2783     end;
2784    
2785     function TIBCustomDataSet.GetBookmarkFlag(Buffer: PChar): TBookmarkFlag;
2786     begin
2787     result := PRecordData(Buffer)^.rdBookmarkFlag;
2788     end;
2789    
2790     function TIBCustomDataSet.GetCanModify: Boolean;
2791     begin
2792     result := (FQInsert.SQL.Text <> '') or
2793     (FQModify.SQL.Text <> '') or
2794     (FQDelete.SQL.Text <> '') or
2795     (Assigned(FUpdateObject));
2796     end;
2797    
2798     function TIBCustomDataSet.GetCurrentRecord(Buffer: PChar): Boolean;
2799     begin
2800     if not IsEmpty and (GetBookmarkFlag(ActiveBuffer) = bfCurrent) then
2801     begin
2802     UpdateCursorPos;
2803     ReadRecordCache(PRecordData(ActiveBuffer)^.rdRecordNumber, Buffer, False);
2804     result := True;
2805     end
2806     else
2807     result := False;
2808     end;
2809    
2810     function TIBCustomDataSet.GetDataSource: TDataSource;
2811     begin
2812     if FDataLink = nil then
2813     result := nil
2814     else
2815     result := FDataLink.DataSource;
2816     end;
2817    
2818 tony 19 function TIBCustomDataSet.GetDBAliasName(FieldNo: integer): string;
2819     begin
2820     Result := FAliasNameMap[FieldNo-1]
2821     end;
2822    
2823     function TIBCustomDataSet.GetFieldDefFromAlias(aliasName: string): TFieldDef;
2824     var
2825     i: integer;
2826     begin
2827     Result := nil;
2828     for i := 0 to Length(FAliasNameMap) - 1 do
2829     if FAliasNameMap[i] = aliasName then
2830     begin
2831     Result := FieldDefs[i+1];
2832     Exit
2833     end;
2834     end;
2835    
2836 tony 17 function TIBCustomDataSet.GetFieldClass(FieldType: TFieldType): TFieldClass;
2837     begin
2838     Result := DefaultFieldClasses[FieldType];
2839     end;
2840    
2841     function TIBCustomDataSet.GetFieldData(FieldNo: Integer; Buffer: Pointer): Boolean;
2842     begin
2843     result := GetFieldData(FieldByNumber(FieldNo), buffer);
2844     end;
2845    
2846     function TIBCustomDataSet.InternalGetFieldData(Field: TField; Buffer: Pointer): Boolean;
2847     var
2848     Buff, Data: PChar;
2849     CurrentRecord: PRecordData;
2850     begin
2851     result := False;
2852     Buff := GetActiveBuf;
2853     if (Buff = nil) or
2854     (not IsVisible(Buff)) or not assigned(Field.DataSet) then
2855     exit;
2856     { The intention here is to stuff the buffer with the data for the
2857     referenced field for the current record }
2858     CurrentRecord := PRecordData(Buff);
2859     if (Field.FieldNo < 0) then
2860     begin
2861     Inc(Buff, FRecordSize + Field.Offset);
2862     result := Boolean(Buff[0]);
2863     if result and (Buffer <> nil) then
2864     Move(Buff[1], Buffer^, Field.DataSize);
2865     end
2866     else if (FMappedFieldPosition[Field.FieldNo - 1] > 0) and
2867     (FMappedFieldPosition[Field.FieldNo - 1] <= CurrentRecord^.rdFieldCount) then
2868     begin
2869     result := not CurrentRecord^.rdFields[FMappedFieldPosition[Field.FieldNo - 1]].fdIsNull;
2870     if result and (Buffer <> nil) then
2871     with CurrentRecord^.rdFields[FMappedFieldPosition[Field.FieldNo - 1]] do
2872     begin
2873     Data := Buff + CurrentRecord^.rdFields[FMappedFieldPosition[Field.FieldNo - 1]].fdDataOfs;
2874     if (fdDataType = SQL_VARYING) or (fdDataType = SQL_TEXT) then
2875     begin
2876 tony 27 if fdDataLength < Field.DataSize then
2877 tony 17 begin
2878     Move(Data^, Buffer^, fdDataLength);
2879     PChar(Buffer)[fdDataLength] := #0;
2880     end
2881     else
2882     IBError(ibxeFieldSizeError,[Field.FieldName])
2883     end
2884     else
2885     Move(Data^, Buffer^, Field.DataSize);
2886     end;
2887     end;
2888     end;
2889    
2890     { GetRecNo and SetRecNo both operate off of 1-based indexes as
2891     opposed to 0-based indexes.
2892     This is because we want LastRecordNumber/RecordCount = 1 }
2893    
2894     function TIBCustomDataSet.GetRecNo: Integer;
2895     begin
2896     if GetActiveBuf = nil then
2897     result := 0
2898     else
2899     result := PRecordData(GetActiveBuf)^.rdRecordNumber + 1;
2900     end;
2901    
2902     function TIBCustomDataSet.GetRecord(Buffer: PChar; GetMode: TGetMode;
2903     DoCheck: Boolean): TGetResult;
2904     var
2905     Accept: Boolean;
2906     SaveState: TDataSetState;
2907     begin
2908     Result := grOK;
2909     if Filtered and Assigned(OnFilterRecord) then
2910     begin
2911     Accept := False;
2912     SaveState := SetTempState(dsFilter);
2913     while not Accept do
2914     begin
2915     Result := InternalGetRecord(Buffer, GetMode, DoCheck);
2916     if Result <> grOK then
2917     break;
2918     FFilterBuffer := Buffer;
2919     try
2920     Accept := True;
2921     OnFilterRecord(Self, Accept);
2922     if not Accept and (GetMode = gmCurrent) then
2923     GetMode := gmPrior;
2924     except
2925     // Application.HandleException(Self);
2926     end;
2927     end;
2928     RestoreState(SaveState);
2929     end
2930     else
2931     Result := InternalGetRecord(Buffer, GetMode, DoCheck);
2932     end;
2933    
2934     function TIBCustomDataSet.InternalGetRecord(Buffer: PChar; GetMode: TGetMode;
2935     DoCheck: Boolean): TGetResult;
2936     begin
2937     result := grError;
2938     case GetMode of
2939     gmCurrent: begin
2940     if (FCurrentRecord >= 0) then begin
2941     if FCurrentRecord < FRecordCount then
2942     ReadRecordCache(FCurrentRecord, Buffer, False)
2943     else begin
2944     while (not FQSelect.EOF) and
2945     (FQSelect.Next <> nil) and
2946     (FCurrentRecord >= FRecordCount) do begin
2947     FetchCurrentRecordToBuffer(FQSelect, FRecordCount, Buffer);
2948     Inc(FRecordCount);
2949     end;
2950     FCurrentRecord := FRecordCount - 1;
2951     if (FCurrentRecord >= 0) then
2952     ReadRecordCache(FCurrentRecord, Buffer, False);
2953     end;
2954     result := grOk;
2955     end else
2956     result := grBOF;
2957     end;
2958     gmNext: begin
2959     result := grOk;
2960     if FCurrentRecord = FRecordCount then
2961     result := grEOF
2962     else if FCurrentRecord = FRecordCount - 1 then begin
2963     if (not FQSelect.EOF) then begin
2964     FQSelect.Next;
2965     Inc(FCurrentRecord);
2966     end;
2967     if (FQSelect.EOF) then begin
2968     result := grEOF;
2969     end else begin
2970     Inc(FRecordCount);
2971     FetchCurrentRecordToBuffer(FQSelect, FCurrentRecord, Buffer);
2972     end;
2973     end else if (FCurrentRecord < FRecordCount) then begin
2974     Inc(FCurrentRecord);
2975     ReadRecordCache(FCurrentRecord, Buffer, False);
2976     end;
2977     end;
2978     else { gmPrior }
2979     begin
2980     if (FCurrentRecord = 0) then begin
2981     Dec(FCurrentRecord);
2982     result := grBOF;
2983     end else if (FCurrentRecord > 0) and
2984     (FCurrentRecord <= FRecordCount) then begin
2985     Dec(FCurrentRecord);
2986     ReadRecordCache(FCurrentRecord, Buffer, False);
2987     result := grOk;
2988     end else if (FCurrentRecord = -1) then
2989     result := grBOF;
2990     end;
2991     end;
2992     if result = grOk then
2993     result := AdjustCurrentRecord(Buffer, GetMode);
2994     if result = grOk then with PRecordData(Buffer)^ do begin
2995     rdBookmarkFlag := bfCurrent;
2996     GetCalcFields(Buffer);
2997     end else if (result = grEOF) then begin
2998     CopyRecordBuffer(FModelBuffer, Buffer);
2999     PRecordData(Buffer)^.rdBookmarkFlag := bfEOF;
3000     end else if (result = grBOF) then begin
3001     CopyRecordBuffer(FModelBuffer, Buffer);
3002     PRecordData(Buffer)^.rdBookmarkFlag := bfBOF;
3003     end else if (result = grError) then begin
3004     CopyRecordBuffer(FModelBuffer, Buffer);
3005     PRecordData(Buffer)^.rdBookmarkFlag := bfEOF;
3006     end;;
3007     end;
3008    
3009     function TIBCustomDataSet.GetRecordCount: Integer;
3010     begin
3011     result := FRecordCount - FDeletedRecords;
3012     end;
3013    
3014     function TIBCustomDataSet.GetRecordSize: Word;
3015     begin
3016     result := FRecordBufferSize;
3017     end;
3018    
3019 tony 23 procedure TIBCustomDataSet.InternalAutoCommit;
3020     begin
3021     with Transaction do
3022     if InTransaction and (FAutoCommit = acCommitRetaining) then
3023     begin
3024     if CachedUpdates then ApplyUpdates;
3025     CommitRetaining;
3026     end;
3027     end;
3028    
3029 tony 17 procedure TIBCustomDataSet.InternalAddRecord(Buffer: Pointer; Append: Boolean);
3030     begin
3031     CheckEditState;
3032     begin
3033     { When adding records, we *always* append.
3034     Insertion is just too costly }
3035     AdjustRecordOnInsert(Buffer);
3036     with PRecordData(Buffer)^ do
3037     begin
3038     rdUpdateStatus := usInserted;
3039     rdCachedUpdateStatus := cusInserted;
3040     end;
3041     if not CachedUpdates then
3042     InternalPostRecord(FQInsert, Buffer)
3043     else begin
3044     WriteRecordCache(FCurrentRecord, Buffer);
3045     FUpdatesPending := True;
3046     end;
3047     Inc(FRecordCount);
3048     InternalSetToRecord(Buffer);
3049     end
3050     end;
3051    
3052     procedure TIBCustomDataSet.InternalCancel;
3053     var
3054     Buff: PChar;
3055     CurRec: Integer;
3056     begin
3057     inherited InternalCancel;
3058     Buff := GetActiveBuf;
3059     if Buff <> nil then begin
3060     CurRec := FCurrentRecord;
3061     AdjustRecordOnInsert(Buff);
3062     if (State = dsEdit) then begin
3063     CopyRecordBuffer(FOldBuffer, Buff);
3064     WriteRecordCache(PRecordData(Buff)^.rdRecordNumber, Buff);
3065     end else begin
3066     CopyRecordBuffer(FModelBuffer, Buff);
3067     PRecordData(Buff)^.rdUpdateStatus := usDeleted;
3068     PRecordData(Buff)^.rdCachedUpdateStatus := cusUnmodified;
3069     PRecordData(Buff)^.rdBookmarkFlag := bfEOF;
3070     FCurrentRecord := CurRec;
3071     end;
3072     end;
3073     end;
3074    
3075    
3076     procedure TIBCustomDataSet.InternalClose;
3077     begin
3078     if FDidActivate then
3079     DeactivateTransaction;
3080     FQSelect.Close;
3081     ClearBlobCache;
3082     FreeRecordBuffer(FModelBuffer);
3083     FreeRecordBuffer(FOldBuffer);
3084     FCurrentRecord := -1;
3085     FOpen := False;
3086     FRecordCount := 0;
3087     FDeletedRecords := 0;
3088     FRecordSize := 0;
3089     FBPos := 0;
3090     FOBPos := 0;
3091     FCacheSize := 0;
3092     FOldCacheSize := 0;
3093     FBEnd := 0;
3094     FOBEnd := 0;
3095     FreeMem(FBufferCache);
3096     FBufferCache := nil;
3097     FreeMem(FOldBufferCache);
3098     FOldBufferCache := nil;
3099     BindFields(False);
3100     if DefaultFields then DestroyFields;
3101     end;
3102    
3103     procedure TIBCustomDataSet.InternalDelete;
3104     var
3105     Buff: PChar;
3106     SetCursor: Boolean;
3107     begin
3108 tony 23 if Assigned(Database) and not Database.SQLHourGlass then
3109     SetCursor := False
3110     else
3111     SetCursor := (GetCurrentThreadID = MainThreadID) and (Screen.Cursor = crDefault);
3112 tony 17 if SetCursor then
3113     Screen.Cursor := crHourGlass;
3114     try
3115     Buff := GetActiveBuf;
3116     if CanDelete then
3117     begin
3118     if not CachedUpdates then
3119     InternalDeleteRecord(FQDelete, Buff)
3120     else
3121     begin
3122     with PRecordData(Buff)^ do
3123     begin
3124     if rdCachedUpdateStatus = cusInserted then
3125     rdCachedUpdateStatus := cusUninserted
3126     else begin
3127     rdUpdateStatus := usDeleted;
3128     rdCachedUpdateStatus := cusDeleted;
3129     end;
3130     end;
3131     WriteRecordCache(PRecordData(Buff)^.rdRecordNumber, Buff);
3132     end;
3133     Inc(FDeletedRecords);
3134     FUpdatesPending := True;
3135     end else
3136     IBError(ibxeCannotDelete, [nil]);
3137     finally
3138     if SetCursor and (Screen.Cursor = crHourGlass) then
3139     Screen.Cursor := crDefault;
3140     end;
3141     end;
3142    
3143     procedure TIBCustomDataSet.InternalFirst;
3144     begin
3145     FCurrentRecord := -1;
3146     end;
3147    
3148     procedure TIBCustomDataSet.InternalGotoBookmark(Bookmark: Pointer);
3149     begin
3150     FCurrentRecord := PInteger(Bookmark)^;
3151     end;
3152    
3153     procedure TIBCustomDataSet.InternalHandleException;
3154     begin
3155     Application.HandleException(Self)
3156     end;
3157    
3158     procedure TIBCustomDataSet.InternalInitFieldDefs;
3159 tony 19 begin
3160     if not InternalPrepared then
3161     begin
3162     InternalPrepare;
3163     exit;
3164     end;
3165     FieldDefsFromQuery(FQSelect);
3166     end;
3167    
3168     procedure TIBCustomDataSet.FieldDefsFromQuery(SourceQuery: TIBSQL);
3169 tony 17 const
3170     DefaultSQL = 'Select F.RDB$COMPUTED_BLR, ' + {do not localize}
3171     'F.RDB$DEFAULT_VALUE, R.RDB$FIELD_NAME ' + {do not localize}
3172     'from RDB$RELATION_FIELDS R, RDB$FIELDS F ' + {do not localize}
3173     'where R.RDB$RELATION_NAME = :RELATION ' + {do not localize}
3174     'and R.RDB$FIELD_SOURCE = F.RDB$FIELD_NAME '+ {do not localize}
3175     'and ((not F.RDB$COMPUTED_BLR is NULL) or ' + {do not localize}
3176     ' (not F.RDB$DEFAULT_VALUE is NULL)) '; {do not localize}
3177     var
3178     FieldType: TFieldType;
3179     FieldSize: Word;
3180 tony 27 CharSetSize: integer;
3181 tony 17 FieldNullable : Boolean;
3182     i, FieldPosition, FieldPrecision: Integer;
3183 tony 19 FieldAliasName, DBAliasName: string;
3184 tony 17 RelationName, FieldName: string;
3185     Query : TIBSQL;
3186     FieldIndex: Integer;
3187     FRelationNodes : TRelationNode;
3188    
3189     function Add_Node(Relation, Field : String) : TRelationNode;
3190     var
3191     FField : TFieldNode;
3192     begin
3193     if FRelationNodes.RelationName = '' then
3194     Result := FRelationNodes
3195     else
3196     begin
3197     Result := TRelationNode.Create;
3198     Result.NextRelation := FRelationNodes;
3199     end;
3200     Result.RelationName := Relation;
3201     FRelationNodes := Result;
3202     Query.Params[0].AsString := Relation;
3203     Query.ExecQuery;
3204     while not Query.Eof do
3205     begin
3206     FField := TFieldNode.Create;
3207     FField.FieldName := Query.Fields[2].AsString;
3208     FField.DEFAULT_VALUE := not Query.Fields[1].IsNull;
3209     FField.COMPUTED_BLR := not Query.Fields[0].IsNull;
3210     FField.NextField := Result.FieldNodes;
3211     Result.FieldNodes := FField;
3212     Query.Next;
3213     end;
3214     Query.Close;
3215     end;
3216    
3217     function Has_COMPUTED_BLR(Relation, Field : String) : Boolean;
3218     var
3219     FRelation : TRelationNode;
3220     FField : TFieldNode;
3221     begin
3222     FRelation := FRelationNodes;
3223     while Assigned(FRelation) and
3224     (FRelation.RelationName <> Relation) do
3225     FRelation := FRelation.NextRelation;
3226     if not Assigned(FRelation) then
3227     FRelation := Add_Node(Relation, Field);
3228     Result := false;
3229     FField := FRelation.FieldNodes;
3230     while Assigned(FField) do
3231     if FField.FieldName = Field then
3232     begin
3233     Result := Ffield.COMPUTED_BLR;
3234     Exit;
3235     end
3236     else
3237     FField := Ffield.NextField;
3238     end;
3239    
3240     function Has_DEFAULT_VALUE(Relation, Field : String) : Boolean;
3241     var
3242     FRelation : TRelationNode;
3243     FField : TFieldNode;
3244     begin
3245     FRelation := FRelationNodes;
3246     while Assigned(FRelation) and
3247     (FRelation.RelationName <> Relation) do
3248     FRelation := FRelation.NextRelation;
3249     if not Assigned(FRelation) then
3250     FRelation := Add_Node(Relation, Field);
3251     Result := false;
3252     FField := FRelation.FieldNodes;
3253     while Assigned(FField) do
3254     if FField.FieldName = Field then
3255     begin
3256     Result := Ffield.DEFAULT_VALUE;
3257     Exit;
3258     end
3259     else
3260     FField := Ffield.NextField;
3261     end;
3262    
3263     Procedure FreeNodes;
3264     var
3265     FRelation : TRelationNode;
3266     FField : TFieldNode;
3267     begin
3268     while Assigned(FRelationNodes) do
3269     begin
3270     While Assigned(FRelationNodes.FieldNodes) do
3271     begin
3272     FField := FRelationNodes.FieldNodes.NextField;
3273     FRelationNodes.FieldNodes.Free;
3274     FRelationNodes.FieldNodes := FField;
3275     end;
3276     FRelation := FRelationNodes.NextRelation;
3277     FRelationNodes.Free;
3278     FRelationNodes := FRelation;
3279     end;
3280     end;
3281    
3282     begin
3283     FRelationNodes := TRelationNode.Create;
3284     FNeedsRefresh := False;
3285     Database.InternalTransaction.StartTransaction;
3286     Query := TIBSQL.Create(self);
3287     try
3288     Query.Database := DataBase;
3289     Query.Transaction := Database.InternalTransaction;
3290     FieldDefs.BeginUpdate;
3291     FieldDefs.Clear;
3292     FieldIndex := 0;
3293 tony 19 if (Length(FMappedFieldPosition) < SourceQuery.Current.Count) then
3294     SetLength(FMappedFieldPosition, SourceQuery.Current.Count);
3295 tony 17 Query.SQL.Text := DefaultSQL;
3296     Query.Prepare;
3297 tony 19 SetLength(FAliasNameMap, SourceQuery.Current.Count);
3298 tony 21 SetLength(FAliasNameList, SourceQuery.Current.Count);
3299 tony 19 for i := 0 to SourceQuery.Current.Count - 1 do
3300     with SourceQuery.Current[i].Data^ do
3301 tony 17 begin
3302     { Get the field name }
3303 tony 19 FieldAliasName := SourceQuery.Current[i].Name;
3304     SetString(DBAliasName, aliasname, aliasname_length);
3305 tony 17 SetString(RelationName, relname, relname_length);
3306     SetString(FieldName, sqlname, sqlname_length);
3307 tony 21 FAliasNameList[i] := DBAliasName;
3308 tony 17 FieldSize := 0;
3309     FieldPrecision := 0;
3310 tony 19 FieldNullable := SourceQuery.Current[i].IsNullable;
3311 tony 17 case sqltype and not 1 of
3312     { All VARCHAR's must be converted to strings before recording
3313     their values }
3314     SQL_VARYING, SQL_TEXT:
3315     begin
3316 tony 27 CharSetSize := FBase.GetCharSetSize(sqlsubtype and $FF);
3317     {FieldSize is encoded for strings - see TIBStringField.SetSize for decode}
3318     FieldSize := sqllen * 4 + (CharSetSize - 1);
3319 tony 17 FieldType := ftString;
3320     end;
3321     { All Doubles/Floats should be cast to doubles }
3322     SQL_DOUBLE, SQL_FLOAT:
3323     FieldType := ftFloat;
3324     SQL_SHORT:
3325     begin
3326     if (sqlscale = 0) then
3327     FieldType := ftSmallInt
3328     else begin
3329     FieldType := ftBCD;
3330     FieldPrecision := 4;
3331     FieldSize := -sqlscale;
3332     end;
3333     end;
3334     SQL_LONG:
3335     begin
3336     if (sqlscale = 0) then
3337     FieldType := ftInteger
3338     else if (sqlscale >= (-4)) then
3339     begin
3340     FieldType := ftBCD;
3341     FieldPrecision := 9;
3342     FieldSize := -sqlscale;
3343     end
3344     else
3345     if Database.SQLDialect = 1 then
3346     FieldType := ftFloat
3347     else
3348     if (FieldCount > i) and (Fields[i] is TFloatField) then
3349     FieldType := ftFloat
3350     else
3351     begin
3352     FieldType := ftFMTBCD;
3353     FieldPrecision := 9;
3354     FieldSize := -sqlscale;
3355     end;
3356     end;
3357    
3358     SQL_INT64:
3359     begin
3360     if (sqlscale = 0) then
3361     FieldType := ftLargeInt
3362     else if (sqlscale >= (-4)) then
3363     begin
3364     FieldType := ftBCD;
3365     FieldPrecision := 18;
3366     FieldSize := -sqlscale;
3367     end
3368     else
3369     FieldType := ftFloat
3370     end;
3371     SQL_TIMESTAMP: FieldType := ftDateTime;
3372     SQL_TYPE_TIME: FieldType := ftTime;
3373     SQL_TYPE_DATE: FieldType := ftDate;
3374     SQL_BLOB:
3375     begin
3376     FieldSize := sizeof (TISC_QUAD);
3377     if (sqlsubtype = 1) then
3378     FieldType := ftmemo
3379     else
3380     FieldType := ftBlob;
3381     end;
3382     SQL_ARRAY:
3383     begin
3384     FieldSize := sizeof (TISC_QUAD);
3385     FieldType := ftUnknown;
3386     end;
3387 tony 23 SQL_BOOLEAN:
3388     FieldType:= ftBoolean;
3389 tony 17 else
3390     FieldType := ftUnknown;
3391     end;
3392     FieldPosition := i + 1;
3393     if (FieldType <> ftUnknown) and (FieldAliasName <> 'IBX_INTERNAL_DBKEY') then {do not localize}
3394     begin
3395     FMappedFieldPosition[FieldIndex] := FieldPosition;
3396     Inc(FieldIndex);
3397     with FieldDefs.AddFieldDef do
3398     begin
3399     Name := FieldAliasName;
3400 tony 19 FAliasNameMap[FieldNo-1] := DBAliasName;
3401 tony 17 DataType := FieldType;
3402     Size := FieldSize;
3403     Precision := FieldPrecision;
3404     Required := not FieldNullable;
3405     InternalCalcField := False;
3406     if (FieldName <> '') and (RelationName <> '') then
3407     begin
3408     if Has_COMPUTED_BLR(RelationName, FieldName) then
3409     begin
3410     Attributes := [faReadOnly];
3411     InternalCalcField := True;
3412     FNeedsRefresh := True;
3413     end
3414     else
3415     begin
3416     if Has_DEFAULT_VALUE(RelationName, FieldName) then
3417     begin
3418     if not FieldNullable then
3419     Attributes := [faRequired];
3420     end
3421     else
3422     FNeedsRefresh := True;
3423     end;
3424     end;
3425     end;
3426     end;
3427     end;
3428     finally
3429     Query.free;
3430     FreeNodes;
3431     Database.InternalTransaction.Commit;
3432     FieldDefs.EndUpdate;
3433     end;
3434     end;
3435    
3436     procedure TIBCustomDataSet.InternalInitRecord(Buffer: PChar);
3437     begin
3438     CopyRecordBuffer(FModelBuffer, Buffer);
3439     end;
3440    
3441     procedure TIBCustomDataSet.InternalLast;
3442     var
3443     Buffer: PChar;
3444     begin
3445     if (FQSelect.EOF) then
3446     FCurrentRecord := FRecordCount
3447     else begin
3448     Buffer := AllocRecordBuffer;
3449     try
3450     while FQSelect.Next <> nil do
3451     begin
3452     FetchCurrentRecordToBuffer(FQSelect, FRecordCount, Buffer);
3453     Inc(FRecordCount);
3454     end;
3455     FCurrentRecord := FRecordCount;
3456     finally
3457     FreeRecordBuffer(Buffer);
3458     end;
3459     end;
3460     end;
3461    
3462     procedure TIBCustomDataSet.InternalSetParamsFromCursor;
3463     var
3464     i: Integer;
3465     cur_param: TIBXSQLVAR;
3466     cur_field: TField;
3467     s: TStream;
3468     begin
3469     if FQSelect.SQL.Text = '' then
3470     IBError(ibxeEmptyQuery, [nil]);
3471     if not FInternalPrepared then
3472     InternalPrepare;
3473     if (SQLParams.Count > 0) and (DataSource <> nil) and (DataSource.DataSet <> nil) then
3474     begin
3475     for i := 0 to SQLParams.Count - 1 do
3476     begin
3477     cur_field := DataSource.DataSet.FindField(SQLParams[i].Name);
3478     cur_param := SQLParams[i];
3479     if (cur_field <> nil) then begin
3480     if (cur_field.IsNull) then
3481     cur_param.IsNull := True
3482     else case cur_field.DataType of
3483     ftString:
3484     cur_param.AsString := cur_field.AsString;
3485 tony 23 ftBoolean:
3486     cur_param.AsBoolean := cur_field.AsBoolean;
3487     ftSmallint, ftWord:
3488 tony 17 cur_param.AsShort := cur_field.AsInteger;
3489     ftInteger:
3490     cur_param.AsLong := cur_field.AsInteger;
3491     ftLargeInt:
3492     cur_param.AsInt64 := TLargeIntField(cur_field).AsLargeInt;
3493     ftFloat, ftCurrency:
3494     cur_param.AsDouble := cur_field.AsFloat;
3495     ftBCD:
3496     cur_param.AsCurrency := cur_field.AsCurrency;
3497     ftDate:
3498     cur_param.AsDate := cur_field.AsDateTime;
3499     ftTime:
3500     cur_param.AsTime := cur_field.AsDateTime;
3501     ftDateTime:
3502     cur_param.AsDateTime := cur_field.AsDateTime;
3503     ftBlob, ftMemo:
3504     begin
3505     s := nil;
3506     try
3507     s := DataSource.DataSet.
3508     CreateBlobStream(cur_field, bmRead);
3509     cur_param.LoadFromStream(s);
3510     finally
3511     s.free;
3512     end;
3513     end;
3514     else
3515     IBError(ibxeNotSupported, [nil]);
3516     end;
3517     end;
3518     end;
3519     end;
3520     end;
3521    
3522     procedure TIBCustomDataSet.ReQuery;
3523     begin
3524     FQSelect.Close;
3525     ClearBlobCache;
3526     FCurrentRecord := -1;
3527     FRecordCount := 0;
3528     FDeletedRecords := 0;
3529     FBPos := 0;
3530     FOBPos := 0;
3531     FBEnd := 0;
3532     FOBEnd := 0;
3533     FQSelect.Close;
3534     FQSelect.ExecQuery;
3535     FOpen := FQSelect.Open;
3536     First;
3537     end;
3538    
3539     procedure TIBCustomDataSet.InternalOpen;
3540     var
3541     SetCursor: Boolean;
3542    
3543     function RecordDataLength(n: Integer): Long;
3544     begin
3545     result := SizeOf(TRecordData) + ((n - 1) * SizeOf(TFieldData));
3546     end;
3547    
3548     begin
3549 tony 23 if Assigned(Database) and not Database.SQLHourGlass then
3550     SetCursor := False
3551     else
3552     SetCursor := (GetCurrentThreadID = MainThreadID) and (Screen.Cursor = crDefault);
3553 tony 17 if SetCursor then
3554     Screen.Cursor := crHourGlass;
3555     try
3556     ActivateConnection;
3557     ActivateTransaction;
3558     if FQSelect.SQL.Text = '' then
3559     IBError(ibxeEmptyQuery, [nil]);
3560     if not FInternalPrepared then
3561     InternalPrepare;
3562     if FQSelect.SQLType = SQLSelect then
3563     begin
3564     if DefaultFields then
3565     CreateFields;
3566     BindFields(True);
3567     FCurrentRecord := -1;
3568     FQSelect.ExecQuery;
3569     FOpen := FQSelect.Open;
3570    
3571     { Initialize offsets, buffer sizes, etc...
3572     1. Initially FRecordSize is just the "RecordDataLength".
3573     2. Allocate a "model" buffer and do a dummy fetch
3574     3. After the dummy fetch, FRecordSize will be appropriately
3575     adjusted to reflect the additional "weight" of the field
3576     data.
3577     4. Set up the FCalcFieldsOffset, FBlobCacheOffset and FRecordBufferSize.
3578     5. Now, with the BufferSize available, allocate memory for chunks of records
3579     6. Re-allocate the model buffer, accounting for the new
3580     FRecordBufferSize.
3581     7. Finally, calls to AllocRecordBuffer will work!.
3582     }
3583     {Step 1}
3584     FRecordSize := RecordDataLength(FQSelect.Current.Count);
3585     {Step 2, 3}
3586     IBAlloc(FModelBuffer, 0, FRecordSize);
3587     FetchCurrentRecordToBuffer(FQSelect, -1, FModelBuffer);
3588     {Step 4}
3589     FCalcFieldsOffset := FRecordSize;
3590     FBlobCacheOffset := FCalcFieldsOffset + CalcFieldsSize;
3591     FRecordBufferSize := (FBlobCacheOffset + (BlobFieldCount * SizeOf(TIBBlobStream)));
3592     {Step 5}
3593     if UniDirectional then
3594     FBufferChunkSize := FRecordBufferSize * UniCache
3595     else
3596     FBufferChunkSize := FRecordBufferSize * BufferChunks;
3597     IBAlloc(FBufferCache, FBufferChunkSize, FBufferChunkSize);
3598     if FCachedUpdates or (csReading in ComponentState) then
3599     IBAlloc(FOldBufferCache, FBufferChunkSize, FBufferChunkSize);
3600     FBPos := 0;
3601     FOBPos := 0;
3602     FBEnd := 0;
3603     FOBEnd := 0;
3604     FCacheSize := FBufferChunkSize;
3605     FOldCacheSize := FBufferChunkSize;
3606     {Step 6}
3607     IBAlloc(FModelBuffer, RecordDataLength(FQSelect.Current.Count),
3608     FRecordBufferSize);
3609     {Step 7}
3610     FOldBuffer := AllocRecordBuffer;
3611     end
3612     else
3613     FQSelect.ExecQuery;
3614     finally
3615     if SetCursor and (Screen.Cursor = crHourGlass) then
3616     Screen.Cursor := crDefault;
3617     end;
3618     end;
3619    
3620     procedure TIBCustomDataSet.InternalPost;
3621     var
3622     Qry: TIBSQL;
3623     Buff: PChar;
3624     SetCursor: Boolean;
3625     bInserting: Boolean;
3626     begin
3627 tony 23 if Assigned(Database) and not Database.SQLHourGlass then
3628     SetCursor := False
3629     else
3630     SetCursor := (GetCurrentThreadID = MainThreadID) and (Screen.Cursor = crDefault);
3631 tony 17 if SetCursor then
3632     Screen.Cursor := crHourGlass;
3633     try
3634     Buff := GetActiveBuf;
3635     CheckEditState;
3636     AdjustRecordOnInsert(Buff);
3637     if (State = dsInsert) then
3638     begin
3639     bInserting := True;
3640     Qry := FQInsert;
3641     PRecordData(Buff)^.rdUpdateStatus := usInserted;
3642     PRecordData(Buff)^.rdCachedUpdateStatus := cusInserted;
3643     WriteRecordCache(FRecordCount, Buff);
3644     FCurrentRecord := FRecordCount;
3645     end
3646     else begin
3647     bInserting := False;
3648     Qry := FQModify;
3649     if PRecordData(Buff)^.rdCachedUpdateStatus = cusUnmodified then
3650     begin
3651     PRecordData(Buff)^.rdUpdateStatus := usModified;
3652     PRecordData(Buff)^.rdCachedUpdateStatus := cusModified;
3653     end
3654     else if PRecordData(Buff)^.
3655     rdCachedUpdateStatus = cusUninserted then
3656     begin
3657     PRecordData(Buff)^.rdCachedUpdateStatus := cusInserted;
3658     Dec(FDeletedRecords);
3659     end;
3660     end;
3661     if (not CachedUpdates) then
3662     InternalPostRecord(Qry, Buff)
3663     else begin
3664     WriteRecordCache(PRecordData(Buff)^.rdRecordNumber, Buff);
3665     FUpdatesPending := True;
3666     end;
3667     if bInserting then
3668     Inc(FRecordCount);
3669     finally
3670     if SetCursor and (Screen.Cursor = crHourGlass) then
3671     Screen.Cursor := crDefault;
3672     end;
3673     end;
3674    
3675     procedure TIBCustomDataSet.InternalRefresh;
3676     begin
3677     inherited InternalRefresh;
3678     InternalRefreshRow;
3679     end;
3680    
3681     procedure TIBCustomDataSet.InternalSetToRecord(Buffer: PChar);
3682     begin
3683     InternalGotoBookmark(@(PRecordData(Buffer)^.rdRecordNumber));
3684     end;
3685    
3686     function TIBCustomDataSet.IsCursorOpen: Boolean;
3687     begin
3688     result := FOpen;
3689     end;
3690    
3691 tony 21 procedure TIBCustomDataSet.Loaded;
3692     begin
3693     if assigned(FQSelect) then
3694     FBaseSQLSelect.assign(FQSelect.SQL);
3695     inherited Loaded;
3696     end;
3697    
3698 tony 27 procedure TIBCustomDataSet.Post;
3699     var CancelPost: boolean;
3700     begin
3701     CancelPost := false;
3702     if assigned(FOnValidatePost) then
3703     OnValidatePost(self,CancelPost);
3704     if CancelPost then
3705     Cancel
3706     else
3707     inherited Post;
3708     end;
3709    
3710 tony 17 function TIBCustomDataSet.Locate(const KeyFields: string; const KeyValues: Variant;
3711     Options: TLocateOptions): Boolean;
3712     var
3713     {$IF FPC_FULLVERSION >= 20700 }
3714     CurBookmark: TBookmark;
3715     {$ELSE}
3716     CurBookmark: string;
3717     {$ENDIF}
3718     begin
3719     DisableControls;
3720     try
3721     CurBookmark := Bookmark;
3722     First;
3723     result := InternalLocate(KeyFields, KeyValues, Options);
3724     if not result then
3725     Bookmark := CurBookmark;
3726     finally
3727     EnableControls;
3728     end;
3729     end;
3730    
3731     function TIBCustomDataSet.Lookup(const KeyFields: string; const KeyValues: Variant;
3732     const ResultFields: string): Variant;
3733     var
3734     fl: TList;
3735     {$IF FPC_FULLVERSION >= 20700 }
3736     CurBookmark: TBookmark;
3737     {$ELSE}
3738     CurBookmark: string;
3739     {$ENDIF}
3740     begin
3741     DisableControls;
3742     fl := TList.Create;
3743     CurBookmark := Bookmark;
3744     try
3745     First;
3746     if InternalLocate(KeyFields, KeyValues, []) then
3747     begin
3748     if (ResultFields <> '') then
3749     result := FieldValues[ResultFields]
3750     else
3751     result := NULL;
3752     end
3753     else
3754     result := Null;
3755     finally
3756     Bookmark := CurBookmark;
3757     fl.Free;
3758     EnableControls;
3759     end;
3760     end;
3761    
3762     procedure TIBCustomDataSet.SetBookmarkData(Buffer: PChar; Data: Pointer);
3763     begin
3764     PRecordData(Buffer)^.rdRecordNumber := PInteger(Data)^;
3765     end;
3766    
3767     procedure TIBCustomDataSet.SetBookmarkFlag(Buffer: PChar; Value: TBookmarkFlag);
3768     begin
3769     PRecordData(Buffer)^.rdBookmarkFlag := Value;
3770     end;
3771    
3772     procedure TIBCustomDataSet.SetCachedUpdates(Value: Boolean);
3773     begin
3774     if not Value and FCachedUpdates then
3775     CancelUpdates;
3776     if (not (csReading in ComponentState)) and Value then
3777     CheckDatasetClosed;
3778     FCachedUpdates := Value;
3779     end;
3780    
3781     procedure TIBCustomDataSet.SetDataSource(Value: TDataSource);
3782     begin
3783     if IsLinkedTo(Value) then
3784     IBError(ibxeCircularReference, [nil]);
3785     if FDataLink <> nil then
3786     FDataLink.DataSource := Value;
3787     end;
3788    
3789     procedure TIBCustomDataSet.InternalSetFieldData(Field: TField; Buffer: Pointer);
3790     var
3791     Buff, TmpBuff: PChar;
3792 tony 21 MappedFieldPos: integer;
3793 tony 17 begin
3794     Buff := GetActiveBuf;
3795     if Field.FieldNo < 0 then
3796     begin
3797     TmpBuff := Buff + FRecordSize + Field.Offset;
3798     Boolean(TmpBuff[0]) := LongBool(Buffer);
3799     if Boolean(TmpBuff[0]) then
3800     Move(Buffer^, TmpBuff[1], Field.DataSize);
3801     WriteRecordCache(PRecordData(Buff)^.rdRecordNumber, Buff);
3802     end
3803     else begin
3804     CheckEditState;
3805     with PRecordData(Buff)^ do
3806     begin
3807     { If inserting, Adjust record position }
3808     AdjustRecordOnInsert(Buff);
3809 tony 21 MappedFieldPos := FMappedFieldPosition[Field.FieldNo - 1];
3810     if (MappedFieldPos > 0) and
3811     (MappedFieldPos <= rdFieldCount) then
3812 tony 17 begin
3813     Field.Validate(Buffer);
3814     if (Buffer = nil) or
3815     (Field is TIBStringField) and (PChar(Buffer)[0] = #0) then
3816 tony 21 rdFields[MappedFieldPos].fdIsNull := True
3817 tony 17 else begin
3818 tony 21 Move(Buffer^, Buff[rdFields[MappedFieldPos].fdDataOfs],
3819     rdFields[MappedFieldPos].fdDataSize);
3820     if (rdFields[MappedFieldPos].fdDataType = SQL_TEXT) or
3821     (rdFields[MappedFieldPos].fdDataType = SQL_VARYING) then
3822     rdFields[MappedFieldPos].fdDataLength := StrLen(PChar(Buffer));
3823     rdFields[MappedFieldPos].fdIsNull := False;
3824 tony 17 if rdUpdateStatus = usUnmodified then
3825     begin
3826     if CachedUpdates then
3827     begin
3828     FUpdatesPending := True;
3829     if State = dsInsert then
3830     rdCachedUpdateStatus := cusInserted
3831     else if State = dsEdit then
3832     rdCachedUpdateStatus := cusModified;
3833     end;
3834    
3835     if State = dsInsert then
3836     rdUpdateStatus := usInserted
3837     else
3838     rdUpdateStatus := usModified;
3839     end;
3840     WriteRecordCache(rdRecordNumber, Buff);
3841     SetModified(True);
3842     end;
3843     end;
3844     end;
3845     end;
3846     if not (State in [dsCalcFields, dsFilter, dsNewValue]) then
3847     DataEvent(deFieldChange, PtrInt(Field));
3848     end;
3849    
3850     procedure TIBCustomDataSet.SetRecNo(Value: Integer);
3851     begin
3852     CheckBrowseMode;
3853     if (Value < 1) then
3854     Value := 1
3855     else if Value > FRecordCount then
3856     begin
3857     InternalLast;
3858     Value := Min(FRecordCount, Value);
3859     end;
3860     if (Value <> RecNo) then
3861     begin
3862     DoBeforeScroll;
3863     FCurrentRecord := Value - 1;
3864     Resync([]);
3865     DoAfterScroll;
3866     end;
3867     end;
3868    
3869     procedure TIBCustomDataSet.Disconnect;
3870     begin
3871     Close;
3872     InternalUnPrepare;
3873     end;
3874    
3875     procedure TIBCustomDataSet.SetUpdateMode(const Value: TUpdateMode);
3876     begin
3877     if not CanModify then
3878     IBError(ibxeCannotUpdate, [nil])
3879     else
3880     FUpdateMode := Value;
3881     end;
3882    
3883    
3884     procedure TIBCustomDataSet.SetUpdateObject(Value: TIBDataSetUpdateObject);
3885     begin
3886     if Value <> FUpdateObject then
3887     begin
3888     if Assigned(FUpdateObject) and (FUpdateObject.DataSet = Self) then
3889     FUpdateObject.DataSet := nil;
3890     FUpdateObject := Value;
3891     if Assigned(FUpdateObject) then
3892     begin
3893     if Assigned(FUpdateObject.DataSet) and
3894     (FUpdateObject.DataSet <> Self) then
3895     FUpdateObject.DataSet.UpdateObject := nil;
3896     FUpdateObject.DataSet := Self;
3897     end;
3898     end;
3899     end;
3900    
3901     function TIBCustomDataSet.ConstraintsStored: Boolean;
3902     begin
3903     Result := Constraints.Count > 0;
3904     end;
3905    
3906     procedure TIBCustomDataSet.ClearCalcFields(Buffer: PChar);
3907     begin
3908     FillChar(Buffer[FRecordSize], CalcFieldsSize, 0);
3909     end;
3910    
3911 tony 27 procedure TIBCustomDataSet.ClearIBLinks;
3912     var i: integer;
3913     begin
3914     for i := FIBLinks.Count - 1 downto 0 do
3915     TIBControlLink(FIBLinks[i]).IBDataSet := nil;
3916     end;
3917 tony 17
3918 tony 27
3919 tony 17 procedure TIBCustomDataSet.InternalUnPrepare;
3920     begin
3921     if FInternalPrepared then
3922     begin
3923     CheckDatasetClosed;
3924     FieldDefs.Clear;
3925 tony 19 FieldDefs.Updated := false;
3926 tony 17 FInternalPrepared := False;
3927 tony 21 Setlength(FAliasNameList,0);
3928 tony 17 end;
3929     end;
3930    
3931     procedure TIBCustomDataSet.InternalExecQuery;
3932     var
3933     DidActivate: Boolean;
3934     SetCursor: Boolean;
3935     begin
3936     DidActivate := False;
3937 tony 23 if Assigned(Database) and not Database.SQLHourGlass then
3938     SetCursor := False
3939     else
3940     SetCursor := (GetCurrentThreadID = MainThreadID) and (Screen.Cursor = crDefault);
3941 tony 17 if SetCursor then
3942     Screen.Cursor := crHourGlass;
3943     try
3944     ActivateConnection;
3945     DidActivate := ActivateTransaction;
3946     if FQSelect.SQL.Text = '' then
3947     IBError(ibxeEmptyQuery, [nil]);
3948     if not FInternalPrepared then
3949     InternalPrepare;
3950     if FQSelect.SQLType = SQLSelect then
3951     begin
3952     IBError(ibxeIsASelectStatement, [nil]);
3953     end
3954     else
3955     FQSelect.ExecQuery;
3956     finally
3957     if DidActivate then
3958     DeactivateTransaction;
3959     if SetCursor and (Screen.Cursor = crHourGlass) then
3960     Screen.Cursor := crDefault;
3961     end;
3962     end;
3963    
3964     function TIBCustomDataSet.GetSelectStmtHandle: TISC_STMT_HANDLE;
3965     begin
3966     Result := FQSelect.Handle;
3967     end;
3968    
3969 tony 21 function TIBCustomDataSet.GetParser: TSelectSQLParser;
3970     begin
3971     if not assigned(FParser) then
3972     FParser := CreateParser;
3973     Result := FParser
3974     end;
3975    
3976     procedure TIBCustomDataSet.ResetParser;
3977     begin
3978     if assigned(FParser) then
3979     begin
3980     FParser.Free;
3981     FParser := nil;
3982     SQLChanging(nil)
3983     end;
3984     end;
3985    
3986     function TIBCustomDataSet.HasParser: boolean;
3987     begin
3988     Result := not (csDesigning in ComponentState) and (FParser <> nil)
3989     end;
3990    
3991 tony 19 procedure TIBCustomDataSet.SetGenerateParamNames(AValue: Boolean);
3992     begin
3993     if FGenerateParamNames = AValue then Exit;
3994     FGenerateParamNames := AValue;
3995     Disconnect
3996     end;
3997    
3998 tony 17 procedure TIBCustomDataSet.InitRecord(Buffer: PChar);
3999     begin
4000     inherited InitRecord(Buffer);
4001     with PRecordData(Buffer)^ do
4002     begin
4003     rdUpdateStatus := TUpdateStatus(usInserted);
4004     rdBookMarkFlag := bfInserted;
4005     rdRecordNumber := -1;
4006     end;
4007     end;
4008    
4009     procedure TIBCustomDataSet.InternalInsert;
4010     begin
4011     CursorPosChanged;
4012     end;
4013    
4014     { TIBDataSet IProviderSupport }
4015    
4016     (*procedure TIBCustomDataSet.PSEndTransaction(Commit: Boolean);
4017     begin
4018     if Commit then
4019     Transaction.Commit else
4020     Transaction.Rollback;
4021     end;
4022    
4023     function TIBCustomDataSet.PSExecuteStatement(const ASQL: string; AParams: TParams;
4024     ResultSet: Pointer = nil): Integer;
4025     var
4026     FQuery: TIBQuery;
4027     begin
4028     if Assigned(ResultSet) then
4029     begin
4030     TDataSet(ResultSet^) := TIBQuery.Create(nil);
4031     with TIBQuery(ResultSet^) do
4032     begin
4033     SQL.Text := ASQL;
4034     Params.Assign(AParams);
4035     Open;
4036     Result := RowsAffected;
4037     end;
4038     end
4039     else
4040     begin
4041     FQuery := TIBQuery.Create(nil);
4042     try
4043     FQuery.Database := Database;
4044     FQuery.Transaction := Transaction;
4045     FQuery.GenerateParamNames := True;
4046     FQuery.SQL.Text := ASQL;
4047     FQuery.Params.Assign(AParams);
4048     FQuery.ExecSQL;
4049     Result := FQuery.RowsAffected;
4050     finally
4051     FQuery.Free;
4052     end;
4053     end;
4054     end;
4055    
4056     function TIBCustomDataSet.PSGetQuoteChar: string;
4057     begin
4058     if Database.SQLDialect = 3 then
4059     Result := '"' else
4060     Result := '';
4061     end;
4062    
4063     function TIBCustomDataSet.PSGetUpdateException(E: Exception; Prev: EUpdateError): EUpdateError;
4064     var
4065     PrevErr: Integer;
4066     begin
4067     if Prev <> nil then
4068     PrevErr := Prev.ErrorCode else
4069     PrevErr := 0;
4070     if E is EIBError then
4071     with EIBError(E) do
4072     Result := EUpdateError.Create(E.Message, '', SQLCode, PrevErr, E) else
4073     Result := inherited PSGetUpdateException(E, Prev);
4074     end;
4075    
4076     function TIBCustomDataSet.PSInTransaction: Boolean;
4077     begin
4078     Result := Transaction.InTransaction;
4079     end;
4080    
4081     function TIBCustomDataSet.PSIsSQLBased: Boolean;
4082     begin
4083     Result := True;
4084     end;
4085    
4086     function TIBCustomDataSet.PSIsSQLSupported: Boolean;
4087     begin
4088     Result := True;
4089     end;
4090    
4091     procedure TIBCustomDataSet.PSReset;
4092     begin
4093     inherited PSReset;
4094     if Active then
4095     begin
4096     Close;
4097     Open;
4098     end;
4099     end;
4100    
4101     function TIBCustomDataSet.PSUpdateRecord(UpdateKind: TUpdateKind; Delta: TDataSet): Boolean;
4102     var
4103     UpdateAction: TIBUpdateAction;
4104     SQL: string;
4105     Params: TParams;
4106    
4107     procedure AssignParams(DataSet: TDataSet; Params: TParams);
4108     var
4109     I: Integer;
4110     Old: Boolean;
4111     Param: TParam;
4112     PName: string;
4113     Field: TField;
4114     Value: Variant;
4115     begin
4116     for I := 0 to Params.Count - 1 do
4117     begin
4118     Param := Params[I];
4119     PName := Param.Name;
4120     Old := CompareText(Copy(PName, 1, 4), 'OLD_') = 0; {do not localize}
4121     if Old then System.Delete(PName, 1, 4);
4122     Field := DataSet.FindField(PName);
4123     if not Assigned(Field) then Continue;
4124     if Old then Param.AssignFieldValue(Field, Field.OldValue) else
4125     begin
4126     Value := Field.NewValue;
4127     if VarIsEmpty(Value) then Value := Field.OldValue;
4128     Param.AssignFieldValue(Field, Value);
4129     end;
4130     end;
4131     end;
4132    
4133     begin
4134     Result := False;
4135     if Assigned(OnUpdateRecord) then
4136     begin
4137     UpdateAction := uaFail;
4138     if Assigned(FOnUpdateRecord) then
4139     begin
4140     FOnUpdateRecord(Delta, UpdateKind, UpdateAction);
4141     Result := UpdateAction = uaApplied;
4142     end;
4143     end
4144     else if Assigned(FUpdateObject) then
4145     begin
4146     SQL := FUpdateObject.GetSQL(UpdateKind).Text;
4147     if SQL <> '' then
4148     begin
4149     Params := TParams.Create;
4150     try
4151     Params.ParseSQL(SQL, True);
4152     AssignParams(Delta, Params);
4153     if PSExecuteStatement(SQL, Params) = 0 then
4154     IBError(ibxeNoRecordsAffected, [nil]);
4155     Result := True;
4156     finally
4157     Params.Free;
4158     end;
4159     end;
4160     end;
4161     end;
4162    
4163     procedure TIBCustomDataSet.PSStartTransaction;
4164     begin
4165     ActivateConnection;
4166     Transaction.StartTransaction;
4167     end;
4168    
4169     function TIBCustomDataSet.PSGetTableName: string;
4170     begin
4171     // if not FInternalPrepared then
4172     // InternalPrepare;
4173     { It is possible for the FQSelectSQL to be unprepared
4174     with FInternalPreprepared being true (see DoBeforeTransactionEnd).
4175     So check the Prepared of the SelectSQL instead }
4176     if not FQSelect.Prepared then
4177     FQSelect.Prepare;
4178     Result := FQSelect.UniqueRelationName;
4179     end;*)
4180    
4181     procedure TIBDataSet.BatchInput(InputObject: TIBBatchInput);
4182     begin
4183     InternalBatchInput(InputObject);
4184     end;
4185    
4186     procedure TIBDataSet.BatchOutput(OutputObject: TIBBatchOutput);
4187     begin
4188     InternalBatchOutput(OutputObject);
4189     end;
4190    
4191     procedure TIBDataSet.ExecSQL;
4192     begin
4193     InternalExecQuery;
4194     end;
4195    
4196     procedure TIBDataSet.Prepare;
4197     begin
4198     InternalPrepare;
4199     end;
4200    
4201     procedure TIBDataSet.UnPrepare;
4202     begin
4203     InternalUnPrepare;
4204     end;
4205    
4206     function TIBDataSet.GetPrepared: Boolean;
4207     begin
4208     Result := InternalPrepared;
4209     end;
4210    
4211     procedure TIBDataSet.InternalOpen;
4212     begin
4213     ActivateConnection;
4214     ActivateTransaction;
4215     InternalSetParamsFromCursor;
4216     Inherited InternalOpen;
4217     end;
4218    
4219     procedure TIBDataSet.SetFiltered(Value: Boolean);
4220     begin
4221     if(Filtered <> Value) then
4222     begin
4223     inherited SetFiltered(value);
4224     if Active then
4225     begin
4226     Close;
4227     Open;
4228     end;
4229     end
4230     else
4231     inherited SetFiltered(value);
4232     end;
4233    
4234     function TIBCustomDataSet.BookmarkValid(Bookmark: TBookmark): Boolean;
4235     begin
4236     Result := false;
4237     if not Assigned(Bookmark) then
4238     exit;
4239     Result := PInteger(Bookmark)^ < FRecordCount;
4240     end;
4241    
4242     function TIBCustomDataSet.GetFieldData(Field: TField;
4243     Buffer: Pointer): Boolean;
4244     {$IFDEF TBCDFIELD_IS_BCD}
4245     var
4246     lTempCurr : System.Currency;
4247     begin
4248     if (Field.DataType = ftBCD) and (Buffer <> nil) then
4249     begin
4250     Result := InternalGetFieldData(Field, @lTempCurr);
4251     if Result then
4252     CurrToBCD(lTempCurr, TBCD(Buffer^), 32, Field.Size);
4253     end
4254     else
4255     {$ELSE}
4256     begin
4257     {$ENDIF}
4258     Result := InternalGetFieldData(Field, Buffer);
4259     end;
4260    
4261     function TIBCustomDataSet.GetFieldData(Field: TField; Buffer: Pointer;
4262     NativeFormat: Boolean): Boolean;
4263     begin
4264     if (Field.DataType = ftBCD) and not NativeFormat then
4265     Result := InternalGetFieldData(Field, Buffer)
4266     else
4267     Result := inherited GetFieldData(Field, Buffer, NativeFormat);
4268     end;
4269    
4270     procedure TIBCustomDataSet.SetFieldData(Field: TField; Buffer: Pointer);
4271     {$IFDEF TDBDFIELD_IS_BCD}
4272     var
4273     lTempCurr : System.Currency;
4274     begin
4275     if (Field.DataType = ftBCD) and (Buffer <> nil) then
4276     begin
4277     BCDToCurr(TBCD(Buffer^), lTempCurr);
4278     InternalSetFieldData(Field, @lTempCurr);
4279     end
4280     else
4281     {$ELSE}
4282     begin
4283     {$ENDIF}
4284     InternalSetFieldData(Field, Buffer);
4285     end;
4286    
4287     procedure TIBCustomDataSet.SetFieldData(Field: TField; Buffer: Pointer;
4288     NativeFormat: Boolean);
4289     begin
4290     if (not NativeFormat) and (Field.DataType = ftBCD) then
4291     InternalSetfieldData(Field, Buffer)
4292     else
4293     inherited SetFieldData(Field, buffer, NativeFormat);
4294     end;
4295    
4296     { TIBDataSetUpdateObject }
4297    
4298     constructor TIBDataSetUpdateObject.Create(AOwner: TComponent);
4299     begin
4300     inherited Create(AOwner);
4301     FRefreshSQL := TStringList.Create;
4302     end;
4303    
4304     destructor TIBDataSetUpdateObject.Destroy;
4305     begin
4306     FRefreshSQL.Free;
4307     inherited Destroy;
4308     end;
4309    
4310     procedure TIBDataSetUpdateObject.SetRefreshSQL(Value: TStrings);
4311     begin
4312     FRefreshSQL.Assign(Value);
4313     end;
4314    
4315     procedure TIBDataSetUpdateObject.InternalSetParams(Query: TIBSQL; buff: PChar);
4316     begin
4317     if not Assigned(DataSet) then Exit;
4318     DataSet.SetInternalSQLParams(Query, buff);
4319     end;
4320    
4321     { TIBDSBlobStream }
4322     constructor TIBDSBlobStream.Create(AField: TField; ABlobStream: TIBBlobStream;
4323     Mode: TBlobStreamMode);
4324     begin
4325     FField := AField;
4326     FBlobStream := ABlobStream;
4327     FBlobStream.Seek(0, soFromBeginning);
4328     if (Mode = bmWrite) then
4329     FBlobStream.Truncate;
4330     end;
4331    
4332     function TIBDSBlobStream.Read(var Buffer; Count: Longint): Longint;
4333     begin
4334     result := FBlobStream.Read(Buffer, Count);
4335     end;
4336    
4337     function TIBDSBlobStream.Seek(Offset: Longint; Origin: Word): Longint;
4338     begin
4339     result := FBlobStream.Seek(Offset, Origin);
4340     end;
4341    
4342     procedure TIBDSBlobStream.SetSize(NewSize: Longint);
4343     begin
4344     FBlobStream.SetSize(NewSize);
4345     end;
4346    
4347     function TIBDSBlobStream.Write(const Buffer; Count: Longint): Longint;
4348     begin
4349     if not (FField.DataSet.State in [dsEdit, dsInsert]) then
4350     IBError(ibxeNotEditing, [nil]);
4351     TIBCustomDataSet(FField.DataSet).RecordModified(True);
4352     TBlobField(FField).Modified := true;
4353     result := FBlobStream.Write(Buffer, Count);
4354     TIBCustomDataSet(FField.DataSet).DataEvent(deFieldChange, PtrInt(FField));
4355     end;
4356    
4357     { TIBGenerator }
4358    
4359     procedure TIBGenerator.SetIncrement(const AValue: integer);
4360     begin
4361     if AValue < 0 then
4362     raise Exception.Create('A Generator Increment cannot be negative');
4363     FIncrement := AValue
4364     end;
4365    
4366     function TIBGenerator.GetNextValue(ADatabase: TIBDatabase;
4367     ATransaction: TIBTransaction): integer;
4368     begin
4369     with TIBSQL.Create(nil) do
4370     try
4371     Database := ADatabase;
4372     Transaction := ATransaction;
4373     if not assigned(Database) then
4374     IBError(ibxeCannotSetDatabase,[]);
4375     if not assigned(Transaction) then
4376     IBError(ibxeCannotSetTransaction,[]);
4377     with Transaction do
4378     if not InTransaction then StartTransaction;
4379     SQL.Text := Format('Select Gen_ID(%s,%d) as ID From RDB$Database',[FGeneratorName,Increment]);
4380     Prepare;
4381     ExecQuery;
4382     try
4383     Result := FieldByName('ID').AsInteger
4384     finally
4385     Close
4386     end;
4387     finally
4388     Free
4389     end;
4390     end;
4391    
4392     constructor TIBGenerator.Create(Owner: TIBCustomDataSet);
4393     begin
4394     FOwner := Owner;
4395     FIncrement := 1;
4396     end;
4397    
4398    
4399     procedure TIBGenerator.Apply;
4400     begin
4401     if (FGeneratorName <> '') and (FFieldName <> '') and Owner.FieldByName(FFieldName).IsNull then
4402     Owner.FieldByName(FFieldName).AsInteger := GetNextValue(Owner.Database,Owner.Transaction);
4403     end;
4404    
4405 tony 23 end.