home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Delphi Anthology
/
aDELPHI.iso
/
Runimage
/
Delphi50
/
Source
/
Vcl
/
dbclient.pas
< prev
next >
Wrap
Pascal/Delphi Source File
|
1999-08-11
|
133KB
|
4,495 lines
{*******************************************************}
{ }
{ Borland Delphi Visual Component Library }
{ Client DataSet }
{ }
{ Copyright (c) 1997,99 Inprise Corporation }
{ }
{*******************************************************}
unit DBClient;
{$R-,T-,H+,X+}
interface
uses Windows, SysUtils, ActiveX, Graphics, Classes, Controls, Forms, Db,
DSIntf, DBCommon, Midas;
type
{ Exceptions }
EDBClient = class(EDatabaseError)
private
FErrorCode: DBResult;
public
constructor Create(Message: string; ErrorCode: DBResult);
property ErrorCode: DBResult read FErrorCode;
end;
EReconcileError = class(EDBClient)
private
FContext: string;
FPreviousError: DBResult;
public
constructor Create(NativeError, Context: string;
ErrorCode, PreviousError: DBResult);
property Context: string read FContext;
property PreviousError: DBResult read FPreviousError;
end;
{ TCustomRemoteServer }
TClientDataSet = class;
TCustomRemoteServer = class(TCustomConnection)
protected
function GetServerList: OleVariant; virtual;
procedure GetProviderNames(Proc: TGetStrProc); virtual;
public
constructor Create(AOwner: TComponent); override;
function GetServer: IAppServer; virtual;
end;
{ TAggregate }
TAggregate = class;
TAggregates = class;
TAggUpdateEvent = procedure(Agg: TAggregate) of object;
TAggregate = class(TCollectionItem)
private
FExpression: string;
FFldDesc: DSFLDDesc;
FHAggregate: hDSAggregate;
FAggregateName: String;
FGroupingLevel: Integer;
FDataSet: TClientDataSet;
FIndexName: string;
FDataBuffer: Array of Byte;
FDataType: TFieldType;
FDataSize: Integer;
FDependentFields: TBits;
FRecBufOfs: Integer;
FInUse: Boolean;
FActive: Boolean;
FVisible: Boolean;
FOutOfDate: Boolean;
FOnUpdate: TAggUpdateEvent;
procedure SetActive(Value: Boolean);
procedure SetExpression(const Text: string);
procedure SetGroupingLevel(GroupingLevel: Integer);
procedure SetIndexName(Value: String);
protected
procedure Activate;
property DependentFields: TBits read FDependentFields;
property RecBufOfs: Integer read FRecBufOfs write FRecBufOfs;
public
constructor Create(Aggregates: TAggregates; ADataSet: TClientDataSet); reintroduce; overload;
destructor Destroy; override;
procedure Assign(Source: TPersistent); override;
function GetDisplayName: string; override;
function Value: Variant;
property AggHandle: hDSAggregate read FHAggregate write FHAggregate;
property InUse: Boolean read FInUse write FInUse default false;
property DataSet: TClientDataSet read FDataSet;
property DataSize: Integer read FDataSize;
property DataType: TFieldType read FDataType;
published
property Active: Boolean read FActive write SetActive default False;
property AggregateName: String read FAggregateName write FAggregateName;
property Expression: string read FExpression write SetExpression;
property GroupingLevel: Integer read FGroupingLevel write SetGroupingLevel default 0;
property IndexName: string read FIndexName write SetIndexName;
property Visible: Boolean read FVisible write FVisible default True;
property OnUpdate: TAggUpdateEvent read FOnUpdate write FOnUpdate;
end;
{ TAggregates }
TAggregates = class(TCollection)
private
FOwner: TPersistent;
function GetItem(Index: Integer): TAggregate;
procedure SetItem(Index: Integer; Value: TAggregate);
protected
function GetOwner: TPersistent; override;
public
constructor Create(Owner: TPersistent);
function Add: TAggregate;
procedure Clear;
function Find(const DisplayName: string): TAggregate;
function IndexOf(const DisplayName: string): Integer;
property Items[Index: Integer]: TAggregate read GetItem write SetItem; default;
end;
{ TClientDataSet }
TFieldDescList = array of DSFLDDesc;
TKeyIndex = (kiLookup, kiRangeStart, kiRangeEnd, kiCurRangeStart,
kiCurRangeEnd, kiSave);
PRecInfo = ^TRecInfo;
TRecInfo = packed record
RecordNumber: Longint;
BookmarkFlag: TBookmarkFlag;
Attribute: DSAttr;
end;
PKeyBuffer = ^TKeyBuffer;
TKeyBuffer = record
Modified: Boolean;
Exclusive: Boolean;
FieldCount: Integer;
Data: record end;
end;
TDataPacketFormat = (dfBinary, dfXML);
TReconcileAction = (raSkip, raAbort, raMerge, raCorrect, raCancel, raRefresh);
TReconcileErrorEvent = procedure(DataSet: TClientDataSet; E: EReconcileError;
UpdateKind: TUpdateKind; var Action: TReconcileAction) of object;
TRemoteEvent = procedure(Sender: TObject; var OwnerData: OleVariant) of object;
TDataSetOption = (doDisableInserts, doDisableDeletes, doDisableEdits, doNoResetCall);
TDataSetOptions = set of TDataSetOption;
TFetchOption = (foRecord, foBlobs, foDetails);
TFetchOptions = set of TFetchOption;
TClientDataSet = class(TDataSet)
private
FActiveAggLists: TList;
FAggFieldsUpdated: TBits;
FAggFieldsInit: Boolean;
FAggFieldsSize: Integer;
FAggGrpIndOfs: Integer;
FAggFieldsOfs: Integer;
FAggGrpIndSize: Integer;
FAggregates: TAggregates;
FAggregatesActive: Boolean;
FCommandText: string;
FDSBase: IDSBase;
FDSCursor: IDSCursor;
FDSOptions: TDataSetOptions;
FFindCursor: IDSCursor;
FCloneSource: TClientDataSet;
FReconcileDataSet: TClientDataSet;
FSavedPacket: TDataPacket;
FDeltaPacket: TDataPacket;
FParams: TParams;
FIndexDefs: TIndexDefs;
FIndexName: string;
FExprFilter: HDSFilter;
FFuncFilter: HDSFilter;
FFileName: string;
FFilterBuffer: PChar;
FGroupingLevel: Integer;
FLastParentBM: array of byte;
FMasterLink: TMasterDataLink;
FIndexFieldMap: DSKEY;
FKeyBuffers: array[TKeyIndex] of PKeyBuffer;
FKeyBuffer: PKeyBuffer;
FNewValueBuffer: PChar;
FOldValueBuffer: PChar;
FCurValueBuffer: PChar;
FIndexFieldCount: Integer;
FIndexGroupingLevel: Integer;
FAppServer: IAppServer;
FProviderName: string;
FRemoteServer: TCustomRemoteServer;
FPacketRecords: Integer;
FConstDisableCount: Integer;
FMaxAggGroupingLevel: Integer;
FParentDataSet: TClientDataSet;
{ Word & Byte size data members }
FKeySize: Word;
FRecordSize: Word;
FBookmarkOfs: Word;
FRecInfoOfs: Word;
FRecBufSize: Word;
FReadOnly: Boolean;
FFieldsIndex: Boolean;
FCanModify: Boolean;
FInReconcileCallback: Boolean;
FNotifyCallback: Boolean;
FOpeningFile: Boolean;
FProviderEOF: Boolean;
FFetchOnDemand: Boolean;
FStoreDefs: Boolean;
FSavePacketOnClose: Boolean;
FOnReconcileError: TReconcileErrorEvent;
FStatusFilter: TUpdateStatusSet;
FBeforeApplyUpdates: TRemoteEvent;
FAfterApplyUpdates: TRemoteEvent;
FBeforeGetRecords: TRemoteEvent;
FAfterGetRecords: TRemoteEvent;
FBeforeRowRequest: TRemoteEvent;
FAfterRowRequest: TRemoteEvent;
FBeforeExecute: TRemoteEvent;
FAfterExecute: TRemoteEvent;
FBeforeGetParams: TRemoteEvent;
FAfterGetParams: TRemoteEvent;
procedure AddExprFilter(const Expr: string; Options: TFilterOptions);
procedure AddFuncFilter;
function CalcFieldsCallBack(RecBuf: PChar): DBResult; stdcall;
procedure CheckFieldProps;
procedure CheckMasterRange;
procedure CheckProviderEOF;
procedure ClearActiveAggs;
procedure ClearSavedPacket;
procedure CloseAggs;
function CreateDSBase: IDSBase;
function CreateDSCursor(SourceCursor: IDSCursor): IDSCursor;
procedure DecodeIndexDesc(const IndexDesc: DSIDXDesc;
var Name, Fields, DescFields, CaseInsFields: string; var Options: TIndexOptions);
procedure EncodeFieldDesc(var FieldDesc: DSFLDDesc; const Name: string;
DataType: TFieldType; Size, Precision: Integer; Calculated: Boolean;
Attributes: TFieldAttributes);
procedure EncodeIndexDesc(var IndexDesc: DSIDXDesc;
const Name, Fields, DescFields, CaseInsFields: string; Options: TIndexOptions);
procedure FetchMoreData(All: Boolean);
function FilterCallback(RecBuf: PChar): Bool; stdcall;
procedure DoAggUpdates(IsUpdate: Boolean);
function GetActiveAggs(Index: Integer) : TList;
function GetActiveRecBuf(var RecBuf: PChar): Boolean;
procedure GetAggFieldData(Buffer: PChar);
function GetChangeCount: Integer;
function GetData: OleVariant;
function GetDataSize: Integer;
function GetDelta: OleVariant;
function GetIndexDefs: TIndexDefs;
function GetIndexFieldNames: string;
function GetIndexName: string;
function GetLogChanges: Boolean;
function GetMasterFields: string;
function GetAppServer: IAppServer;
function GetProviderEOF: Boolean;
function GetSavePoint: Integer;
function GetHasAppServer: Boolean;
procedure InitBufferPointers(GetProps: Boolean);
function InternalGetGroupState(Level: Integer): TGroupPosInds;
procedure InternalFetch(Options: TFetchOptions);
procedure MasterChanged(Sender: TObject);
procedure MasterDisabled(Sender: TObject);
procedure NotifyCallback; stdcall;
procedure ReadData(Stream: TStream);
function ReconcileCallback(iRslt: Integer; iUpdateKind: DSAttr;
iResAction: dsCBRType; iErrCode: Integer; pErrMessage, pErrContext: PChar;
pRecUpd, pRecOrg, pRecConflict: Pointer; iLevels: Integer;
piFieldIDs: PInteger): dsCBRType; stdcall;
procedure ResetAgg(Agg: TAggregate; DeleteFirst: Boolean);
procedure ResetAllAggs(Value: Boolean);
procedure ResetGrouping;
procedure SetAggsActive(Value: Boolean);
procedure SaveDataPacket(XMLFormat: Boolean = False);
procedure SetData(const Value: OleVariant);
procedure SetDataSource(Value: TDataSource);
procedure SetIndex(const Value: string; FieldsIndex: Boolean);
procedure SetIndexDefs(Value: TIndexDefs);
procedure SetIndexFieldNames(const Value: string);
procedure SetIndexName(const Value: string);
procedure SetLogChanges(Value: Boolean);
procedure SetMasterFields(const Value: string);
procedure SetNotifyCallback;
procedure SetParams(Value: TParams);
procedure SetAppServer(Value: IAppServer);
procedure SetProviderEOF(Value: Boolean);
procedure SetProviderName(const Value: string);
procedure SetReadOnly(Value: Boolean);
procedure SetSavePoint(Value: Integer);
procedure SortOnFields(Cursor: IDSCursor; const Fields: string;
CaseInsensitive, Descending: Boolean);
procedure SetupConstraints;
procedure SetupInternalCalcFields(Add: Boolean);
procedure WriteData(Stream: TStream);
procedure SetAggregates(Value: TAggregates);
procedure SetStatusFilter(const Value: TUpdateStatusSet);
protected
{ DataIntf Helper functions }
function DoApplyUpdates(Delta: OleVariant; MaxErrors: Integer; out ErrorCount: Integer): OleVariant; virtual;
function DoGetRecords(Count: Integer; out RecsOut: Integer; Options: Integer;
const CommandText: WideString; Params: OleVariant): OleVariant; virtual;
function DoRowRequest(Row: OleVariant; RequestType: Integer): OleVariant; virtual;
procedure DoExecute(Params: OleVariant); virtual;
{ DataSet methods }
procedure ResetAggField(Field: TField); override;
procedure ActivateFilters;
procedure AddDataPacket(const Data: OleVariant; HitEOF: Boolean); virtual;
procedure AddFieldDesc(FieldDescs: TFieldDescList; var DescNo: Integer;
var FieldID: Integer; FieldDefs: TFieldDefs);
procedure AllocKeyBuffers;
function AllocRecordBuffer: PChar; override;
procedure Check(Status: DBResult);
procedure CheckDetailRecords; virtual;
procedure CheckSetKeyMode;
procedure ClearCalcFields(Buffer: PChar); override;
procedure CloseCursor; override;
procedure DataConvert(Field: TField; Source, Dest: Pointer; ToNative: Boolean); override;
procedure DataEvent(Event: TDataEvent; Info: Longint); override;
procedure DeactivateFilters;
procedure DefChanged(Sender: TObject); override;
procedure DefineProperties(Filer: TFiler); override;
procedure DestroyLookupCursor; virtual;
procedure DoBeforeInsert; override;
procedure DoOnNewRecord; override;
function FindRecord(Restart, GoForward: Boolean): Boolean; override;
procedure FreeKeyBuffers;
procedure FreeRecordBuffer(var Buffer: PChar); override;
function GetAggregateValue(Field: TField): Variant; override;
procedure GetBookmarkData(Buffer: PChar; Data: Pointer); override;
function GetBookmarkFlag(Buffer: PChar): TBookmarkFlag; override;
function GetCanModify: Boolean; override;
function GetDataSource: TDataSource; override;
function GetIndexField(Index: Integer): TField;
function GetIndexFieldCount: Integer;
function GetIsIndexField(Field: TField): Boolean; override;
function GetKeyBuffer(KeyIndex: TKeyIndex): PKeyBuffer;
function GetKeyExclusive: Boolean;
function GetKeyFieldCount: Integer;
function GetRecord(Buffer: PChar; GetMode: TGetMode; DoCheck: Boolean): TGetResult; override;
function GetRecordCount: Integer; override;
function GetRecNo: Integer; override;
function GetRecordSize: Word; override;
function GetRemoteServer: TCustomRemoteServer; virtual;
function GetStateFieldValue(State: TDataSetState; Field: TField): Variant; override;
function InitKeyBuffer(Buffer: PKeyBuffer): PKeyBuffer;
procedure InitRecord(Buffer: PChar); override;
procedure InternalAddRecord(Buffer: Pointer; Append: Boolean); override;
procedure InternalCancel; override;
procedure InternalClose; override;
procedure InternalDelete; override;
procedure InternalEdit; override;
procedure InternalFirst; override;
function InternalGetOptionalParam(const ParamName: string;
FieldNo: Integer = 0): OleVariant;
procedure InternalGotoBookmark(Bookmark: TBookmark); override;
procedure InternalHandleException; override;
procedure InternalInitFieldDefs; override;
procedure InternalInitRecord(Buffer: PChar); override;
procedure InternalInsert; override;
procedure InternalLast; override;
procedure InternalOpen; override;
procedure InternalRefresh; override;
procedure InternalPost; override;
procedure InternalSetToRecord(Buffer: PChar); override;
function IsCursorOpen: Boolean; override;
procedure Loaded; override;
function LocateRecord(const KeyFields: string; const KeyValues: Variant;
Options: TLocateOptions; SyncCursor: Boolean): Boolean;
procedure OpenCursor(InfoQuery: Boolean); override;
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
procedure PostKeyBuffer(Commit: Boolean);
procedure RefreshInternalCalcFields(Buffer: PChar); override;
procedure ReadDataPacket(Stream: TStream; ReadSize: Boolean);
function ResetCursorRange: Boolean;
procedure SetBookmarkData(Buffer: PChar; Data: Pointer); override;
procedure SetBookmarkFlag(Buffer: PChar; Value: TBookmarkFlag); override;
function SetCursorRange: Boolean;
procedure SetDataSetField(const Value: TDataSetField); override;
procedure SetFieldData(Field: TField; Buffer: Pointer); override;
procedure SetFilterData(const Text: string; Options: TFilterOptions);
procedure SetFiltered(Value: Boolean); override;
procedure SetFilterOptions(Value: TFilterOptions); override;
procedure SetFilterText(const Value: string); override;
procedure SetIndexField(Index: Integer; Value: TField);
procedure SetKeyBuffer(KeyIndex: TKeyIndex; Clear: Boolean);
procedure SetKeyExclusive(Value: Boolean);
procedure SetKeyFieldCount(Value: Integer);
procedure SetKeyFields(KeyIndex: TKeyIndex; const Values: array of const);
procedure SetLinkRanges(MasterFields: TList);
procedure SetOnFilterRecord(const Value: TFilterRecordEvent); override;
procedure SetRecNo(Value: Integer); override;
procedure SetRemoteServer(Value: TCustomRemoteServer); virtual;
procedure SwitchToIndex(const IndexName: string);
procedure SyncCursors(Cursor1, Cursor2: IDSCursor);
procedure UpdateIndexDefs; override;
procedure WriteDataPacket(Stream: TStream; WriteSize: Boolean; XMLFormat: Boolean = False);
function ConstraintsStored: Boolean;
property DSBase: IDSBase read FDSBase write FDSBase;
property DSCursor: IDSCursor read FDSCursor;
property ProviderEOF: Boolean read GetProviderEOF write SetProviderEOF;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure AddIndex(const Name, Fields: string; Options: TIndexOptions;
const DescFields: string = ''; const CaseInsFields: string = '';
const GroupingLevel: Integer = 0);
procedure AppendData(const Data: OleVariant; HitEOF: Boolean);
procedure ApplyRange;
function ApplyUpdates(MaxErrors: Integer): Integer; virtual;
function BookmarkValid(Bookmark: TBookmark): Boolean; override;
procedure Cancel; override;
procedure CancelRange;
procedure CancelUpdates;
function CreateBlobStream(Field: TField; Mode: TBlobStreamMode): TStream; override;
procedure CreateDataSet;
procedure CloneCursor(Source: TClientDataSet; Reset: Boolean;
KeepSettings: Boolean = False);
function CompareBookmarks(Bookmark1, Bookmark2: TBookmark): Integer; override;
function ConstraintsDisabled: Boolean;
function DataRequest(Data: OleVariant): OleVariant; virtual;
procedure DeleteIndex(const Name: string);
procedure DisableConstraints;
procedure EnableConstraints;
procedure EditKey;
procedure EditRangeEnd;
procedure EditRangeStart;
procedure EmptyDataSet;
procedure Execute; virtual;
procedure FetchBlobs;
procedure FetchDetails;
procedure RefreshRecord;
procedure FetchParams;
function FindKey(const KeyValues: array of const): Boolean; virtual;
procedure FindNearest(const KeyValues: array of const);
function GetCurrentRecord(Buffer: PChar): Boolean; override;
function GetFieldData(Field: TField; Buffer: Pointer): Boolean; override;
function GetFieldData(FieldNo: Integer; Buffer: Pointer): Boolean; overload; override;
function GetGroupState(Level: Integer): TGroupPosInds;
procedure GetIndexInfo(IndexName: string);
procedure GetIndexNames(List: TStrings);
function GetNextPacket: Integer;
function GetOptionalParam(const ParamName: string): OleVariant;
procedure GotoCurrent(DataSet: TClientDataSet);
function GotoKey: Boolean;
procedure GotoNearest;
property HasAppServer: Boolean read GetHasAppServer;
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;
procedure LoadFromFile(const FileName: string = '');
procedure LoadFromStream(Stream: TStream);
procedure MergeChangeLog;
procedure Post; override;
function Reconcile(const Results: OleVariant): Boolean;
procedure RevertRecord;
procedure SaveToFile(const FileName: string = ''; Format: TDataPacketFormat = dfBinary);
procedure SaveToStream(Stream: TStream; Format: TDataPacketFormat = dfBinary);
procedure SetAltRecBuffers(Old, New, Cur: PChar);
procedure SetKey;
procedure SetOptionalParam(const ParamName: string; const Value: OleVariant;
IncludeInDelta: Boolean = False);
procedure SetProvider(Provider: TComponent);
procedure SetRange(const StartValues, EndValues: array of const);
procedure SetRangeEnd;
procedure SetRangeStart;
function UndoLastChange(FollowChange: Boolean): Boolean;
function UpdateStatus: TUpdateStatus; override;
property ActiveAggs[Index: Integer] : TList read GetActiveAggs;
property ChangeCount: Integer read GetChangeCount;
property CloneSource: TClientDataSet read FCloneSource;
property Data: OleVariant read GetData write SetData;
property AppServer: IAppServer read GetAppServer write SetAppServer;
property DataSize: Integer read GetDataSize;
property Delta: OleVariant read GetDelta;
property GroupingLevel: Integer read FGroupingLevel;
property IndexFieldCount: Integer read GetIndexFieldCount;
property IndexFields[Index: Integer]: TField read GetIndexField write SetIndexField;
property KeyExclusive: Boolean read GetKeyExclusive write SetKeyExclusive;
property KeyFieldCount: Integer read GetKeyFieldCount write SetKeyFieldCount;
property KeySize: Word read FKeySize;
property LogChanges: Boolean read GetLogChanges write SetLogChanges;
property SavePoint: Integer read GetSavePoint write SetSavePoint;
property StatusFilter: TUpdateStatusSet read FStatusFilter write SetStatusFilter;
published
property Active;
property Aggregates: TAggregates read FAggregates write SetAggregates;
property AggregatesActive: Boolean read FAggregatesActive write SetAggsActive default False;
property AutoCalcFields;
property CommandText: string read FCommandText write FCommandText;
property Constraints stored ConstraintsStored;
property DataSetField;
property FileName: string read FFileName write FFileName;
property Filter;
property Filtered;
property FilterOptions;
property FieldDefs stored FStoreDefs;
property IndexDefs: TIndexDefs read GetIndexDefs write SetIndexDefs stored FStoreDefs;
property IndexFieldNames: string read GetIndexFieldNames write SetIndexFieldNames;
property IndexName: string read GetIndexName write SetIndexName;
property FetchOnDemand: Boolean read FFetchOnDemand write FFetchOnDemand default True;
property MasterFields: string read GetMasterFields write SetMasterFields;
property MasterSource: TDataSource read GetDataSource write SetDataSource;
property ObjectView default True;
property PacketRecords: Integer read FPacketRecords write FPacketRecords default -1;
property Params: TParams read FParams write SetParams;
property ProviderName: string read FProviderName write SetProviderName;
property ReadOnly: Boolean read FReadOnly write SetReadOnly default False;
property RemoteServer: TCustomRemoteServer read GetRemoteServer write SetRemoteServer;
property StoreDefs: Boolean read FStoreDefs write FStoreDefs default False;
property BeforeOpen;
property AfterOpen;
property BeforeClose;
property AfterClose;
property BeforeInsert;
property AfterInsert;
property BeforeEdit;
property AfterEdit;
property BeforePost;
property AfterPost;
property BeforeCancel;
property AfterCancel;
property BeforeDelete;
property AfterDelete;
property BeforeScroll;
property AfterScroll;
property BeforeRefresh;
property AfterRefresh;
property OnCalcFields;
property OnDeleteError;
property OnEditError;
property OnFilterRecord;
property OnNewRecord;
property OnPostError;
property OnReconcileError: TReconcileErrorEvent read FOnReconcileError write FOnReconcileError;
property BeforeApplyUpdates: TRemoteEvent read FBeforeApplyUpdates write FBeforeApplyUpdates;
property AfterApplyUpdates: TRemoteEvent read FAfterApplyUpdates write FAfterApplyUpdates;
property BeforeGetRecords: TRemoteEvent read FBeforeGetRecords write FBeforeGetRecords;
property AfterGetRecords: TRemoteEvent read FAfterGetRecords write FAfterGetRecords;
property BeforeRowRequest: TRemoteEvent read FBeforeRowRequest write FBeforeRowRequest;
property AfterRowRequest: TRemoteEvent read FAfterRowRequest write FAfterRowRequest;
property BeforeExecute: TRemoteEvent read FBeforeExecute write FBeforeExecute;
property AfterExecute: TRemoteEvent read FAfterExecute write FAfterExecute;
property BeforeGetParams: TRemoteEvent read FBeforeGetParams write FBeforeGetParams;
property AfterGetParams: TRemoteEvent read FAfterGetParams write FAfterGetParams;
end;
{ TClientBlobStream }
TClientBlobStream = class(TMemoryStream)
private
FField: TBlobField;
FDataSet: TClientDataSet;
FBuffer: PChar;
FFieldNo: Integer;
FModified: Boolean;
procedure ReadBlobData;
public
constructor Create(Field: TBlobField; Mode: TBlobStreamMode);
destructor Destroy; override;
function Write(const Buffer; Count: Longint): Longint; override;
procedure Truncate;
end;
const
AllParamTypes = [ptUnknown, ptInput, ptOutput, ptInputOutput, ptResult];
function PackageParams(Params: TParams; Types: TParamTypes = AllParamTypes): OleVariant;
procedure UnpackParams(const Source: OleVariant; Dest: TParams);
const
AllRecords = -1;
implementation
uses DBConsts, MidConst, Provider, TypInfo, ComObj;
{ Exceptions }
constructor EDBClient.Create(Message: string; ErrorCode: DBResult);
begin
FErrorCode := ErrorCode;
inherited Create(Message);
end;
constructor EReconcileError.Create(NativeError, Context: string;
ErrorCode, PreviousError: DBResult);
begin
FContext := Context;
FPreviousError := PreviousError;
inherited Create(NativeError, ErrorCode);
end;
{ Utility functions }
function PackageParams(Params: TParams; Types: TParamTypes = AllParamTypes): OleVariant;
var
I, Idx, Count: Integer;
begin
Result := NULL;
Count := 0;
for I := 0 to Params.Count - 1 do
if Params[I].ParamType in Types then Inc(Count);
if Count > 0 then
begin
Idx := 0;
Result := VarArrayCreate([0, Count - 1], varVariant);
for I := 0 to Params.Count - 1 do
with Params[I] do
if ParamType in Types then
begin
Result[Idx] := VarArrayOf([Name, Value, Ord(DataType), Ord(ParamType)]);
Inc(Idx);
end;
end;
end;
procedure UnpackParams(const Source: OleVariant; Dest: TParams);
var
TempParams: TParams;
i: Integer;
begin
if not VarIsNull(Source) and VarIsArray(Source) and VarIsArray(Source[0]) then
begin
TempParams := TParams.Create;
try
for i := 0 to VarArrayHighBound(Source, 1) do
begin
with TParam(TempParams.Add) do
begin
if VarArrayHighBound(Source[i], 1) > 1 then
DataType := TFieldType(Source[i][2]);
if VarArrayHighBound(Source[i], 1) > 2 then
ParamType := TParamType(Source[i][3]);
Name := Source[i][0];
Value := Source[i][1];
end;
end;
Dest.Assign(TempParams);
finally
TempParams.Free;
end;
end;
end;
{ TCustomRemoteServer }
constructor TCustomRemoteServer.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
RCS;
end;
function TCustomRemoteServer.GetServerList: OleVariant;
begin
Result := NULL;
end;
function TCustomRemoteServer.GetServer: IAppServer;
begin
Result := nil;
end;
procedure TCustomRemoteServer.GetProviderNames(Proc: TGetStrProc);
begin
end;
{ TClientDataSet }
constructor TClientDataSet.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FMasterLink := TMasterDataLink.Create(Self);
FMasterLink.OnMasterChange := MasterChanged;
FMasterLink.OnMasterDisable := MasterDisabled;
FPacketRecords := AllRecords;
FFetchOnDemand := True;
FParams := TParams.Create(Self);
FAggregates := TAggregates.Create(Self);
FActiveAggLists := TList.Create;
FOpeningFile := False;
ObjectView := True;
RCS;
end;
destructor TClientDataSet.Destroy;
begin
FSavePacketOnClose := False;
inherited Destroy;
ClearSavedPacket;
FreeDataPacket(FDeltaPacket);
SetRemoteServer(nil);
AppServer := nil;
FMasterLink.Free;
FIndexDefs.Free;
FParams.Free;
FAggregates.Free;
ClearActiveAggs;
FActiveAggLists.Free;
FAggFieldsUpdated.Free;
end;
function TClientDataSet.CreateDSBase: IDSBase;
begin
CreateDbClientObject(CLSID_DSBase, IDSBase, Result);
end;
function TClientDataSet.CreateDSCursor(SourceCursor: IDSCursor): IDSCursor;
begin
CreateDbClientObject(CLSID_DSCursor, IDSCursor, Result);
if Assigned(SourceCursor) then
Check(Result.CloneCursor(SourceCursor)) else
Check(Result.InitCursor(FDSBase));
end;
procedure TClientDataSet.SetParams(Value: TParams);
begin
FParams.Assign(Value);
end;
procedure TClientDataSet.SetOptionalParam(const ParamName: string;
const Value: OleVariant; IncludeInDelta: Boolean);
const
ParamTypeMap: array[varSmallInt..varByte] of Integer =
( dsfldINT, dsfldINT, dsfldFLOATIEEE, dsfldFLOATIEEE, dsfldBCD,
dsfldTIMESTAMP, dsfldZSTRING, 0, 0, dsfldBOOL, 0, 0, 0, 0, 0, dsfldINT);
ParamTypeSize: array[varSmallInt..varByte] of Integer =
( SizeOf(SmallInt), SizeOf(Integer), SizeOf(Single), SizeOf(Double),
SizeOf(Currency), SizeOf(TDateTime), 0, 0, 0, SizeOf(WordBool), 0, 0, 0,
0, 0, SizeOf(Byte));
var
ParamType, ParamLen, t, l: DWord;
S: string;
P: Pointer;
Unlock: Boolean;
V, Name: Pointer;
begin
CheckActive;
if ((VarType(Value) and varTypeMask) in [varSmallInt, varInteger, varSingle,
varDouble, varCurrency, varDate, varOleStr, varBoolean, varByte]) and
((not VarIsArray(Value)) or (VarType(Value) and varTypeMask = varByte)) then
begin
Unlock := False;
try
ParamType := ParamTypeMap[VarType(Value) and varTypeMask];
ParamLen := ParamTypeSize[VarType(Value) and varTypeMask];
if ParamType = dsfldZSTRING then
begin
S := Value;
P := PChar(S);
ParamLen := Length(S) + 1;
end else
if VarIsArray(Value) then
begin
ParamType := dsfldBYTES;
ParamLen := VarArrayHighBound(Value, 1) - VarArrayLowBound(Value, 1);
P := VarArrayLock(Value);
Unlock := True;
end else
if (VarType(Value) and varByRef) = varByRef then
P := TVarData(Value).VPointer else
P := @TVarData(Value).VPointer;
ParamType := ParamType shl dsSizeBitsLen or ParamLen;
if IncludeInDelta then
ParamType := ParamType or dsIncInDelta;
Name := PChar(ParamName);
if FDSBase.GetOptParameter(0, 0, Name, t, l, v) = 0 then
Check(FDSBase.DropOptParameter(0, PChar(ParamName)));
Check(FDSBase.AddOptParameter(0, PChar(ParamName), ParamType, ParamLen, P));
finally
if Unlock then
VarArrayUnlock(Value);
end;
end else
DatabaseError(SInvalidOptParamType, Self);
end;
function TClientDataSet.GetOptionalParam(const ParamName: string): OleVariant;
begin
Result := InternalGetOptionalParam(ParamName);
end;
function TClientDataSet.InternalGetOptionalParam(const ParamName: string;
FieldNo: Integer = 0): OleVariant;
var
ParamType, ParamLen: DWord;
Name: PChar;
Value, P: Pointer;
S: string;
begin
if not Assigned(FDSBase) then CheckActive;
VarClear(Result);
Name := PChar(ParamName);
if FDSBase.GetOptParameter(0, FieldNo, Pointer(Name), ParamType,
ParamLen, Value) <> 0 then Exit;
case (ParamType and dsTypeBitsMask) shr dsSizeBitsLen of
dsfldINT,
dsfldUINT:
begin
case ParamLen of
1: Result := Byte(Value^);
2: Result := SmallInt(Value^);
4: Result := Integer(Value^);
end;
end;
dsfldBOOL: Result := WordBool(Value^);
dsfldFLOATIEEE: Result := Double(Value^);
dsfldBCD: Result := Currency(Value^);
dsfldDATE: Result := TDateTimeRec(Value^).Date - DateDelta;
dsfldTIME: Result := TDateTimeRec(Value^).Time / MSecsPerDay;
dsfldTIMESTAMP: Result := (TDateTimeRec(Value^).DateTime / MSecsPerDay) - DateDelta;
dsfldZSTRING:
begin
SetString(S, PChar(Value), ParamLen-1);
Result := S;
end;
dsfldBYTES:
begin
Result := VarArrayCreate([0, ParamLen], varByte);
P := VarArrayLock(Result);
try
Move(Value^, P^, ParamLen);
finally
VarArrayUnlock(Result);
end;
end;
else
VarClear(Result);
end;
end;
procedure TClientDataSet.OpenCursor(InfoQuery: Boolean);
var
RecsOut: Integer;
Options: TGetRecordOptions;
DataPacket: TDataPacket;
Stream: TFileStream;
begin
FProviderEOF := True;
FSavePacketOnClose := False;
if not FOpeningFile and (FileName <> '') and FileExists(FileName) then
begin
Stream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
try
ReadDataPacket(Stream, False);
finally
Stream.Free;
end;
end;
if DataSetField <> nil then
begin
FParentDataSet := DataSetField.DataSet as TClientDataSet;
OpenParentDataSet(FParentDataSet);
Check(FParentDataSet.DSBase.GetEmbeddedDS(DataSetField.FieldNo, FDSBase));
FieldDefs.HiddenFields := FParentDataSet.FieldDefs.HiddenFields;
end
else if not Assigned(FDSBase) then
begin
if Assigned(FSavedPacket) then DataPacket := FSavedPacket else
begin
Options := [grMetaData];
DataPacket := VarToDataPacket(DoGetRecords(FPacketRecords, RecsOut,
Byte(Options), CommandText, PackageParams(Params)));
ProviderEOF := RecsOut <> FPacketRecords;
end;
if not Assigned(DataPacket) then DatabaseError(SNoDataProvider, Self);
FDSBase := CreateDSBase;
Check(FDSBase.AppendData(DataPacket, ProviderEOF));
end;
inherited OpenCursor(InfoQuery);
if not InfoQuery and Assigned(FCloneSource) and not FCloneSource.BOF then
begin
SyncCursors(FDSCursor, FCloneSource.FDSCursor);
CursorPosChanged;
Resync([]);
end;
{ DSBase now has the data packet so we don't need to hold on to it }
ClearSavedPacket;
FSavePacketOnClose := True;
end;
procedure TClientDataSet.FetchParams;
var
OwnerData: OleVariant;
begin
if Assigned(FBeforeExecute) then FBeforeExecute(Self, OwnerData);
UnpackParams(AppServer.AS_GetParams(ProviderName, OwnerData), Params);
if Assigned(FAfterExecute) then FAfterExecute(Self, OwnerData);
end;
procedure TClientDataSet.Check(Status: DBResult);
var
ErrMsg: array[0..2048] of Char;
begin
if Status <> 0 then
begin
FDSBase.GetErrorString(Status, ErrMsg);
raise EDBClient.Create(ErrMsg, Status);
end;
end;
procedure TClientDataSet.CloseCursor;
var
Params: OleVariant;
RecsOut: Integer;
Options: TGetRecordOptions;
begin
if (FileName <> '') and not (csDesigning in ComponentState) then
SaveToFile(FileName);
inherited CloseCursor;
if HasAppServer then
begin
if not (csDestroying in ComponentState) then
begin
if FMasterLink.Active and (FMasterLink.Fields.Count > 0) and
(PacketRecords = 0) then
Params := Null else
Params := Unassigned;
if not (doNoResetCall in FDSOptions) then
begin
Options := [grReset];
DoGetRecords(0, RecsOut, Byte(Options), '', Unassigned);
end;
FAppServer := nil;
end;
end
else if FSavePacketOnClose and (FileName <> '') then
SaveDataPacket;
FDSBase := nil;
FParentDataSet := nil;
end;
procedure TClientDataSet.DefChanged(Sender: TObject);
begin
FStoreDefs := True;
end;
procedure TClientDataSet.InternalInitFieldDefs;
var
FieldID, I: Integer;
FieldDescs: TFieldDescList;
CursorProps: DSProps;
begin
FDSBase.SetProp(dspropCOMPRESSARRAYS, Integer(True));
Check(FDSBase.GetProps(CursorProps));
SetLength(FieldDescs, CursorProps.iFields);
Check(FDSBase.GetFieldDescs(PDSFldDesc(FieldDescs)));
FieldDefs.Clear;
I := 0;
FieldID := 1;
while I < CursorProps.iFields do
AddFieldDesc(FieldDescs, I, FieldID, FieldDefs);
end;
type
TPropReader = class(TReader);
procedure TClientDataSet.CheckFieldProps;
procedure GetTypeName(Field: TObjectField);
var
V: Variant;
i: Integer;
begin
V := InternalGetOptionalParam(szTYPENAME, Field.FieldNo);
if not VarIsNull(V) and not VarIsEmpty(V) then
Field.ObjectType := V;
if Field.DataType in [ftADT, ftArray] then
for i := 0 to Field.FieldCount - 1 do
if Field.Fields[i] is TObjectField then
GetTypeName(TObjectField(Field.Fields[i]));
end;
var
V: Variant;
P: Pointer;
Stream: TMemoryStream;
Reader: TPropReader;
i: Integer;
begin
Stream := TMemoryStream.Create;
try
for i := 0 to FieldCount - 1 do
begin
if Fields[i] is TObjectField then
GetTypeName(TObjectField(Fields[i]));
V := InternalGetOptionalParam(szORIGIN, Fields[i].FieldNo);
if not VarIsNull(V) and not VarIsEmpty(V) then
Fields[i].Origin := VarToStr(V);
V := InternalGetOptionalParam(szFIELDPROPS, Fields[i].FieldNo);
if VarIsNull(V) or VarIsEmpty(V) or not VarIsArray(V) then continue;
Stream.Size := VarArrayHighBound(V, 1) - SizeOf(Integer);
P := VarArrayLock(V);
try
Stream.Position := 0;
Stream.Write(Pointer(Integer(P) + SizeOf(Integer))^, Stream.Size);
Stream.Position := 0;
finally
VarArrayUnlock(V);
end;
V := NULL;
Reader := TPropReader.Create(Stream, 1024);
try
Reader.ReadListBegin;
while not Reader.EndOfList do
Reader.ReadProperty(Fields[i]);
finally
Stream.Clear;
Reader.Free;
end;
end;
finally
Stream.Free;
end;
end;
procedure TClientDataSet.InternalOpen;
function GetBoolParam(const ParamName: string): Boolean;
var
V: OleVariant;
begin
V := GetOptionalParam(ParamName);
Result := not VarIsNull(V) and not VarIsEmpty(V) and (VarType(V) = varBoolean);
if Result then
Result := V;
end;
var
CursorProps: DSProps;
begin
if Assigned(FCloneSource) then
FDSCursor := CreateDSCursor(FCloneSource.FDSCursor)
else
begin
SetupInternalCalcFields(True);
FDSCursor := CreateDSCursor(nil);
end;
if DataSetField <> nil then
Check(FParentDataSet.FDSCursor.LinkCursors(0, nil, nil, FDSCursor));
FDSOptions := [];
if GetBoolParam(szDISABLE_EDITS) then
Include(FDSOptions, doDisableEdits);
if GetBoolParam(szDISABLE_INSERTS) then
Include(FDSOptions, doDisableInserts);
if GetBoolParam(szDISABLE_DELETES) then
Include(FDSOptions, doDisableDeletes);
if GetBoolParam(szNO_RESET_CALL) then
Include(FDSOptions, doNoResetCall);
Check(FDSCursor.GetCursorProps(CursorProps));
FRecordSize := CursorProps.iRecBufSize;
BookmarkSize := CursorProps.iBookmarkSize;
SetLength(FLastParentBM, BookMarkSize);
FCanModify := not CursorProps.bReadOnly;
FieldDefs.Updated := False;
FieldDefs.Update;
FieldDefList.Update;
IndexDefs.Updated := False;
GetIndexInfo('');
if DefaultFields then CreateFields;
BindFields(True);
CheckFieldProps;
AllocKeyBuffers;
FDSCursor.MoveToBOF;
if not Assigned(FCloneSource) then
begin
if InternalCalcFields and not (csDesigning in ComponentState) then
Check(FDSBase.SetFieldCalculation(Integer(Self),
@TClientDataSet.CalcFieldsCallback));
if FIndexName <> '' then
if FFieldsIndex then
SortOnFields(FDSCursor, FIndexName, False, False) else
SwitchToIndex(FIndexName);
CheckMasterRange;
if FReadOnly then FDSBase.SetProp(dspropREADONLY, Integer(True));
ResetAllAggs(FAggregatesActive);
if Filtered then ActivateFilters;
end;
InitBufferPointers(False);
if (DataSetField <> nil) and FetchOnDemand then
CheckDetailRecords;
SetupConstraints;
end;
procedure TClientDataSet.InternalClose;
begin
if Filtered then DeactivateFilters;
FreeKeyBuffers;
if not Assigned(FCloneSource) then
SetupInternalCalcFields(False);
BindFields(False);
if DefaultFields then DestroyFields;
CloseAggs;
FIndexFieldCount := 0;
FKeySize := 0;
FDSCursor := nil;
FFindCursor := nil;
FNotifyCallback := False;
end;
procedure TClientDataSet.InternalRefresh;
const
Options: TGetRecordOptions = [grReset];
var
SeqNo: DWord;
RecCount, RecsOut: Integer;
DataPacket: TDataPacket;
begin
CheckBrowseMode;
if ChangeCount > 0 then
DatabaseError(SRefreshError, Self);
if FMasterLink.Active and (FMasterLink.Fields.Count > 0) and
((DataSetField <> nil) or (PacketRecords <> -1)) then
begin
FDSBase.Reset;
if FetchOnDemand then CheckDetailRecords;
end else
begin
FDSCursor.GetSequenceNumber(SeqNo);
if not ProviderEOF then
FDSBase.GetProp(dspropRECORDSINDS, @RecCount) else
RecCount := AllRecords;
DataPacket := VarToDataPacket(DoGetRecords(RecCount, RecsOut, Byte(Options), '', Unassigned));
ProviderEOF := RecsOut <> RecCount;
FDSBase.Reset;
FDSBase.SetProp(dspropDSISPARTIAL, Integer(False));
Check(FDSBase.AppendData(DataPacket, ProviderEOF));
FDSCursor.MoveToSeqNo(SeqNo);
end;
end;
function TClientDataSet.IsCursorOpen: Boolean;
begin
Result := FDSCursor <> nil;
end;
procedure TClientDataSet.InternalHandleException;
begin
Application.HandleException(Self)
end;
procedure TClientDataSet.SetData(const Value: OleVariant);
begin
FSavePacketOnClose := False;
Close;
ClearSavedPacket;
if not VarIsNull(Value) then
begin
SafeArrayCopy(VarToDataPacket(Value), FSavedPacket);
Open;
end;
end;
function TClientDataSet.GetData: OleVariant;
var
DataPacket: TDataPacket;
begin
if Active then
begin
CheckBrowseMode;
Check(FDSBase.StreamDS(DataPacket));
end else
DataPacket := FSavedPacket;
DataPacketToVariant(DataPacket, Result);
end;
procedure TClientDataSet.ClearSavedPacket;
begin
FreeDataPacket(FSavedPacket);
end;
procedure TClientDataSet.SaveDataPacket(XMLFormat: Boolean = False);
const
StreamMode: array[Boolean] of DWord = (xmlOFF, xmlON);
begin
if Assigned(FDSBase) and (DataSetField = nil) then
begin
FDSBase.SetProp(dspropXML_STREAMMODE, StreamMode[XMLFormat]);
ClearSavedPacket;
Check(FDSBase.StreamDS(FSavedPacket));
end;
end;
function TClientDataSet.GetDataSize: Integer;
begin
if Assigned(DataSetField) then
Result := -1
else if Active then
begin
SaveDataPacket;
Result := DataPacketSize(FSavedPacket);
ClearSavedPacket;
end
else if Assigned(FSavedPacket) then
Result := DataPacketSize(FSavedPacket)
else
Result := 0;
end;
procedure TClientDataSet.FetchMoreData(All: Boolean);
var
Count: Integer;
RecsOut: Integer;
begin
if All then Count := AllRecords else Count := FPacketRecords;
if Count = 0 then Exit;
AddDataPacket(DoGetRecords(Count, RecsOut, 0, '', Unassigned), RecsOut <> Count);
ProviderEOF := RecsOut <> Count;
end;
procedure TClientDataSet.InternalFetch(Options: TFetchOptions);
var
DataPacket: TDataPacket;
NewData: OleVariant;
BaseDS: TClientDataSet;
begin
CheckActive;
UpdateCursorPos;
Check(DSCursor.GetRowRequestPacket(foRecord in Options, foBlobs in Options,
foDetails in Options, True, DataPacket));
DataPacketToVariant(DataPacket, NewData);
BaseDS := Self;
while Assigned(BaseDS.FParentDataSet) do BaseDS := BaseDS.FParentDataSet;
NewData := BaseDS.DoRowRequest(NewData, Byte(Options));
UpdateCursorPos;
Check(DSCursor.RefreshRecord(VarToDataPacket(NewData)));
DSCursor.GetCurrentRecord(ActiveBuffer);
if Options = [foDetails] then
DataEvent(deDataSetChange, 0);
end;
procedure TClientDataSet.FetchBlobs;
begin
InternalFetch([foBlobs]);
end;
procedure TClientDataSet.FetchDetails;
begin
InternalFetch([foDetails]);
end;
procedure TClientDataSet.RefreshRecord;
begin
InternalFetch([foRecord]);
end;
procedure TClientDataSet.CheckProviderEOF;
begin
if HasAppServer and not ProviderEOF and FFetchOnDemand and (FPacketRecords <> 0) then
FetchMoreData(True);
end;
procedure TClientDataSet.AddDataPacket(const Data: OleVariant; HitEOF: Boolean);
begin
Check(FDSBase.AppendData(VarToDataPacket(Data), HitEOF));
end;
procedure TClientDataSet.AppendData(const Data: OleVariant; HitEOF: Boolean);
begin
if not Active then
begin
Self.Data := Data;
if not HitEOF then
FDSBase.SetProp(dspropDSISPARTIAL, Integer(False));
end else
begin
AddDataPacket(Data, HitEOF);
if State <> dsBrowse then Exit;
if IsEmpty then First else
begin
UpdateCursorPos;
Resync([]);
end;
end;
end;
function TClientDataSet.GetNextPacket: Integer;
begin
CheckActive;
if ProviderEOF then Result := 0 else
begin
UpdateCursorPos;
if (FPacketRecords = 0) and FMasterLink.Active and
(FMasterLink.Fields.Count > 0) then CheckDetailRecords else
begin
AddDataPacket(DoGetRecords(FPacketRecords, Result, 0, '', Unassigned),
Result <> FPacketRecords);
ProviderEOF := Result <> FPacketRecords;
end;
Resync([]);
end;
end;
procedure TClientDataSet.SetProviderName(const Value: string);
begin
if Value = FProviderName then Exit;
if (Value <> '') then
begin
CheckInactive;
ClearSavedPacket;
end;
FAppServer := nil;
FProviderName := Value;
end;
procedure TClientDataSet.SetProvider(Provider: TComponent);
begin
if Provider is TCustomProvider then
AppServer := TLocalAppServer.Create(TCustomProvider(Provider)) else
AppServer := nil;
end;
function TClientDataSet.GetAppServer: IAppServer;
var
ProvComp: TComponent;
DS: TObject;
begin
if not HasAppServer then
begin
if ProviderName <> '' then
if Assigned(RemoteServer) then
FAppServer := RemoteServer.GetServer else
begin
if Assigned(Owner) then
begin
ProvComp := Owner.FindComponent(ProviderName);
if Assigned(ProvComp) and (ProvComp is TCustomProvider) then
begin
DS := GetObjectProperty(ProvComp, 'DataSet');
if Assigned(DS) and (DS = Self) then
DatabaseError(SNoCircularReference, Self);
FAppServer := TLocalAppServer.Create(TCustomProvider(ProvComp));
end;
end;
end;
if not HasAppServer then
DatabaseError(SNoDataProvider, Self);
end;
Result := FAppServer;
end;
function TClientDataSet.GetHasAppServer: Boolean;
begin
Result := Assigned(FAppServer);
end;
procedure TClientDataSet.SetAppServer(Value: IAppServer);
begin
FAppServer := Value;
if Assigned(Value) then
ClearSavedPacket;
end;
procedure TClientDataSet.SetProviderEOF(Value: Boolean);
begin
FProviderEOF := Value;
if Assigned(FCloneSource) then
FCloneSource.ProviderEOF := Value;
end;
function TClientDataSet.GetProviderEOF: Boolean;
begin
if Assigned(FCloneSource) then
FProviderEOF := FCloneSource.ProviderEOF;
Result := FProviderEOF;
end;
function TClientDataSet.GetRemoteServer: TCustomRemoteServer;
begin
Result := FRemoteServer;
end;
procedure TClientDataSet.SetRemoteServer(Value: TCustomRemoteServer);
begin
if Value = FRemoteServer then Exit;
AppServer := nil;
if Assigned(FRemoteServer) then FRemoteServer.UnRegisterClient(Self);
if Assigned(Value) then
begin
CheckInactive;
Value.RegisterClient(Self);
ClearSavedPacket;
Value.FreeNotification(Self);
end;
FRemoteServer := Value;
end;
procedure TClientDataSet.Notification(AComponent: TComponent; Operation: TOperation);
begin
inherited Notification(AComponent, Operation);
if (Operation = opRemove) and (AComponent = RemoteServer) then
RemoteServer := nil;
if (Operation = opRemove) and (AComponent = FCloneSource) then
begin
FProviderEOF := FCloneSource.ProviderEOF;
FCloneSource := nil;
end;
end;
procedure TClientDataSet.DataEvent(Event: TDataEvent; Info: Integer);
begin
case Event of
deParentScroll: MasterChanged(Self);
deDataSetScroll,
deDataSetChange: SetAltRecBuffers(nil, nil, nil);
deFieldListChange: FAggFieldsInit := False;
deConnectChange:
if not Bool(Info) then
AppServer := nil;
end;
inherited;
end;
function TClientDataSet.GetDelta: OleVariant;
var
FDeltaDS: IDSBase;
TempPacket: TDataPacket;
begin
CheckBrowseMode;
Check(FDSBase.GetDelta(FDeltaDS));
FreeDataPacket(FDeltaPacket);
Check(FDeltaDS.StreamDS(FDeltaPacket));
SafeArrayCopy(FDeltaPacket, TempPacket);
DataPacketToVariant(TempPacket, Result);
end;
procedure TClientDataSet.Execute;
begin
DoExecute(PackageParams(Params));
end;
function TClientDataSet.DataRequest(Data: OleVariant): OleVariant;
begin
Result := AppServer.AS_DataRequest(ProviderName, Data);
end;
function TClientDataSet.ApplyUpdates(MaxErrors: Integer): Integer;
begin
CheckBrowseMode;
if ChangeCount = 0 then
Result := 0 else
Reconcile(DoApplyUpdates(Delta, MaxErrors, Result));
end;
procedure TClientDataSet.MergeChangeLog;
begin
CheckBrowseMode;
FDSBase.AcceptChanges;
end;
procedure TClientDataSet.SetAltRecBuffers(Old, New, Cur: PChar);
begin
FOldValueBuffer := Old;
FNewValueBuffer := New;
FCurValueBuffer := Cur;
end;
function TClientDataSet.ReconcileCallback(
iRslt : Integer; { Previous error if any }
iUpdateKind : DSAttr; { Update request Insert/Modify/Delete }
iResAction : dsCBRType; { Resolver response }
iErrCode : Integer; { Native error-code, (BDE or ..) }
pErrMessage, { Native errormessage, if any (otherwise Null) }
pErrContext : PChar; { 1-level error context, if any (otherwise Null) }
pRecUpd, { Record that failed update }
pRecOrg, { Original record, if any }
pRecConflict : Pointer; { Conflicting error, if any }
iLevels : Integer; { Number of levels to error0level }
piFieldIDs : PInteger { Array of fieldIDS to navigate to error-dataset }
): dsCBRType;
var
I: Integer;
Action: TReconcileAction;
UpdateKind: TUpdateKind;
DataSet: TClientDataSet;
E: EReconcileError;
begin
FInReconcileCallback := True;
try
if iUpdateKind = dsRecDeleted then
UpdateKind := ukDelete
else if iUpdateKind = dsRecNew then
UpdateKind := ukInsert
else
UpdateKind := ukModify;
if iResAction = dscbrSkip then
Action := raSkip else
Action := raAbort;
FReconcileDataSet.First;
E := EReconcileError.Create(pErrMessage, pErrContext, iErrCode, iRslt);
try
DataSet := FReconcileDataSet;
for I := 1 to iLevels do
begin
DataSet := TClientDataSet((DataSet.Fields.FieldByNumber(piFieldIDs^) as TDataSetField).NestedDataSet);
inc(piFieldIDs);
end;
if UpdateKind = ukDelete then
DataSet.SetAltRecBuffers(pRecUpd, pRecOrg, pRecConflict) else
DataSet.SetAltRecBuffers(pRecOrg, pRecUpd, pRecConflict);
FOnReconcileError(DataSet, E, UpdateKind, Action);
finally
E.Free;
end;
except
Application.HandleException(Self);
Action := raAbort;
end;
Result := Ord(Action) + 1;
FInReconcileCallback := False;
end;
function TClientDataSet.Reconcile(const Results: OleVariant): Boolean;
var
RCB: Pointer;
begin
if VarIsNull(Results) then MergeChangeLog else
begin
UpdateCursorPos;
if Assigned(FOnReconcileError) then
RCB := @TClientDataSet.ReconcileCallback else
RCB := nil;
FReconcileDataSet := TClientDataSet.Create(Self);
try
Check(FDSBase.Clone(0, True, False, FReconcileDataSet.FDSBase));
FReconcileDataSet.ObjectView := True;
FReconcileDataSet.Open;
Check(FDSBase.Reconcile_MD(FReconcileDataSet.FDSBase, FDeltaPacket,
VarToDataPacket(Results), Integer(Self), RCB));
finally
FReconcileDataSet.Free;
FReconcileDataSet := nil;
end;
Resync([]);
end;
Result := (ChangeCount = 0);
end;
procedure TClientDataSet.NotifyCallback;
begin
try
if State = dsBrowse then
begin
UpdateCursorPos;
Resync([]);
end;
except
end;
end;
procedure TClientDataSet.SetNotifyCallback;
begin
if not FNotifyCallback then
begin
Check(FDSCursor.SetNotifyCallBack(Integer(Self), @TClientDataSet.NotifyCallback));
FNotifyCallback := True;
end;
end;
procedure TClientDataSet.CloneCursor(Source: TClientDataSet; Reset, KeepSettings: Boolean);
begin
Source.CheckActive;
Close;
FDSBase := Source.DSBase;
Source.UpdateCursorPos;
FCloneSource := Source;
FParentDataSet := Source.FParentDataSet;
if Reset then
begin
Filtered := False;
Filter := '';
OnFilterRecord := nil;
IndexName := '';
MasterSource := nil;
MasterFields := '';
ReadOnly := False;
RemoteServer := nil;
ProviderName := '';
AppServer := nil;
end else
if not KeepSettings then
begin
Filter := Source.Filter;
OnFilterRecord := Source.OnFilterRecord;
FilterOptions := Source.FilterOptions;
Filtered := Source.Filtered;
if Source.IndexName <> '' then
IndexName := Source.IndexName else
IndexFieldNames := Source.IndexFieldNames;
MasterSource := Source.MasterSource;
MasterFields := Source.MasterFields;
ReadOnly := Source.ReadOnly;
RemoteServer := Source.RemoteServer;
ProviderName := Source.ProviderName;
if Source.HasAppServer then
AppServer := Source.AppServer;
end;
Open;
if Reset then
begin
if Source.FExprFilter <> nil then FDSCursor.DropFilter(Source.FExprFilter);
if Source.FFuncFilter <> nil then FDSCursor.DropFilter(Source.FFuncFilter);
Resync([]);
end;
SetNotifyCallback;
Source.SetNotifyCallback;
end;
procedure TClientDataSet.EncodeFieldDesc(var FieldDesc: DSFLDDesc;
const Name: string; DataType: TFieldType; Size, Precision: Integer;
Calculated: Boolean; Attributes: TFieldAttributes);
begin
with FieldDesc do
begin
FillChar(FieldDesc, SizeOf(FieldDesc), #0);
StrCopy(szName, PChar(Name));
iFldType := FieldTypeMap[DataType];
iFldSubType := FldSubTypeMap[DataType];
bCalculated := Calculated;
iFldAttr := Integer(Byte(Attributes));
case DataType of
ftADT, ftArray, ftDataSet, ftString, ftFixedChar, ftGUID, ftBytes,
ftVarBytes, ftBlob..ftTypedBinary, ftOraClob, ftOraBlob, ftWideString:
iUnits1 := Size;
ftBCD:
begin
{ Default precision is 32, Size = Scale }
if (Precision > 0) and (Precision <= 32) then
iUnits1 := Precision else
iUnits1 := 32;
iUnits2 := Size; {Scale}
end;
end;
end;
end;
procedure TClientDataSet.CreateDataSet;
procedure GetFieldDefCount(FieldDefs: TFieldDefs; var Count: Integer);
var
I: Integer;
begin
Inc(Count, FieldDefs.Count);
for I := 0 to FieldDefs.Count - 1 do
with FieldDefs[I] do
if HasChildDefs then
GetFieldDefCount(ChildDefs, Count);
end;
procedure EncodeFieldDescs(FieldDefs: TFieldDefs; FieldDescs: TFieldDescList;
var DescNo: Integer);
var
I: Integer;
begin
for I := 0 to FieldDefs.Count - 1 do
with FieldDefs[I] do
begin
EncodeFieldDesc(FieldDescs[DescNo], Name, DataType, Size, Precision, False, Attributes);
Inc(DescNo);
if HasChildDefs then
begin
if DataType = ftDataSet then
GetFieldDefCount(ChildDefs, FieldDescs[DescNo-1].iUnits2);
EncodeFieldDescs(ChildDefs, FieldDescs, DescNo);
end;
end;
end;
procedure CreateIndexes;
var
I: Integer;
IndexDesc: DSIdxDesc;
begin
for I := 0 to IndexDefs.Count - 1 do
begin
with IndexDefs[I] do
EncodeIndexDesc(IndexDesc, Name, Fields, DescFields, CaseInsFields, Options);
Check(FDSBase.CreateIndex(IndexDesc));
end;
end;
var
FieldDefCount, DescNo: Integer;
FieldDescs: TFieldDescList;
begin
CheckInactive;
InitFieldDefsFromFields;
FieldDefCount := 0;
GetFieldDefCount(FieldDefs, FieldDefCount);
if FieldDefCount = 0 then
DatabaseError(SCannotCreateDataSet);
SetLength(FieldDescs, FieldDefCount);
DescNo := 0;
EncodeFieldDescs(FieldDefs, FieldDescs, DescNo);
FDSBase := CreateDSBase;
try
Check(FDSBase.Create(FieldDefCount, pDSFLDDesc(FieldDescs), PChar(Name)));
CreateIndexes;
except
FDSBase := nil;
raise;
end;
Open;
end;
procedure TClientDataSet.EmptyDataSet;
begin
CheckBrowseMode;
Check(FDSBase.Reset);
ProviderEOF := True;
Resync([]);
InitRecord(ActiveBuffer);
end;
procedure TClientDataSet.SetupInternalCalcFields(Add: Boolean);
var
Precision, I: Integer;
FieldDesc: DSFLDDesc;
begin
if Add and not DefaultFields then
begin
for I := 0 to FieldCount - 1 do
begin
if Fields[I].FieldKind = fkInternalCalc then
with Fields[I] do
begin
if DataType = ftBCD then
Precision := TBCDField(Fields[I]).Precision else
Precision := 0;
EncodeFieldDesc(FieldDesc, FieldName, DataType, Size, Precision, True, []);
Check(FDSBase.AddField(@FieldDesc));
end;
end;
end
else if InternalCalcFields then
begin
Check(FDSBase.SetFieldCalculation(0, nil));
Check(FDSBase.AddField(nil));
end;
end;
procedure TClientDataSet.WriteDataPacket(Stream: TStream; WriteSize: Boolean;
XMLFormat: Boolean = False);
var
Size: Integer;
DataPtr: Pointer;
begin
RCS;
if Active then CheckBrowseMode;
if IsCursorOpen then
begin
CheckProviderEOF;
SaveDataPacket(XMLFormat);
end;
if Assigned(FSavedPacket) then
begin
Size := DataPacketSize(FSavedPacket);
SafeArrayAccessData(FSavedPacket, DataPtr);
try
if WriteSize then
Stream.Write(Size, SizeOf(Size));
Stream.Write(DataPtr^, Size);
finally
SafeArrayUnAccessData(FSavedPacket);
end;
if Active then ClearSavedPacket;
end;
end;
procedure TClientDataSet.ReadDataPacket(Stream: TStream; ReadSize: Boolean);
var
Size: Integer;
DataPtr: Pointer;
VarBound: TVarArrayBound;
begin
RCS;
if ReadSize then
Stream.ReadBuffer(Size, SizeOf(Size)) else
Size := Stream.Size - Stream.Position;
if Size > 0 then
begin
ClearSavedPacket;
FillChar(VarBound, SizeOf(VarBound), 0);
VarBound.ElementCount := Size;
FSavedPacket := TDataPacket(SafeArrayCreate(varByte, 1, VarBound));
try
SafeArrayAccessData(FSavedPacket, DataPtr);
try
Stream.Read(DataPtr^, Size);
finally
SafeArrayUnAccessData(FSavedPacket);
end;
except
ClearSavedPacket;
raise;
end;
end;
end;
procedure TClientDataSet.LoadFromStream(Stream: TStream);
begin
Close;
ReadDataPacket(Stream, False);
Open;
end;
procedure TClientDataSet.SaveToStream(Stream: TStream; Format: TDataPacketFormat = dfBinary);
begin
WriteDataPacket(Stream, False, (Format=dfXML));
end;
procedure TClientDataSet.LoadFromFile(const FileName: string = '');
var
Stream: TStream;
begin
Close;
if FileName = '' then
Stream := TFileStream.Create(Self.FileName, fmOpenRead) else
Stream := TFileStream.Create(FileName, fmOpenRead);
try
FOpeningFile := True;
try
LoadFromStream(Stream);
finally
FOpeningFile := False;
end;
finally
Stream.Free;
end;
end;
procedure TClientDataSet.SaveToFile(const FileName: string = '';
Format: TDataPacketFormat = dfBinary);
var
Stream: TStream;
begin
if FileName = '' then
Stream := TFileStream.Create(Self.FileName, fmCreate) else
Stream := TFileStream.Create(FileName, fmCreate);
try
if LowerCase(ExtractFileExt(FileName)) = '.xml' then
Format := dfXML;
SaveToStream(Stream, Format);
finally
Stream.Free;
end;
end;
procedure TClientDataSet.SetLogChanges(Value: Boolean);
begin
CheckBrowseMode;
Check(FDSBase.SetProp(dspropLOGCHANGES, Integer(Value)));
end;
function TClientDataSet.GetLogChanges: Boolean;
var
LogChanges: Integer;
begin
CheckBrowseMode;
Check(FDSBase.GetProp(dspropLOGCHANGES, @LogChanges));
Result := Boolean(LogChanges);
end;
function TClientDataSet.GetCanModify: Boolean;
begin
Result := FCanModify and not ReadOnly;
end;
procedure TClientDataSet.SetReadOnly(Value: Boolean);
begin
FReadOnly := Value;
if Assigned(FDSBase) then
begin
Check(FDSBase.SetProp(dspropREADONLY, Integer(Value)));
FCanModify := not Value;
end;
end;
function TClientDataSet.ConstraintsDisabled: Boolean;
begin
Result := FConstDisableCount > 0;
end;
procedure TClientDataSet.DisableConstraints;
begin
if FConstDisableCount = 0 then
Check(FDSBase.SetProp(dspropCONSTRAINTS_DISABLED, Longint(True)));
Inc(FConstDisableCount);
end;
procedure TClientDataSet.EnableConstraints;
begin
if FConstDisableCount <> 0 then
begin
Dec(FConstDisableCount);
if FConstDisableCount = 0 then
Check(FDSBase.SetProp(dspropCONSTRAINTS_DISABLED, Longint(False)));
end;
end;
{ Record Functions }
procedure TClientDataSet.InitBufferPointers(GetProps: Boolean);
var
CursorProps: DSProps;
begin
if GetProps then
begin
Check(FDSCursor.GetCursorProps(CursorProps));
BookmarkSize := CursorProps.iBookmarkSize;
SetLength(FLastParentBM, BookMarkSize);
FRecordSize := CursorProps.iRecBufSize;
end;
FRecInfoOfs := FRecordSize + CalcFieldsSize;
FBookmarkOfs := FRecInfoOfs + SizeOf(TRecInfo);
FAggGrpIndOfs := FBookmarkOfs + BookMarkSize;
FAggFieldsOfs := FAggGrpIndOfs + FAggGrpIndSize;
FRecBufSize := FAggFieldsOfs + FAggFieldsSize;
end;
function TClientDataSet.AllocRecordBuffer: PChar;
begin
Result := AllocMem(FRecBufSize);
end;
procedure TClientDataSet.FreeRecordBuffer(var Buffer: PChar);
begin
FreeMem(Buffer);
end;
procedure TClientDataSet.InternalInitRecord(Buffer: PChar);
begin
Check(FDSCursor.InitRecord(Buffer));
end;
procedure TClientDataSet.ClearCalcFields(Buffer: PChar);
begin
FillChar(Buffer[RecordSize], CalcFieldsSize, 0);
end;
procedure TClientDataSet.InitRecord(Buffer: PChar);
begin
inherited InitRecord(Buffer);
with PRecInfo(Buffer + FRecInfoOfs)^ do
begin
BookMarkFlag := bfInserted;
RecordNumber := -1;
Attribute := dsRecNew;
end;
end;
function TClientDataSet.GetRecord(Buffer: PChar; GetMode: TGetMode;
DoCheck: Boolean): TGetResult;
var
Status: DBResult;
begin
with FDSCursor do
begin
case GetMode of
gmNext:
begin
Status := MoveRelative(1);
if (Status = DBERR_EOF) and not ProviderEOF and FFetchOnDemand then
begin
MoveRelative(-1);
FetchMoreData(False);
Status := MoveRelative(1);
end;
end;
gmPrior: Status := MoveRelative(-1);
else
Status := DBERR_NONE;
end;
if Status = DBERR_NONE then
Status := GetCurrentRecord(Buffer);
case Status of
DBERR_NONE:
begin
if (AggFields.Count > 0) and AggregatesActive then
GetAggFieldData(Buffer);
with PRecInfo(Buffer + FRecInfoOfs)^ do
begin
BookmarkFlag := bfCurrent;
GetSequenceNumber(DWord(RecordNumber));
GetRecordAttribute(Attribute);
end;
GetCalcFields(Buffer);
Check(GetCurrentBookmark(Buffer + FBookmarkOfs));
Result := grOK;
end;
DBERR_BOF: Result := grBOF;
DBERR_EOF: Result := grEOF;
else
Result := grError;
if DoCheck then Check(Status);
end;
end;
end;
function TClientDataSet.GetCurrentRecord(Buffer: PChar): Boolean;
begin
if not IsEmpty and (GetBookmarkFlag(ActiveBuffer) = bfCurrent) then
begin
UpdateCursorPos;
Result := (FDSCursor.GetCurrentRecord(Buffer) = DBERR_NONE);
end else
Result := False;
end;
function TClientDataSet.GetRecordCount: Integer;
var
Status: DBResult;
begin
CheckActive;
if FParentDataSet <> nil then
FParentDataSet.UpdateCursorPos;
Status := FDSCursor.GetRecordCount(Result);
if Status <> DBERR_DETAILSNOTFETCHED then
Check(Status);
end;
function TClientDataSet.GetRecNo: Integer;
var
BufPtr: PChar;
begin
CheckActive;
if State = dsInternalCalc then
Result := -1
else
begin
if State = dsCalcFields then
BufPtr := CalcBuffer else
BufPtr := ActiveBuffer;
Result := PRecInfo(BufPtr + FRecInfoOfs).RecordNumber;
end;
end;
procedure TClientDataSet.SetRecNo(Value: Integer);
begin
CheckBrowseMode;
if Value <> RecNo then
begin
DoBeforeScroll;
Check(FDSCursor.MoveToSeqNo(Value));
Resync([rmCenter]);
DoAfterScroll;
end;
end;
function TClientDataSet.GetRecordSize: Word;
begin
Result := FRecordSize;
end;
function TClientDataSet.GetActiveRecBuf(var RecBuf: PChar): Boolean;
function GetOriginalBuffer: PChar;
begin
UpdateCursorPos;
Result := TempBuffer;
if FDSCursor.GetProp(curpropGETORG_RECBUF, Result) <> DBERR_NONE then
Result := nil;
end;
begin
case State of
dsBlockRead,
dsBrowse: if IsEmpty then RecBuf := nil else RecBuf := ActiveBuffer;
dsEdit, dsInsert: RecBuf := ActiveBuffer;
dsSetKey: RecBuf := PChar(FKeyBuffer) + SizeOf(TKeyBuffer);
dsCalcFields,
dsInternalCalc: RecBuf := CalcBuffer;
dsFilter: RecBuf := FFilterBuffer;
dsNewValue: RecBuf := FNewValueBuffer;
dsOldValue: if FOldValueBuffer <> nil then
RecBuf := FOldValueBuffer else
RecBuf := GetOriginalBuffer;
dsCurValue: RecBuf := FCurValueBuffer;
dsInActive: RecBuf := nil;
else
RecBuf := nil;
end;
Result := RecBuf <> nil;
end;
function TClientDataSet.GetChangeCount: Integer;
begin
if Active then
Check(FDSBase.GetProp(dspropNOOFCHANGES, @Result)) else
Result := 0;
end;
function TClientDataSet.UpdateStatus: TUpdateStatus;
var
BufPtr: PChar;
Attr: Byte;
begin
CheckActive;
if State = dsInternalCalc then
Result := usUnModified
else
begin
if State = dsCalcFields then
BufPtr := CalcBuffer else
BufPtr := ActiveBuffer;
Attr := PRecInfo(BufPtr + FRecInfoOfs).Attribute;
if (Attr and dsRecModified) <> 0 then
Result := usModified
else if (Attr and dsRecDeleted) <> 0 then
Result := usDeleted
else if (Attr and dsRecNew) <> 0 then
Result := usInserted
else
Result := usUnModified;
end;
end;
{ Field Related }
procedure TClientDataSet.AddFieldDesc(FieldDescs: TFieldDescList;
var DescNo: Integer; var FieldID: Integer; FieldDefs: TFieldDefs);
var
FPrecision, I: Integer;
FType: TFieldType;
FSize: Integer;
FName: string;
FieldDesc: DSFLDDesc;
begin
FieldDesc := FieldDescs[DescNo];
Inc(DescNo);
with FieldDesc do
begin
if ((fldAttrLINK and iFldAttr) <> 0) then
begin
Inc(FieldID);
Exit;
end;
FName := szName;
I := 0;
while FieldDefs.IndexOf(FName) >= 0 do
begin
Inc(I);
FName := Format('%s_%d', [szName, I]);
end;
if iFldType < MAXLOGFLDTYPES then
FType := DataTypeMap[iFldType] else
if iFldType = fldUNICODE then
FType := ftWideString else
FType := ftUnknown;
FSize := 0;
FPrecision := 0;
case iFldType of
fldZSTRING, fldBYTES, fldVARBYTES, fldADT, fldArray:
begin
FSize := iUnits1;
if iFldSubType = fldstGuid then
FType := ftGuid;
end;
fldUNICODE:
FSize := iUnits1 div 2;
fldINT16, fldUINT16:
if iFldLen <> 2 then FType := ftUnknown;
fldINT32:
if iFldSubType = fldstAUTOINC then FType := ftAutoInc;
fldFLOAT:
if iFldSubType = fldstMONEY then FType := ftCurrency;
fldBCD:
begin
FSize := Abs(iUnits2);
FPrecision := iUnits1;
end;
fldBLOB:
begin
FSize := iUnits1;
if ( (iFldSubType >= fldstMEMO) and (iFldSubType <= fldstTYPEDBINARY))
or (iFldSubType = fldstHMEMO ) or ( iFldSubType = fldstHBINARY) then
FType := BlobTypeMap[iFldSubType];
end;
fldTABLE:
if iFldSubType = fldstREFERENCE then FType := ftReference;
end;
if FType <> ftUnknown then
with FieldDefs.AddFieldDef do
begin
FieldNo := FieldID;
Inc(FieldID);
Name := FName;
DataType := FType;
Size := FSize;
Precision := FPrecision;
Attributes := TFieldAttributes(Byte(iFldAttr));
if iFldSubType = fldstFIXED then
Attributes := Attributes + [faFixed];
InternalCalcField := bCalculated;
case FType of
ftADT:
for I := 0 to iUnits1 - 1 do
AddFieldDesc(FieldDescs, DescNo, FieldID, ChildDefs);
ftArray:
begin
I := FieldID;
AddFieldDesc(FieldDescs, DescNo, I, ChildDefs);
Inc(FieldID, iUnits2);
end;
end; { case }
end;
end;
end;
function TClientDataSet.GetFieldData(Field: TField; Buffer: Pointer): Boolean;
var
IsBlank: LongBool;
RecBuf: PChar;
begin
Result := False;
if GetActiveRecBuf(RecBuf) then
with Field do
if FieldKind in [fkData, fkInternalCalc] then
begin
Check(FDSCursor.GetField(RecBuf, FieldNo, Buffer, IsBlank));
Result := not IsBlank;
end else
if State in [dsBrowse, dsEdit, dsInsert, dsCalcFields] then
begin
Inc(RecBuf, FRecordSize + Offset);
Result := Boolean(RecBuf[0]);
if Result and (Buffer <> nil) then
Move(RecBuf[1], Buffer^, DataSize);
end;
end;
function TClientDataSet.GetFieldData(FieldNo: Integer; Buffer: Pointer): Boolean;
var
RecBuf: PChar;
IsBlank: LongBool;
begin
Result := GetActiveRecBuf(RecBuf);
if Result then
begin
Check(FDSCursor.GetFIeld(RecBuf, FieldNo, Buffer, IsBlank));
Result := not IsBlank;
end;
end;
function TClientDataSet.GetStateFieldValue(State: TDataSetState; Field: TField): Variant;
function CheckNotChanged(Buffer: PChar): Variant;
var
IsBlank: Integer;
begin
if (Buffer = nil) then
IsBlank := BLANK_NOTCHANGED else
Check(FDSCursor.GetField(Buffer, Field.FieldNo, nil, LongBool(IsBlank)));
if IsBlank = BLANK_NOTCHANGED then
Result := UnAssigned
else if IsBlank = BLANK_NULL then
Result := Null else
Result := inherited GetStateFieldValue(State, Field);
end;
begin
case State of
dsNewValue:
if FNewValueBuffer = nil then
begin
FNewValueBuffer := ActiveBuffer;
try
Result := CheckNotChanged(FNewValueBuffer);
finally
FNewValueBuffer := nil;
end;
end else
Result := CheckNotChanged(FNewValueBuffer);
dsCurValue: Result := CheckNotChanged(FCurValueBuffer);
else
Result := inherited GetStateFieldValue(State, Field);
end;
end;
procedure TClientDataSet.SetFieldData(Field: TField; Buffer: Pointer);
var
RecBuf: PChar;
begin
with Field do
begin
if not (State in dsWriteModes) then DatabaseError(SNotEditing, Self);
if (State = dsSetKey) and ((FieldNo < 0) or (FIndexFieldCount > 0) and
not IsIndexField) then DatabaseErrorFmt(SNotIndexField, [DisplayName]);
GetActiveRecBuf(RecBuf);
if FieldKind in [fkData, fkInternalCalc] then
begin
if ReadOnly and not (State in [dsSetKey, dsFilter]) then
DatabaseErrorFmt(SFieldReadOnly, [DisplayName]);
Validate(Buffer);
if State in [dsEdit, dsInsert] then
Check(FDSCursor.VerifyField(FieldNo, Buffer));
Check(FDSCursor.PutField(RecBuf, FieldNo, Buffer));
if FAggFieldsUpdated <> nil then
FAggFieldsUpdated[FieldNo-1] := True;
end else
begin
if State = dsInternalCalc then Exit;
Inc(RecBuf, FRecordSize + Offset);
Boolean(RecBuf[0]) := LongBool(Buffer);
if Boolean(RecBuf[0]) then Move(Buffer^, RecBuf[1], DataSize);
end;
if not (State in [dsCalcFields, dsInternalCalc, dsFilter, dsNewValue]) then
DataEvent(deFieldChange, Longint(Field));
end;
end;
function TClientDataSet.CreateBlobStream(Field: TField; Mode: TBlobStreamMode): TStream;
var
Status: DBResult;
BlobLen: DWord;
Buffer: PChar;
begin
if GetActiveRecBuf(Buffer) then
begin
Status := FDSCursor.GetBlobLen(Buffer, Field.FieldNo, BlobLen);
if (Status = DBERR_BLOBNOTFETCHED) and FetchOnDemand then
FetchBlobs;
end;
Result := TClientBlobStream.Create(Field as TBlobField, Mode);
end;
procedure TClientDataSet.RefreshInternalCalcFields(Buffer: PChar);
begin
CalculateFields(Buffer);
end;
function TClientDataSet.CalcFieldsCallBack(RecBuf: PChar): DBResult;
var
SaveState: TDataSetState;
begin
if State <> dsInactive then
try
SaveState := SetTempState(dsInternalCalc);
try
CalculateFields(RecBuf);
finally
RestoreState(SaveState);
end;
except
end;
Result := 0;
end;
procedure TClientDataSet.DataConvert(Field: TField; Source, Dest: Pointer;
ToNative: Boolean);
begin
if Field.DataType = ftWideString then
begin
if ToNative then
begin
Word(Dest^) := Length(PWideString(Source)^)*2;
Move(PWideChar(Source^)^, (PWideChar(Dest)+1)^, Word(Dest^));
end else
PWideChar(Dest^) := SysAllocStringLen(PWideChar(PChar(Source)+2), Word(Source^) div 2);
end else
inherited;
end;
{ Navigation / Editing }
procedure TClientDataSet.InternalFirst;
begin
Check(FDSCursor.MoveToBOF);
end;
procedure TClientDataSet.InternalLast;
begin
CheckProviderEOF;
Check(FDSCursor.MoveToEOF);
end;
procedure TClientDataSet.InternalPost;
begin
if State = dsEdit then
Check(FDSCursor.ModifyRecord(ActiveBuffer)) else
Check(FDSCursor.InsertRecord(ActiveBuffer));
if AggregatesActive then
DoAggUpdates(State = dsEdit);
end;
procedure TClientDataSet.InternalCancel;
begin
if BlobFieldCount > 0 then
FDSBase.ReleaseBlobs(0);
end;
procedure TClientDataSet.InternalDelete;
begin
if doDisableDeletes in FDSOptions then
DatabaseError(SNoDeletesAllowed, Self);
Check(DSCursor.DeleteRecord);
if AggregatesActive then
DoAggUpdates(False);
end;
procedure TClientDataSet.InternalEdit;
begin
if doDisableEdits in FDSOptions then
DatabaseError(SNoEditsAllowed, Self);
end;
procedure TClientDataSet.DoBeforeInsert;
begin
if doDisableInserts in FDSOptions then
DatabaseError(SNoInsertsAllowed, Self);
inherited DoBeforeInsert;
if (DataSetField <> nil) then
begin
{ Force inserted master to post before allowing insert on nested dataset }
if DataSetField.DataSet.State = dsInsert then
DataSetField.DataSet.Post;
end;
end;
procedure TClientDataSet.InternalInsert;
begin
DSCursor.SetProp(curpropSETCRACK, 0);
CursorPosChanged;
end;
procedure TClientDataSet.Post;
var
CursorProps: DSProps;
begin
Check(FDSBase.GetProps(CursorProps));
if CursorProps.bDelta and (State in [dsEdit, dsInsert]) then
begin
UpdateRecord;
InternalPost;
SetState(dsBrowse);
end else
begin
inherited Post;
if State = dsSetKey then
PostKeyBuffer(True);
end;
end;
procedure TClientDataSet.Cancel;
begin
inherited Cancel;
if State = dsSetKey then
PostKeyBuffer(False);
end;
procedure TClientDataSet.InternalAddRecord(Buffer: Pointer; Append: Boolean);
begin
if Append then FDSCursor.MoveToEOF;
Check(FDSCursor.InsertRecord(Buffer));
end;
procedure TClientDataSet.InternalGotoBookmark(Bookmark: TBookmark);
begin
Check(FDSCursor.MoveToBookmark(Bookmark));
end;
procedure TClientDataSet.InternalSetToRecord(Buffer: PChar);
begin
InternalGotoBookmark(Buffer + FBookmarkOfs);
end;
function TClientDataSet.GetBookmarkFlag(Buffer: PChar): TBookmarkFlag;
begin
Result := PRecInfo(Buffer + FRecInfoOfs).BookmarkFlag;
end;
procedure TClientDataSet.SetBookmarkFlag(Buffer: PChar; Value: TBookmarkFlag);
begin
PRecInfo(Buffer + FRecInfoOfs).BookmarkFlag := Value;
end;
procedure TClientDataSet.GetBookmarkData(Buffer: PChar; Data: Pointer);
begin
Move(Buffer[FBookmarkOfs], Data^, BookmarkSize);
end;
procedure TClientDataSet.SetBookmarkData(Buffer: PChar; Data: Pointer);
begin
Move(Data^, ActiveBuffer[FBookmarkOfs], BookmarkSize);
end;
function TClientDataSet.CompareBookmarks(Bookmark1, Bookmark2: TBookmark): Integer;
const
RetCodes: array[Boolean, Boolean] of ShortInt = ((2, -1),(1, 0));
begin
{ Check for uninitialized bookmarks }
Result := RetCodes[Bookmark1 = nil, Bookmark2 = nil];
if Result = 2 then
begin
Check(FDSCursor.CompareBookmarks(Bookmark1, Bookmark2, Result));
if Result = 2 then Result := 0;
end;
end;
function TClientDataSet.BookmarkValid(Bookmark: TBookmark): Boolean;
begin
Result := FDSCursor <> nil;
if Result then
begin
CursorPosChanged;
Result := (FDSCursor.MoveToBookmark(Bookmark) = DBERR_NONE) and
(FDSCursor.GetCurrentRecord(nil) = DBERR_NONE);
end;
end;
procedure TClientDataSet.SyncCursors(Cursor1, Cursor2: IDSCursor);
var
BM: DSBOOKMRK;
begin
Cursor2.GetCurrentBookmark(@BM);
Cursor1.MoveToBookmark(@BM);
end;
function TClientDataSet.UndoLastChange(FollowChange: Boolean): Boolean;
begin
Cancel;
CheckBrowseMode;
UpdateCursorPos;
Result := (FDSCursor.UndoLastChange(FollowChange) = DBERR_NONE);
if Result then
begin
if FollowChange then CursorPosChanged;
Resync([]);
end;
end;
procedure TClientDataSet.RevertRecord;
begin
Cancel;
CheckBrowseMode;
UpdateCursorPos;
Check(FDSCursor.RevertRecord);
Resync([]);
end;
function TClientDataSet.GetSavePoint: Integer;
begin
CheckBrowseMode;
FDSBase.GetProp(dspropGETSAVEPOINT, @Result);
end;
procedure TClientDataSet.SetSavePoint(Value: Integer);
begin
Cancel;
CheckBrowseMode;
UpdateCursorPos;
Check(FDSBase.RollBack(Value));
CursorPosChanged;
Resync([]);
end;
procedure TClientDataSet.CancelUpdates;
begin
SetSavePoint(0);
end;
{ Indexes }
procedure TClientDataSet.UpdateIndexDefs;
var
I: Integer;
CursorProps: DSProps;
IndexDescs: array of DSIDXDesc;
Opts: TIndexOptions;
IdxName, Flds: string;
DescFlds, CaseInsFlds: string;
begin
if (csDesigning in ComponentState) and (IndexDefs.Count > 0) then Exit;
if Active and not IndexDefs.Updated then
begin
FieldDefs.Update;
Check(FDSCursor.GetCursorProps(CursorProps));
SetLength(IndexDescs, CursorProps.iIndexes);
IndexDefs.Clear;
Check(FDSBase.GetIndexDescs(PDSIDXDesc(IndexDescs)));
for I := 0 to CursorProps.iIndexes - 1 do
begin
DecodeIndexDesc(IndexDescs[I], IdxName, Flds, DescFlds, CaseInsFlds, Opts);
with IndexDefs.AddIndexDef do
begin
Name := IdxName;
Fields := Flds;
DescFields := DescFlds;
CaseInsFields := CaseInsFlds;
Options := Opts;
end;
end;
IndexDefs.Updated := True;
end;
end;
procedure TClientDataSet.DecodeIndexDesc(const IndexDesc: DSIDXDesc;
var Name, Fields, DescFields, CaseInsFields: string; var Options: TIndexOptions);
procedure ConcatField(var FieldList: string; const FieldName: string);
begin
if FieldList = '' then
FieldList := FieldName else
FieldList := Format('%s;%s', [FieldList, FieldName]);
end;
procedure CheckOption(const FieldList: string; var OptionFields: string;
Option: TIndexOption);
begin
if (FieldList <> '') and (OptionFields = FieldList) then
begin
Include(Options, Option);
OptionFields := '';
end;
end;
var
I: Integer;
FieldName: string;
begin
FieldDefList.Update;
with IndexDesc do
begin
Name := szName;
Fields := '';
DescFields := '';
CaseInsFields := '';
for I := 0 to iFields - 1 do
begin
if iKeyFields[I] <= FieldDefList.Count then
begin
FieldName := FieldDefList.Strings[iKeyFields[I] - 1];
ConcatField(Fields, FieldName);
end else
FieldName := '';
if bDescending[I] then
ConcatField(DescFields, FieldName);
if bCaseInsensitive[I] then
ConcatField(CaseInsFields, FieldName);
end;
Options := [];
if bUnique then Include(Options, ixUnique);
CheckOption(Fields, DescFields, ixDescending);
CheckOption(Fields, CaseInsFields, ixCaseInsensitive);
end;
end;
procedure TClientDataSet.GetIndexNames(List: TStrings);
begin
IndexDefs.Update;
IndexDefs.GetItemNames(List);
end;
function TClientDataSet.GetIndexDefs: TIndexDefs;
begin
if FIndexDefs = nil then
FIndexDefs := TIndexDefs.Create(Self);
Result := FIndexDefs;
end;
procedure TClientDataSet.SetIndexDefs(Value: TIndexDefs);
begin
IndexDefs.Assign(Value);
end;
procedure TClientDataSet.GetIndexInfo(IndexName: string);
var
Index: Integer;
IndexDesc: DSIDXDesc;
begin
if FDSCursor.GetIndexDescs(True, IndexDesc) = 0 then
begin
FIndexFieldCount := IndexDesc.iFields;
FIndexFieldMap := IndexDesc.iKeyFields;
FKeySize := IndexDesc.iKeyLen;
end;
Index := IndexDefs.IndexOf(IndexName);
if Index <> -1 then
FIndexGroupingLevel := IndexDefs[Index].GroupingLevel else
FIndexGroupingLevel := 0;
end;
procedure TClientDataSet.SwitchToIndex(const IndexName: string);
var
Status: DBResult;
IndexDesc: DSIDXDesc;
begin
ResetCursorRange;
Status := FDSCursor.UseIndexOrder(PChar(IndexName));
if Status <> DBERR_NONE then
begin
if Status = DBERR_NOSUCHINDEX then
begin
with IndexDefs.Find(IndexName) do
begin
EncodeIndexDesc(IndexDesc, Name, Fields, DescFields, CaseInsFields, Options);
Check(FDSBase.CreateIndex(IndexDesc));
Check(FDSCursor.UseIndexOrder(PChar(IndexName)));
end;
end else
Check(Status);
end;
GetIndexInfo(IndexName);
end;
procedure TClientDataSet.SetIndex(const Value: string; FieldsIndex: Boolean);
begin
if Active then
begin
CheckBrowseMode;
UpdateCursorPos;
CheckProviderEOF;
if (FIndexName <> Value) or (FFieldsIndex <> FieldsIndex) then
begin
if FieldsIndex then
SortOnFields(FDSCursor, Value, False, False) else
SwitchToIndex(Value);
FIndexName := Value;
FFieldsIndex := FieldsIndex;
if FAggregatesActive then
begin
FAggFieldsInit := False;
ResetAllAggs(FAggregatesActive);
SetBufListSize(0);
InitBufferPointers(True);
try
SetBufListSize(BufferCount + 1);
except
SetState(dsInactive);
CloseCursor;
raise;
end;
end;
ResetCursorRange;
CheckMasterRange;
Resync([]);
end;
end;
FIndexName := Value;
FFieldsIndex := FieldsIndex;
end;
procedure TClientDataSet.EncodeIndexDesc(var IndexDesc: DSIDXDesc;
const Name, Fields, DescFields, CaseInsFields: string; Options: TIndexOptions);
function IndexFieldOfs(const FieldName: string): Integer;
var
FieldNo: Integer;
begin
FieldNo := FieldDefs.Find(FieldName).FieldNo;
for Result := 0 to IndexDesc.iFields - 1 do
if IndexDesc.iKeyfields[Result] = FieldNo then Exit;
DatabaseErrorFmt(SIndexFieldMissing, [FieldName], Self);
Result := -1;
end;
var
Pos: Integer;
Descending,
CaseInsensitive: Bool;
begin
FillChar(IndexDesc, SizeOf(IndexDesc), 0);
with IndexDesc do
begin
StrCopy(szName, PChar(Name));
bUnique := ixUnique in Options;
Descending := (ixDescending in Options) and (DescFields = '');
CaseInsensitive := (ixCaseInsensitive in Options) and (CaseInsFields = '');
Pos := 1;
while (Pos <= Length(Fields)) and (iFields < MAXKEYFIELDS) do
begin
iKeyFields[iFields] :=
FieldDefList.FieldByName(ExtractFieldName(Fields, Pos)).FieldNo;
bDescending[iFields] := Descending;
bCaseInsensitive[iFields] := CaseInsensitive;
Inc(iFields);
end;
Pos := 1;
while Pos <= Length(DescFields) do
bDescending[IndexFieldOfs(ExtractFieldName(DescFields, Pos))] := True;
Pos := 1;
while Pos <= Length(CaseInsFields) do
bCaseInsensitive[IndexFieldOfs(ExtractFieldName(CaseInsFields, Pos))] := True;
end;
end;
procedure TClientDataSet.AddIndex(const Name, Fields: string;
Options: TIndexOptions; const DescFields, CaseInsFields: string;
const GroupingLevel: Integer);
var
IndexDesc: DSIDXDesc;
IndexDef: TIndexDef;
begin
CheckBrowseMode;
FieldDefs.Update;
EncodeIndexDesc(IndexDesc, Name, Fields, DescFields, CaseInsFields, Options);
CursorPosChanged;
Check(FDSBase.CreateIndex(IndexDesc));
if GroupingLevel > 0 then
begin
IndexDefs.Update;
IndexDef := IndexDefs.Find(Name);
if IndexDef <> nil then
IndexDef.GroupingLevel := GroupingLevel;
end
else
IndexDefs.Updated := False;
end;
procedure TClientDataSet.DeleteIndex(const Name: string);
begin
CheckBrowseMode;
if AnsiCompareText(Name, IndexName) = 0 then IndexName := '';
Check(FDSBase.RemoveIndex(PChar(Name)));
IndexDefs.Updated := False;
end;
function TClientDataSet.GetIndexField(Index: Integer): TField;
var
FieldNo: Integer;
begin
if (Index < 0) or (Index >= FIndexFieldCount) then
DatabaseError(SFieldIndexError, Self);
FieldNo := FIndexFieldMap[Index];
Result := FieldByNumber(FieldNo);
if Result = nil then
DatabaseErrorFmt(SIndexFieldMissing, [FieldDefs[FieldNo - 1].Name], Self);
end;
function TClientDataSet.GetIsIndexField(Field: TField): Boolean;
var
I: Integer;
begin
Result := False;
with Field do
if FieldNo > 0 then
for I := 0 to FIndexFieldCount - 1 do
if FIndexFieldMap[I] = FieldNo then
begin
Result := True;
Exit;
end;
end;
function TClientDataSet.GetIndexName: string;
begin
if FFieldsIndex then Result := '' else Result := FIndexName;
end;
procedure TClientDataSet.SetIndexName(const Value: string);
begin
SetIndex(Value, False);
end;
procedure TClientDataSet.SetIndexField(Index: Integer; Value: TField);
begin
GetIndexField(Index).Assign(Value);
end;
function TClientDataSet.GetIndexFieldNames: string;
begin
if FFieldsIndex then Result := FIndexName else Result := '';
end;
procedure TClientDataSet.SetIndexFieldNames(const Value: string);
begin
SetIndex(Value, Value <> '');
end;
function TClientDataSet.GetIndexFieldCount: Integer;
begin
Result := FIndexFieldCount;
end;
procedure TClientDataSet.SortOnFields(Cursor: IDSCursor; const Fields: string;
CaseInsensitive, Descending: Boolean);
var
I: Integer;
FieldList: TList;
DescFlags, CaseFlags: DSKEYBOOL;
function GetFlags(Flag: Bool; var FlagArray: DSKEYBOOL): Pointer;
var
J: Integer;
begin
if not Flag then Result := nil else
begin
for J := 0 to FieldList.Count - 1 do
FlagArray[J] := True;
Result := @FlagArray;
end;
end;
begin
FieldList := TList.Create;
try
GetFieldList(FieldList, Fields);
for I := 0 to FieldList.Count - 1 do
if TField(FieldList[I]).FieldNo > 0 then
FieldList[I] := Pointer(TField(FieldList[I]).FieldNo) else
DatabaseError(SFieldIndexError, Self);
Check(Cursor.SortOnFields(FieldList.Count, PDWord(FieldList.List),
GetFlags(Descending, DescFlags), GetFlags(CaseInsensitive, CaseFlags)));
GetIndexInfo('');
finally
FieldList.Free;
end;
end;
{ Ranges / Keys }
procedure TClientDataSet.AllocKeyBuffers;
var
KeyIndex: TKeyIndex;
begin
try
for KeyIndex := Low(TKeyIndex) to High(TKeyIndex) do
FKeyBuffers[KeyIndex] := AllocMem(SizeOf(TKeyBuffer) + FRecordSize);
if Assigned(FCloneSource) then
for KeyIndex := Low(TKeyIndex) to High(TKeyIndex) do
Move(FCloneSource.FKeyBuffers[KeyIndex]^, FKeyBuffers[KeyIndex]^,
SizeOf(TKeyBuffer) + FRecordSize);
except
FreeKeyBuffers;
raise;
end;
end;
function TClientDataSet.GetKeyBuffer(KeyIndex: TKeyIndex): PKeyBuffer;
begin
Result := FKeyBuffers[KeyIndex];
end;
procedure TClientDataSet.FreeKeyBuffers;
var
KeyIndex: TKeyIndex;
begin
for KeyIndex := Low(TKeyIndex) to High(TKeyIndex) do
DisposeMem(FKeyBuffers[KeyIndex], SizeOf(TKeyBuffer) + FRecordSize);
end;
function TClientDataSet.InitKeyBuffer(Buffer: PKeyBuffer): PKeyBuffer;
begin
FillChar(Buffer^, SizeOf(TKeyBuffer) + FRecordSize, 0);
Check(FDSCursor.InitRecord(PChar(Buffer) + SizeOf(TKeyBuffer)));
Result := Buffer;
end;
procedure TClientDataSet.CheckSetKeyMode;
begin
if State <> dsSetKey then DatabaseError(SNotEditing, Self);
end;
function TClientDataSet.SetCursorRange: Boolean;
var
RangeStart, RangeEnd: PKeyBuffer;
StartKey, EndKey: PChar;
begin
Result := False;
if not (
BuffersEqual(FKeyBuffers[kiRangeStart], FKeyBuffers[kiCurRangeStart],
SizeOf(TKeyBuffer) + FRecordSize) and
BuffersEqual(FKeyBuffers[kiRangeEnd], FKeyBuffers[kiCurRangeEnd],
SizeOf(TKeyBuffer) + FRecordSize)) then
begin
CheckProviderEOF;
RangeStart := FKeyBuffers[kiRangeStart];
RangeEnd := FKeyBuffers[kiRangeEnd];
StartKey := PChar(RangeStart) + SizeOf(TKeyBuffer);
if not RangeStart.Modified then
StartKey := nil;
EndKey := PChar(RangeEnd) + SizeOf(TKeyBuffer);
if not RangeEnd.Modified then
EndKey := nil;
Check(FDSCursor.SetRange(RangeStart.FieldCount, StartKey,
not RangeStart.Exclusive, EndKey, not RangeEnd.Exclusive));
Move(FKeyBuffers[kiRangeStart]^, FKeyBuffers[kiCurRangeStart]^,
SizeOf(TKeyBuffer) + FRecordSize);
Move(FKeyBuffers[kiRangeEnd]^, FKeyBuffers[kiCurRangeEnd]^,
SizeOf(TKeyBuffer) + FRecordSize);
DestroyLookupCursor;
Result := True;
end;
end;
function TClientDataSet.ResetCursorRange: Boolean;
begin
Result := False;
if FKeyBuffers[kiCurRangeStart].Modified or
FKeyBuffers[kiCurRangeEnd].Modified then
begin
Check(FDSCursor.DropRange);
InitKeyBuffer(FKeyBuffers[kiCurRangeStart]);
InitKeyBuffer(FKeyBuffers[kiCurRangeEnd]);
DestroyLookupCursor;
Result := True;
end;
end;
procedure TClientDataSet.SetLinkRanges(MasterFields: TList);
var
I: Integer;
SaveState: TDataSetState;
begin
SaveState := SetTempState(dsSetKey);
try
FKeyBuffer := InitKeyBuffer(FKeyBuffers[kiRangeStart]);
FKeyBuffer^.Modified := True;
for I := 0 to MasterFields.Count - 1 do
GetIndexField(I).Assign(TField(MasterFields[I]));
FKeyBuffer^.FieldCount := MasterFields.Count;
finally
RestoreState(SaveState);
end;
Move(FKeyBuffers[kiRangeStart]^, FKeyBuffers[kiRangeEnd]^,
SizeOf(TKeyBuffer) + FRecordSize);
end;
procedure TClientDataSet.SetKeyBuffer(KeyIndex: TKeyIndex; Clear: Boolean);
begin
CheckBrowseMode;
FKeyBuffer := FKeyBuffers[KeyIndex];
Move(FKeyBuffer^, FKeyBuffers[kiSave]^, SizeOf(TKeyBuffer) + FRecordSize);
if Clear then InitKeyBuffer(FKeyBuffer);
SetState(dsSetKey);
SetModified(FKeyBuffer.Modified);
DataEvent(deDataSetChange, 0);
end;
procedure TClientDataSet.PostKeyBuffer(Commit: Boolean);
begin
DataEvent(deCheckBrowseMode, 0);
if Commit then
FKeyBuffer.Modified := Modified else
Move(FKeyBuffers[kiSave]^, FKeyBuffer^, SizeOf(TKeyBuffer) + FRecordSize);
SetState(dsBrowse);
DataEvent(deDataSetChange, 0);
end;
function TClientDataSet.GetKeyExclusive: Boolean;
begin
CheckSetKeyMode;
Result := FKeyBuffer.Exclusive;
end;
procedure TClientDataSet.SetKeyExclusive(Value: Boolean);
begin
CheckSetKeyMode;
FKeyBuffer.Exclusive := Value;
end;
function TClientDataSet.GetKeyFieldCount: Integer;
begin
CheckSetKeyMode;
Result := FKeyBuffer.FieldCount;
end;
procedure TClientDataSet.SetKeyFieldCount(Value: Integer);
begin
CheckSetKeyMode;
FKeyBuffer.FieldCount := Value;
end;
procedure TClientDataSet.SetKeyFields(KeyIndex: TKeyIndex;
const Values: array of const);
var
I: Integer;
SaveState: TDataSetState;
begin
DoBeforeScroll;
if FIndexFieldCount = 0 then DatabaseError(SNoFieldIndexes, Self);
SaveState := SetTempState(dsSetKey);
try
FKeyBuffer := InitKeyBuffer(FKeyBuffers[KeyIndex]);
if FParentDataSet = nil then
begin
for I := 0 to High(Values) do GetIndexField(I).AssignValue(Values[I]);
FKeyBuffer^.FieldCount := High(Values) + 1;
end else
begin
{ Skip the linking field for nested datasets }
for I := 0 to High(Values) do GetIndexField(I+1).AssignValue(Values[I]);
FKeyBuffer^.FieldCount := High(Values);
end;
FKeyBuffer^.Modified := Modified;
finally
RestoreState(SaveState);
end;
DoAfterScroll;
end;
function TClientDataSet.FindKey(const KeyValues: array of const): Boolean;
begin
CheckBrowseMode;
SetKeyFields(kiLookup, KeyValues);
Result := GotoKey;
end;
procedure TClientDataSet.FindNearest(const KeyValues: array of const);
begin
CheckBrowseMode;
SetKeyFields(kiLookup, KeyValues);
GotoNearest
end;
function TClientDataSet.GotoKey: Boolean;
var
KeyBuffer: PKeyBuffer;
RecBuffer: PChar;
begin
CheckBrowseMode;
CursorPosChanged;
CheckProviderEOF;
KeyBuffer := FKeyBuffers[kiLookup];
RecBuffer := PChar(KeyBuffer) + SizeOf(TKeyBuffer);
Result := FDSCursor.GetRecordForKey(KeyBuffer.FieldCount, 0, RecBuffer, nil) = 0;
if Result then Resync([rmExact, rmCenter]);
end;
procedure TClientDataSet.GotoNearest;
var
SearchCond: DBSearchCond;
KeyBuffer: PKeyBuffer;
RecBuffer: PChar;
begin
CheckBrowseMode;
CheckProviderEOF;
KeyBuffer := FKeyBuffers[kiLookup];
RecBuffer := PChar(KeyBuffer) + SizeOf(TKeyBuffer);
if KeyBuffer^.Exclusive then
SearchCond := keySEARCHGT else
SearchCond := keySEARCHGEQ;
Check(FDSCursor.MoveToKey(SearchCond, KeyBuffer.FieldCount, 0, RecBuffer));
Resync([rmCenter]);
end;
procedure TClientDataSet.SetKey;
begin
SetKeyBuffer(kiLookup, True);
end;
procedure TClientDataSet.EditKey;
begin
SetKeyBuffer(kiLookup, False);
end;
procedure TClientDataSet.ApplyRange;
begin
CheckBrowseMode;
if SetCursorRange then First;
end;
procedure TClientDataSet.CancelRange;
begin
CheckBrowseMode;
UpdateCursorPos;
if ResetCursorRange then Resync([]);
end;
procedure TClientDataSet.SetRange(const StartValues, EndValues: array of const);
begin
CheckBrowseMode;
SetKeyFields(kiRangeStart, StartValues);
SetKeyFields(kiRangeEnd, EndValues);
ApplyRange;
end;
procedure TClientDataSet.SetRangeEnd;
begin
SetKeyBuffer(kiRangeEnd, True);
end;
procedure TClientDataSet.SetRangeStart;
begin
SetKeyBuffer(kiRangeStart, True);
end;
procedure TClientDataSet.EditRangeEnd;
begin
SetKeyBuffer(kiRangeEnd, False);
end;
procedure TClientDataSet.EditRangeStart;
begin
SetKeyBuffer(kiRangeStart, False);
end;
{ Master / Detail }
procedure TClientDataSet.CheckDetailRecords;
var
I, RecCount: Integer;
MasterValues: OleVariant;
Status: DBResult;
begin
Status := FDSCursor.GetRecordCount(RecCount);
if DataSetField <> nil then
begin
if (Status = DBERR_DETAILSNOTFETCHED) and FetchOnDemand then
FParentDataSet.FetchDetails;
end else
begin
if (RecCount = 0) and HasAppServer and not ProviderEOF and
(FPacketRecords = 0) and not MasterSource.DataSet.IsEmpty and
(MasterSource.DataSet.State <> dsInsert) then
begin
MasterValues := VarArrayCreate([0, FMasterLink.Fields.Count - 1], varVariant);
for I := 0 to FMasterLink.Fields.Count - 1 do
with TField(FMasterLink.Fields[I]) do
MasterValues[I] := VarArrayOf([IndexFields[I].FieldName, Value]);
AddDataPacket(DoGetRecords(-1, RecCount, 0, '', MasterValues), False);
if Active then First;
end;
end;
end;
procedure TClientDataSet.CheckMasterRange;
begin
if FMasterLink.Active and (FMasterLink.Fields.Count > 0) then
begin
SetLinkRanges(FMasterLink.Fields);
SetCursorRange;
if FetchOnDemand then CheckDetailRecords;
end;
end;
procedure TClientDataSet.MasterChanged(Sender: TObject);
begin
CheckBrowseMode;
if DataSetField = nil then
begin
SetLinkRanges(FMasterLink.Fields);
ApplyRange;
if FetchOnDemand then CheckDetailRecords;
end else
begin
if FParentDataSet.State = dsInsert then
First
else if not CompareMem(FLastParentBM, @FParentDataSet.ActiveBuffer[FParentDataset.FBookmarkOfs], FParentDataSet.BookmarkSize) then
begin
if FetchOnDemand then CheckDetailRecords;
First;
Move(FParentDataSet.ActiveBuffer[FParentDataSet.FBookmarkOfs], FLastParentBM[0], FParentDataSet.BookmarkSize);
end else
begin
UpdateCursorPos;
Resync([]);
end;
end;
end;
procedure TClientDataSet.MasterDisabled(Sender: TObject);
begin
CancelRange;
end;
procedure TClientDataSet.SetDataSetField(const Value: TDataSetField);
begin
if Assigned(Value) then
begin
Close;
ProviderName := '';
RemoteServer := nil;
FileName := '';
end;
inherited;
end;
procedure TClientDataSet.Loaded;
begin
inherited Loaded;
if Active and Assigned(FSavedPacket) then Open;
end;
procedure TClientDataSet.ReadData(Stream: TStream);
begin
ReadDataPacket(Stream, True);
end;
procedure TClientDataSet.WriteData(Stream: TStream);
begin
WriteDataPacket(Stream, True);
end;
function TClientDataSet.GetDataSource: TDataSource;
begin
Result := FMasterLink.DataSource;
end;
procedure TClientDataSet.SetDataSource(Value: TDataSource);
begin
if (Value <> nil) and (DataSetField <> nil) then
DatabaseError(SNoNestedMasterSource, Self);
if IsLinkedTo(Value) then DatabaseError(SCircularDataLink, Self);
FMasterLink.DataSource := Value;
if not (csDesigning in ComponentState) or
(csLoading in ComponentState) then Exit;
if Assigned(Value) then
begin
if FPacketRecords = -1 then FPacketRecords := 0;
end else
begin
if FPacketRecords = 0 then FPacketRecords := -1;
end;
end;
function TClientDataSet.GetMasterFields: string;
begin
Result := FMasterLink.FieldNames;
end;
procedure TClientDataSet.SetMasterFields(const Value: string);
begin
FMasterLink.FieldNames := Value;
end;
procedure TClientDataSet.DoOnNewRecord;
var
I: Integer;
begin
if DataSetField = nil then
if FMasterLink.Active and (FMasterLink.Fields.Count > 0) then
for I := 0 to FMasterLink.Fields.Count - 1 do
IndexFields[I] := TField(FMasterLink.Fields[I]);
if (AggFields.Count > 0) and AggregatesActive then
GetAggFieldData(ActiveBuffer);
inherited DoOnNewRecord;
end;
procedure TClientDataSet.DefineProperties(Filer: TFiler);
function DataStored: Boolean;
begin
Result := Active and (DataSetField = nil) and not HasAppServer and
(ProviderName = '') and (FCloneSource = nil);
if Result and Assigned(Filer.Ancestor) then
with TClientDataSet(Filer.Ancestor) do
Result := not Active or HasAppServer or (ProviderName <> '') or
(DataSize <> Self.DataSize);
end;
begin
inherited DefineProperties(Filer);
Filer.DefineBinaryProperty('Data', ReadData, WriteData, DataStored);
end;
{ Filters }
procedure TClientDataSet.ActivateFilters;
begin
if Filter <> '' then
AddExprFilter(Filter, FilterOptions);
if Assigned(OnFilterRecord) then
AddFuncFilter;
end;
procedure TClientDataSet.DeactivateFilters;
begin
if FFuncFilter <> nil then
begin
FDSCursor.DropFilter(FFuncFilter);
FFuncFilter := nil;
end;
if FExprFilter <> nil then
begin
FDSCursor.DropFilter(FExprFilter);
FExprFilter := nil;
end;
end;
procedure TClientDataSet.AddExprFilter(const Expr: string; Options: TFilterOptions);
begin
if FExprFilter <> nil then FDSCursor.DropFilter(FExprFilter);
if Expr <> '' then
with TExprParser.Create(Self, Expr, Options, [poExtSyntax], '', nil, FieldTypeMap) do
try
CheckProviderEOF;
Check(FDSCursor.AddFilter(FilterData, DataSize, FExprFilter));
finally
Free;
end;
end;
function TClientDataSet.FilterCallback(RecBuf: PChar): Bool;
var
SaveState: TDataSetState;
Accept: Boolean;
begin
SaveState := SetTempState(dsFilter);
FFilterBuffer := RecBuf;
try
Accept := True;
OnFilterRecord(Self, Accept);
except
Application.HandleException(Self);
end;
RestoreState(SaveState);
Result := Accept;
end;
procedure TClientDataSet.AddFuncFilter;
begin
if FFuncFilter <> nil then FDSCursor.DropFilter(FFuncFilter);
CheckProviderEOF;
Check(FDSCursor.AddFilterCallBack(Integer(Self), @TClientDataSet.FilterCallback,
FFuncFilter));
end;
procedure TClientDataSet.SetFilterData(const Text: string; Options: TFilterOptions);
begin
if Active and Filtered then
begin
CheckBrowseMode;
if (Filter <> Text) or (FilterOptions <> Options) then
AddExprFilter(Text, Options);
DestroyLookupCursor;
First;
end;
inherited SetFilterText(Text);
inherited SetFilterOptions(Options);
end;
procedure TClientDataSet.SetFilterText(const Value: string);
begin
SetFilterData(Value, FilterOptions);
end;
procedure TClientDataSet.SetFilterOptions(Value: TFilterOptions);
begin
SetFilterData(Filter, Value);
end;
procedure TClientDataSet.SetOnFilterRecord(const Value: TFilterRecordEvent);
begin
if Active and Filtered then
begin
CheckBrowseMode;
if Assigned(OnFilterRecord) <> Assigned(Value) then
begin
if Assigned(Value) then
begin
inherited SetOnFilterRecord(Value);
AddFuncFilter;
end else
FDSCursor.DropFilter(FFuncFilter);
end;
DestroyLookupCursor;
First;
end;
inherited SetOnFilterRecord(Value);
end;
procedure TClientDataSet.SetFiltered(Value: Boolean);
begin
if Active then
begin
CheckBrowseMode;
if Filtered <> Value then
begin
DestroyLookupCursor;
FDSCursor.MoveToBOF;
if Value then ActivateFilters else DeactivateFilters;
inherited SetFiltered(Value);
end;
First;
end else
inherited SetFiltered(Value);
end;
procedure TClientDataSet.SetStatusFilter(const Value: TUpdateStatusSet);
var
StatusValues: Integer;
begin
CheckBrowseMode;
if Value <> [] then
begin
StatusValues := 0;
if usModified in Value then
StatusValues := dsRecModified;
if usInserted in Value then
StatusValues := StatusValues + dsRecNew;
if usDeleted in Value then
StatusValues := StatusValues + dsRecDeleted;
IndexName := szCHANGEINDEX;
Check(FDSBase.SetProp(dspropCHANGEINDEX_VIEW, StatusValues));
end else
begin
if IndexName = szCHANGEINDEX then
IndexName := szDEFAULT_ORDER;
Check(FDSBase.SetProp(dspropCHANGEINDEX_VIEW, 0));
end;
FStatusFilter := Value;
Resync([]);
end;
function TClientDataSet.FindRecord(Restart, GoForward: Boolean): Boolean;
var
Status: DBResult;
Cursor: IDSCursor;
begin
CheckBrowseMode;
SetFound(False);
UpdateCursorPos;
CursorPosChanged;
CheckProviderEOF;
DoBeforeScroll;
if not Filtered then
begin
if Restart then FFindCursor := nil;
if not Assigned(FFindCursor) then
begin
ActivateFilters;
try
FFindCursor := CreateDSCursor(FDSCursor)
finally
DeactivateFilters;
end;
end else
if not Restart then SyncCursors(FFindCursor, FDSCursor);
Cursor := FFindCursor;
end else
Cursor := FDSCursor;
if GoForward then
begin
if Restart then Check(Cursor.MoveToBOF);
Status := Cursor.MoveRelative(1);
end else
begin
if Restart then Check(Cursor.MoveToEOF);
Status := Cursor.MoveRelative(-1);
end;
if Cursor <> FDSCursor then
begin
SyncCursors(FDSCursor, FFindCursor);
Status := FDSCursor.GetCurrentRecord(nil);
end;
if Status = DBERR_NONE then
begin
Resync([rmExact, rmCenter]);
SetFound(True);
end;
Result := Found;
if Result then DoAfterScroll;
end;
procedure TClientDataSet.DestroyLookupCursor;
begin
FFindCursor := nil;
end;
function TClientDataSet.LocateRecord(const KeyFields: string;
const KeyValues: Variant; Options: TLocateOptions;
SyncCursor: Boolean): Boolean;
var
Fields: TList;
I: Integer;
Status: DBResult;
FilterOptions: TFilterOptions;
ExprParser: TExprParser;
ValStr, Expr: string;
Value: Variant;
begin
CheckBrowseMode;
CursorPosChanged;
CheckProviderEOF;
Fields := TList.Create;
try
GetFieldList(Fields, KeyFields);
Expr := '';
for i := 0 to Fields.Count - 1 do
begin
if (Fields.Count = 1) and not VarIsArray(KeyValues) then
Value := KeyValues else
Value := KeyValues[i];
case TField(Fields[i]).DataType of
ftString, ftFixedChar, ftWideString, ftGUID:
if (i = Fields.Count - 1) and (loPartialKey in Options) then
ValStr := Format('''%s*''',[VarToStr(Value)]) else
ValStr := Format('''%s''',[VarToStr(Value)]);
ftDate, ftTime, ftDateTime:
ValStr := Format('''%s''',[VarToStr(Value)]);
ftSmallint, ftInteger, ftWord, ftAutoInc, ftBoolean, ftFloat, ftCurrency, ftBCD:
ValStr := VarToStr(Value);
else
DatabaseErrorFmt(SBadFieldType, [TField(Fields[i]).FieldName]);
end;
if Expr <> '' then
Expr := Expr + ' and ';
if VarIsNull(Value) then
Expr := Expr + Format('[%s] IS NULL',[TField(Fields[i]).FieldName])
else
Expr := Expr + Format('[%s]=%s',[TField(Fields[i]).FieldName, ValStr]);
end;
FilterOptions := [];
if loCaseInsensitive in Options then
FilterOptions := [foCaseInsensitive];
if not (loPartialKey in Options) then
Include(FilterOptions, foNoPartialCompare);
ExprParser := TExprParser.Create(Self, Expr, FilterOptions, [], '', nil, FieldTypeMap);
try
FDSCursor.MoveToBOF;
Status := FDSCursor.LocateWithFilter(ExprParser.FilterData, ExprParser.DataSize);
if Status = DBERR_NONE then
FDSCursor.GetCurrentRecord(TempBuffer);
finally
ExprParser.Free;
end;
finally
Fields.Free;
end;
Result := Status = DBERR_NONE;
end;
function TClientDataSet.Lookup(const KeyFields: string; const KeyValues: Variant;
const ResultFields: string): Variant;
begin
Result := Null;
if LocateRecord(KeyFields, KeyValues, [], False) then
begin
SetTempState(dsCalcFields);
try
CalculateFields(TempBuffer);
Result := FieldValues[ResultFields];
finally
RestoreState(dsBrowse);
end;
end;
end;
function TClientDataSet.Locate(const KeyFields: string;
const KeyValues: Variant; Options: TLocateOptions): Boolean;
begin
DoBeforeScroll;
Result := LocateRecord(KeyFields, KeyValues, Options, True);
if Result then
begin
Resync([rmExact, rmCenter]);
DoAfterScroll;
end;
end;
procedure TClientDataSet.GotoCurrent(DataSet: TClientDataSet);
begin
CheckBrowseMode;
CheckProviderEOF;
DataSet.CheckActive;
BookMark := DataSet.BookMark;
end;
{ Aggregates }
function AggValueAsVariant(Agg: TAggregate; Buffer: Pointer): Variant;
var
C: Currency;
begin
case Agg.DataType of
ftInteger:
Result := LongInt(Buffer^);
ftSmallInt:
Result := SmallInt(Buffer^);
ftBoolean:
Result := WordBool(Buffer^);
ftFloat:
Result := Double(Buffer^);
ftBCD:
begin
BCDToCurr(TBcd(Buffer^), C);
Result := C;
end;
ftDate:
Result := VarFromDateTime(TDateTimeRec(Buffer^).Date - DateDelta);
ftTime:
Result := VarFromDateTime(TDateTimeRec(Buffer^).Time / MSecsPerDay);
ftDateTime:
Result := VarFromDateTime((TDateTimeRec(Buffer^).DateTime / MSecsPerDay) - DateDelta);
ftString, ftGUID:
Result := string(PChar(Buffer));
ftWideString:
with TVarData(Result) do
begin
VType := varOleStr;
VOleStr := SysAllocStringLen(PWideChar(PChar(Buffer)+2), Word(Buffer^) div 2);
end;
else
Result := Null;
end;
end;
procedure TClientDataSet.SetAggregates(Value: TAggregates);
begin
FAggregates.Assign(Value);
end;
procedure TClientDataSet.SetAggsActive(Value: Boolean);
begin
if FAggregatesActive <> Value then
begin
FAggregatesActive := Value;
if FAggregatesActive and Active then
begin
ResetAllAggs(FAggregatesActive);
if AggFields.Count > 0 then
begin
UpdateCursorPos;
Resync([]);
end;
end;
end;
end;
procedure TClientDataSet.ClearActiveAggs;
var
I: Integer;
begin
if FActiveAggLists <> nil then
for I:= 0 to FActiveAggLists.Count - 1 do
if FActiveAggLists[I] <> nil then TList(FActiveAggLists[I]).Free;
FActiveAggLists.Clear;
end;
procedure TClientDataSet.CloseAggs;
var
I: Integer;
Field: TAggregateField;
begin
for I := 0 to FAggregates.Count - 1 do
begin
if (FAggregates[I].AggHandle <> 0) and (FDSCursor <> nil) then
Check(FDSCursor.DropAggregate(FAggregates[I].AggHandle));
FAggregates[I].AggHandle := 0;
end;
for I := 0 to AggFields.Count - 1 do
begin
Field := AggFields[I] as TAggregateField;
if Field.Handle <> nil then
begin
TAggregate(Field.Handle).Free;
Field.Handle := nil;
end;
end;
FAggFieldsInit := False;
end;
procedure TClientDataSet.ResetGrouping;
function HasAggs(Level: Integer): Boolean;
var
I: Integer;
begin
Result := False;
for I := 0 to FAggregates.Count - 1 do
if TAggregate(FAggregates[I]).GroupingLevel = Level then
begin
Result := True;
Exit;
end;
end;
var
I: Integer;
Agg: TAggregate;
begin
FGroupingLevel := FMaxAggGroupingLevel;
if FIndexGroupingLevel > FGroupingLevel then
FGroupingLevel := FIndexGroupingLevel;
for I:= 1 to FGroupingLevel do
begin
if not HasAggs(I) then
begin
Agg := FAggregates.Add;
Agg.GroupingLevel := I;
Agg.IndexName := FIndexName;
Agg.Active := True;
Agg.Activate;
end;
end;
end;
procedure TClientDataSet.ResetAgg(Agg: TAggregate; DeleteFirst: Boolean);
var
I, J: Integer;
begin
if DeleteFirst then
for I:=0 to FActiveAggLists.Count - 1 do
begin
J := TList(FActiveAggLists.Items[I]).IndexOf(Agg);
if J <> -1 then
begin
with TList(FActiveAggLists.Items[I]) do
begin
Delete(J);
Pack;
Capacity := Count;
end;
Break;
end;
end;
if Agg.Active and ((AnsiCompareText(Agg.IndexName, FIndexName) = 0)
or (Agg.GroupingLevel = 0)) then
begin
if Agg.DataSet = nil then Agg.FDataSet := Self;
Agg.Activate;
Agg.FInUse := True;
if Agg.GroupingLevel > FMaxAggGroupingLevel then
FMaxAggGroupingLevel := Agg.GroupingLevel;
while FActiveAggLists.Count <= Agg.GroupingLevel do
FActiveAggLists.Add(TList.Create);
if Agg.Expression <> '' then
TList(FActiveAggLists.Items[Agg.GroupingLevel]).Add(Agg);
end else
begin
Agg.FInUse := False;
if Agg.AggHandle <> 0 then
begin
Check(DSCursor.DropAggregate(Agg.AggHandle));
Agg.AggHandle := 0;
end
end;
if FMaxAggGroupingLevel > GroupingLevel then
FGroupingLevel := FMaxAggGroupingLevel;
end;
procedure TClientDataSet.ResetAllAggs(Value: Boolean);
procedure CreateAggsFromAggFields;
var
I, MaxGrp: Integer;
Agg: TAggregate;
Field: TAggregateField;
begin
{ Link persistent aggregate fields with TAggregate objects }
MaxGrp := 0;
FAggFieldsSize := 0;
for I := 0 to AggFields.Count - 1 do
begin
Field := AggFields[I] as TAggregateField;
if (Field.GroupingLevel = 0) or (Field.IndexName = FIndexName) then
begin
if Field.GroupingLevel > MaxGrp then
MaxGrp := Field.GroupingLevel;
if Field.Handle = nil then
begin
Agg := TAggregate.Create(nil, Self);
Agg.Assign(Field);
if not Agg.Active and (Agg.Expression <> '') then
begin
Agg.Active := True;
Agg.Active := False;
end;
Field.Handle := Pointer(Agg);
Field.ResultType := Agg.DataType;
end else
begin
Agg := TAggregate(Field.Handle);
Field.ResultType := Agg.DataType;
end;
Agg.RecBufOfs := FAggFieldsSize;
Inc(FAggFieldsSize, Agg.DataSize + 1); { add one for null ind. }
end;
end;
FAggGrpIndSize := MaxGrp * Sizeof(TGroupPosInds);
FAggFieldsInit := True;
end;
var
I: Integer;
Agg: TAggregate;
Field: TAggregateField;
begin
if (csLoading in ComponentState) then Exit;
ClearActiveAggs;
if FAggFieldsUpdated = nil then
FAggFieldsUpdated := TBits.Create;
if AggFields.Count + FAggregates.Count = 0 then Exit;
FGroupingLevel := 0;
FMaxAggGroupingLevel := 0;
if not FAggFieldsInit then
CreateAggsFromAggFields;
if Assigned(DSCursor) and FAggregatesActive then
begin
for I := 0 to FAggregates.Count - 1 do
begin
Agg := FAggregates.GetItem(I);
if Value then
ResetAgg(Agg, False) else
Agg.FInUse := False;
end;
for I := 0 to AggFields.Count - 1 do
begin
Field := AggFields[I] as TAggregateField;
if (Field.Handle <> nil) then
begin
if (Field.GroupingLevel <> 0) and (Field.IndexName <> FIndexName) then
TAggregate(Field.Handle).FInUse := False else
TAggregate(Field.Handle).FInUse := True;
end;
end;
ResetGrouping;
DoAggUpdates(False);
end;
end;
function TClientDataSet.InternalGetGroupState(Level: Integer): TGroupPosInds;
var
Status: DBResult;
DSGrpState: GROUPSTATE;
begin
Status := FDSCursor.GetSubGroupState(Level, DSGrpState);
if (Status = DBERR_NONE) or (Status = DBERR_BOF) or (Status = DBERR_EOF) then
case DSGrpState of
grSTATEMIDDLE: Result := [gbMiddle];
grSTATEFIRST: Result := [gbFirst];
grSTATELAST: Result := [gbLast];
grSTATEFIRSTLAST: Result := [gbFirst, gbLast];
end
else
Result := [];
end;
function TClientDataSet.GetGroupState(Level: Integer): TGroupPosInds;
begin
if not Active or not AggregatesActive or (FIndexName = '') then
Result := []
else
begin
if Level > FGroupingLevel then
DatabaseError(SAggsNoSuchLevel, Self);
UpdateCursorPos;
Result := InternalGetGroupState(Level);
end;
end;
function TClientDataSet.GetActiveAggs(Index: Integer): TList;
begin
if Index < FActiveAggLists.Count then
Result := FActiveAggLists.Items[Index] else
Result := nil;
end;
procedure TClientDataSet.DoAggUpdates(IsUpdate: Boolean);
function Intersect(List1, List2: TBits): Boolean;
var
I: Integer;
begin
for I := 0 to List1.Size - 1 do
if List1[I] and List2[I] then
begin
Result := True;
Exit;
end;
Result := False;
end;
var
I: Integer;
Agg: TAggregate;
begin
for I := 0 to FAggregates.Count - 1 do
begin
Agg := FAggregates.Items[I];
if Assigned(Agg.OnUpdate) and Agg.Active and Agg.InUse then
begin
if not IsUpdate or Intersect(FAggFieldsUpdated, Agg.DependentFields) then
Agg.OnUpdate(Agg);
end;
end;
for I := 0 to FAggFieldsUpdated.Size - 1 do
FAggFieldsUpdated[I] := False;
end;
procedure TClientDataSet.GetAggFieldData(Buffer: PChar);
type
PTGroupPosInds = ^TGroupPosInds;
var
I: Integer;
Agg: TAggregate;
Blank: Bool;
PAggData: PChar;
begin
for I := 0 to AggFields.Count - 1 do
begin
Agg := TAggregate(TAggregateField(AggFields[I]).Handle);
if (Agg <> nil) and Agg.InUse then
begin
PAggData := Buffer + FAggFieldsOfs + Agg.RecBufOfs;
if Agg.InUse and Agg.Active and (FDSCursor.GetAggregateValue(Agg.AggHandle,
Pointer(PAggData+1), Blank) = DBERR_NONE) and not Blank then
PAggData[0] := #0 else
PAggData[0] := #1;
end;
end;
end;
function TClientDataSet.GetAggregateValue(Field: TField): Variant;
var
Agg: TAggregate;
RecBuf: PChar;
begin
Result := NULL;
if GetActiveRecBuf(RecBuf) then
begin
Agg := TAggregate(TAggregateField(Field).Handle);
if Agg <> nil then
begin
if Agg.InUse then
begin
Inc(RecBuf, FAggFieldsOfs + Agg.RecBufOfs);
if RecBuf[0] = #1 then
Result := NULL else
Result := AggValueAsVariant(Agg, RecBuf + 1)
end else
Result := NULL;
end;
end;
end;
procedure TClientDataSet.ResetAggField(Field: TField);
var
I: Integer;
Agg: TAggregate;
AggF: TAggregateField;
begin
for I := 0 to AggFields.Count - 1 do
if AggFields[I] = Field then
begin
AggF := AggFields[I] as TAggregateField;
Agg := TAggregate(AggF.Handle);
if Agg <> nil then
begin
Agg.Active := False;
Agg.Assign(AggF);
end;
end;
end;
function TClientDataSet.DoApplyUpdates(Delta: OleVariant; MaxErrors: Integer;
out ErrorCount: Integer): OleVariant;
var
OwnerData: OleVariant;
begin
if Assigned(FBeforeApplyUpdates) then FBeforeApplyUpdates(Self, OwnerData);
Result := AppServer.AS_ApplyUpdates(ProviderName, Delta, MaxErrors, ErrorCount, OwnerData);
if Assigned(FAfterApplyUpdates) then FAfterApplyUpdates(Self, OwnerData);
end;
function TClientDataSet.DoGetRecords(Count: Integer; out RecsOut: Integer;
Options: Integer; const CommandText: WideString; Params: OleVariant): OleVariant;
var
OwnerData: OleVariant;
begin
if Assigned(FBeforeGetRecords) then FBeforeGetRecords(Self, OwnerData);
Result := AppServer.AS_GetRecords(ProviderName, Count, RecsOut, Options,
CommandText, Params, OwnerData);
UnPackParams(Params, Self.Params);
if Assigned(FAfterGetRecords) then FAfterGetRecords(Self, OwnerData);
end;
function TClientDataSet.DoRowRequest(Row: OleVariant; RequestType: Integer): OleVariant;
var
OwnerData: OleVariant;
begin
if Assigned(FBeforeRowRequest) then FBeforeRowRequest(Self, OwnerData);
Result := AppServer.AS_RowRequest(ProviderName, Row, RequestType, OwnerData);
if Assigned(FAfterRowRequest) then FAfterRowRequest(Self, OwnerData);
end;
procedure TClientDataSet.DoExecute(Params: OleVariant);
var
OwnerData: OleVariant;
begin
if Assigned(FBeforeExecute) then FBeforeExecute(Self, OwnerData);
AppServer.AS_Execute(ProviderName, CommandText, Params, OwnerData);
UnPackParams(Params, Self.Params);
if Assigned(FAfterExecute) then FAfterExecute(Self, OwnerData);
end;
function TClientDataSet.ConstraintsStored: Boolean;
begin
Result := Constraints.Count > 0;
end;
procedure TClientDataSet.SetupConstraints;
type
TConstraintType = (ctField, ctRecord, ctDefault);
procedure AddSQLExprAttr(ExprParser: TExprParser; const ExprText, ExprErrMsg,
FieldName: string; FieldIndex: Integer; ConstraintType: TConstraintType;
Required: Boolean);
type
PSQLExprInfo = ^TSQLExprInfo;
TSQLExprInfo = packed record
iErrStrLen: Integer;
iFldNum: Integer;
bReqExpr: BYTE;
end;
const
TypeStr: array[TConstraintType] of PChar = (szBDEDOMCL, szBDERECCL, szBDEDEFCL);
Attr: Integer = dsVaryingFldType or SizeOf(Integer) or (dsfldBYTES shl dsSizeBitsLen);
var
ErrorStr: string;
AttrType: PChar;
Len, AttrSize: Integer;
SQLExprInfo: PSQLExprInfo;
Options: TParserOptions;
FBuffer: array of Byte;
begin
try
SetLength(FBuffer, 4096);
if ExprText = '' then Exit;
if (ConstraintType <> ctDefault) and (ExprErrMsg = '') then
begin
if (ConstraintType = ctField) and (FieldName <> '') then
ErrorStr := Format('%s %s: %s %s',[SConstraintFailed, SField, FieldName, ExprText]) else
ErrorStr := Format('%s %s',[SConstraintFailed, ExprText]);
end else
ErrorStr := ExprErrMsg;
Len := Length(ErrorStr);
if (Len > 0) then Inc(Len);
SQLExprInfo := @FBuffer[0];
SQLExprInfo.iErrStrLen := Len;
SQLExprInfo.iFldNum := FieldIndex;
SQLExprInfo.bReqExpr := Ord(Required);
Options := [poExtSyntax];
if ConstraintType = ctDefault then Include(Options, poDefaultExpr);
if ConstraintType = ctRecord then Include(Options, poUseOrigNames);
if FieldName <> '' then Include(Options, poFieldNameGiven);
with ExprParser do
begin
SetExprParams(ExprText, [], Options, FieldName);
Move(FilterData[0], FBuffer[SizeOf(TSQLExprInfo) + Len ], DataSize);
AttrSize := DataSize + SizeOf(TSQLExprInfo) + Len;
end;
if Len > 0 then
StrCopy(@FBuffer[SizeOf(TSQLExprInfo)], PChar(ErrorStr));
AttrType := TypeStr[ConstraintType];
Check(FDSBase.AddOptParameter(0, AttrType, Attr, AttrSize + SizeOf(Integer), PByte(FBuffer)));
finally
FBuffer := nil;
end;
end;
var
i: Integer;
ExprParser: TExprParser;
ErrMsg: string;
begin
ExprParser := TExprParser.Create(Self, '', [], [], '', nil, FieldTypeMap);
try
if Constraints.Count > 0 then
begin
try
for i := 0 to Constraints.Count - 1 do
with Constraints[i] do
begin
AddSQLExprAttr(ExprParser, ImportedConstraint, ErrorMessage, '', 0,
ctRecord, False);
AddSQLExprAttr(ExprParser, CustomConstraint, ErrorMessage, '', 0,
ctRecord, False);
end;
except
on E: Exception do
begin
if Name <> '' then
ErrMsg := Format('%s: %s',[Name, SRecConstFail]) else
ErrMsg := SRecConstFail;
raise EDSWriter.CreateFmt(ErrMsg, [E.Message]);
end;
end;
end;
for i := 0 to FieldList.Count - 1 do
with FieldList[i] do
begin
try
AddSQLExprAttr(ExprParser, DefaultExpression, '', FullName, i + 1,
ctDefault, False);
except
on E: Exception do
begin
if Name <> '' then
ErrMsg := Format('%s: %s',[Name, SDefExprFail]) else
if DataSet.Name <> '' then
ErrMsg := Format('%s.%s: %s',[Name, FullName, SDefExprFail]) else
ErrMsg := Format('%s: %s',[FullName, SDefExprFail]);
raise EDSWriter.CreateFmt(ErrMsg, [E.Message]);
end;
end;
try
AddSQLExprAttr(ExprParser, ImportedConstraint, ConstraintErrorMessage,
FullName, i + 1, ctField, False);
AddSQLExprAttr(ExprParser, CustomConstraint, ConstraintErrorMessage,
FullName, i + 1, ctField, False);
except
on E: Exception do
begin
if Name <> '' then
ErrMsg := Format('%s: %s',[Name, SFieldConstFail]) else
{
if DataSet.Name <> '' then
ErrMsg := Format('%s.%s: %s',[DataSet.Name, FullName, SFieldConstFail]) else
ErrMsg := Format('%s: %s',[FullName, SFieldConstFail]); }
raise EDSWriter.CreateFmt(ErrMsg, [E.Message]);
end;
end;
end;
finally
ExprParser.Free;
end;
end;
{ TClientBlobStream }
constructor TClientBlobStream.Create(Field: TBlobField; Mode: TBlobStreamMode);
begin
FField := Field;
FFieldNo := FField.FieldNo;
FDataSet := FField.DataSet as TClientDataSet;
if not FDataSet.GetActiveRecBuf(FBuffer) then Exit;
if Mode <> bmRead then
begin
if FField.ReadOnly then
DatabaseErrorFmt(SFieldReadOnly, [FField.DisplayName], FDataSet);
if not (FDataSet.State in [dsEdit, dsInsert, dsNewValue]) then
DatabaseError(SNotEditing, FDataSet);
end;
if Mode = bmWrite then Truncate
else ReadBlobData;
end;
destructor TClientBlobStream.Destroy;
begin
if FModified then
try
FDataSet.Check(FDataSet.FDSCursor.PutBlob(FBuffer, FFieldNo, 0, Memory, Size));
FField.Modified := True;
FDataSet.DataEvent(deFieldChange, Longint(FField));
except
Application.HandleException(Self);
end;
inherited Destroy;
end;
procedure TClientBlobStream.ReadBlobData;
var
BlobLen: DWord;
begin
FDataSet.Check(FDataSet.FDSCursor.GetBlobLen(FBuffer, FFieldNo, BlobLen));
if BlobLen > 0 then
begin
Position := 0;
SetSize(BlobLen);
FDataSet.Check(FDataSet.FDSCursor.GetBlob(FBuffer, FFieldNo, 0, Memory, BlobLen));
end;
end;
function TClientBlobStream.Write(const Buffer; Count: Longint): Longint;
begin
Result := inherited Write(Buffer, Count);
FModified := True;
end;
procedure TClientBlobStream.Truncate;
begin
Clear;
FModified := True;
end;
{ TAggregates }
constructor TAggregates.Create(Owner: TPersistent);
begin
inherited Create(TAggregate);
FOwner := Owner;
end;
function TAggregates.Add: TAggregate;
begin
Result := TAggregate(inherited Add);
Result.FDataSet := TClientDataSet(GetOwner);
end;
procedure TAggregates.Clear;
var
DataSet: TClientDataSet;
begin
inherited Clear;
DataSet := TClientDataSet(GetOwner);
if DataSet <> nil then
DataSet.ResetAllAggs(DataSet.AggregatesActive);
end;
function TAggregates.GetOwner: TPersistent;
begin
Result := FOwner;
end;
function TAggregates.GetItem(Index: Integer): TAggregate;
begin
Result := TAggregate(inherited GetItem(Index));
end;
procedure TAggregates.SetItem(Index: Integer; Value: TAggregate);
begin
inherited SetItem(Index, Value);
end;
function TAggregates.IndexOf(const DisplayName: string): Integer;
begin
for Result := 0 to Count - 1 do
if AnsiCompareText(TAggregate(Items[Result]).DisplayName, DisplayName) = 0 then Exit;
Result := -1;
end;
function TAggregates.Find(const DisplayName: string): TAggregate;
var
I: Integer;
begin
I := IndexOf(DisplayName);
if I < 0 then Result := nil else Result := TAggregate(Items[I]);
end;
{ TAggregate }
constructor TAggregate.Create(Aggregates: TAggregates; ADataSet: TClientDataSet);
begin
FDataSet := ADataSet;
inherited Create(Aggregates);
end;
destructor TAggregate.Destroy;
var
I: Integer;
begin
if Assigned(FDataSet) and Assigned(FDataSet.AggFields) then
for I := 0 to FDataSet.AggFields.Count - 1 do
with TAggregateField(FDataSet.AggFields[I]) do
begin
if FHAggregate <> 0 then
begin
FDataset.FDSCursor.DropAggregate(FHAggregate);
FHAggregate := 0;
end;
if Handle = Self then Handle := nil;
end;
if FDependentFields <> nil then
FDependentFields.Free;
inherited Destroy;
end;
procedure TAggregate.Activate;
var
Parser: TExprParser;
begin
if FOutOfDate and (FHAggregate <> 0) then
begin
FDataSet.Check(FDataSet.DSCursor.DropAggregate(FHAggregate));
FHAggregate := 0;
end;
FOutOfDate := False;
if FHAggregate = 0 then
begin
if FDependentFields = nil then
FDependentFields := TBits.Create;
if FExpression <> '' then
begin
Parser := TExprParser.Create(FDataSet, FExpression, [],
[poExtSyntax, poAggregate, poFieldDepend], '', FDependentFields, FieldTypeMap);
try
FDataset.Check(FDataSet.DSCursor.AddAggregate(GroupingLevel,
Parser.DataSize, Parser.FilterData, FHAggregate));
FDataset.Check(FDataSet.DSCursor.GetAggregateDesc(FHAggregate, FFldDesc));
SetLength(FDataBuffer, FFldDesc.iFldLen);
if FFldDesc.iFldType < MAXLOGFLDTYPES then
FDataType := DataTypeMap[FFldDesc.iFldType]
else if FFldDesc.iFldType = fldUNICODE then
FDataType := ftWideString else
FDataType := ftUnknown;
FDataSize := FFldDesc.iFldLen;
finally
Parser.Free;
end;
end else
FDataSet.Check(FDataSet.DSCursor.AddAggregate(GroupingLevel, 0, nil,
FHAggregate));
end;
end;
procedure TAggregate.Assign(Source: TPersistent);
begin
if Source is TAggregate then
begin
DisplayName := TAggregate(Source).Displayname;
Visible := TAggregate(Source).Visible;
Expression := TAggregate(Source).Expression;
IndexName := TAggregate(Source).IndexName;
GroupingLevel := TAggregate(Source).GroupingLevel;
Active := TAggregate(Source).Active;
end
else if Source is TAggregateField then
begin
DisplayName := TAggregateField(Source).DisplayName;
Visible := TAggregateField(Source).Visible;
Expression := TAggregateField(Source).Expression;
IndexName := TAggregateField(Source).IndexName;
GroupingLevel := TAggregateField(Source).GroupingLevel;
Active := TAggregateField(Source).Active;
end
else
inherited Assign(Source);
end;
function TAggregate.GetDisplayName: string;
begin
Result := FAggregateName;
if Result = '' then Result := Expression;
if Result = '' then Result := inherited GetDisplayName;
end;
procedure TAggregate.SetActive(Value: Boolean);
begin
if Value <> FActive then
begin
FActive := Value;
if (FDataSet <> nil) and (FDataSet.FDSCursor <> nil) then
try
FDataSet.ResetAgg(Self, True);
except
FActive := False;
raise;
end;
end;
end;
procedure TAggregate.SetExpression(const Text: string);
begin
if ( FDataSet <> nil ) and (FExpression <> Text ) and Active
and not (csLoading in FDataSet.ComponentState) then
DatabaseError(SAggActive, FDataSet);
if Text <> FExpression then
FOutOfDate := True;
FExpression := Text;
end;
procedure TAggregate.SetGroupingLevel(GroupingLevel: Integer);
begin
if ( FDataSet <> nil ) and (GroupingLevel <> FGroupingLevel ) and Active
and not (csLoading in FDataSet.ComponentState) then
DatabaseError(SAggActive, FDataSet);
if GroupingLevel <> FGroupingLevel then
FOutOfDate := True;
FGroupingLevel := GroupingLevel;
end;
procedure TAggregate.SetIndexName(Value: String);
begin
if ( FDataSet <> nil ) and (FIndexName <> Value) and Active
and not (csLoading in FDataSet.ComponentState) then
DatabaseError(SAggActive, FDataSet);
FIndexName := Value;
end;
function TAggregate.Value: Variant;
var
Blank: Bool;
begin
Result := Null;
if InUse and Active and Assigned(FDataSet) then
begin
FDataSet.UpdateCursorPos;
FDataSet.DSCursor.GetAggregateValue(FHAggregate, Pointer(FDataBuffer), Blank);
if Blank then
Result := NULL else
Result := AggValueAsVariant(Self, FDataBuffer);
end;
end;
end.