home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Delphi Anthology
/
aDELPHI.iso
/
Runimage
/
Delphi50
/
Source
/
Vcl
/
ibcustomdataset.pas
< prev
next >
Wrap
Pascal/Delphi Source File
|
1999-08-11
|
105KB
|
3,572 lines
{********************************************************}
{ }
{ Borland Delphi Visual Component Library }
{ InterBase Express core components }
{ }
{ Copyright (c) 1998-1999 Inprise Corporation }
{ }
{ InterBase Express is based in part on the product }
{ Free IB Components, written by Gregory H. Deatz for }
{ Hoagland, Longo, Moran, Dunst & Doukas Company. }
{ Free IB Components is used under license. }
{ }
{********************************************************}
unit IBCustomDataSet;
interface
uses
Windows, SysUtils, Classes, Forms, Controls, StdVCL,
IBExternals, IB, IBHeader, IBDatabase, IBSQL, Db,
IBUtils, IBBlob;
const
BufferCacheSize = 32; { Allocate cache in this many record chunks}
UniCache = 2; { Uni-directional cache is 2 records big }
type
TIBCustomDataSet = class;
TIBDataSet = class;
TIBDataSetUpdateObject = class(TComponent)
private
FRefreshSQL: TStrings;
procedure SetRefreshSQL(value: TStrings);
protected
function GetDataSet: TIBCustomDataSet; virtual; abstract;
procedure SetDataSet(ADataSet: TIBCustomDataSet); virtual; abstract;
procedure Apply(UpdateKind: TUpdateKind); virtual; abstract;
function GetSQL(UpdateKind: TUpdateKind): TStrings; virtual; abstract;
property DataSet: TIBCustomDataSet read GetDataSet write SetDataSet;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
property RefreshSQL: TStrings read FRefreshSQL write SetRefreshSQL;
end;
PDateTime = ^TDateTime;
TBlobDataArray = array[0..0] of TIBBlobStream;
PBlobDataArray = ^TBlobDataArray;
{ TIBCustomDataSet }
TFieldData = record
fdDataType: Short;
fdDataScale: Short;
fdNullable: Boolean;
fdIsNull: Boolean;
fdDataSize: Short;
fdDataLength: Short;
fdDataOfs: Integer;
end;
PFieldData = ^TFieldData;
TCachedUpdateStatus = (
cusUnmodified, cusModified, cusInserted,
cusDeleted, cusUninserted
);
TIBDBKey = record
DBKey: array[0..7] of Byte;
end;
PIBDBKey = ^TIBDBKey;
TRecordData = record
rdBookmarkFlag: TBookmarkFlag;
rdFieldCount: Short;
rdRecordNumber: Long;
rdCachedUpdateStatus: TCachedUpdateStatus;
rdUpdateStatus: TUpdateStatus;
rdSavedOffset: DWORD;
rdDBKey: TIBDBKey;
rdFields: array[1..1] of TFieldData;
end;
PRecordData = ^TRecordData;
{ TIBStringField allows us to have strings longer than 8196 }
TIBStringField = class(TStringField)
private
FBlanksToNULL: Boolean;
public
constructor create(AOwner: TComponent); override;
class procedure CheckTypeSize(Value: Integer); override;
function GetAsString: string; override;
function GetAsVariant: Variant; override;
function GetValue(var Value: string): Boolean;
procedure SetAsString(const Value: string); override;
published
property BlanksToNULL: Boolean read FBlanksToNULL
write FBlanksToNULL default True;
end;
{ TIBBCDField }
{ Actually, there is no BCD involved in this type,
instead it deals with currency types.
In IB, this is an encapsulation of Numeric (x, y)
where x < 18 and y <= 4.
Note: y > 4 will default to Floats
}
TIBBCDField = class(TBCDField)
protected
class procedure CheckTypeSize(Value: Integer); override;
function GetAsCurrency: Currency; override;
function GetAsString: string; override;
function GetAsVariant: Variant; override;
function GetDataSize: Integer; override;
procedure GetText(var Text: string; DisplayText: Boolean); override;
function GetValue(var Value: Currency): Boolean;
procedure SetAsCurrency(Value: Currency); override;
public
constructor Create(AOwner: TComponent); override;
published
property Size default 8;
end;
TIBDataLink = class(TDetailDataLink)
private
FDataSet: TIBCustomDataSet;
protected
procedure ActiveChanged; override;
procedure RecordChanged(Field: TField); override;
function GetDetailDataSet: TDataSet; override;
procedure CheckBrowseMode; override;
public
constructor Create(ADataSet: TIBCustomDataSet);
destructor Destroy; override;
end;
{ TIBCustomDataSet }
TIBUpdateAction = (uaFail, uaAbort, uaSkip, uaRetry, uaApply, uaApplied);
TIBUpdateErrorEvent = procedure(DataSet: TDataSet; E: EDatabaseError;
UpdateKind: TUpdateKind; var UpdateAction: TIBUpdateAction)
of object;
TIBUpdateRecordEvent = procedure(DataSet: TDataSet; UpdateKind: TUpdateKind;
var UpdateAction: TIBUpdateAction) of object;
TIBUpdateRecordTypes = set of TCachedUpdateStatus;
TIBCustomDataSet = class(TDataset)
private
FDidActivate: Boolean;
FIBLoaded: Boolean;
FBase: TIBBase;
FBlobCacheOffset: Integer;
FBlobStreamList: TList;
FBufferChunks: Integer;
FBufferCache,
FOldBufferCache: PChar;
FBufferChunkSize,
FCacheSize,
FOldCacheSize: Integer;
FFilterBuffer: PChar;
FBPos,
FOBPos,
FBEnd,
FOBEnd: DWord;
FCachedUpdates: Boolean;
FCalcFieldsOffset: Integer;
FCurrentRecord: Long;
FDeletedRecords: Long;
FModelBuffer,
FOldBuffer: PChar;
FOpen: Boolean;
FInternalPrepared: Boolean;
FQDelete,
FQInsert,
FQRefresh,
FQSelect,
FQModify: TIBSQL;
FRecordBufferSize: Integer;
FRecordCount: Integer;
FRecordSize: Integer;
FUniDirectional: Boolean;
FUpdateMode: TUpdateMode;
FUpdateObject: TIBDataSetUpdateObject;
FParamCheck: Boolean;
FUpdatesPending: Boolean;
FUpdateRecordTypes: TIBUpdateRecordTypes;
FMappedFieldPosition: array of Integer;
FBeforeDatabaseDisconnect,
FAfterDatabaseDisconnect,
FDatabaseFree: TNotifyEvent;
FOnUpdateError: TIBUpdateErrorEvent;
FOnUpdateRecord: TIBUpdateRecordEvent;
FBeforeTransactionEnd,
FAfterTransactionEnd,
FTransactionFree: TNotifyEvent;
function GetSelectStmtHandle: TISC_STMT_HANDLE;
procedure SetUpdateMode(const Value: TUpdateMode);
procedure SetUpdateObject(Value: TIBDataSetUpdateObject);
function AdjustCurrentRecord(Buffer: Pointer; GetMode: TGetMode): TGetResult;
procedure AdjustRecordOnInsert(Buffer: Pointer);
function CanEdit: Boolean;
function CanInsert: Boolean;
function CanDelete: Boolean;
function CanRefresh: Boolean;
procedure CheckEditState;
procedure ClearBlobCache;
procedure CopyRecordBuffer(Source, Dest: Pointer);
procedure DoBeforeDatabaseDisconnect(Sender: TObject);
procedure DoAfterDatabaseDisconnect(Sender: TObject);
procedure DoDatabaseFree(Sender: TObject);
procedure DoBeforeTransactionEnd(Sender: TObject);
procedure DoAfterTransactionEnd(Sender: TObject);
procedure DoTransactionFree(Sender: TObject);
procedure FetchCurrentRecordToBuffer(Qry: TIBSQL; RecordNumber: Integer;
Buffer: PChar);
function GetDatabase: TIBDatabase;
function GetDBHandle: PISC_DB_HANDLE;
function GetDeleteSQL: TStrings;
function GetInsertSQL: TStrings;
function GetSQLParams: TIBXSQLDA;
function GetRefreshSQL: TStrings;
function GetSelectSQL: TStrings;
function GetStatementType: TIBSQLTypes;
function GetModifySQL: TStrings;
function GetTransaction: TIBTransaction;
function GetTRHandle: PISC_TR_HANDLE;
procedure InternalDeleteRecord(Qry: TIBSQL; Buff: Pointer);
function InternalLocate(const KeyFields: string; const KeyValues: Variant;
Options: TLocateOptions): Boolean; virtual;
procedure InternalPostRecord(Qry: TIBSQL; Buff: Pointer);
procedure InternalRevertRecord(RecordNumber: Integer);
function IsVisible(Buffer: PChar): Boolean;
procedure SaveOldBuffer(Buffer: PChar);
procedure SetBufferChunks(Value: Integer);
procedure SetDatabase(Value: TIBDatabase);
procedure SetDeleteSQL(Value: TStrings);
procedure SetInsertSQL(Value: TStrings);
procedure SetInternalSQLParams(Qry: TIBSQL; Buffer: Pointer);
procedure SetRefreshSQL(Value: TStrings);
procedure SetSelectSQL(Value: TStrings);
procedure SetModifySQL(Value: TStrings);
procedure SetTransaction(Value: TIBTransaction);
procedure SetUpdateRecordTypes(Value: TIBUpdateRecordTypes);
procedure SetUniDirectional(Value: Boolean);
procedure RefreshParams;
procedure SQLChanging(Sender: TObject); virtual;
function AdjustPosition(FCache: PChar; Offset: DWORD;
Origin: Integer): Integer;
procedure ReadCache(FCache: PChar; Offset: DWORD; Origin: Integer;
Buffer: PChar);
procedure ReadRecordCache(RecordNumber: Integer; Buffer: PChar;
ReadOldBuffer: Boolean);
procedure WriteCache(FCache: PChar; Offset: DWORD; Origin: Integer;
Buffer: PChar);
procedure WriteRecordCache(RecordNumber: Integer; Buffer: PChar);
function InternalGetRecord(Buffer: PChar; GetMode: TGetMode;
DoCheck: Boolean): TGetResult;
protected
FDataLink: TIBDataLink;
procedure ActivateConnection;
function ActivateTransaction: Boolean;
procedure DeactivateTransaction;
procedure CheckDatasetClosed;
procedure CheckDatasetOpen;
function GetActiveBuf: PChar;
procedure InternalBatchInput(InputObject: TIBBatchInput);
procedure InternalBatchOutput(OutputObject: TIBBatchOutput);
procedure InternalPrepare;
procedure InternalUnPrepare;
procedure InternalExecQuery;
procedure InternalRefreshRow; virtual;
procedure InternalSetParamsFromCusror;
procedure CheckNotUniDirectional;
{ IProviderSupport }
procedure PSEndTransaction(Commit: Boolean); override;
function PSExecuteStatement(const ASQL: string; AParams: TParams;
ResultSet: Pointer = nil): Integer; override;
function PsGetTableName: string; override;
function PSGetQuoteChar: string; override;
function PSGetUpdateException(E: Exception; Prev: EUpdateError): EUpdateError; override;
function PSInTransaction: Boolean; override;
function PSIsSQLBased: Boolean; override;
function PSIsSQLSupported: Boolean; override;
procedure PSStartTransaction; override;
procedure PSReset; override;
function PSUpdateRecord(UpdateKind: TUpdateKind; Delta: TDataSet): Boolean; override;
{ TDataSet support }
procedure InternalInsert; override;
procedure InitRecord(Buffer: PChar); override;
procedure Disconnect; virtual;
function ConstraintsStored: Boolean;
procedure ClearCalcFields(Buffer: PChar); override;
function AllocRecordBuffer: PChar; override;
procedure DoBeforeDelete; override;
procedure DoBeforeEdit; override;
procedure DoBeforeInsert; override;
procedure FreeRecordBuffer(var Buffer: PChar); override;
procedure GetBookmarkData(Buffer: PChar; Data: Pointer); override;
function GetBookmarkFlag(Buffer: PChar): TBookmarkFlag; override;
function GetCanModify: Boolean; override;
function GetDataSource: TDataSource; override;
function GetFieldClass(FieldType: TFieldType): TFieldClass; override;
function GetRecNo: Integer; override;
function GetRecord(Buffer: PChar; GetMode: TGetMode;
DoCheck: Boolean): TGetResult; override;
function GetRecordCount: Integer; override;
function GetRecordSize: Word; override;
procedure InternalAddRecord(Buffer: Pointer; Append: Boolean); override;
procedure InternalCancel; override;
procedure InternalClose; override;
procedure InternalDelete; override;
procedure InternalFirst; override;
procedure InternalGotoBookmark(Bookmark: Pointer); override;
procedure InternalHandleException; override;
procedure InternalInitFieldDefs; override;
procedure InternalInitRecord(Buffer: PChar); override;
procedure InternalLast; override;
procedure InternalOpen; override;
procedure InternalPost; override;
procedure InternalRefresh; override;
procedure InternalSetToRecord(Buffer: PChar); override;
function IsCursorOpen: Boolean; override;
procedure ReQuery;
procedure SetBookmarkFlag(Buffer: PChar; Value: TBookmarkFlag); override;
procedure SetBookmarkData(Buffer: PChar; Data: Pointer); override;
procedure SetCachedUpdates(Value: Boolean);
procedure SetDataSource(Value: TDataSource);
procedure SetFieldData(Field: TField; Buffer: Pointer); override;
procedure SetRecNo(Value: Integer); override;
protected
property SelectStmtHandle: TISC_STMT_HANDLE read GetSelectStmtHandle;
{Likely to be made public by descendant classes}
property SQLParams: TIBXSQLDA read GetSQLParams;
property InternalPrepared: Boolean read FInternalPrepared;
property QDelete: TIBSQL read FQDelete;
property QInsert: TIBSQL read FQInsert;
property QRefresh: TIBSQL read FQRefresh;
property QSelect: TIBSQL read FQSelect;
property QModify: TIBSQL read FQModify;
property StatementType: TIBSQLTypes read GetStatementType;
{Likely candiatets to be made published by descendant classes}
property BufferChunks: Integer read FBufferChunks write SetBufferChunks;
property CachedUpdates: Boolean read FCachedUpdates write SetCachedUpdates;
property UniDirectional: Boolean read FUniDirectional write SetUniDirectional default False;
property DeleteSQL: TStrings read GetDeleteSQL write SetDeleteSQL;
property InsertSQL: TStrings read GetInsertSQL write SetInsertSQL;
property RefreshSQL: TStrings read GetRefreshSQL write SetRefreshSQL;
property SelectSQL: TStrings read GetSelectSQL write SetSelectSQL;
property ModifySQL: TStrings read GetModifySQL write SetModifySQL;
property UpdateMode: TUpdateMode read FUpdateMode write SetUpdateMode default upWhereAll;
property ParamCheck: Boolean read FParamCheck write FParamCheck default True;
property BeforeDatabaseDisconnect: TNotifyEvent read FBeforeDatabaseDisconnect
write FBeforeDatabaseDisconnect;
property AfterDatabaseDisconnect: TNotifyEvent read FAfterDatabaseDisconnect
write FAfterDatabaseDisconnect;
property DatabaseFree: TNotifyEvent read FDatabaseFree
write FDatabaseFree;
property BeforeTransactionEnd: TNotifyEvent read FBeforeTransactionEnd
write FBeforeTransactionEnd;
property AfterTransactionEnd: TNotifyEvent read FAfterTransactionEnd
write FAfterTransactionEnd;
property TransactionFree: TNotifyEvent read FTransactionFree
write FTransactionFree;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure ApplyUpdates;
function CachedUpdateStatus: TCachedUpdateStatus;
procedure CancelUpdates;
procedure FetchAll;
function LocateNext(const KeyFields: string; const KeyValues: Variant;
Options: TLocateOptions): Boolean;
procedure RecordModified(Value: Boolean);
procedure RevertRecord;
procedure Undelete;
{ TDataSet support methods }
function CompareBookmarks(Bookmark1, Bookmark2: TBookmark): Integer; override;
function CreateBlobStream(Field: TField; Mode: TBlobStreamMode): TStream; override;
function GetCurrentRecord(Buffer: PChar): Boolean; override;
function GetFieldData(Field: TField; Buffer: Pointer): Boolean; overload; override;
function GetFieldData(FieldNo: Integer; Buffer: Pointer): Boolean; overload; override;
function Locate(const KeyFields: string; const KeyValues: Variant;
Options: TLocateOptions): Boolean; override;
function Lookup(const KeyFields: string; const KeyValues: Variant;
const ResultFields: string): Variant; override;
function UpdateStatus: TUpdateStatus; override;
function IsSequenced: Boolean; override;
property DBHandle: PISC_DB_HANDLE read GetDBHandle;
property TRHandle: PISC_TR_HANDLE read GetTRHandle;
property UpdateObject: TIBDataSetUpdateObject read FUpdateObject write SetUpdateObject;
property UpdatesPending: Boolean read FUpdatesPending;
property UpdateRecordTypes: TIBUpdateRecordTypes read FUpdateRecordTypes
write SetUpdateRecordTypes;
published
property Database: TIBDatabase read GetDatabase write SetDatabase;
property Transaction: TIBTransaction read GetTransaction
write SetTransaction;
property Active;
property AutoCalcFields;
property ObjectView default False;
property AfterCancel;
property AfterClose;
property AfterDelete;
property AfterEdit;
property AfterInsert;
property AfterOpen;
property AfterPost;
property AfterRefresh;
property AfterScroll;
property BeforeCancel;
property BeforeClose;
property BeforeDelete;
property BeforeEdit;
property BeforeInsert;
property BeforeOpen;
property BeforePost;
property BeforeRefresh;
property BeforeScroll;
property OnCalcFields;
property OnDeleteError;
property OnEditError;
property OnNewRecord;
property OnPostError;
property OnUpdateError: TIBUpdateErrorEvent read FOnUpdateError
write FOnUpdateError;
property OnUpdateRecord: TIBUpdateRecordEvent read FOnUpdateRecord
write FOnUpdateRecord;
end;
TIBDataSet = class(TIBCustomDataSet)
private
function GetPrepared: Boolean;
protected
procedure SetFiltered(Value: Boolean); override;
procedure InternalOpen; override;
public
procedure Prepare;
procedure UnPrepare;
procedure BatchInput(InputObject: TIBBatchInput);
procedure BatchOutput(OutputObject: TIBBatchOutput);
public
property Params: TIBXSQLDA read GetSQLParams;
property Prepared : Boolean read GetPrepared;
property QDelete;
property QInsert;
property QRefresh;
property QSelect;
property QModify;
property StatementType;
property UpdatesPending;
{ TDataSet support }
property Bof;
property Bookmark;
property DefaultFields;
property Designer;
property Eof;
property FieldCount;
property FieldDefs;
property Fields;
property FieldValues;
property Found;
property Modified;
property RecordCount;
property State;
published
{ TIBCustomDataSet }
property BufferChunks;
property CachedUpdates;
property DeleteSQL;
property InsertSQL;
property RefreshSQL;
property SelectSQL;
property UniDirectional;
property BeforeDatabaseDisconnect;
property AfterDatabaseDisconnect;
property DatabaseFree;
property OnUpdateError;
property OnUpdateRecord;
property BeforeTransactionEnd;
property AfterTransactionEnd;
property TransactionFree;
property UpdateRecordTypes;
property ModifySQL;
{ TIBDataSet }
property Active;
property AutoCalcFields;
property DataSource read GetDataSource write SetDataSource;
property AfterCancel;
property AfterClose;
property AfterDelete;
property AfterEdit;
property AfterInsert;
property AfterOpen;
property AfterPost;
property AfterScroll;
property BeforeCancel;
property BeforeClose;
property BeforeDelete;
property BeforeEdit;
property BeforeInsert;
property BeforeOpen;
property BeforePost;
property BeforeScroll;
property OnCalcFields;
property OnDeleteError;
property OnEditError;
property OnNewRecord;
property OnPostError;
end;
{ TIBDSBlobStream }
TIBDSBlobStream = class(TStream)
protected
FField: TField;
FBlobStream: TIBBlobStream;
public
constructor Create(AField: TField; ABlobStream: TIBBlobStream;
Mode: TBlobStreamMode);
function Read(var Buffer; Count: Longint): Longint; override;
function Seek(Offset: Longint; Origin: Word): Longint; override;
procedure SetSize(NewSize: Longint); override;
function Write(const Buffer; Count: Longint): Longint; override;
end;
const
DefaultFieldClasses: array[TFieldType] of TFieldClass = (
nil, { ftUnknown }
TIBStringField, { ftString }
TSmallintField, { ftSmallint }
TIntegerField, { ftInteger }
TWordField, { ftWord }
TBooleanField, { ftBoolean }
TFloatField, { ftFloat }
TCurrencyField, { ftCurrency }
TIBBCDField, { ftBCD }
TDateField, { ftDate }
TTimeField, { ftTime }
TDateTimeField, { ftDateTime }
TBytesField, { ftBytes }
TVarBytesField, { ftVarBytes }
TAutoIncField, { ftAutoInc }
TBlobField, { ftBlob }
TMemoField, { ftMemo }
TGraphicField, { ftGraphic }
TBlobField, { ftFmtMemo }
TBlobField, { ftParadoxOle }
TBlobField, { ftDBaseOle }
TBlobField, { ftTypedBinary }
nil, { ftCursor }
TStringField, { ftFixedChar }
nil, {TWideStringField } { ftWideString }
TLargeIntField, { ftLargeInt }
TADTField, { ftADT }
TArrayField, { ftArray }
TReferenceField, { ftReference }
TDataSetField, { ftDataSet }
TBlobField, { ftOraBlob }
TMemoField, { ftOraClob }
TVariantField, { ftVariant }
TInterfaceField, { ftInterface }
TIDispatchField, { ftIDispatch }
TGuidField); { ftGuid }
var
CreateProviderProc: function(DataSet: TIBCustomDataSet): IProvider = nil;
implementation
uses IBIntf, IBQuery;
{ TIBStringField}
constructor TIBStringField.Create(AOwner: TComponent);
begin
FBlanksToNULL := True;
inherited;
end;
class procedure TIBStringField.CheckTypeSize(Value: Integer);
begin
{ don't check string size. all sizes valid }
end;
function TIBStringField.GetAsString: string;
begin
if not GetValue(Result) then Result := '';
end;
function TIBStringField.GetAsVariant: Variant;
var
S: string;
begin
if GetValue(S) then Result := S else Result := Null;
end;
function TIBStringField.GetValue(var Value: string): Boolean;
var
Buffer: PChar;
begin
Buffer := nil;
IBAlloc(Buffer, 0, Size + 1);
try
Result := GetData(Buffer);
if Result then
begin
Value := string(Buffer);
if Transliterate and (Value <> '') then
DataSet.Translate(PChar(Value), PChar(Value), False);
end
finally
IBAlloc(Buffer, 0, 0);
end;
end;
procedure TIBStringField.SetAsString(const Value: string);
var
Buffer: PChar;
begin
Buffer := nil;
IBAlloc(Buffer, 0, Size + 1);
try
StrLCopy(Buffer, PChar(Value), Size);
if Transliterate then
DataSet.Translate(Buffer, Buffer, True);
SetData(Buffer);
finally
IBAlloc(Buffer, 0, 0);
end;
end;
{ TIBBCDField }
constructor TIBBCDField.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
SetDataType(ftBCD);
Size := 8;
end;
class procedure TIBBCDField.CheckTypeSize(Value: Integer);
begin
{ No need to check as the base type is currency, not BCD }
end;
function TIBBCDField.GetAsCurrency: Currency;
begin
if not GetValue(Result) then
Result := 0;
end;
function TIBBCDField.GetAsString: string;
var
C: System.Currency;
begin
if GetValue(C) then
Result := CurrToStr(C)
else
Result := '';
end;
function TIBBCDField.GetAsVariant: Variant;
var
C: System.Currency;
begin
if GetValue(C) then
Result := C
else
Result := Null;
end;
function TIBBCDField.GetDataSize: Integer;
begin
Result := 8;
end;
procedure TIBBCDField.GetText(var Text: string; DisplayText: Boolean);
var
Format: TFloatFormat;
FmtStr: string;
Digits: Integer;
C: System.Currency;
begin
if GetData(@C) then
begin
if DisplayText or (EditFormat = '') then
FmtStr := DisplayFormat else
FmtStr := EditFormat;
if FmtStr = '' then
begin
if currency then
begin
if DisplayText then
Format := ffCurrency
else
Format := ffFixed;
Digits := CurrencyDecimals;
end
else begin
Format := ffGeneral;
Digits := 0;
end;
Text := CurrToStrF(C, Format, Digits);
end
else
Text := FormatCurr(FmtStr, C);
end
else
Text := '';
end;
function TIBBCDField.GetValue(var Value: Currency): Boolean;
begin
Result := GetData(@Value);
end;
procedure TIBBCDField.SetAsCurrency(Value: Currency);
begin
if (MinValue <> 0) or (MaxValue <> 0) then
begin
if (Value < MinValue) or (Value > MaxValue) then
RangeError(Value, MinValue, MaxValue);
end;
SetData(@Value);
end;
{ TIBDataLink }
constructor TIBDataLink.Create(ADataSet: TIBCustomDataSet);
begin
inherited Create;
FDataSet := ADataSet;
end;
destructor TIBDataLink.Destroy;
begin
FDataSet.FDataLink := nil;
inherited;
end;
procedure TIBDataLink.ActiveChanged;
begin
if FDataSet.Active then
FDataSet.RefreshParams;
end;
function TIBDataLink.GetDetailDataSet: TDataSet;
begin
Result := FDataSet;
end;
procedure TIBDataLink.RecordChanged(Field: TField);
begin
if (Field = nil) and FDataSet.Active then
FDataSet.RefreshParams;
end;
procedure TIBDataLink.CheckBrowseMode;
begin
if FDataSet.Active then
FDataSet.CheckBrowseMode;
end;
{ TIBCustomDataSet }
constructor TIBCustomDataSet.Create(AOwner: TComponent);
begin
inherited;
FIBLoaded := False;
CheckIBLoaded;
FIBLoaded := True;
FBase := TIBBase.Create(Self);
FCurrentRecord := -1;
FDeletedRecords := 0;
FUniDirectional := False;
FBufferChunks := BufferCacheSize;
FBlobStreamList := TList.Create;
FDataLink := TIBDataLink.Create(Self);
FQDelete := TIBSQL.Create(Self);
FQDelete.OnSQLChanging := SQLChanging;
FQDelete.GoToFirstRecordOnExecute := False;
FQInsert := TIBSQL.Create(Self);
FQInsert.OnSQLChanging := SQLChanging;
FQInsert.GoToFirstRecordOnExecute := False;
FQRefresh := TIBSQL.Create(Self);
FQRefresh.OnSQLChanging := SQLChanging;
FQRefresh.GoToFirstRecordOnExecute := False;
FQSelect := TIBSQL.Create(Self);
FQSelect.OnSQLChanging := SQLChanging;
FQSelect.GoToFirstRecordOnExecute := False;
FQModify := TIBSQL.Create(Self);
FQModify.OnSQLChanging := SQLChanging;
FQModify.GoToFirstRecordOnExecute := False;
FUpdateRecordTypes := [cusUnmodified, cusModified, cusInserted];
FParamCheck := True;
{Bookmark Size is Integer for IBX}
BookmarkSize := SizeOf(Integer);
FBase.BeforeDatabaseDisconnect := DoBeforeDatabaseDisconnect;
FBase.AfterDatabaseDisconnect := DoAfterDatabaseDisconnect;
FBase.OnDatabaseFree := DoDatabaseFree;
FBase.BeforeTransactionEnd := DoBeforeTransactionEnd;
FBase.AfterTransactionEnd := DoAfterTransactionEnd;
FBase.OnTransactionFree := DoTransactionFree;
end;
destructor TIBCustomDataSet.Destroy;
begin
inherited;
if FIBLoaded then
begin
FDataLink.Free;
FBase.Free;
ClearBlobCache;
FBlobStreamList.Free;
IBAlloc(FBufferCache, 0, 0);
IBAlloc(FOldBufferCache, 0, 0);
FCacheSize := 0;
FOldCacheSize := 0;
FMappedFieldPosition := nil;
end;
end;
function TIBCustomDataSet.AdjustCurrentRecord(Buffer: Pointer; GetMode: TGetMode):
TGetResult;
begin
while not IsVisible(Buffer) do
begin
if GetMode = gmPrior then
begin
Dec(FCurrentRecord);
if FCurrentRecord = -1 then
begin
result := grBOF;
exit;
end;
ReadRecordCache(FCurrentRecord, Buffer, False);
end
else begin
Inc(FCurrentRecord);
if (FCurrentRecord = FRecordCount) then
begin
if (not FQSelect.EOF) and (FQSelect.Next <> nil) then
begin
FetchCurrentRecordToBuffer(FQSelect, FCurrentRecord, Buffer);
Inc(FRecordCount);
end
else begin
result := grEOF;
exit;
end;
end
else
ReadRecordCache(FCurrentRecord, Buffer, False);
end;
end;
result := grOK;
end;
procedure TIBCustomDataSet.ApplyUpdates;
var
CurBookmark: string;
Buffer: PRecordData;
CurUpdateTypes: TIBUpdateRecordTypes;
UpdateAction: TIBUpdateAction;
UpdateKind: TUpdateKind;
bRecordsSkipped: Boolean;
procedure GetUpdateKind;
begin
case Buffer^.rdCachedUpdateStatus of
cusModified:
UpdateKind := ukModify;
cusInserted:
UpdateKind := ukInsert;
else
UpdateKind := ukDelete;
end;
end;
procedure ResetBufferUpdateStatus;
begin
case Buffer^.rdCachedUpdateStatus of
cusModified:
begin
PRecordData(Buffer)^.rdUpdateStatus := usUnmodified;
PRecordData(Buffer)^.rdCachedUpdateStatus := cusUnmodified;
end;
cusInserted:
begin
PRecordData(Buffer)^.rdUpdateStatus := usUnmodified;
PRecordData(Buffer)^.rdCachedUpdateStatus := cusUnmodified;
end;
cusDeleted:
begin
PRecordData(Buffer)^.rdUpdateStatus := usDeleted;
PRecordData(Buffer)^.rdCachedUpdateStatus := cusUnmodified;
end;
end;
WriteRecordCache(PRecordData(Buffer)^.rdRecordNumber, Pointer(Buffer));
end;
procedure UpdateUsingOnUpdateRecord;
begin
UpdateAction := uaFail;
try
FOnUpdateRecord(Self, UpdateKind, UpdateAction);
except
on E: Exception do
begin
if (E is EDatabaseError) and Assigned(FOnUpdateError) then
FOnUpdateError(Self, EIBError(E), UpdateKind, UpdateAction);
if UpdateAction = uaFail then
raise;
end;
end;
end;
procedure UpdateUsingUpdateObject;
begin
try
FUpdateObject.Apply(UpdateKind);
ResetBufferUpdateStatus;
except
on E: Exception do
if (E is EDatabaseError) and Assigned(FOnUpdateError) then
FOnUpdateError(Self, EIBError(E), UpdateKind, UpdateAction);
end;
end;
procedure UpdateUsingInternalquery;
begin
try
case Buffer^.rdCachedUpdateStatus of
cusModified:
InternalPostRecord(FQModify, Buffer);
cusInserted:
InternalPostRecord(FQInsert, Buffer);
cusDeleted:
InternalDeleteRecord(FQDelete, Buffer);
end;
except
on E: EIBError do begin
UpdateAction := uaFail;
if Assigned(FOnUpdateError) then
FOnUpdateError(Self, E, UpdateKind, UpdateAction);
case UpdateAction of
uaFail: raise;
uaAbort: SysUtils.Abort;
uaSkip: bRecordsSkipped := True;
end;
end;
end;
end;
begin
if State in [dsEdit, dsInsert] then
Post;
FBase.CheckDatabase;
FBase.CheckTransaction;
DisableControls;
CurBookmark := Bookmark;
CurUpdateTypes := FUpdateRecordTypes;
FUpdateRecordTypes := [cusModified, cusInserted, cusDeleted];
try
First;
bRecordsSkipped := False;
while not EOF do
begin
Buffer := PRecordData(GetActiveBuf);
GetUpdateKind;
UpdateAction := uaApply;
if Assigned(FUpdateObject) or Assigned(FOnUpdateRecord) then
begin
if (Assigned(FOnUpdateRecord)) then
UpdateUsingOnUpdateRecord
else if Assigned(FUpdateObject) then
UpdateUsingUpdateObject;
case UpdateAction of
uaFail:
IBError(ibxeUserAbort, [nil]);
uaAbort:
SysUtils.Abort;
uaApplied:
ResetBufferUpdateStatus;
uaSkip:
bRecordsSkipped := True;
uaRetry:
Continue;
end;
end;
if (not Assigned(FUpdateObject)) and (UpdateAction = UaApply) then
begin
UpdateUsingInternalquery;
UpdateAction := uaApplied;
end;
Next;
end;
FUpdatesPending := bRecordsSkipped;
finally
FUpdateRecordTypes := CurUpdateTypes;
Bookmark := CurBookmark;
EnableControls;
end;
end;
procedure TIBCustomDataSet.InternalBatchInput(InputObject: TIBBatchInput);
begin
FQSelect.BatchInput(InputObject);
end;
procedure TIBCustomDataSet.InternalBatchOutput(OutputObject: TIBBatchOutput);
var
Qry: TIBSQL;
begin
Qry := TIBSQL.Create(Self);
try
Qry.Database := FBase.Database;
Qry.Transaction := FBase.Transaction;
Qry.SQL.Assign(FQSelect.SQL);
Qry.BatchOutput(OutputObject);
finally
Qry.Free;
end;
end;
procedure TIBCustomDataSet.CancelUpdates;
var
CurUpdateTypes: TIBUpdateRecordTypes;
begin
if State in [dsEdit, dsInsert] then
Post;
if FCachedUpdates and FUpdatesPending then
begin
DisableControls;
CurUpdateTypes := UpdateRecordTypes;
UpdateRecordTypes := [cusModified, cusInserted, cusDeleted];
try
First;
while not EOF do
begin
RevertRecord;
Next;
end;
finally
UpdateRecordTypes := CurUpdateTypes;
First;
FUpdatesPending := False;
EnableControls;
end;
end;
end;
procedure TIBCustomDataSet.ActivateConnection;
begin
if not Assigned(Database) then
IBError(ibxeDatabaseNotAssigned, [nil]);
if not Assigned(Transaction) then
IBError(ibxeTransactionNotAssigned, [nil]);
if not Database.Connected then Database.Open;
end;
function TIBCustomDataSet.ActivateTransaction: Boolean;
begin
Result := False;
if not Assigned(Transaction) then
IBError(ibxeTransactionNotAssigned, [nil]);
if not Transaction.Active then
begin
Result := True;
Transaction.StartTransaction;
FDidActivate := True;
end;
end;
procedure TIBCustomDataSet.DeactivateTransaction;
var
i: Integer;
begin
if not Assigned(Transaction) then
IBError(ibxeTransactionNotAssigned, [nil]);
with Transaction do
begin
for i := 0 to SQLObjectCount - 1 do
begin
if (SQLObjects[i] <> nil) and ((SQLObjects[i]).owner is TDataSet) then
begin
if TDataSet(SQLObjects[i].owner).Active then
begin
FDidActivate := False;
exit;
end;
end;
end;
end;
FInternalPrepared := False;
if Transaction.InTransaction then
Transaction.Commit;
FDidActivate := False;
end;
procedure TIBCustomDataSet.CheckDatasetClosed;
begin
if FOpen then
IBError(ibxeDatasetOpen, [nil]);
end;
procedure TIBCustomDataSet.CheckDatasetOpen;
begin
if not FOpen then
IBError(ibxeDatasetClosed, [nil]);
end;
procedure TIBCustomDataSet.CheckNotUniDirectional;
begin
if UniDirectional then
IBError(ibxeDataSetUniDirectional, [nil]);
end;
procedure TIBCustomDataSet.AdjustRecordOnInsert(Buffer: Pointer);
begin
with PRecordData(Buffer)^ do
if (State = dsInsert) and (not Modified) then
begin
rdRecordNumber := FRecordCount;
FCurrentRecord := FRecordCount;
end;
end;
function TIBCustomDataSet.CanEdit: Boolean;
var
Buff: PRecordData;
begin
Buff := PRecordData(GetActiveBuf);
result := (FQModify.SQL.Text <> '') or
(Assigned(FUpdateObject) and (FUpdateObject.GetSQL(ukModify).Text <> '')) or
((Buff <> nil) and (Buff^.rdCachedUpdateStatus = cusInserted) and
(FCachedUpdates));
end;
function TIBCustomDataSet.CanInsert: Boolean;
begin
result := (FQInsert.SQL.Text <> '') or
(Assigned(FUpdateObject) and (FUpdateObject.GetSQL(ukInsert).Text <> ''));
end;
function TIBCustomDataSet.CanDelete: Boolean;
begin
if (FQDelete.SQL.Text <> '') or
(Assigned(FUpdateObject) and (FUpdateObject.GetSQL(ukDelete).Text <> '')) then
result := True
else
result := False;
end;
function TIBCustomDataSet.CanRefresh: Boolean;
begin
result := (FQRefresh.SQL.Text <> '') or
(Assigned(FUpdateObject) and (FUpdateObject.RefreshSQL.Text <> ''));
end;
procedure TIBCustomDataSet.CheckEditState;
begin
case State of
dsEdit: if not CanEdit then IBError(ibxeCannotUpdate, [nil]);
dsInsert: if not CanInsert then IBError(ibxeCannotInsert, [nil]);
end;
end;
procedure TIBCustomDataSet.ClearBlobCache;
var
i: Integer;
begin
for i := 0 to FBlobStreamList.Count - 1 do
begin
TIBBlobStream(FBlobStreamList[i]).Free;
FBlobStreamList[i] := nil;
end;
FBlobStreamList.Pack;
end;
procedure TIBCustomDataSet.CopyRecordBuffer(Source, Dest: Pointer);
begin
Move(Source^, Dest^, FRecordBufferSize);
end;
procedure TIBCustomDataSet.DoBeforeDatabaseDisconnect(Sender: TObject);
begin
if Active then
Active := False;
FInternalPrepared := False;
if Assigned(FBeforeDatabaseDisconnect) then
FBeforeDatabaseDisconnect(Sender);
end;
procedure TIBCustomDataSet.DoAfterDatabaseDisconnect(Sender: TObject);
begin
if Assigned(FAfterDatabaseDisconnect) then
FAfterDatabaseDisconnect(Sender);
end;
procedure TIBCustomDataSet.DoDatabaseFree(Sender: TObject);
begin
if Assigned(FDatabaseFree) then
FDatabaseFree(Sender);
end;
procedure TIBCustomDataSet.DoBeforeTransactionEnd(Sender: TObject);
begin
if Active then
Active := False;
if FQSelect <> nil then
FQSelect.FreeHandle;
if FQDelete <> nil then
FQDelete.FreeHandle;
if FQInsert <> nil then
FQInsert.FreeHandle;
if FQModify <> nil then
FQModify.FreeHandle;
if FQRefresh <> nil then
FQRefresh.FreeHandle;
if Assigned(FBeforeTransactionEnd) then
FBeforeTransactionEnd(Sender);
end;
procedure TIBCustomDataSet.DoAfterTransactionEnd(Sender: TObject);
begin
if Assigned(FAfterTransactionEnd) then
FAfterTransactionEnd(Sender);
end;
procedure TIBCustomDataSet.DoTransactionFree(Sender: TObject);
begin
if Assigned(FTransactionFree) then
FTransactionFree(Sender);
end;
{ Read the record from FQSelect.Current into the record buffer
Then write the buffer to in memory cache }
procedure TIBCustomDataSet.FetchCurrentRecordToBuffer(Qry: TIBSQL;
RecordNumber: Integer; Buffer: PChar);
var
p: PRecordData;
pbd: PBlobDataArray;
i, j: Integer;
LocalData: Pointer;
LocalDate, LocalDouble: Double;
LocalInt: Integer;
LocalInt64: Int64;
LocalCurrency: Currency;
FieldsLoaded: Integer;
begin
p := PRecordData(Buffer);
{ Make sure blob cache is empty }
pbd := PBlobDataArray(Buffer + FBlobCacheOffset);
if RecordNumber > -1 then
for i := 0 to BlobFieldCount - 1 do
pbd^[i] := nil;
{ Get record information }
p^.rdBookmarkFlag := bfCurrent;
p^.rdFieldCount := Qry.Current.Count;
p^.rdRecordNumber := RecordNumber;
p^.rdUpdateStatus := usUnmodified;
p^.rdCachedUpdateStatus := cusUnmodified;
p^.rdSavedOffset := $FFFFFFFF;
{ Load up the fields }
FieldsLoaded := FQSelect.Current.Count;
j := 1;
for i := 0 to Qry.Current.Count - 1 do
begin
if (Qry = FQSelect) then
j := i + 1
else begin
if FieldsLoaded = 0 then
break;
j := FQSelect.FieldIndex[Qry.Current[i].Name] + 1;
if j < 1 then
continue
else
Dec(FieldsLoaded);
end;
with FQSelect.Current[j - 1].Data^ do
if aliasname = 'IBX_INTERNAL_DBKEY' then {do not localize}
begin
if sqllen <= 8 then
p^.rdDBKey := PIBDBKEY(Qry.Current[i].AsPointer)^;
continue;
end;
if j > 0 then with p^ do
begin
rdFields[j].fdDataType :=
Qry.Current[i].Data^.sqltype and (not 1);
rdFields[j].fdDataScale :=
Qry.Current[i].Data^.sqlscale;
rdFields[j].fdNullable :=
(Qry.Current[i].Data^.sqltype and 1 = 1);
rdFields[j].fdIsNull :=
(rdFields[j].fdNullable and (Qry.Current[i].Data^.sqlind^ = -1));
LocalData := Qry.Current[i].Data^.sqldata;
case rdFields[j].fdDataType of
SQL_TIMESTAMP:
begin
rdFields[j].fdDataSize := SizeOf(TDateTime);
if RecordNumber >= 0 then
LocalDate := TimeStampToMSecs(DateTimeToTimeStamp(Qry.Current[i].AsDateTime));
LocalData := PChar(@LocalDate);
end;
SQL_TYPE_DATE:
begin
rdFields[j].fdDataSize := SizeOf(TDateTime);
if RecordNumber >= 0 then
LocalInt := DateTimeToTimeStamp(Qry.Current[i].AsDateTime).Date;
LocalData := PChar(@LocalInt);
end;
SQL_TYPE_TIME:
begin
rdFields[j].fdDataSize := SizeOf(TDateTime);
if RecordNumber >= 0 then
LocalInt := DateTimeToTimeStamp(Qry.Current[i].AsDateTime).Time;
LocalData := PChar(@LocalInt);
end;
SQL_SHORT, SQL_LONG:
begin
if (rdFields[j].fdDataScale = 0) then
begin
rdFields[j].fdDataSize := SizeOf(Integer);
if RecordNumber >= 0 then
LocalInt := Qry.Current[i].AsLong;
LocalData := PChar(@LocalInt);
end
else if (rdFields[j].fdDataScale >= (-4)) then
begin
rdFields[j].fdDataSize := SizeOf(Currency);
if RecordNumber >= 0 then
LocalCurrency := Qry.Current[i].AsCurrency;
LocalData := PChar(@LocalCurrency);
end
else begin
rdFields[j].fdDataSize := SizeOf(Double);
if RecordNumber >= 0 then
LocalDouble := Qry.Current[i].AsDouble;
LocalData := PChar(@LocalDouble);
end;
end;
SQL_INT64:
begin
if (rdFields[j].fdDataScale = 0) then
begin
rdFields[j].fdDataSize := SizeOf(Int64);
if RecordNumber >= 0 then
LocalInt64 := Qry.Current[i].AsInt64;
LocalData := PChar(@LocalInt64);
end
else if (rdFields[j].fdDataScale >= (-4)) then
begin
rdFields[j].fdDataSize := SizeOf(Currency);
if RecordNumber >= 0 then
LocalCurrency := Qry.Current[i].AsCurrency;
LocalData := PChar(@LocalCurrency);
end
else begin
rdFields[j].fdDataSize := SizeOf(Double);
if RecordNumber >= 0 then
LocalDouble := Qry.Current[i].AsDouble;
LocalData := PChar(@LocalDouble);
end
end;
SQL_DOUBLE, SQL_FLOAT, SQL_D_FLOAT:
begin
rdFields[j].fdDataSize := SizeOf(Double);
if RecordNumber >= 0 then
LocalDouble := Qry.Current[i].AsDouble;
LocalData := PChar(@LocalDouble);
end;
SQL_VARYING:
begin
rdFields[j].fdDataSize := Qry.Current[i].Data^.sqllen;
rdFields[j].fdDataLength := isc_vax_integer(Qry.Current[i].Data^.sqldata, 2);
if RecordNumber >= 0 then
begin
if (rdFields[j].fdDataLength = 0) then
LocalData := nil
else
LocalData := @Qry.Current[i].Data^.sqldata[2];
end;
end;
else { SQL_TEXT, SQL_BLOB, SQL_ARRAY, SQL_QUAD }
begin
rdFields[j].fdDataSize := Qry.Current[i].Data^.sqllen;
if (rdFields[j].fdDataType = SQL_TEXT) then
rdFields[j].fdDataLength := rdFields[j].fdDataSize;
end;
end;
if RecordNumber < 0 then
begin
rdFields[j].fdIsNull := True;
rdFields[j].fdDataOfs := FRecordSize;
Inc(FRecordSize, rdFields[j].fdDataSize);
end
else begin
if rdFields[j].fdDataType = SQL_VARYING then
begin
if LocalData <> nil then
Move(LocalData^, Buffer[rdFields[j].fdDataOfs], rdFields[j].fdDataLength)
end
else
Move(LocalData^, Buffer[rdFields[j].fdDataOfs], rdFields[j].fdDataSize)
end;
end;
end;
WriteRecordCache(RecordNumber, PChar(p));
end;
function TIBCustomDataSet.GetActiveBuf: PChar;
begin
case State of
dsBrowse:
if IsEmpty then
result := nil
else
result := ActiveBuffer;
dsEdit, dsInsert:
result := ActiveBuffer;
dsCalcFields:
result := CalcBuffer;
dsFilter:
result := FFilterBuffer;
dsNewValue:
result := ActiveBuffer;
dsOldValue:
if (PRecordData(ActiveBuffer)^.rdRecordNumber =
PRecordData(FOldBuffer)^.rdRecordNumber) then
result := FOldBuffer
else
result := ActiveBuffer;
else if not FOpen then
result := nil
else
result := ActiveBuffer;
end;
end;
function TIBCustomDataSet.CachedUpdateStatus: TCachedUpdateStatus;
begin
if Active then
result := PRecordData(GetActiveBuf)^.rdCachedUpdateStatus
else
result := cusUnmodified;
end;
function TIBCustomDataSet.GetDatabase: TIBDatabase;
begin
result := FBase.Database;
end;
function TIBCustomDataSet.GetDBHandle: PISC_DB_HANDLE;
begin
result := FBase.DBHandle;
end;
function TIBCustomDataSet.GetDeleteSQL: TStrings;
begin
result := FQDelete.SQL;
end;
function TIBCustomDataSet.GetInsertSQL: TStrings;
begin
result := FQInsert.SQL;
end;
function TIBCustomDataSet.GetSQLParams: TIBXSQLDA;
begin
result := FQSelect.Params;
end;
function TIBCustomDataSet.GetRefreshSQL: TStrings;
begin
result := FQRefresh.SQL;
end;
function TIBCustomDataSet.GetSelectSQL: TStrings;
begin
result := FQSelect.SQL;
end;
function TIBCustomDataSet.GetStatementType: TIBSQLTypes;
begin
result := FQSelect.SQLType;
end;
function TIBCustomDataSet.GetModifySQL: TStrings;
begin
result := FQModify.SQL;
end;
function TIBCustomDataSet.GetTransaction: TIBTransaction;
begin
result := FBase.Transaction;
end;
function TIBCustomDataSet.GetTRHandle: PISC_TR_HANDLE;
begin
result := FBase.TRHandle;
end;
procedure TIBCustomDataSet.InternalDeleteRecord(Qry: TIBSQL; Buff: Pointer);
begin
if (Assigned(FUpdateObject) and (FUpdateObject.GetSQL(ukDelete).Text <> '')) then
FUpdateObject.Apply(ukDelete)
else begin
SetInternalSQLParams(FQDelete, Buff);
FQDelete.ExecQuery;
end;
with PRecordData(Buff)^ do
begin
rdUpdateStatus := usDeleted;
rdCachedUpdateStatus := cusUnmodified;
end;
WriteRecordCache(PRecordData(Buff)^.rdRecordNumber, Buff);
end;
function TIBCustomDataSet.InternalLocate(const KeyFields: string;
const KeyValues: Variant; Options: TLocateOptions): Boolean;
var
fl: TList;
CurBookmark: string;
fld, val: Variant;
i, fld_cnt: Integer;
begin
fl := TList.Create;
try
GetFieldList(fl, KeyFields);
fld_cnt := fl.Count;
CurBookmark := Bookmark;
result := False;
while ((not result) and (not EOF)) do
begin
i := 0;
result := True;
while (result and (i < fld_cnt)) do
begin
if fld_cnt > 1 then
val := KeyValues[i]
else
val := KeyValues;
fld := TField(fl[i]).Value;
result := not (VarIsNull(val) or VarIsNull(fld));
if result then
try
fld := VarAsType(fld, VarType(val));
except
on E: EVariantError do result := False;
end;
if result then
begin
if TField(fl[i]).DataType = ftString then
begin
if (loCaseInsensitive in Options) then
begin
fld := AnsiUpperCase(fld);
val := AnsiUpperCase(val);
end;
fld := TrimRight(fld);
val := TrimRight(val);
if (loPartialKey in Options) then
result := result and (AnsiPos(val, fld) = 1)
else
result := result and (val = fld);
end else
result := result and (val = fld);
end;
Inc(i);
end;
if not result then
Next;
end;
if not result then
Bookmark := CurBookmark
else
CursorPosChanged;
finally
fl.Free;
end;
end;
procedure TIBCustomDataSet.InternalPostRecord(Qry: TIBSQL; Buff: Pointer);
var
i, j, k: Integer;
pbd: PBlobDataArray;
begin
pbd := PBlobDataArray(PChar(Buff) + FBlobCacheOffset);
j := 0;
for i := 0 to FieldCount - 1 do
if Fields[i].IsBlob then
begin
k := FMappedFieldPosition[Fields[i].FieldNo -1];
if pbd^[j] <> nil then
begin
pbd^[j].Finalize;
PISC_QUAD(
PChar(Buff) + PRecordData(Buff)^.rdFields[k].fdDataOfs)^ :=
pbd^[j].BlobID;
PRecordData(Buff)^.rdFields[k].fdIsNull := pbd^[j].Size = 0;
end;
Inc(j);
end;
if Assigned(FUpdateObject) then
begin
if (Qry = FQDelete) then
FUpdateObject.Apply(ukDelete)
else if (Qry = FQInsert) then
FUpdateObject.Apply(ukInsert)
else
FUpdateObject.Apply(ukModify);
end
else begin
SetInternalSQLParams(Qry, Buff);
Qry.ExecQuery;
end;
PRecordData(Buff)^.rdUpdateStatus := usUnmodified;
PRecordData(Buff)^.rdCachedUpdateStatus := cusUnmodified;
SetModified(False);
WriteRecordCache(PRecordData(Buff)^.rdRecordNumber, Buff);
if CanRefresh then
InternalRefreshRow;
end;
procedure TIBCustomDataSet.InternalRefreshRow;
var
Buff: PChar;
iCurScreenState: Integer;
ofs: DWORD;
Qry: TIBSQL;
begin
iCurScreenState := Screen.Cursor;
Screen.Cursor := crHourglass;
try
Buff := GetActiveBuf;
if CanRefresh and (Buff <> nil) then
begin
if (Assigned(FUpdateObject) and (FUpdateObject.RefreshSQL.Text <> '')) then
begin
Qry := TIBSQL.Create(self);
Qry.Database := Database;
Qry.Transaction := Transaction;
Qry.GoToFirstRecordOnExecute := False;
Qry.SQL.Text := FUpdateObject.RefreshSQL.Text;
end
else
Qry := FQRefresh;
SetInternalSQLParams(Qry, Buff);
Qry.ExecQuery;
try
if (Qry.SQLType = SQLExecProcedure) or
(Qry.Next <> nil) then
begin
ofs := PRecordData(Buff)^.rdSavedOffset;
FetchCurrentRecordToBuffer(Qry,
PRecordData(Buff)^.rdRecordNumber,
Buff);
if (ofs <> $FFFFFFFF) then
begin
PRecordData(Buff)^.rdSavedOffset := ofs;
WriteRecordCache(PRecordData(Buff)^.rdRecordNumber, Buff);
SaveOldBuffer(Buff);
end;
end;
finally
Qry.Close;
end;
if Qry <> FQRefresh then
Qry.Free;
end
else
IBError(ibxeCannotRefresh, [nil]);
finally
Screen.Cursor := iCurScreenState;
end;
end;
procedure TIBCustomDataSet.InternalRevertRecord(RecordNumber: Integer);
var
NewBuffer, OldBuffer: PRecordData;
begin
NewBuffer := nil;
OldBuffer := nil;
NewBuffer := PRecordData(AllocRecordBuffer);
OldBuffer := PRecordData(AllocRecordBuffer);
try
ReadRecordCache(RecordNumber, PChar(NewBuffer), False);
ReadRecordCache(RecordNumber, PChar(OldBuffer), True);
case NewBuffer^.rdCachedUpdateStatus of
cusInserted:
begin
NewBuffer^.rdCachedUpdateStatus := cusUninserted;
Inc(FDeletedRecords);
end;
cusModified,
cusDeleted:
begin
if (NewBuffer^.rdCachedUpdateStatus = cusDeleted) then
Dec(FDeletedRecords);
CopyRecordBuffer(OldBuffer, NewBuffer);
end;
end;
if State in dsEditModes then
Cancel;
WriteRecordCache(RecordNumber, PChar(NewBuffer));
if (NewBuffer^.rdCachedUpdateStatus = cusUninserted ) then
ReSync([]);
finally
FreeRecordBuffer(PChar(NewBuffer));
FreeRecordBuffer(PChar(OldBuffer));
end;
end;
{ A visible record is one that is not truly deleted,
and it is also listed in the FUpdateRecordTypes set }
function TIBCustomDataSet.IsVisible(Buffer: PChar): Boolean;
begin
result := True;
if not (State = dsOldValue) then
result :=
(PRecordData(Buffer)^.rdCachedUpdateStatus in FUpdateRecordTypes) and
(not ((PRecordData(Buffer)^.rdCachedUpdateStatus = cusUnmodified) and
(PRecordData(Buffer)^.rdUpdateStatus = usDeleted)));
end;
function TIBCustomDataSet.LocateNext(const KeyFields: string;
const KeyValues: Variant; Options: TLocateOptions): Boolean;
begin
DisableControls;
try
result := InternalLocate(KeyFields, KeyValues, Options);
finally
EnableControls;
end;
end;
procedure TIBCustomDataSet.InternalPrepare;
var
iCurScreenState: Integer;
DidActivate: Boolean;
begin
DidActivate := False;
iCurScreenState := Screen.Cursor;
Screen.Cursor := crHourglass;
try
ActivateConnection;
DidActivate := ActivateTransaction;
FBase.CheckDatabase;
FBase.CheckTransaction;
if FQSelect.SQL.Text <> '' then
begin
if not FQSelect.Prepared then
begin
FQSelect.ParamCheck := ParamCheck;
FQSelect.Prepare;
end;
if (FQDelete.SQL.Text <> '') and (not FQDelete.Prepared) then
FQDelete.Prepare;
if (FQInsert.SQL.Text <> '') and (not FQInsert.Prepared) then
FQInsert.Prepare;
if (FQRefresh.SQL.Text <> '') and (not FQRefresh.Prepared) then
FQRefresh.Prepare;
if (FQModify.SQL.Text <> '') and (not FQModify.Prepared) then
FQModify.Prepare;
FInternalPrepared := True;
InternalInitFieldDefs;
end else
IBError(ibxeEmptyQuery, [nil]);
finally
if DidActivate then
DeactivateTransaction;
Screen.Cursor := iCurScreenState;
end;
end;
procedure TIBCustomDataSet.RecordModified(Value: Boolean);
begin
SetModified(Value);
end;
procedure TIBCustomDataSet.RevertRecord;
var
Buff: PRecordData;
begin
if FCachedUpdates and FUpdatesPending then
begin
Buff := PRecordData(GetActiveBuf);
InternalRevertRecord(Buff^.rdRecordNumber);
ReadRecordCache(Buff^.rdRecordNumber, PChar(Buff), False);
DataEvent(deRecordChange, 0);
end;
end;
procedure TIBCustomDataSet.SaveOldBuffer(Buffer: PChar);
var
OldBuffer: Pointer;
procedure CopyOldBuffer;
begin
CopyRecordBuffer(Buffer, OldBuffer);
if BlobFieldCount > 0 then
FillChar(PChar(OldBuffer)[FBlobCacheOffset], BlobFieldCount * SizeOf(TIBBlobStream),
0);
end;
begin
if (Buffer <> nil) and (PRecordData(Buffer)^.rdRecordNumber >= 0) then
begin
OldBuffer := AllocRecordBuffer;
try
if (PRecordData(Buffer)^.rdSavedOffset = $FFFFFFFF) then
begin
PRecordData(Buffer)^.rdSavedOffset := AdjustPosition(FOldBufferCache, 0,
FILE_END);
CopyOldBuffer;
WriteCache(FOldBufferCache, 0, FILE_CURRENT, OldBuffer);
WriteCache(FBufferCache, PRecordData(Buffer)^.rdRecordNumber * FRecordBufferSize,
FILE_BEGIN, Buffer);
end
else begin
CopyOldBuffer;
WriteCache(FOldBufferCache, PRecordData(Buffer)^.rdSavedOffset, FILE_BEGIN,
OldBuffer);
end;
finally
FreeRecordBuffer(PChar(OldBuffer));
end;
end;
end;
procedure TIBCustomDataSet.SetBufferChunks(Value: Integer);
begin
if (Value <= 0) then
FBufferChunks := BufferCacheSize
else
FBufferChunks := Value;
end;
procedure TIBCustomDataSet.SetDatabase(Value: TIBDatabase);
begin
if FBase.Database <> Value then
begin
CheckDatasetClosed;
FBase.Database := Value;
FQDelete.Database := Value;
FQInsert.Database := Value;
FQRefresh.Database := Value;
FQSelect.Database := Value;
FQModify.Database := Value;
end;
end;
procedure TIBCustomDataSet.SetDeleteSQL(Value: TStrings);
begin
CheckDatasetClosed;
FQDelete.SQL.Assign(Value);
end;
procedure TIBCustomDataSet.SetInsertSQL(Value: TStrings);
begin
CheckDatasetClosed;
FQInsert.SQL.Assign(Value);
end;
procedure TIBCustomDataSet.SetInternalSQLParams(Qry: TIBSQL; Buffer: Pointer);
var
i, j: Integer;
cr, data: PChar;
fn, st: string;
OldBuffer: Pointer;
ts: TTimeStamp;
begin
if (Buffer = nil) then
IBError(ibxeBufferNotSet, [nil]);
if (not FInternalPrepared) then
InternalPrepare;
OldBuffer := nil;
try
for i := 0 to Qry.Params.Count - 1 do
begin
fn := Qry.Params[i].Name;
if (Pos('OLD_', fn) = 1) then {mbcs ok}
begin
fn := Copy(fn, 5, Length(fn));
OldBuffer := AllocRecordBuffer;
ReadRecordCache(PRecordData(Buffer)^.rdRecordNumber, OldBuffer, True);
cr := OldBuffer;
end
else if (Pos('NEW_', fn) = 1) then {mbcs ok}
begin
fn := Copy(fn, 5, Length(fn));
cr := Buffer;
end
else
cr := Buffer;
j := FQSelect.FieldIndex[fn] + 1;
if (j > 0) then
with PRecordData(cr)^ do
begin
if Qry.Params[i].name = 'IBX_INTERNAL_DBKEY' then {do not localize}
begin
PIBDBKey(Qry.Params[i].AsPointer)^ := rdDBKey;
continue;
end;
if rdFields[j].fdIsNull then
Qry.Params[i].IsNull := True
else begin
Qry.Params[i].IsNull := False;
data := cr + rdFields[j].fdDataOfs;
case rdFields[j].fdDataType of
SQL_TEXT, SQL_VARYING:
begin
SetString(st, data, rdFields[j].fdDataLength);
Qry.Params[i].AsString := st;
end;
SQL_FLOAT, SQL_DOUBLE, SQL_D_FLOAT:
Qry.Params[i].AsDouble := PDouble(data)^;
SQL_SHORT, SQL_LONG:
begin
if rdFields[j].fdDataScale = 0 then
Qry.Params[i].AsLong := PLong(data)^
else if rdFields[j].fdDataScale >= (-4) then
Qry.Params[i].AsCurrency := PCurrency(data)^
else
Qry.Params[i].AsDouble := PDouble(data)^;
end;
SQL_INT64:
begin
if rdFields[j].fdDataScale = 0 then
Qry.Params[i].AsInt64 := PInt64(data)^
else if rdFields[j].fdDataScale >= (-4) then
Qry.Params[i].AsCurrency := PCurrency(data)^
else
Qry.Params[i].AsDouble := PDouble(data)^;
end;
SQL_BLOB, SQL_ARRAY, SQL_QUAD:
Qry.Params[i].AsQuad := PISC_QUAD(data)^;
SQL_TYPE_DATE:
begin
ts.Date := PInt(data)^;
ts.Time := 0;
Qry.Params[i].AsDate :=
TimeStampToDateTime(ts);
end;
SQL_TYPE_TIME:
begin
ts.Date := 0;
ts.Time := PInt(data)^;
Qry.Params[i].AsTime :=
TimeStampToDateTime(ts);
end;
SQL_TIMESTAMP:
Qry.Params[i].AsDateTime :=
TimeStampToDateTime(
MSecsToTimeStamp(PDouble(data)^));
end;
end;
end;
end;
finally
if (OldBuffer <> nil) then
FreeRecordBuffer(PChar(OldBuffer));
end;
end;
procedure TIBCustomDataSet.SetRefreshSQL(Value: TStrings);
begin
CheckDatasetClosed;
FQRefresh.SQL.Assign(Value);
end;
procedure TIBCustomDataSet.SetSelectSQL(Value: TStrings);
begin
CheckDatasetClosed;
FQSelect.SQL.Assign(Value);
end;
procedure TIBCustomDataSet.SetModifySQL(Value: TStrings);
begin
CheckDatasetClosed;
FQModify.SQL.Assign(Value);
end;
procedure TIBCustomDataSet.SetTransaction(Value: TIBTransaction);
begin
if (FBase.Transaction <> Value) then
begin
CheckDatasetClosed;
FBase.Transaction := Value;
FQDelete.Transaction := Value;
FQInsert.Transaction := Value;
FQRefresh.Transaction := Value;
FQSelect.Transaction := Value;
FQModify.Transaction := Value;
end;
end;
procedure TIBCustomDataSet.SetUniDirectional(Value: Boolean);
begin
CheckDatasetClosed;
FUniDirectional := Value;
end;
procedure TIBCustomDataSet.SetUpdateRecordTypes(Value: TIBUpdateRecordTypes);
begin
FUpdateRecordTypes := Value;
if Active then
First;
end;
procedure TIBCustomDataSet.RefreshParams;
var
DataSet: TDataSet;
begin
DisableControls;
try
if FDataLink.DataSource <> nil then
begin
DataSet := FDataLink.DataSource.DataSet;
if DataSet <> nil then
if DataSet.Active and (DataSet.State <> dsSetKey) then
begin
Close;
Open;
end;
end;
finally
EnableControls;
end;
end;
procedure TIBCustomDataSet.SQLChanging(Sender: TObject);
begin
InternalUnPrepare;
end;
{ I can "undelete" uninserted records (make them "inserted" again).
I can "undelete" cached deleted (the deletion hasn't yet occurred) }
procedure TIBCustomDataSet.Undelete;
var
Buff: PRecordData;
begin
CheckActive;
Buff := PRecordData(GetActiveBuf);
with Buff^ do
begin
if rdCachedUpdateStatus = cusUninserted then
begin
rdCachedUpdateStatus := cusInserted;
Dec(FDeletedRecords);
end
else if (rdUpdateStatus = usDeleted) and
(rdCachedUpdateStatus = cusDeleted) then
begin
rdCachedUpdateStatus := cusUnmodified;
rdUpdateStatus := usUnmodified;
Dec(FDeletedRecords);
end;
WriteRecordCache(rdRecordNumber, PChar(Buff));
end;
end;
function TIBCustomDataSet.UpdateStatus: TUpdateStatus;
begin
if Active then
result := PRecordData(GetActiveBuf)^.rdUpdateStatus
else
result := usUnmodified;
end;
function TIBCustomDataSet.IsSequenced: Boolean;
begin
Result := Assigned( FQSelect ) and FQSelect.EOF;
end;
function TIBCustomDataSet.AdjustPosition(FCache: PChar; Offset: DWORD;
Origin: Integer): Integer;
var
OldCacheSize: Integer;
begin
if (FCache = FBufferCache) then
begin
case Origin of
FILE_BEGIN: FBPos := Offset;
FILE_CURRENT: FBPos := FBPos + Offset;
FILE_END: FBPos := DWORD(FBEnd) + Offset;
end;
OldCacheSize := FCacheSize;
while (FBPos >= DWORD(FCacheSize)) do
Inc(FCacheSize, FBufferChunkSize);
if FCacheSize > OldCacheSize then
IBAlloc(FBufferCache, FCacheSize, FCacheSize);
result := FBPos;
end
else begin
case Origin of
FILE_BEGIN: FOBPos := Offset;
FILE_CURRENT: FOBPos := FOBPos + Offset;
FILE_END: FOBPos := DWORD(FOBEnd) + Offset;
end;
OldCacheSize := FOldCacheSize;
while (FBPos >= DWORD(FOldCacheSize)) do
Inc(FOldCacheSize, FBufferChunkSize);
if FOldCacheSize > OldCacheSize then
IBAlloc(FOldBufferCache, FOldCacheSize, FOldCacheSize);
result := FOBPos;
end;
end;
procedure TIBCustomDataSet.ReadCache(FCache: PChar; Offset: DWORD; Origin: Integer;
Buffer: PChar);
var
pCache: PChar;
bOld: Boolean;
begin
bOld := (FCache = FOldBufferCache);
pCache := PChar(AdjustPosition(FCache, Offset, Origin));
if not bOld then
pCache := FBufferCache + Integer(pCache)
else
pCache := FOldBufferCache + Integer(pCache);
Move(pCache^, Buffer^, DWORD(FRecordBufferSize));
AdjustPosition(FCache, FRecordBufferSize, FILE_CURRENT);
end;
procedure TIBCustomDataSet.ReadRecordCache(RecordNumber: Integer; Buffer: PChar;
ReadOldBuffer: Boolean);
begin
if FUniDirectional then
RecordNumber := RecordNumber mod UniCache;
if (ReadOldBuffer) then
begin
ReadRecordCache(RecordNumber, Buffer, False);
if (PRecordData(Buffer)^.rdSavedOffset <> $FFFFFFFF) then
ReadCache(FOldBufferCache, PRecordData(Buffer)^.rdSavedOffset, FILE_BEGIN,
Buffer)
end
else
ReadCache(FBufferCache, RecordNumber * FRecordBufferSize, FILE_BEGIN, Buffer);
end;
procedure TIBCustomDataSet.WriteCache(FCache: PChar; Offset: DWORD; Origin: Integer;
Buffer: PChar);
var
pCache: PChar;
bOld: Boolean;
dwEnd: DWORD;
begin
bOld := (FCache = FOldBufferCache);
pCache := PChar(AdjustPosition(FCache, Offset, Origin));
if not bOld then
pCache := FBufferCache + Integer(pCache)
else
pCache := FOldBufferCache + Integer(pCache);
Move(Buffer^, pCache^, FRecordBufferSize);
dwEnd := AdjustPosition(FCache, FRecordBufferSize, FILE_CURRENT);
if not bOld then
begin
if (dwEnd > FBEnd) then
FBEnd := dwEnd;
end
else begin
if (dwEnd > FOBEnd) then
FOBEnd := dwEnd;
end;
end;
procedure TIBCustomDataSet.WriteRecordCache(RecordNumber: Integer; Buffer: PChar);
begin
if RecordNumber >= 0 then
begin
if FUniDirectional then
RecordNumber := RecordNumber mod UniCache;
WriteCache(FBufferCache, RecordNumber * FRecordBufferSize, FILE_BEGIN, Buffer);
end;
end;
function TIBCustomDataSet.AllocRecordBuffer: PChar;
begin
result := nil;
IBAlloc(result, FRecordBufferSize, FRecordBufferSize);
Move(FModelBuffer^, result^, FRecordBufferSize);
end;
function TIBCustomDataSet.CreateBlobStream(Field: TField; Mode: TBlobStreamMode): TStream;
var
pb: PBlobDataArray;
fs: TIBBlobStream;
Buff: PChar;
bTr, bDB: Boolean;
begin
Buff := GetActiveBuf;
if Buff = nil then
begin
fs := TIBBlobStream.Create;
fs.Mode := bmReadWrite;
FBlobStreamList.Add(Pointer(fs));
result := TIBDSBlobStream.Create(Field, fs, Mode);
exit;
end;
pb := PBlobDataArray(Buff + FBlobCacheOffset);
if pb^[Field.Offset] = nil then
begin
AdjustRecordOnInsert(Buff);
pb^[Field.Offset] := TIBBlobStream.Create;
fs := pb^[Field.Offset];
FBlobStreamList.Add(Pointer(fs));
fs.Mode := bmReadWrite;
fs.Database := Database;
fs.Transaction := Transaction;
fs.BlobID :=
PISC_QUAD(@Buff[PRecordData(Buff)^.rdFields[FMappedFieldPosition[Field.FieldNo - 1]].fdDataOfs])^;
if (CachedUpdates) then
begin
bTr := not Transaction.InTransaction;
bDB := not Database.Connected;
if bDB then
Database.Open;
if bTr then
Transaction.StartTransaction;
fs.Seek(0, soFromBeginning);
if bTr then
Transaction.Commit;
if bDB then
Database.Close;
end;
WriteRecordCache(PRecordData(Buff)^.rdRecordNumber, Pointer(Buff));
end else
fs := pb^[Field.Offset];
result := TIBDSBlobStream.Create(Field, fs, Mode);
end;
function TIBCustomDataSet.CompareBookmarks(Bookmark1, Bookmark2: TBookmark): Integer;
const
CMPLess = -1;
CMPEql = 0;
CMPGtr = 1;
RetCodes: array[Boolean, Boolean] of ShortInt = ((2, CMPLess),
(CMPGtr, CMPEql));
begin
result := RetCodes[Bookmark1 = nil, Bookmark2 = nil];
if Result = 2 then
begin
if PInteger(Bookmark1)^ < PInteger(Bookmark2)^ then
Result := CMPLess
else
if PInteger(Bookmark1)^ > PInteger(Bookmark2)^ then
Result := CMPGtr
else
Result := CMPEql;
end;
end;
procedure TIBCustomDataSet.DoBeforeDelete;
var
Buff: PRecordData;
begin
if not CanDelete then
IBError(ibxeCannotDelete, [nil]);
Buff := PRecordData(GetActiveBuf);
if Buff^.rdCachedUpdateStatus in [cusUnmodified] then
SaveOldBuffer(PChar(Buff));
inherited;
end;
procedure TIBCustomDataSet.DoBeforeEdit;
var
Buff: PRecordData;
begin
Buff := PRecordData(GetActiveBuf);
if not(CanEdit or (FQModify.SQL.Count <> 0) or
(FCachedUpdates and Assigned(FOnUpdateRecord))) then
IBError(ibxeCannotUpdate, [nil]);
if Buff^.rdCachedUpdateStatus in [cusUnmodified, cusInserted] then
SaveOldBuffer(PChar(Buff));
CopyRecordBuffer(GetActiveBuf, FOldBuffer);
inherited;
end;
procedure TIBCustomDataSet.DoBeforeInsert;
begin
if not CanInsert then
IBError(ibxeCannotInsert, [nil]);
inherited;
end;
procedure TIBCustomDataSet.FetchAll;
var
CurBookmark: string;
iCurScreenState: Integer;
begin
iCurScreenState := Screen.Cursor;
Screen.Cursor := crHourglass;
try
if FQSelect.EOF or not FQSelect.Open then
exit;
DisableControls;
try
CurBookmark := Bookmark;
Last;
Bookmark := CurBookmark;
finally
EnableControls;
end;
finally
Screen.Cursor := iCurScreenState;
end;
end;
procedure TIBCustomDataSet.FreeRecordBuffer(var Buffer: PChar);
begin
IBAlloc(Buffer, 0, 0);
end;
procedure TIBCustomDataSet.GetBookmarkData(Buffer: PChar; Data: Pointer);
begin
Move(PRecordData(Buffer)^.rdRecordNumber, Data^, BookmarkSize);
end;
function TIBCustomDataSet.GetBookmarkFlag(Buffer: PChar): TBookmarkFlag;
begin
result := PRecordData(Buffer)^.rdBookmarkFlag;
end;
function TIBCustomDataSet.GetCanModify: Boolean;
begin
result := (FQInsert.SQL.Text <> '') or
(FQModify.SQL.Text <> '') or
(FQDelete.SQL.Text <> '') or
(Assigned(FUpdateObject));
end;
function TIBCustomDataSet.GetCurrentRecord(Buffer: PChar): Boolean;
begin
if not IsEmpty and (GetBookmarkFlag(ActiveBuffer) = bfCurrent) then
begin
UpdateCursorPos;
ReadRecordCache(PRecordData(ActiveBuffer)^.rdRecordNumber, Buffer, False);
result := True;
end
else
result := False;
end;
function TIBCustomDataSet.GetDataSource: TDataSource;
begin
if FDataLink = nil then
result := nil
else
result := FDataLink.DataSource;
end;
function TIBCustomDataSet.GetFieldClass(FieldType: TFieldType): TFieldClass;
begin
Result := DefaultFieldClasses[FieldType];
end;
function TIBCustomDataSet.GetFieldData(FieldNo: Integer; Buffer: Pointer): Boolean;
begin
result := GetFieldData(FieldByNumber(FieldNo), buffer);
end;
function TIBCustomDataSet.GetFieldData(Field: TField; Buffer: Pointer): Boolean;
var
Buff, Data: PChar;
CurrentRecord: PRecordData;
begin
result := False;
Buff := GetActiveBuf;
if (Buff = nil)
or (not IsVisible(Buff)) then
exit;
{ The intention here is to stuff the buffer with the data for the
referenced field for the current record }
CurrentRecord := PRecordData(Buff);
if (Field.FieldNo < 0) then
begin
Inc(Buff, FRecordSize + Field.Offset);
result := Boolean(Buff[0]);
if result and (Buffer <> nil) then
Move(Buff[1], Buffer^, Field.DataSize);
end
else if (FMappedFieldPosition[Field.FieldNo - 1] > 0) and
(FMappedFieldPosition[Field.FieldNo - 1] <= CurrentRecord^.rdFieldCount) then
begin
result := not CurrentRecord^.rdFields[FMappedFieldPosition[Field.FieldNo - 1]].fdIsNull;
if result and (Buffer <> nil) then
with CurrentRecord^.rdFields[FMappedFieldPosition[Field.FieldNo - 1]] do
begin
Data := Buff + CurrentRecord^.rdFields[FMappedFieldPosition[Field.FieldNo - 1]].fdDataOfs;
if (fdDataType = SQL_VARYING) or (fdDataType = SQL_TEXT) then
begin
Move(Data^, Buffer^, fdDataLength);
PChar(Buffer)[fdDataLength] := #0;
end
else
Move(Data^, Buffer^, Field.DataSize);
end;
end;
end;
{ GetRecNo and SetRecNo both operate off of 1-based indexes as
opposed to 0-based indexes.
This is because we want LastRecordNumber/RecordCount = 1 }
function TIBCustomDataSet.GetRecNo: Integer;
begin
if GetActiveBuf = nil then
result := 0
else
result := PRecordData(GetActiveBuf)^.rdRecordNumber + 1;
end;
function TIBCustomDataSet.GetRecord(Buffer: PChar; GetMode: TGetMode;
DoCheck: Boolean): TGetResult;
var
Accept: Boolean;
SaveState: TDataSetState;
begin
Result := grOK;
if Filtered and Assigned(OnFilterRecord) then
begin
Accept := False;
SaveState := SetTempState(dsFilter);
while not Accept do
begin
Result := InternalGetRecord(Buffer, GetMode, DoCheck);
if Result <> grOK then
break;
FFilterBuffer := Buffer;
try
Accept := True;
OnFilterRecord(Self, Accept);
except
Application.HandleException(Self);
end;
end;
RestoreState(SaveState);
end
else
Result := InternalGetRecord(Buffer, GetMode, DoCheck);
end;
function TIBCustomDataSet.InternalGetRecord(Buffer: PChar; GetMode: TGetMode;
DoCheck: Boolean): TGetResult;
begin
result := grError;
case GetMode of
gmCurrent: begin
if (FCurrentRecord >= 0) then begin
if FCurrentRecord < FRecordCount then
ReadRecordCache(FCurrentRecord, Buffer, False)
else begin
while (not FQSelect.EOF) and
(FQSelect.Next <> nil) and
(FCurrentRecord >= FRecordCount) do begin
FetchCurrentRecordToBuffer(FQSelect, FRecordCount, Buffer);
Inc(FRecordCount);
end;
FCurrentRecord := FRecordCount - 1;
if (FCurrentRecord >= 0) then
ReadRecordCache(FCurrentRecord, Buffer, False);
end;
result := grOk;
end else
result := grBOF;
end;
gmNext: begin
result := grOk;
if FCurrentRecord = FRecordCount then
result := grEOF
else if FCurrentRecord = FRecordCount - 1 then begin
if (not FQSelect.EOF) then begin
FQSelect.Next;
Inc(FCurrentRecord);
end;
if (FQSelect.EOF) then begin
result := grEOF;
end else begin
Inc(FRecordCount);
FetchCurrentRecordToBuffer(FQSelect, FCurrentRecord, Buffer);
end;
end else if (FCurrentRecord < FRecordCount) then begin
Inc(FCurrentRecord);
ReadRecordCache(FCurrentRecord, Buffer, False);
end;
end;
else { gmPrior }
begin
if (FCurrentRecord = 0) then begin
Dec(FCurrentRecord);
result := grBOF;
end else if (FCurrentRecord > 0) and
(FCurrentRecord <= FRecordCount) then begin
Dec(FCurrentRecord);
ReadRecordCache(FCurrentRecord, Buffer, False);
result := grOk;
end else if (FCurrentRecord = -1) then
result := grBOF;
end;
end;
if result = grOk then
result := AdjustCurrentRecord(Buffer, GetMode);
if result = grOk then with PRecordData(Buffer)^ do begin
rdBookmarkFlag := bfCurrent;
GetCalcFields(Buffer);
end else if (result = grEOF) then begin
CopyRecordBuffer(FModelBuffer, Buffer);
PRecordData(Buffer)^.rdBookmarkFlag := bfEOF;
end else if (result = grBOF) then begin
CopyRecordBuffer(FModelBuffer, Buffer);
PRecordData(Buffer)^.rdBookmarkFlag := bfBOF;
end else if (result = grError) then begin
CopyRecordBuffer(FModelBuffer, Buffer);
PRecordData(Buffer)^.rdBookmarkFlag := bfEOF;
end;;
end;
function TIBCustomDataSet.GetRecordCount: Integer;
begin
result := FRecordCount - FDeletedRecords;
end;
function TIBCustomDataSet.GetRecordSize: Word;
begin
result := FRecordBufferSize;
end;
procedure TIBCustomDataSet.InternalAddRecord(Buffer: Pointer; Append: Boolean);
begin
CheckEditState;
begin
{ When adding records, we *always* append.
Insertion is just too costly }
AdjustRecordOnInsert(Buffer);
with PRecordData(Buffer)^ do
begin
rdUpdateStatus := usInserted;
rdCachedUpdateStatus := cusInserted;
end;
if not CachedUpdates then
InternalPostRecord(FQInsert, Buffer)
else begin
WriteRecordCache(FCurrentRecord, Buffer);
FUpdatesPending := True;
end;
Inc(FRecordCount);
InternalSetToRecord(Buffer);
end
end;
procedure TIBCustomDataSet.InternalCancel;
var
Buff: PChar;
CurRec: Integer;
begin
inherited;
Buff := GetActiveBuf;
if Buff <> nil then begin
CurRec := FCurrentRecord;
AdjustRecordOnInsert(Buff);
if (State = dsEdit) then begin
CopyRecordBuffer(FOldBuffer, Buff);
WriteRecordCache(PRecordData(Buff)^.rdRecordNumber, Buff);
end else begin
CopyRecordBuffer(FModelBuffer, Buff);
PRecordData(Buff)^.rdUpdateStatus := usDeleted;
PRecordData(Buff)^.rdCachedUpdateStatus := cusUnmodified;
PRecordData(Buff)^.rdBookmarkFlag := bfEOF;
FCurrentRecord := CurRec;
end;
end;
end;
procedure TIBCustomDataSet.InternalClose;
begin
if FDidActivate then
DeactivateTransaction;
FQSelect.Close;
ClearBlobCache;
FreeRecordBuffer(FModelBuffer);
FreeRecordBuffer(FOldBuffer);
FCurrentRecord := -1;
FOpen := False;
FRecordCount := 0;
FDeletedRecords := 0;
FRecordSize := 0;
FBPos := 0;
FOBPos := 0;
FCacheSize := 0;
FOldCacheSize := 0;
FBEnd := 0;
FOBEnd := 0;
IBAlloc(FBufferCache, 0, 0);
IBAlloc(FOldBufferCache, 0, 0);
BindFields(False);
if DefaultFields then DestroyFields;
end;
procedure TIBCustomDataSet.InternalDelete;
var
Buff: PChar;
iCurScreenState: Integer;
begin
iCurScreenState := Screen.Cursor;
Screen.Cursor := crHourglass;
try
Buff := GetActiveBuf;
if CanDelete then
begin
if not CachedUpdates then
InternalDeleteRecord(FQDelete, Buff)
else
begin
with PRecordData(Buff)^ do
begin
if rdCachedUpdateStatus = cusInserted then
rdCachedUpdateStatus := cusUninserted
else begin
rdUpdateStatus := usDeleted;
rdCachedUpdateStatus := cusDeleted;
end;
end;
WriteRecordCache(PRecordData(Buff)^.rdRecordNumber, Buff);
end;
Inc(FDeletedRecords);
FUpdatesPending := True;
end else
IBError(ibxeCannotDelete, [nil]);
finally
Screen.Cursor := iCurScreenState;
end;
end;
procedure TIBCustomDataSet.InternalFirst;
begin
FCurrentRecord := -1;
end;
procedure TIBCustomDataSet.InternalGotoBookmark(Bookmark: Pointer);
begin
FCurrentRecord := PInteger(Bookmark)^;
end;
procedure TIBCustomDataSet.InternalHandleException;
begin
Application.HandleException(Self)
end;
procedure TIBCustomDataSet.InternalInitFieldDefs;
var
FieldType: TFieldType;
FieldSize: Word;
FieldNullable : Boolean;
i, FieldPosition, FieldPrecision: Integer;
FieldAliasName: string;
RelationName, FieldName: string;
Query : TIBSQL;
FieldIndex: Integer;
begin
if not InternalPrepared then
begin
InternalPrepare;
exit;
end;
Database.InternalTransaction.StartTransaction;
Query := TIBSQL.Create(self);
try
Query.Database := DataBase;
Query.Transaction := Database.InternalTransaction;
FieldDefs.BeginUpdate;
FieldDefs.Clear;
FieldIndex := 0;
if (Length(FMappedFieldPosition) < FQSelect.Current.Count) then
SetLength(FMappedFieldPosition, FQSelect.Current.Count);
for i := 0 to FQSelect.Current.Count - 1 do
with FQSelect.Current[i].Data^ do
begin
{ Get the field name }
SetString(FieldAliasName, aliasname, aliasname_length);
SetString(RelationName, relname, relname_length);
SetString(FieldName, sqlname, sqlname_length);
FieldSize := 0;
FieldPrecision := 0;
FieldNullable := FQSelect.Current[i].IsNullable;
case sqltype and not 1 of
{ All VARCHAR's must be converted to strings before recording
their values }
SQL_VARYING, SQL_TEXT:
begin
FieldSize := sqllen;
FieldType := ftString;
end;
{ All Doubles/Floats should be cast to doubles }
SQL_DOUBLE, SQL_FLOAT:
FieldType := ftFloat;
SQL_SHORT:
begin
if (sqlscale = 0) then
FieldType := ftSmallInt
else begin
FieldType := ftBCD;
FieldPrecision := 4;
end;
end;
SQL_LONG:
begin
if (sqlscale = 0) then
FieldType := ftInteger
else if (sqlscale >= (-4)) then
begin
FieldType := ftBCD;
FieldPrecision := 9;
end
else
FieldType := ftFloat;
end;
SQL_INT64:
begin
if (sqlscale = 0) then
FieldType := ftLargeInt
else if (sqlscale >= (-4)) then
begin
FieldType := ftBCD;
FieldPrecision := 18;
end
else
FieldType := ftFloat;
end;
SQL_TIMESTAMP: FieldType := ftDateTime;
SQL_TYPE_TIME: FieldType := ftTime;
SQL_TYPE_DATE: FieldType := ftDate;
SQL_BLOB:
begin
FieldSize := sizeof (TISC_QUAD);
if (sqlsubtype = 1) then
FieldType := ftmemo
else
FieldType := ftBlob;
end;
SQL_ARRAY:
begin
FieldSize := sizeof (TISC_QUAD);
FieldType := ftUnknown;
end;
else
FieldType := ftUnknown;
end;
FieldPosition := i + 1;
if (FieldType <> ftUnknown) and (FieldAliasName <> 'IBX_INTERNAL_DBKEY') then {do not localize}
begin
FMappedFieldPosition[FieldIndex] := FieldPosition;
Inc(FieldIndex);
with FieldDefs.AddFieldDef do
begin
Name := string( FieldAliasName );
FieldNo := FieldPosition;
DataType := FieldType;
Size := FieldSize;
Precision := FieldPrecision;
Required := False;
InternalCalcField := False;
if (FieldName <> '') and (RelationName <> '') then
begin
Query.SQL.Text := 'Select F.RDB$COMPUTED_BLR, ' + {do not localize}
'F.RDB$DEFAULT_VALUE ' + {do not localize}
'from RDB$RELATION_FIELDS R, RDB$FIELDS F ' + {do not localize}
'where R.RDB$RELATION_NAME = ' + '''' + {do not localize}
FormatIdentifierValue(Database.SQLDialect, RelationName) + ''' ' +
'and R.RDB$FIELD_SOURCE = F.RDB$FIELD_NAME '+ {do not localize}
'and R.RDB$FIELD_NAME = ' + '''' + {do not localize}
FormatIdentifierValue(Database.SQLDialect, FieldName) + '''';
Query.Prepare;
Query.ExecQuery;
if not (Query.Current.ByName('RDB$COMPUTED_BLR').IsNull) then {do not localize}
begin
Attributes := [faReadOnly];
InternalCalcField := True;
end;
if (not InternalCalcField) and (not FieldNullable) and
Query.Current.ByName('RDB$DEFAULT_VALUE').IsNull then {do not localize}
begin
Attributes := [faRequired];
end;
end;
Query.Close;
end;
end;
end;
finally
Query.free;
Database.InternalTransaction.Commit;
FieldDefs.EndUpdate;
end;
end;
procedure TIBCustomDataSet.InternalInitRecord(Buffer: PChar);
begin
CopyRecordBuffer(FModelBuffer, Buffer);
end;
procedure TIBCustomDataSet.InternalLast;
var
Buffer: PChar;
begin
if (FQSelect.EOF) then
FCurrentRecord := FRecordCount
else begin
Buffer := AllocRecordBuffer;
try
while FQSelect.Next <> nil do
begin
FetchCurrentRecordToBuffer(FQSelect, FRecordCount, Buffer);
Inc(FRecordCount);
end;
FCurrentRecord := FRecordCount;
finally
FreeRecordBuffer(Buffer);
end;
end;
end;
procedure TIBCustomDataSet.InternalSetParamsFromCusror;
var
i: Integer;
cur_param: TIBXSQLVAR;
cur_field: TField;
s: TStream;
begin
if FQSelect.SQL.Text = '' then
IBError(ibxeEmptyQuery, [nil]);
if not FInternalPrepared then
InternalPrepare;
if (SQLParams.Count > 0) and (DataSource <> nil) and (DataSource.DataSet <> nil) then
begin
for i := 0 to SQLParams.Count - 1 do
begin
cur_field := DataSource.DataSet.FindField(SQLParams[i].Name);
cur_param := SQLParams[i];
if (cur_field <> nil) then begin
if (cur_field.IsNull) then
cur_param.IsNull := True
else case cur_field.DataType of
ftString:
cur_param.AsString := cur_field.AsString;
ftBoolean, ftSmallint, ftWord:
cur_param.AsShort := cur_field.AsInteger;
ftInteger:
cur_param.AsLong := cur_field.AsInteger;
ftLargeInt:
cur_param.AsInt64 := TLargeIntField(cur_field).AsLargeInt;
ftFloat, ftCurrency:
cur_param.AsDouble := cur_field.AsFloat;
ftBCD:
cur_param.AsCurrency := cur_field.AsCurrency;
ftDate:
cur_param.AsDate := cur_field.AsDateTime;
ftTime:
cur_param.AsTime := cur_field.AsDateTime;
ftDateTime:
cur_param.AsDateTime := cur_field.AsDateTime;
ftBlob, ftMemo:
begin
s := nil;
try
s := DataSource.DataSet.
CreateBlobStream(cur_field, bmRead);
cur_param.LoadFromStream(s);
finally
s.free;
end;
end;
else
IBError(ibxeNotSupported, [nil]);
end;
end;
end;
end;
end;
procedure TIBCustomDataSet.ReQuery;
begin
FQSelect.Close;
ClearBlobCache;
FCurrentRecord := -1;
FRecordCount := 0;
FDeletedRecords := 0;
FBPos := 0;
FOBPos := 0;
FBEnd := 0;
FOBEnd := 0;
FQSelect.Close;
FQSelect.ExecQuery;
FOpen := FQSelect.Open;
First;
end;
procedure TIBCustomDataSet.InternalOpen;
var
iCurScreenState: Integer;
function RecordDataLength(n: Integer): Long;
begin
result := SizeOf(TRecordData) + ((n - 1) * SizeOf(TFieldData));
end;
begin
iCurScreenState := Screen.Cursor;
Screen.Cursor := crHourglass;
try
ActivateConnection;
ActivateTransaction;
if FQSelect.SQL.Text = '' then
IBError(ibxeEmptyQuery, [nil]);
if not FInternalPrepared then
InternalPrepare;
if FQSelect.SQLType = SQLSelect then
begin
if DefaultFields then
CreateFields;
BindFields(True);
FCurrentRecord := -1;
FQSelect.ExecQuery;
FOpen := FQSelect.Open;
{ Initialize offsets, buffer sizes, etc...
1. Initially FRecordSize is just the "RecordDataLength".
2. Allocate a "model" buffer and do a dummy fetch
3. After the dummy fetch, FRecordSize will be appropriately
adjusted to reflect the additional "weight" of the field
data.
4. Set up the FCalcFieldsOffset, FBlobCacheOffset and FRecordBufferSize.
5. Now, with the BufferSize available, allocate memory for chunks of records
6. Re-allocate the model buffer, accounting for the new
FRecordBufferSize.
7. Finally, calls to AllocRecordBuffer will work!.
}
{Step 1}
FRecordSize := RecordDataLength(FQSelect.Current.Count);
{Step 2, 3}
IBAlloc(FModelBuffer, 0, FRecordSize);
FetchCurrentRecordToBuffer(FQSelect, -1, FModelBuffer);
{Step 4}
FCalcFieldsOffset := FRecordSize;
FBlobCacheOffset := FCalcFieldsOffset + CalcFieldsSize;
FRecordBufferSize := (FBlobCacheOffset + (BlobFieldCount * SizeOf(TIBBlobStream)));
{Step 5}
if UniDirectional then
FBufferChunkSize := FRecordBufferSize * UniCache
else
FBufferChunkSize := FRecordBufferSize * BufferChunks;
IBAlloc(FBufferCache, FBufferChunkSize, FBufferChunkSize);
IBAlloc(FOldBufferCache, FBufferChunkSize, FBufferChunkSize);
FBPos := 0;
FOBPos := 0;
FBEnd := 0;
FOBEnd := 0;
FCacheSize := FBufferChunkSize;
FOldCacheSize := FBufferChunkSize;
{Step 6}
IBAlloc(FModelBuffer, RecordDataLength(FQSelect.Current.Count),
FRecordBufferSize);
{Step 7}
FOldBuffer := AllocRecordBuffer;
end
else
FQSelect.ExecQuery;
finally
Screen.Cursor := iCurScreenState;
end;
end;
procedure TIBCustomDataSet.InternalPost;
var
Qry: TIBSQL;
Buff: PChar;
iCurScreenState: Integer;
bInserting: Boolean;
begin
iCurScreenState := Screen.Cursor;
Screen.Cursor := crHourglass;
try
Buff := GetActiveBuf;
CheckEditState;
AdjustRecordOnInsert(Buff);
if (State = dsInsert) then
begin
bInserting := True;
Qry := FQInsert;
PRecordData(Buff)^.rdUpdateStatus := usInserted;
PRecordData(Buff)^.rdCachedUpdateStatus := cusInserted;
WriteRecordCache(FRecordCount, Buff);
FCurrentRecord := FRecordCount;
end
else begin
bInserting := False;
Qry := FQModify;
if PRecordData(Buff)^.rdCachedUpdateStatus = cusUnmodified then
begin
PRecordData(Buff)^.rdUpdateStatus := usModified;
PRecordData(Buff)^.rdCachedUpdateStatus := cusModified;
end
else if PRecordData(Buff)^.
rdCachedUpdateStatus = cusUninserted then
begin
PRecordData(Buff)^.rdCachedUpdateStatus := cusInserted;
Dec(FDeletedRecords);
end;
end;
if (not CachedUpdates) then
InternalPostRecord(Qry, Buff)
else begin
WriteRecordCache(PRecordData(Buff)^.rdRecordNumber, Buff);
FUpdatesPending := True;
end;
if bInserting then
Inc(FRecordCount);
finally
Screen.Cursor := iCurScreenState;
end;
end;
procedure TIBCustomDataSet.InternalRefresh;
begin
inherited;
InternalRefreshRow;
end;
procedure TIBCustomDataSet.InternalSetToRecord(Buffer: PChar);
begin
InternalGotoBookmark(@(PRecordData(Buffer)^.rdRecordNumber));
end;
function TIBCustomDataSet.IsCursorOpen: Boolean;
begin
result := FOpen;
end;
function TIBCustomDataSet.Locate(const KeyFields: string; const KeyValues: Variant;
Options: TLocateOptions): Boolean;
var
CurBookmark: string;
begin
DisableControls;
try
CurBookmark := Bookmark;
First;
result := InternalLocate(KeyFields, KeyValues, Options);
if not result then
Bookmark := CurBookmark;
finally
EnableControls;
end;
end;
function TIBCustomDataSet.Lookup(const KeyFields: string; const KeyValues: Variant;
const ResultFields: string): Variant;
var
fl: TList;
CurBookmark: string;
begin
DisableControls;
fl := TList.Create;
CurBookmark := Bookmark;
try
First;
if InternalLocate(KeyFields, KeyValues, []) then
begin
if (ResultFields <> '') then
result := FieldValues[ResultFields]
else
result := NULL;
end
else
result := Null;
finally
Bookmark := CurBookmark;
fl.Free;
EnableControls;
end;
end;
procedure TIBCustomDataSet.SetBookmarkData(Buffer: PChar; Data: Pointer);
begin
PRecordData(Buffer)^.rdRecordNumber := PInteger(Data)^;
end;
procedure TIBCustomDataSet.SetBookmarkFlag(Buffer: PChar; Value: TBookmarkFlag);
begin
PRecordData(Buffer)^.rdBookmarkFlag := Value;
end;
procedure TIBCustomDataSet.SetCachedUpdates(Value: Boolean);
begin
if not Value and FCachedUpdates then
CancelUpdates;
FCachedUpdates := Value;
end;
procedure TIBCustomDataSet.SetDataSource(Value: TDataSource);
begin
if IsLinkedTo(Value) then
IBError(ibxeCircularReference, [nil]);
if FDataLink <> nil then
FDataLink.DataSource := Value;
end;
procedure TIBCustomDataSet.SetFieldData(Field: TField; Buffer: Pointer);
var
Buff, TmpBuff: PChar;
begin
Buff := GetActiveBuf;
if Field.FieldNo < 0 then
begin
TmpBuff := Buff + FRecordSize + Field.Offset;
Boolean(TmpBuff[0]) := LongBool(Buffer);
if Boolean(TmpBuff[0]) then
Move(Buffer^, TmpBuff[1], Field.DataSize);
WriteRecordCache(PRecordData(Buff)^.rdRecordNumber, Buff);
end
else begin
CheckEditState;
with PRecordData(Buff)^ do
begin
{ If inserting, Adjust record position }
AdjustRecordOnInsert(Buff);
if (FMappedFieldPosition[Field.FieldNo - 1] > 0) and
(FMappedFieldPosition[Field.FieldNo - 1] <= rdFieldCount) then
begin
Field.Validate(Buffer);
if (Buffer = nil) or
(Field is TIBStringField) and (PChar(Buffer)[0] = #0) then
rdFields[FMappedFieldPosition[Field.FieldNo - 1]].fdIsNull := True
else begin
Move(Buffer^, Buff[rdFields[FMappedFieldPosition[Field.FieldNo - 1]].fdDataOfs],
rdFields[FMappedFieldPosition[Field.FieldNo - 1]].fdDataSize);
if (rdFields[FMappedFieldPosition[Field.FieldNo - 1]].fdDataType = SQL_TEXT) or
(rdFields[FMappedFieldPosition[Field.FieldNo - 1]].fdDataType = SQL_VARYING) then
rdFields[FMappedFieldPosition[Field.FieldNo - 1]].fdDataLength := StrLen(PChar(Buffer));
rdFields[FMappedFieldPosition[Field.FieldNo - 1]].fdIsNull := False;
if rdUpdateStatus = usUnmodified then
begin
if CachedUpdates then
begin
FUpdatesPending := True;
if State = dsInsert then
rdCachedUpdateStatus := cusInserted
else if State = dsEdit then
rdCachedUpdateStatus := cusModified;
end;
if State = dsInsert then
rdUpdateStatus := usInserted
else
rdUpdateStatus := usModified;
end;
WriteRecordCache(rdRecordNumber, Buff);
SetModified(True);
end;
end;
end;
end;
if not (State in [dsCalcFields, dsFilter, dsNewValue]) then
DataEvent(deFieldChange, Longint(Field));
end;
procedure TIBCustomDataSet.SetRecNo(Value: Integer);
begin
CheckBrowseMode;
if (Value < 1) then
Value := 1
else if Value > FRecordCount then
begin
InternalLast;
Value := Min(FRecordCount, Value);
end;
if (Value <> RecNo) then
begin
DoBeforeScroll;
FCurrentRecord := Value - 1;
Resync([]);
DoAfterScroll;
end;
end;
procedure TIBCustomDataSet.Disconnect;
begin
Close;
end;
procedure TIBCustomDataSet.SetUpdateMode(const Value: TUpdateMode);
begin
if not CanModify then
IBError(ibxeCannotUpdate, [nil])
else
FUpdateMode := Value;
end;
procedure TIBCustomDataSet.SetUpdateObject(Value: TIBDataSetUpdateObject);
begin
if Value <> FUpdateObject then
begin
if Assigned(FUpdateObject) and (FUpdateObject.DataSet = Self) then
FUpdateObject.DataSet := nil;
FUpdateObject := Value;
if Assigned(FUpdateObject) then
begin
if Assigned(FUpdateObject.DataSet) and
(FUpdateObject.DataSet <> Self) then
FUpdateObject.DataSet.UpdateObject := nil;
FUpdateObject.DataSet := Self;
end;
end;
end;
function TIBCustomDataSet.ConstraintsStored: Boolean;
begin
Result := Constraints.Count > 0;
end;
procedure TIBCustomDataSet.ClearCalcFields(Buffer: PChar);
begin
FillChar(Buffer[FRecordSize], CalcFieldsSize, 0);
end;
procedure TIBCustomDataSet.InternalUnPrepare;
begin
CheckDatasetClosed;
FieldDefs.Clear;
FInternalPrepared := False;
end;
procedure TIBCustomDataSet.InternalExecQuery;
var
DidActivate: Boolean;
iCurScreenState: Integer;
begin
DidActivate := False;
iCurScreenState := Screen.Cursor;
Screen.Cursor := crHourglass;
try
ActivateConnection;
DidActivate := ActivateTransaction;
if FQSelect.SQL.Text = '' then
IBError(ibxeEmptyQuery, [nil]);
if not FInternalPrepared then
InternalPrepare;
if FQSelect.SQLType = SQLSelect then
begin
IBError(ibxeIsASelectStatement, [nil]);
end
else
FQSelect.ExecQuery;
finally
Screen.Cursor := iCurScreenState;
if DidActivate then
DeactivateTransaction;
end;
end;
function TIBCustomDataSet.GetSelectStmtHandle: TISC_STMT_HANDLE;
begin
Result := FQSelect.Handle;
end;
procedure TIBCustomDataSet.InitRecord(Buffer: PChar);
begin
inherited InitRecord(Buffer);
with PRecordData(Buffer)^ do
begin
rdUpdateStatus := TUpdateStatus(usInserted);
rdBookMarkFlag := bfInserted;
rdRecordNumber := -1;
end;
end;
procedure TIBCustomDataSet.InternalInsert;
begin
CursorPosChanged;
end;
{ TIBDataSet IProviderSupport }
procedure TIBCustomDataSet.PSEndTransaction(Commit: Boolean);
begin
if Commit then
Transaction.Commit else
Transaction.Rollback;
end;
function TIBCustomDataSet.PSExecuteStatement(const ASQL: string; AParams: TParams;
ResultSet: Pointer = nil): Integer;
var
FQuery: TIBQuery;
begin
if Assigned(ResultSet) then
begin
TDataSet(ResultSet^) := TIBQuery.Create(nil);
with TIBQuery(ResultSet^) do
begin
SQL.Text := ASQL;
Params.Assign(AParams);
Open;
Result := RowsAffected;
end;
end else
begin
FQuery := TIBQuery.Create(nil);
try
FQuery.Database := Database;
FQuery.Transaction := Transaction;
FQuery.GenerateParamNames := True;
FQuery.SQL.Text := ASQL;
FQuery.Params.Assign(AParams);
FQuery.ExecSQL;
Result := FQuery.RowsAffected;
finally
FQuery.Free;
end;
end;
end;
function TIBCustomDataSet.PSGetQuoteChar: string;
begin
if Database.SQLDialect = 3 then
Result := '"' else
Result := '';
end;
function TIBCustomDataSet.PSGetUpdateException(E: Exception; Prev: EUpdateError): EUpdateError;
var
PrevErr: Integer;
begin
if Prev <> nil then
PrevErr := Prev.ErrorCode else
PrevErr := 0;
if E is EIBError then
with EIBError(E) do
Result := EUpdateError.Create(E.Message, '', SQLCode, PrevErr, E) else
Result := inherited PSGetUpdateException(E, Prev);
end;
function TIBCustomDataSet.PSInTransaction: Boolean;
begin
Result := Transaction.InTransaction;
end;
function TIBCustomDataSet.PSIsSQLBased: Boolean;
begin
Result := True;
end;
function TIBCustomDataSet.PSIsSQLSupported: Boolean;
begin
Result := True;
end;
procedure TIBCustomDataSet.PSReset;
begin
inherited PSReset;
if Active then
begin
Close;
Open;
end;
end;
function TIBCustomDataSet.PSUpdateRecord(UpdateKind: TUpdateKind; Delta: TDataSet): Boolean;
var
UpdateAction: TIBUpdateAction;
SQL: string;
Params: TParams;
procedure AssignParams(DataSet: TDataSet; Params: TParams);
var
I: Integer;
Old: Boolean;
Param: TParam;
PName: string;
Field: TField;
Value: Variant;
begin
for I := 0 to Params.Count - 1 do
begin
Param := Params[I];
PName := Param.Name;
Old := CompareText(Copy(PName, 1, 4), 'OLD_') = 0; {do not localize}
if Old then System.Delete(PName, 1, 4);
Field := DataSet.FindField(PName);
if not Assigned(Field) then Continue;
if Old then Param.AssignFieldValue(Field, Field.OldValue) else
begin
Value := Field.NewValue;
if VarIsEmpty(Value) then Value := Field.OldValue;
Param.AssignFieldValue(Field, Value);
end;
end;
end;
begin
Result := False;
if Assigned(OnUpdateRecord) then
begin
UpdateAction := uaFail;
if Assigned(FOnUpdateRecord) then
begin
FOnUpdateRecord(Delta, UpdateKind, UpdateAction);
Result := UpdateAction = uaApplied;
end;
end
else if Assigned(FUpdateObject) then
begin
SQL := FUpdateObject.GetSQL(UpdateKind).Text;
if SQL <> '' then
begin
Params := TParams.Create;
try
Params.ParseSQL(SQL, True);
AssignParams(Delta, Params);
if PSExecuteStatement(SQL, Params) = 0 then
IBError(ibxeNoRecordsAffected, [nil]);
Result := True;
finally
Params.Free;
end;
end;
end;
end;
procedure TIBCustomDataSet.PSStartTransaction;
begin
ActivateConnection;
Transaction.StartTransaction;
end;
function TIBCustomDataSet.PSGetTableName: string;
begin
Result := FQSelect.UniqueRelationName;
end;
procedure TIBDataSet.BatchInput(InputObject: TIBBatchInput);
begin
InternalBatchInput(InputObject);
end;
procedure TIBDataSet.BatchOutput(OutputObject: TIBBatchOutput);
begin
InternalBatchOutput(OutputObject);
end;
procedure TIBDataSet.Prepare;
begin
InternalPrepare;
end;
procedure TIBDataSet.UnPrepare;
begin
InternalUnPrepare;
end;
function TIBDataSet.GetPrepared: Boolean;
begin
Result := InternalPrepared;
end;
procedure TIBDataSet.InternalOpen;
begin
ActivateConnection;
ActivateTransaction;
InternalSetParamsFromCusror;
Inherited;
end;
procedure TIBDataSet.SetFiltered(Value: Boolean);
begin
if Value <> False then
IBError(ibxeNotSupported, [nil]);
end;
{ TIBDataSetUpdateObject }
constructor TIBDataSetUpdateObject.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FRefreshSQL := TStringList.Create;
end;
destructor TIBDataSetUpdateObject.Destroy;
begin
FRefreshSQL.Free;
inherited destroy;
end;
procedure TIBDataSetUpdateObject.SetRefreshSQL(Value: TStrings);
begin
FRefreshSQL.Assign(Value);
end;
{ TIBDSBlobStream }
constructor TIBDSBlobStream.Create(AField: TField; ABlobStream: TIBBlobStream;
Mode: TBlobStreamMode);
begin
FField := AField;
FBlobStream := ABlobStream;
FBlobStream.Seek(0, soFromBeginning);
if (Mode = bmWrite) then
FBlobStream.Truncate;
end;
function TIBDSBlobStream.Read(var Buffer; Count: Longint): Longint;
begin
result := FBlobStream.Read(Buffer, Count);
end;
function TIBDSBlobStream.Seek(Offset: Longint; Origin: Word): Longint;
begin
result := FBlobStream.Seek(Offset, Origin);
end;
procedure TIBDSBlobStream.SetSize(NewSize: Longint);
begin
FBlobStream.SetSize(NewSize);
end;
function TIBDSBlobStream.Write(const Buffer; Count: Longint): Longint;
begin
if not (FField.DataSet.State in [dsEdit, dsInsert]) then
IBError(ibxeNotEditing, [nil]);
TIBCustomDataSet(FField.DataSet).RecordModified(True);
result := FBlobStream.Write(Buffer, Count);
TIBCustomDataSet(FField.DataSet).DataEvent(deFieldChange, Longint(FField));
end;
end.