{ *********************************************************************** }
{                                                                         }
{ Delphi Visual Component Library                                         }
{                                                                         }
{ Copyright (c) 1997-2004 Borland Software Corporation                    }
{                                                                         }
{ *********************************************************************** }

unit Borland.Vcl.DBClient platform;

{$R-,T-,H+,X+}

interface

uses Windows, ActiveX, SysUtils, Variants, Classes, DB, DBCommon,
  DSIntf, Midas, SqlTimSt;

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 }

  TCustomClientDataSet = class;
  TClientDataSet = class;

  TGetUsernameEvent = procedure(Sender: TObject; var Username: string) of object;

  TCustomRemoteServer = class(TCustomConnection)
  private
    FAppServer: IAppServer; {Note: this changed from a Variant}
    FOnGetUsername: TGetUsernameEvent;
  protected
    function GetAppServer: IAppServer; virtual;
    procedure SetAppServer(Value: IAppServer); virtual;
    procedure RegisterClient(Client: TObject; Event: TConnectChangeEvent = nil); override;
    procedure UnRegisterClient(Client: TObject); override;
    property OnGetUsername: TGetUsernameEvent read FOnGetUsername write FOnGetUsername;
  public
    constructor Create(AOwner: TComponent); override;
    function GetServer: IAppServer; virtual;
    function GetServerList: OleVariant; virtual;
    property AppServer: IAppServer read GetAppServer;
    procedure GetProviderNames(Proc: TGetStrProc); virtual;
  end;

{ TConnectionBroker }

  TConnectionBroker = class(TCustomRemoteServer)
  private
    FConnection: TCustomRemoteServer;
    FStreamedConnected: Boolean;
    procedure SetConnection(const Value: TCustomRemoteServer);
  protected
    function GetAppServer: IAppServer; override;
    function GetConnected: Boolean; override;
    procedure Loaded; override;
    procedure Notification(AComponent: TComponent; Operation: TOperation); override;
    procedure SetConnected(Value: Boolean); override;
  public
    constructor Create(AOwner: TComponent); override;
    function GetServer: IAppServer; override;
  published
    property Connected;
    property Connection: TCustomRemoteServer read FConnection write SetConnection;
    property LoginPrompt default False;
    property AfterConnect;
    property AfterDisconnect;
    property BeforeConnect;
    property BeforeDisconnect;
    property OnGetUsername;
    property OnLogin;
  end;

{ TAggregate }

  TAggregate = class;
  TAggregates = class;
  TAggUpdateEvent = procedure(Agg: TAggregate) of object;

  TCursorWrapper = class
  private
    FCursor: IDSCursor;
    FExprFilter: IntPtr {HDSFilter};
    FFuncFilter: IntPtr {HDSFilter};
  strict protected
    procedure Finalize; override;
  end;

  TAggCursor = class
  private
    FDSCursor: TCursorWrapper;
    FHAggregate: hDSAggregate;
  strict protected
    procedure Finalize; override;
  end;

  TAggregate = class(TCollectionItem)
  private
    FExpression: string;
    FFldDesc: DSFLDDesc;
    FAggHandle: TAggCursor;
    FAggregateName: String;
    FGroupingLevel: Integer;
    FDataSet: TCustomClientDataSet;
    FIndexName: string;
    FBufferSize: Integer;
    FDataType: TFieldType;
    FDataSize: Integer;
    FDependentFields: TBits;
    FRecBufOfs: Integer;
    FInUse: Boolean;
    FActive: Boolean;
    FVisible: Boolean;
    FOutOfDate: Boolean;
    FOnUpdate: TAggUpdateEvent;
    function GetAggHandle: hDSAggregate;
    procedure SetActive(Value: Boolean);
    procedure SetAggHandle(Value: hDSAggregate);
    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;
    function GetDisplayName: string; override;
  public
    constructor Create(Aggregates: TAggregates; ADataSet: TCustomClientDataSet); reintroduce; overload;
    destructor Destroy; override;
    procedure Assign(Source: TPersistent); override;
    function Value: Variant;
    property AggHandle: hDSAggregate read GetAggHandle write SetAggHandle;
    property InUse: Boolean read FInUse write FInUse default false;
    property DataSet: TCustomClientDataSet 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);

  TRecInfo = packed record
    RecordNumber: Longint;
    BookmarkFlag: TBookmarkFlag;
    Attribute: DSAttr;
  end;

  TKeyBuffer = record
    Modified: Boolean;
    Exclusive: Boolean;
    FieldCount: Integer;
    Data: TRecordBuffer;
  end;

  TDataPacketWrapper = class
  private
    FPacket: TDataPacket;
  strict protected
    procedure Finalize; override;
  end;

  TDataPacketFormat = (dfBinary, dfXML, dfXMLUTF8);

  TReconcileAction = (raSkip, raAbort, raMerge, raCorrect, raCancel, raRefresh);
  TReconcileErrorEvent = procedure(DataSet: TCustomClientDataSet; E: EReconcileError;
    UpdateKind: TUpdateKind; var Action: TReconcileAction) of object;

  {Note: .NET Variants are more general that Windows Variants. Make sure
   that OleVariant is a type that can be marshaled}
  TRemoteEvent = procedure(Sender: TObject; var OwnerData: OleVariant) of object;

  TReconcileInfo = class
    DataSet: TDataSet;
    UpdateKind: TUpdateKind;
    ReconcileError: EReconcileError;
    ActionRef: TReconcileAction;
  end;

  TDataSetOption = (doDisableInserts, doDisableDeletes, doDisableEdits, doNoResetCall);
  TDataSetOptions = set of TDataSetOption;

  TFetchOption = (foRecord, foBlobs, foDetails);
  TFetchOptions = set of TFetchOption;

  TDSBaseWrapper = class
  private
    FDSBase: IDSBase;
    FCalcFields: Boolean;
  strict protected
    procedure Finalize; override;
  end;

  TCustomClientDataSet = class(TDataSet)
  private
    FActiveAggLists: TList;
    FAggFieldsUpdated: TBits;
    FAggFieldsInit: Boolean;
    FAggFieldsSize: Integer;
    FAggGrpIndOfs: Integer;
    FAggFieldsOfs: Integer;
    FAggGrpIndSize: Integer;
    FAggregates: TAggregates;
    FAggregatesActive: Boolean;
    FBufferManager: TDBBufferList;
    FCommandText: string;
    FDisableStringTrim: Boolean;
    FDSBase: TDSBaseWrapper;
    FDSCursor: TCursorWrapper;
    FDSOptions: TDataSetOptions;
    FFindCursor: IDSCursor;
    FCloneSource: TCustomClientDataSet;
    FReconcileDataSet: TCustomClientDataSet;
    FSavedPacket: TDataPacketWrapper;
    FDeltaPacket: TDataPacketWrapper;
    FParams: TParams;
    FIndexDefs: TIndexDefs;
    FIndexName: string;
    FFileName: string;
    FFilterBuffer: TRecordBuffer; {IntPtr}
    FGroupingLevel: Integer;
    FLastParentBM: array of byte;
    FMasterLink: TMasterDataLink;
    FIndexFieldMap: DSKEY;
    FKeyBuffers: array[TKeyIndex] of TKeyBuffer;
    FKeyBuffer: TKeyIndex;
    FNewValueBuffer: TRecordBuffer;
    FOldValueBuffer: TRecordBuffer;
    FCurValueBuffer: TRecordBuffer;
    FIndexFieldCount: Integer;
    FIndexGroupingLevel: Integer;
    FAppServer: IAppServer;
    FProviderName: string;
    FRemoteServer: TCustomRemoteServer;
    FPacketRecords: Integer;
    FConstDisableCount: Integer;
    FMaxAggGroupingLevel: Integer;
    FParentDataSet: TCustomClientDataSet;
    { Word & Byte size data members }
    FKeySize: Word;
    FRecordSize: Integer;
    FBookmarkOfs: Integer;
    FRecInfoOfs: Integer;
    FRecBufSize: Integer;
    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;
    FConnectionBroker: TConnectionBroker;
    procedure AddExprFilter(const Expr: string; Options: TFilterOptions);
    procedure AddFuncFilter;
    function CalcFieldsCallBack(ClientData: Integer; RecBuf: TRecordBuffer): DBResult;
    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(ClientData: Integer; RecBuf: TRecordBuffer): LongBool;
    procedure DoAggUpdates(IsUpdate: Boolean);
    function  GetActiveAggs(Index: Integer) : TList;
    function GetActiveRecBuf(var RecBuf: TRecordBuffer): Boolean;
    procedure GetAggFieldData(Buffer: TRecordBuffer);
    function GetChangeCount: Integer;
    function GetData: OleVariant;
    function GetDataSize: Integer;
    function GetDelta: OleVariant;
    function GetDSBase: IDSBase;
    function GetDSCursor: IDSCursor;
    function GetExprFilter: IntPtr;
    function GetFuncFilter: IntPtr;
    function GetIndexDefs: TIndexDefs;
    function GetIndexFieldNames: string;
    function GetIndexName: string;
    function GetLogChanges: Boolean;
    function GetMasterFields: string;
    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(ClientData: Integer);
    procedure ReadData(Stream: TStream);
    function ReconcileCallback(iClientData: Integer; iRslt: Integer;
      iUpdateKind: DSAttr; iResAction: dsCBRType; iErrCode: Integer;
      pErrMessage, pErrContext: string;pRecUpd, pRecOrg, pRecConflict: TRecordBuffer;
      iLevels: Integer; piFieldIDs: IntPtr): dsCBRType;
    procedure ResetAgg(Agg: TAggregate; DeleteFirst: Boolean);
    procedure ResetAllAggs(Value: Boolean);
    procedure ResetGrouping;
    procedure SetAggsActive(Value: Boolean);
    procedure SetConnectionBroker(const Value: TConnectionBroker);
    procedure SaveDataPacket(Format: TDataPacketFormat = dfBinary);
    procedure SetData(const Value: OleVariant);
    procedure SetDataSource(Value: TDataSource);
    procedure SetDSBase(Value: IDSBase);
    procedure SetDSCursor(Value: IDSCursor);
    procedure SetDisableStringTrim(Value: Boolean);
    procedure SetIndex(const Value: string; FieldsIndex: Boolean);
    procedure SetIndexDefs(Value: TIndexDefs);
    procedure SetFileName(const Value: string);
    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 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 SetStatusFilter(const Value: TUpdateStatusSet);
    function GetXMLData: string;
    procedure SetXMLData(const Value: string);
    procedure CopyKeyBuffer(Source: TKeyBuffer; var Dest: TKeyBuffer);
  protected
    { IProviderSupport }
    function PSGetIndexDefs(IndexTypes: TIndexOptions): TIndexDefs; override;
  protected
    { DataIntf Helper functions }
    procedure DoAfterApplyUpdates(var OwnerData: OleVariant); virtual;
    procedure DoBeforeApplyUpdates(var OwnerData: OleVariant); virtual;
    function DoApplyUpdates(Delta: OleVariant; MaxErrors: Integer; out ErrorCount: Integer): OleVariant; virtual;
    procedure DoAfterGetParams(var OwnerData: OleVariant); virtual;
    procedure DoBeforeGetParams(var OwnerData: OleVariant); virtual;
    procedure DoAfterGetRecords(var OwnerData: OleVariant); virtual;
    procedure DoBeforeGetRecords(var OwnerData: OleVariant); virtual;
    function DoGetRecords(Count: Integer; out RecsOut: Integer; Options: Integer;
       const CommandText: WideString; Params: OleVariant): TDataPacket; virtual;
    procedure DoAfterRowRequest(var OwnerData: OleVariant); virtual;
    procedure DoBeforeRowRequest(var OwnerData: OleVariant); virtual;
    function DoRowRequest(Row: OleVariant; RequestType: Integer): OleVariant; virtual;
    procedure DoAfterExecute(var OwnerData: OleVariant); virtual;
    procedure DoBeforeExecute(var OwnerData: OleVariant); virtual;
    procedure DoExecute(Params: OleVariant); virtual;
    { DataSet methods }
    procedure ResetAggField(Field: TField); override;
    procedure ActivateFilters;
    procedure AddDataPacket(const Data: TDataPacket; HitEOF: Boolean); virtual;
    procedure AddFieldDesc(FieldDescs: TFieldDescList; var DescNo: Integer;
      var FieldID: Integer; FieldDefs: TFieldDefs);
    procedure AllocKeyBuffers;
    function AllocRecordBuffer: TRecordBuffer; override;
    procedure Check(Status: DBResult);
    procedure CheckDetailRecords; virtual;
    procedure CheckSetKeyMode;
    procedure ClearCalcFields(Buffer: TRecordBuffer); override;
    procedure CloseCursor; override;
    procedure DataConvert(Field: TField; Source, Dest: TValueBuffer; ToNative: Boolean); override;
    procedure DataEvent(Event: TDataEvent; Info: TObject); 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: TRecordBuffer); override;
    function GetAggregateValue(Field: TField): Variant; override;
    function GetAppServer: IAppServer; virtual;
    procedure GetBookmarkData(Buffer: TRecordBuffer; var Bookmark: TBookmark); override;
    function GetBookmarkFlag(Buffer: TRecordBuffer): 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 GetKeyExclusive: Boolean;
    function GetKeyFieldCount: Integer;
    function GetRecord(Buffer: TRecordBuffer; 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;
    procedure InitKeyBuffer(var Buffer: TKeyBuffer);
    procedure InitRecord(Buffer: TRecordBuffer); override;
    procedure InternalAddRecord(Buffer: TRecordBuffer; 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): Variant;
    procedure InternalGotoBookmark(const Bookmark: TBookmark); override;
    procedure InternalInitFieldDefs; override;
    procedure InternalInitRecord(Buffer: TRecordBuffer); override;
    procedure InternalInsert; override;
    procedure InternalLast; override;
    procedure InternalOpen; override;
    procedure InternalRefresh; override;
    procedure InternalPost; override;
    procedure InternalSetToRecord(Buffer: TRecordBuffer); 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: TRecordBuffer); override;
    procedure ReadDataPacket(Stream: TStream; ReadSize: Boolean);
    function ResetCursorRange: Boolean;
    procedure SetAggregates(Value: TAggregates); virtual;
    procedure SetAppServer(Value: IAppServer); virtual;
    procedure SetBookmarkData(Buffer: TRecordBuffer; const Bookmark: TBookmark); override;
    procedure SetBookmarkFlag(Buffer: TRecordBuffer; Value: TBookmarkFlag); override;
    procedure SetCommandText(Value: String); virtual;
    function SetCursorRange: Boolean;
    procedure SetDataSetField(const Value: TDataSetField); override;
    procedure SetFieldData(Field: TField; Buffer: TValueBuffer); 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;
      Format: TDataPacketFormat = dfBinary);
    function ConstraintsStored: Boolean;
    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 SetCommandText;
    property Constraints stored ConstraintsStored;
    property DataSetField;
    property DisableStringTrim: Boolean read FDisableStringTrim write SetDisableStringTrim default False;
    property DSBase: IDSBase read GetDSBase write SetDSBase;
    property DSCursor: IDSCursor read GetDSCursor write SetDSCursor;
    property ExprFilter: IntPtr read GetExprFilter;
    property FuncFilter: IntPtr read GetFuncFilter;
    property FileName: string read FFileName write SetFileName;
    property Filter;
    property Filtered;
    property FilterOptions;
    property FieldDefs stored FStoreDefs;
    property IndexDefs: TIndexDefs read GetIndexDefs write SetIndexDefs stored FStoreDefs;
    property IndexName: string read GetIndexName write SetIndexName;
    property FetchOnDemand: Boolean read FFetchOnDemand write FFetchOnDemand default True;
    property MasterSource: TDataSource read GetDataSource write SetDataSource;
    property ObjectView default True;
    property Params: TParams read FParams write SetParams;
    property ProviderEOF: Boolean read GetProviderEOF write SetProviderEOF;
    property ProviderName: string read FProviderName write SetProviderName;
    property ReadOnly: Boolean read FReadOnly write SetReadOnly default False;
    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;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    property ConnectionBroker: TConnectionBroker read FConnectionBroker write SetConnectionBroker;
    property IndexFieldNames: string read GetIndexFieldNames write SetIndexFieldNames;
    property MasterFields: string read GetMasterFields write SetMasterFields;
    property PacketRecords: Integer read FPacketRecords write FPacketRecords default -1;
    property RemoteServer: TCustomRemoteServer read GetRemoteServer write SetRemoteServer;
    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(const Bookmark: TBookmark): Boolean; override;
    procedure Cancel; override;
    procedure CancelRange;
    procedure CancelUpdates;
    function CreateBlobStream(Field: TField; Mode: TBlobStreamMode): TStream; override;
    procedure CreateDataSet;
    procedure CloneCursor(Source: TCustomClientDataSet; Reset: Boolean;
      KeepSettings: Boolean = False); virtual;
    function CompareBookmarks(const 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: TRecordBuffer): Boolean; override;
    function GetFieldData(Field: TField; Buffer: TValueBuffer): Boolean; override;
    function GetFieldData(FieldNo: Integer; Buffer: TValueBuffer): Boolean; overload; override;
    function GetGroupState(Level: Integer): TGroupPosInds;
    procedure GetIndexInfo(IndexName: string);
    procedure GetIndexNames(List: TStrings);
    function GetNextPacket: Integer;
    function GetOptionalParam(const ParamName: string): Variant;
    procedure GotoCurrent(DataSet: TCustomClientDataSet);
    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: TRecordBuffer);
    procedure SetKey;
    procedure SetOptionalParam(const ParamName: string; const Value: Variant;
      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: TCustomClientDataSet read FCloneSource;
    property Data: OleVariant read GetData write SetData;
    property XMLData: string read GetXMLData write SetXMLData;
    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;
  end;

  TClientDataSet = class(TCustomClientDataSet)
  published
    property Active;
    property Aggregates;
    property AggregatesActive;
    property AutoCalcFields;
    property CommandText;
    property ConnectionBroker;
    property Constraints;
    property DataSetField;
    property DisableStringTrim;
    property FileName;
    property Filter;
    property Filtered;
    property FilterOptions;
    property FieldDefs;
    property IndexDefs;
    property IndexFieldNames;
    property IndexName;
    property FetchOnDemand;
    property MasterFields;
    property MasterSource;
    property ObjectView;
    property PacketRecords;
    property Params;
    property ProviderName;
    property ReadOnly;
    property RemoteServer;
    property StoreDefs;
    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;
    property BeforeApplyUpdates;
    property AfterApplyUpdates;
    property BeforeGetRecords;
    property AfterGetRecords;
    property BeforeRowRequest;
    property AfterRowRequest;
    property BeforeExecute;
    property AfterExecute;
    property BeforeGetParams;
    property AfterGetParams;
  end;

{ TClientBlobStream }

  TClientBlobStream = class(TMemoryStream)
  private
    FField: TBlobField;
    FDataSet: TCustomClientDataSet;
    FBuffer: TRecordBuffer;
    FFieldNo: Integer;
    FModified: Boolean;
    procedure ReadBlobData;
  public
    constructor Create(Field: TBlobField; Mode: TBlobStreamMode);
    destructor Destroy; override;
    function Write(const Buffer: array of Byte; Offset, 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);
function WriteVariantToPointer(Value: Variant; AType: TVarType; P: IntPtr): Boolean;
function ParamTypeFromVarType(T: TVarType): Integer;
function ParamLenFromVarType(T: TVarType): Integer;
type
  TConstraintType = (ctField, ctRecord, ctDefault);
procedure AddSQLExprAttr(ExprParser: TExprParser; const ExprText, ExprErrMsg,
  FieldName: string; FieldIndex: Integer; ConstraintType: TConstraintType;
  Required: Boolean; Mem: IntPtr; var Size: Integer);

const
  AllRecords = -1;

implementation

uses TypInfo, Contnrs, WinUtils, FMTBcd, Provider, DBConsts, MidConst,
  System.Runtime.InteropServices, System.Text;

{ 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;
  Val: Variant;
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], varObject);
    for I := 0 to Params.Count - 1 do
      with Params[I] do
        if ParamType in Types then
        begin
          Val := Value;
          // .NET TDateTime is different from D7 TDateTime. 
          if (VarType(Value) in [varDateTime, varDate]) then
            Val := VarAsType(Value, varDouble);
          Result[Idx] := VarArrayOf([Name, Val, Ord(DataType), Ord(ParamType)]);
          Inc(Idx);
        end;
  end;
end;

procedure UnpackParams(const Source: OleVariant; Dest: TParams);
var
  TempParams: TParams;
  i, temp: 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
          begin
             temp := Source[i][2];
             DataType := TFieldType(temp);
          end;
          if VarArrayHighBound(Source[i], 1) > 2 then
          begin
            temp := Source[i][3];
            ParamType := TParamType(temp);
          end;
          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);
end;

function TCustomRemoteServer.GetServerList: OleVariant;
begin
  Result := NULL;
end;

function TCustomRemoteServer.GetServer: IAppServer;
begin
  Result := nil;
end;

procedure TCustomRemoteServer.GetProviderNames(Proc: TGetStrProc);
var
  List: OleVariant;
  I: Integer;
begin
  Connected := True;
  try
    List := AppServer.AS_GetProviderNames;
  except
    { Assume any errors means the list is not available. }
  end;
  if VarIsArray(List) and (VarArrayDimCount(List) = 1) then
    for I := VarArrayLowBound(List, 1) to VarArrayHighBound(List, 1) do
      Proc(List[I]);
end;

procedure TCustomRemoteServer.RegisterClient(Client: TObject; Event: TConnectChangeEvent = nil);
begin
  inherited; 
end;

procedure TCustomRemoteServer.UnRegisterClient(Client: TObject);
begin
  inherited; 
end;

function TCustomRemoteServer.GetAppServer: IAppServer;
begin
  Result := FAppServer;
end;

procedure TCustomRemoteServer.SetAppServer(Value: IAppServer);
begin
  if Assigned(FAppServer) then
    Marshal.ReleaseComObject(FAppServer);
  FAppServer := Value;
end;

 { TDSBaseWrapper }

procedure TDSBaseWrapper.Finalize;
begin
  if Assigned(FDSBase) and FCalcFields then
    if FDSBase.SetFieldCalculation(0, nil) = 0 then
      FDSBase.AddField(nil);
end;

 { TCursorWrapper }

procedure TCursorWrapper.Finalize;
begin
  if FCursor = nil then Exit;
  if FExprFilter <> nil then
  begin
    FCursor.DropFilter(FExprFilter);
    FExprFilter := nil;
  end;
  if FFuncFilter <> nil then
  begin
    FCursor.DropFilter(FFuncFilter);
    FFuncFilter := nil;
  end;
end;

 { TDataPacketWrapper }

procedure TDataPacketWrapper.Finalize;
begin
  if FPacket <> nil then
  begin
    SafeArrayCheck(SafeArrayDestroy(FPacket));
    FPacket := nil;
  end;
end;

{ TCustomClientDataSet }

constructor TCustomClientDataSet.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FBufferManager := TDBBufferList.Create;
  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;
  FDisableStringTrim := False;
  ObjectView := True;
end;

destructor TCustomClientDataSet.Destroy;
begin
  FSavePacketOnClose := False;
  inherited Destroy;
  ClearSavedPacket;
  if Assigned(FDeltaPacket) then
    FreeDataPacket(FDeltaPacket.FPacket);
  SetRemoteServer(nil);
  SetConnectionBroker(nil);
  AppServer := nil;
  FreeAndNil(FMasterLink);
  FreeAndNil(FIndexDefs);
  FreeAndNil(FParams);
  FreeAndNil(FAggregates);
  FreeAndNil(FBufferManager);
  ClearActiveAggs;
  FreeAndNil(FActiveAggLists);
  FreeAndNil(FAggFieldsUpdated);
end;

function TCustomClientDataSet.CreateDSBase: IDSBase;
begin
  CreateDbClientObject(StringToGUID(CLSID_DSBase), StringToGUID(GUID_DSBASE), Result);
end;

function TCustomClientDataSet.GetDSBase: IDSBase;
begin
  if Assigned(FDSBase) then
    Result := FDSBase.FDSBase
  else
    Result := nil;
end;

procedure TCustomClientDataSet.SetDSBase(Value: IDSBase);
begin
  if not Assigned(FDSBase) then
  begin
    if Value = nil then Exit;
    FDSBase := TDSBaseWrapper.Create;
  end;
  FDSBase.FDSBase := Value;
end;

function TCustomClientDataSet.CreateDSCursor(SourceCursor: IDSCursor): IDSCursor;
begin
  CreateDbClientObject(StringToGUID(CLSID_DSCursor), StringToGUID(GUID_DSCursor), Result);

  if Assigned(SourceCursor) then
    Check(Result.CloneCursor(SourceCursor))
  else
    Check(Result.InitCursor(DSBase));
end;

function TCustomClientDataSet.GetDSCursor: IDSCursor;
begin
  if Assigned(FDSCursor) then
    Result := FDSCursor.FCursor
  else
    Result := nil;
end;

procedure TCustomClientDataSet.SetDSCursor(Value: IDSCursor);
begin
  if not Assigned(FDSCursor) then
  begin
    if Value = nil then Exit;
    FDSCursor := TCursorWrapper.Create;
  end;
  FDSCursor.FCursor := Value;
end;

procedure TCustomClientDataSet.SetCommandText(Value: String);
var
  SQL: String;
  List: TParams;
begin
  if FCommandText <> Value then
  begin
    FCommandText := Value;
    if Value <> '' then
    begin
      List := TParams.Create(Self);
      try
        SQL := copy(Value, 1, Length(Value));
        List.ParseSQL(SQL, True);
        List.AssignValues(FParams);
        FParams.Clear;
        FParams.Assign(List);
      finally
        List.Free;
      end;
    end else
      FParams.Clear;
  end;
end;

procedure TCustomClientDataSet.SetDisableStringTrim(Value: Boolean);
begin
  CheckInactive;
  FDisableStringTrim := Value;
end;

procedure TCustomClientDataSet.SetParams(Value: TParams);
begin
  FParams.Assign(Value);
end;

function WriteVariantToPointer(Value: Variant; AType: TVarType; P: IntPtr): Boolean;
var
  D: Double;
begin
  Result := True;
  case AType of
    varSmallInt,
    varWord:
      Marshal.WriteInt16(P, SmallInt(Value));
    varInteger,
    varLongWord:
      Marshal.WriteInt32(P, Integer(Value));
    varSingle:
      Marshal.Copy(BitConverter.GetBytes(Single(Value)), 0, P, SizeOf(Single));
    varDouble:
      Marshal.WriteInt64(P, BitConverter.DoubleToInt64Bits(Value));
    varDecimal:
      Marshal.WriteInt64(P, System.Decimal.ToOACurrency(System.Decimal(Value)));
    varDateTime:
       begin
         D := TimeStampToMSecs(DateTimeToTimeStamp(Value));
         Marshal.WriteInt64(P, BitConverter.DoubleToInt64Bits(D));
       end;
    varBoolean:
      if Boolean(Value) then
        Marshal.WriteInt16(P, 1)
      else
        Marshal.WriteInt16(P, 0);
    varByte:
      Marshal.WriteByte(P, Byte(Value));
    varInt64:
      Marshal.WriteInt64(P, Int64(Value));
  else
    Result := False;
  end;
end;

const
  ParamTypeMap: array[varSmallInt..varInt64] of Integer =
    ( dsfldINT, dsfldINT, dsfldFLOATIEEE, dsfldFLOATIEEE, dsfldBCD,
      dsfldTIMESTAMP, dsfldZSTRING, 0, 0, dsfldBOOL, 0, 0, dsfldBCD, 0, 0,
      dsfldINT, dsfldUINT, dsfldUINT, dsfldFLOATIEEE);
  ParamTypeSize: array[varSmallInt..varInt64] of Integer =
    ( SizeOf(SmallInt), SizeOf(Integer), SizeOf(Single), SizeOf(Double),
      SizeOf(Int64), SizeOf(Double), 0, 0, 0, SizeOf(WordBool), 0, 0,
      SizeOf(Int64), 0, 0, SizeOf(Byte), SizeOf(SmallInt), SizeOf(Integer),
      SizeOf(Int64));

function ParamTypeFromVarType(T: TVarType): Integer;
begin
  if (T < varSmallInt) or (T > varInt64) then
    Result := 0
  else
    Result := ParamTypeMap[T];
end;

function ParamLenFromVarType(T: TVarType): Integer;
begin
  if (T < varSmallInt) or (T > varInt64) then
    Result := 0
  else
    Result := ParamTypeSize[T];
end;


procedure TCustomClientDataSet.SetOptionalParam(const ParamName: string;
  const Value: Variant; IncludeInDelta: Boolean);
var
  ParamType, ParamLen, t, l: DWord;
  B, PN: TBytes;
  P, V, PNPtr: IntPtr;
  TimeStampRec: TSQLTimeStamp;
  I : Integer;
begin
  CheckActive;
  if (VarIsArray(Value) and (VarArrayElementsType(Value) = varByte)) or
    (VarType(Value) in [varSmallInt, varInteger, varSingle, varDouble,
      varDecimal, varDateTime, varString, varBoolean, varByte, varWord,
      varLongWord, varInt64]) then
  begin
    ParamType := ParamTypeFromVarType(VarType(Value) and varTypeMask);
    ParamLen := ParamLenFromVarType(VarType(Value) and varTypeMask);
    P := nil;
    try
      if VarIsArray(Value) then
      begin
        ParamType := dsfldBYTES;
        ParamLen := Length(TBytes(Value));
        P := Marshal.AllocHGlobal(ParamLen + SizeOf(Integer));
        Marshal.WriteInt32(P, ParamLen);
        for I := 0 to ParamLen - 1 do
          Marshal.WriteByte(P, SizeOf(Integer) + I, TBytes(Value)[I]);
        Inc(ParamLen, SizeOf(Integer));
      end else
      if varType(Value) <> varString then
      begin
        P := Marshal.AllocHGlobal(ParamLen);
        if not WriteVariantToPointer(Value, varType(Value), P) then
          DatabaseError(SInvalidOptParamType, Self);
      end else
      begin
        if TSQLTimeStamp.TryParse(Value, TimeStampRec) then
        begin
          ParamType := dsfldDATETIME; // was dsFldTIMESTAMP
          ParamLen := Marshal.sizeof(TypeOf(TSQLTimeStamp));
          P := Marshal.AllocHGlobal(ParamLen);
          Marshal.StructureToPtr(TObject(TimeStampRec), P, False);
        end else
        begin
          B := BytesOf(String(Value));
          ParamLen := Length(B) + 1;
          SetLength(B, ParamLen); // add null terminator
          P := Marshal.AllocHGlobal(ParamLen);
          Marshal.Copy(B, 0, P, ParamLen);
        end;
      end;
      ParamType := ParamType shl dsSizeBitsLen or ParamLen;
      if IncludeInDelta then
        ParamType := ParamType or dsIncInDelta;
      PN := BytesOf(ParamName + #0);
      PNPtr := Marshal.AllocHGlobal(Length(PN));
      try
        Marshal.Copy(PN, 0, PNPtr, Length(PN));
        if DSBase.GetOptParameter(0, 0, PNPtr, t, l, v) = 0 then
        begin
          Marshal.Copy(PN, 0, PNPtr, Length(B));
          Check(DSBase.DropOptParameter(0, PNPtr));
        end;
        Check(DSBase.AddOptParameter(0, PNPtr, ParamType, ParamLen, P));
      finally
        Marshal.FreeHGlobal(PNPtr);
      end;
    finally
      if P <> nil then
        Marshal.FreeHGlobal(P);
    end;
  end else
    DatabaseError(SInvalidOptParamType, Self);
end;

function TCustomClientDataSet.GetOptionalParam(const ParamName: string): Variant;
begin
  Result := InternalGetOptionalParam(ParamName);
end;

function TCustomClientDataSet.InternalGetOptionalParam(const ParamName: string;
  FieldNo: Integer = 0): Variant;
var
  ParamType, ParamLen: DWord;
  Value, PNPtr, PNPtrSave: IntPtr;
  B, PN: TBytes;
  T: TTimeStamp;
begin
  if not Assigned(DSBase) then CheckActive;
  PN := BytesOf(ParamName + #0);
  PNPtr := Marshal.AllocHGlobal(Length(PN));
  try
    Marshal.Copy(PN, 0, PNPtr, Length(PN));
    PNPtrSave := PNPtr;
    if DSBase.GetOptParameter(0, FieldNo, PNPtr, ParamType, ParamLen, Value) <> 0 then
      Exit;
  finally
    Marshal.FreeHGlobal(PNPtrSave);
  end;
  ParamType := (ParamType and dsTypeBitsMask) shr dsSizeBitsLen;
  if (ParamType = dsfldBYTES) or
     ((ParamType in [dsfldINT, dsfldUINT]) and (ParamLen > 4 )) then
  begin
    SetLength(B, ParamLen - sizeof(Integer));
    Marshal.Copy(IntPtr(Integer(Value) + sizeof(Integer)), B, 0, ParamLen - SizeOf(Integer));
    Result := B;
  end else
  begin
    case ParamType of
      dsfldINT,
      dsfldUINT:
      begin
        case ParamLen of
          1: Result := Marshal.ReadByte(Value);
          2: Result := Marshal.ReadInt16(Value);
          4: Result := Marshal.ReadInt32(Value);
        end;
      end;
      dsfldBOOL:
        Result := Marshal.ReadInt16(Value) <> 0;
      dsfldFLOATIEEE:
        Result := BitConverter.Int64BitsToDouble(Marshal.ReadInt64(Value));
      dsfldBCD:
        Result := Decimal.FromOACurrency(Marshal.ReadInt64(Value));
      dsfldDATE:
        begin
          T.Time := 0;
          T.Date := Marshal.ReadInt32(Value);
          Result := TimeStampToDateTime(T);
        end;
      dsfldTIME:
        begin
          T.Time := Marshal.ReadInt32(Value);
          T.Date := DateDelta;
          Result := TimeStampToDateTime(T);
        end;
      dsfldDATETIME:
        Result := VarSQLTimeStampCreate(TSQLTimeStamp(
          Marshal.PtrToStructure(Value, TypeOf(TSQLTimeStamp))));
      dsfldTIMESTAMP:
        Result := TimeStampToDateTime(MSecsToTimeStamp(Trunc(
            BitConverter.Int64BitsToDouble(Marshal.ReadInt64(Value)))));
      dsfldZSTRING:
        Result := Marshal.PtrToStringAnsi(Value, ParamLen - 1);
      else
        Result := Unassigned;
    end;
  end;
end;

procedure TCustomClientDataSet.OpenCursor(InfoQuery: Boolean);

  procedure CheckCircularLinks;
  var
    ProvComp: TComponent;
  begin
    if Assigned(MasterSource) and Assigned(Owner) and (not Assigned(RemoteServer))
       and (ProviderName <> '') and (not Assigned(ConnectionBroker)) then
    begin
      ProvComp := Owner.FindComponent(ProviderName);
      if Assigned(ProvComp) and (ProvComp is TDataSetProvider) and
                 Assigned(MasterSource.DataSet) and
                 Assigned(TDataSetProvider(ProvComp).DataSet)  then
        if TDataSetProvider(ProvComp).DataSet = MasterSource.DataSet then
          DatabaseError(SCircularDataLink, MasterSource.DataSet);
    end;
  end;

var
  RecsOut: Integer;
  Options: TGetRecordOptions;
  DataPacket: TDataPacket;
  Stream: TFileStream;
begin
  FProviderEOF := True;
  FSavePacketOnClose := False;
  CheckCircularLinks;
  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 TCustomClientDataSet;
    OpenParentDataSet(FParentDataSet);
    if not Assigned(FDSBase) then
      FDSBase := TDSBaseWrapper.Create;
    Check(FParentDataSet.DSBase.GetEmbeddedDS(DataSetField.FieldNo, FDSBase.FDSBase));
    FieldDefs.HiddenFields := FParentDataSet.FieldDefs.HiddenFields;
  end
  else if not Assigned(DSBase) then
  begin
    if Assigned(FSavedPacket) and Assigned(FSavedPacket.FPacket) then
      DataPacket := FSavedPacket.FPacket else
    begin
      Options := [grMetaData];
      DataPacket := DoGetRecords(FPacketRecords, RecsOut,
        Byte(Options), CommandText, PackageParams(Params));
      ProviderEOF := RecsOut <> FPacketRecords;
    end;
    if not Assigned(DataPacket) then DatabaseError(SNoDataProvider, Self);
    DSBase := CreateDSBase;
    Check(DSBase.AppendData(DataPacket, ProviderEOF));
  end;
  inherited OpenCursor(InfoQuery);
  if not InfoQuery and Assigned(FCloneSource) and not FCloneSource.BOF then
  begin
    SyncCursors(DSCursor, FCloneSource.DSCursor);
    CursorPosChanged;
    Resync([]);
  end;
  { DSBase now has the data packet so we don't need to hold on to it }
  ClearSavedPacket;
  FSavePacketOnClose := True;
end;

procedure TCustomClientDataSet.DoAfterGetParams(var OwnerData: OleVariant);
begin
  if Assigned(FAfterGetParams) then FAfterGetParams(Self, OwnerData);
end;

procedure TCustomClientDataSet.DoBeforeGetParams(var OwnerData: OleVariant);
begin
  if Assigned(FBeforeGetParams) then FBeforeGetParams(Self, OwnerData);
end;

procedure TCustomClientDataSet.FetchParams;
var
  OwnerData: OleVariant;
begin
  DoBeforeGetParams(OwnerData);
  UnpackParams(AppServer.AS_GetParams(ProviderName, OwnerData), Params);
  DoAfterGetParams(OwnerData);
end;

procedure TCustomClientDataSet.Check(Status: DBResult);
var
  ErrMsg: String;
  ptr: IntPtr;
begin
  if Status <> 0 then
  begin
    ptr := Marshal.AllocHGlobal(2048);
    DSBase.GetErrorString(Status, ptr);
    try
      ErrMsg := Marshal.PtrToStringAnsi(ptr);
    finally
      Marshal.FreeHGlobal(ptr);
    end;
    raise EDBClient.Create(ErrMsg, Status);
  end;
end;

procedure TCustomClientDataSet.CloseCursor;
var
//  Params: Variant; {TODO: what does this do?}
  RecsOut: Integer;
  Options: TGetRecordOptions;
  ChangesMade: LongBool;
  Mem: IntPtr;
begin
  ChangesMade := True;
  if Assigned(DSBase) then
  begin
    Mem := Marshal.AllocHGlobal(SizeOf(LongBool));
    try
      DSBase.GetProp(dspropDATAHASCHANGED, Mem);
      ChangesMade := Marshal.ReadInt32(Mem) <> 0;
    finally
      Marshal.FreeHGlobal(Mem);
    end;
  end;
  if (FileName <> '') and ChangesMade 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 = '') and (ProviderName = '') and
     (DataSetField = nil) then
    SaveDataPacket;
  DSBase := nil;
  FCloneSource := nil;
  FParentDataSet := nil;
end;

procedure TCustomClientDataSet.DefChanged(Sender: TObject);
begin
  FStoreDefs := True;
end;

procedure TCustomClientDataSet.InternalInitFieldDefs;
var
  FieldID, I: Integer;
  FieldDescs: TFieldDescList;
  CursorProps: DSProps;
  Mem: IntPtr;
begin
  DSBase.SetProp(dspropCOMPRESSARRAYS, Ord(True));
  Check(DSBase.GetProps(CursorProps));
  SetLength(FieldDescs, CursorProps.iFields);
  Mem := Marshal.AllocHGlobal(CursorProps.iFields * Marshal.SizeOf(TypeOf(DSFLDDesc)));
  try
    Check(DSBase.GetFieldDescs(Mem));
    FieldDescs := TFieldDescList(NativeBufToArray(Mem, FieldDescs));
  finally
    Marshal.FreeHGlobal(Mem);
  end;
  FieldDefs.Clear;
  I := 0;
  FieldID := 1;
  while I < CursorProps.iFields do
    AddFieldDesc(FieldDescs, I, FieldID, FieldDefs);
end;

type
  TPropReader = class(TReader)
  protected
    procedure ReadProperty(AInstance: TPersistent);
  end;

procedure TPropReader.ReadProperty(AInstance: TPersistent);
begin
  inherited; 
end;

procedure TCustomClientDataSet.CheckFieldProps;

  procedure GetTypeName(Field: TObjectField);
  var
    V: Variant;
    i: Integer;
  begin
    V := InternalGetOptionalParam(szTYPENAME, Field.FieldNo);
    if not VarIsNull(V) and not VarIsClear(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;
  Stream: TMemoryStream;
  Reader: TPropReader;
  i, B: 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 VarIsClear(V) then
        Fields[i].Origin := VarToStr(V);
      V := InternalGetOptionalParam(szPROVFLAGS, Fields[i].FieldNo);
      if not (VarIsNull(V) or VarIsClear(V)) then
      begin
        B := V;
        Fields[i].ProviderFlags := TProviderFlags(B);
      end;
      V := InternalGetOptionalParam(szFIELDPROPS, Fields[i].FieldNo);
      if VarIsNull(V) or VarIsClear(V) or not VarIsArray(V) then continue;
      Stream.Size := Length(TBytes(V));
      Stream.Position := 0;
      Stream.Write(TBytes(V), Stream.Size);
      Stream.Position := 0;
      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 TCustomClientDataSet.InternalOpen;

  function GetBoolParam(const ParamName: string): Boolean;
  var
    V: Variant;
  begin
    V := GetOptionalParam(ParamName);
    Result := not VarIsNull(V) and not VarIsClear(V) and (VarType(V) = varBoolean);
    if Result then
      Result := V;
  end;

var
  CursorProps: DSProps;
begin
  if Assigned(FCloneSource) then
    DSCursor := CreateDSCursor(FCloneSource.DSCursor)
  else
  begin
    SetupInternalCalcFields(True);
    DSCursor := CreateDSCursor(nil);
  end;
  if DataSetField <> nil then
    with FParentDataSet do
    begin
      if State = dsInActive then
      begin
        DSCursor.MoveToBOF;
        DSCursor.MoveRelative(1);
      end;
      Check(DSCursor.LinkCursors(0, nil, nil, Self.DSCursor));
    end;
  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(DSCursor.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;
  DSCursor.MoveToBOF;
  if not Assigned(FCloneSource) then
  begin
    if InternalCalcFields and not (csDesigning in ComponentState) then
    begin
      Check(DSBase.SetFieldCalculation(0, @CalcFieldsCallback));
      FDSBase.FCalcFields := True;
    end;
    if FIndexName <> '' then
       if FFieldsIndex then
         SortOnFields(DSCursor, FIndexName, False, False) else
         SwitchToIndex(FIndexName);
    CheckMasterRange;
    if DisableStringTrim then DSBase.SetProp(dspropDISABLESTRINGTRIM, Ord(True));
    if FReadOnly then DSBase.SetProp(dspropREADONLY, Integer(True));
    ResetAllAggs(FAggregatesActive);
    if Filtered then ActivateFilters;
  end;
  InitBufferPointers(False);
  if (DataSetField <> nil) and FetchOnDemand then
    CheckDetailRecords;
  SetupConstraints;
end;

procedure TCustomClientDataSet.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;
  DSCursor := nil;
  FFindCursor := nil;
  FNotifyCallback := False;
end;

procedure TCustomClientDataSet.InternalRefresh;
const
  Options: TGetRecordOptions = [grReset];
var
  SeqNo: DWord;
  RecCount, RecsOut: Integer;
  DataPacket: TDataPacket;
  Mem: IntPtr;
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
    DSBase.Reset;
    if FetchOnDemand then CheckDetailRecords;
  end else
  begin
    DSCursor.GetSequenceNumber(SeqNo);
    if not ProviderEOF then
    begin
      Mem := Marshal.AllocHGlobal(SizeOf(Integer));
      try
        DSBase.GetProp(dspropRECORDSINDS, Mem);
        RecCount := Marshal.ReadInt32(Mem);
      finally
        Marshal.FreeHGlobal(Mem);
      end;
    end else
      RecCount := AllRecords;
    DataPacket := DoGetRecords(RecCount, RecsOut, Byte(Options), '', Unassigned);
    ProviderEOF := RecsOut <> RecCount;
    DSBase.Reset;
    DSBase.SetProp(dspropDSISPARTIAL, Ord(False));
    Check(DSBase.AppendData(DataPacket, ProviderEOF));
    DSCursor.MoveToSeqNo(SeqNo);
  end;
end;

function TCustomClientDataSet.IsCursorOpen: Boolean;
begin
  Result := DSCursor <> nil;
end;

function TCustomClientDataSet.GetData: OleVariant;
var
  DataPacket: TDataPacket;
begin
  if Active then
  begin
    CheckBrowseMode;
    DSBase.SetProp(dspropXML_STREAMMODE, xmlOFF);
    Check(DSBase.StreamDS(DataPacket));
  end else
  begin
    if Assigned(FSavedPacket) then
      DataPacket := FSavedPacket.FPacket
    else
      DataPacket := nil;
  end;
  DataPacketToVariant(DataPacket, Result);
end;

procedure TCustomClientDataSet.SetData(const Value: OleVariant);
begin
  FSavePacketOnClose := False;
  Close;
  ClearSavedPacket;
  if not VarIsNull(Value) then
  begin
    if not Assigned(FSavedPacket) then
      FSavedPacket := TDataPacketWrapper.Create;
    FSavedPacket.FPacket := VarToDataPacket(Value);
    Open;
  end;
end;

function TCustomClientDataSet.GetXMLData: string;
var
  DataPacket: TDataPacket;
  VarPacket: OleVariant;
begin
  if Active then
  begin
    CheckBrowseMode;
    DSBase.SetProp(dspropXML_STREAMMODE, xmlON);
    Check(DSBase.StreamDS(DataPacket));
    DataPacketToVariant(DataPacket, VarPacket);
    Result := VariantArrayToString(VarPacket);
  end;
end;

procedure TCustomClientDataSet.SetXMLData(const Value: string);
begin
  SetData(StringToVariantArray(Value));
end;

procedure TCustomClientDataSet.ClearSavedPacket;
begin
  if Assigned(FSavedPacket) then
    FreeDataPacket(FSavedPacket.FPacket);
end;

procedure TCustomClientDataSet.SaveDataPacket(Format: TDataPacketFormat);
const
  StreamMode: array[TDataPacketFormat] of DWord = (xmlOFF, xmlON, xmlUTF8);
begin
  if Assigned(DSBase) and (DataSetField = nil) then
  begin
    DSBase.SetProp(dspropXML_STREAMMODE, StreamMode[Format]);
    ClearSavedPacket;
    if not Assigned(FSavedPacket) then
      FSavedPacket := TDataPacketWrapper.Create;
    Check(DSBase.StreamDS(FSavedPacket.FPacket));
  end;
end;

function TCustomClientDataSet.GetDataSize: Integer;
begin
  if Assigned(DataSetField) then
    Result := -1
  else if Active then
  begin
    SaveDataPacket;
    if Assigned(FSavedPacket) and Assigned(FSavedPacket.FPacket) then
      Result := DataPacketSize(FSavedPacket.FPacket)
    else
      Result := 0;
    ClearSavedPacket;
  end
  else if Assigned(FSavedPacket) and Assigned(FSavedPacket.FPacket) then
    Result := DataPacketSize(FSavedPacket.FPacket)
  else
    Result := 0;
end;

procedure TCustomClientDataSet.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 TCustomClientDataSet.InternalFetch(Options: TFetchOptions);
var
  DataPacket: TDataPacket;
  NewData: OleVariant;
  BaseDS: TCustomClientDataSet;
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, TObject(Integer(0)));
end;

procedure TCustomClientDataSet.FetchBlobs;
begin
  InternalFetch([foBlobs]);
end;

procedure TCustomClientDataSet.FetchDetails;
begin
  InternalFetch([foDetails]);
end;

procedure TCustomClientDataSet.RefreshRecord;
begin
  InternalFetch([foRecord]);
  Resync([]);
end;

procedure TCustomClientDataSet.CheckProviderEOF;
begin
  if HasAppServer and not ProviderEOF and FFetchOnDemand and (FPacketRecords <> 0) then
    FetchMoreData(True);
end;

procedure TCustomClientDataSet.AddDataPacket(const Data: TDataPacket; HitEOF: Boolean);
begin
  Check(DSBase.AppendData(Data, HitEOF));
end;

procedure TCustomClientDataSet.AppendData(const Data: OleVariant; HitEOF: Boolean);
begin
  if not Active then
  begin
    Self.Data := Data;
    if not HitEOF then
      DSBase.SetProp(dspropDSISPARTIAL, Integer(False));
  end else
  begin
    AddDataPacket(VarToDataPacket(Data), HitEOF);
    if State <> dsBrowse then Exit;
    if IsEmpty then First else
    begin
      UpdateCursorPos;
      Resync([]);
    end;
  end;
end;

function TCustomClientDataSet.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 TCustomClientDataSet.SetProviderName(const Value: string);
begin
  if Value = FProviderName then Exit;
  if (Value <> '') then
  begin
    CheckInactive;
    ClearSavedPacket;
  end;
  FAppServer := nil;
  FProviderName := Value;
end;

procedure TCustomClientDataSet.SetProvider(Provider: TComponent);
begin
  if Provider is TCustomProvider then
    AppServer := TLocalAppServer.Create(TCustomProvider(Provider)) else
  if Provider is TDataset then
    AppServer := TLocalAppServer.Create(TDataset(Provider)) else
    AppServer := nil;
end;

function TCustomClientDataSet.GetAppServer: IAppServer;
var
  ProvComp: TComponent;
  DS: TObject;
begin
  if not HasAppServer then
  begin
    if ProviderName <> '' then
      if Assigned(RemoteServer) then
        FAppServer := RemoteServer.GetServer else
      if Assigned(ConnectionBroker) then
        FAppServer := ConnectionBroker.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 TCustomClientDataSet.GetHasAppServer: Boolean;
begin
  Result := Assigned(FAppServer);
end;

procedure TCustomClientDataSet.SetAppServer(Value: IAppServer);
begin
  FAppServer := Value;
  if Assigned(Value) then
    ClearSavedPacket;
end;

procedure TCustomClientDataSet.SetProviderEOF(Value: Boolean);
begin
  FProviderEOF := Value;
  if Assigned(FCloneSource) then
    FCloneSource.ProviderEOF := Value;
end;

function TCustomClientDataSet.GetProviderEOF: Boolean;
begin
  if Assigned(FCloneSource) then
    FProviderEOF := FCloneSource.ProviderEOF;
  Result := FProviderEOF;
end;

function TCustomClientDataSet.GetRemoteServer: TCustomRemoteServer;
begin
  Result := FRemoteServer;
end;

procedure TCustomClientDataSet.SetRemoteServer(Value: TCustomRemoteServer);
begin
  if Value = FRemoteServer then Exit;
  if Assigned(Value) and Assigned(ConnectionBroker) then
    SetConnectionBroker(Nil);
  AppServer := nil;
  if Assigned(FRemoteServer) then FRemoteServer.UnRegisterClient(Self);
  FRemoteServer := Value;
  if Assigned(Value) then
  begin
    CheckInactive;
    Value.RegisterClient(Self);
    ClearSavedPacket;
    Value.FreeNotification(Self);
  end;
  FRemoteServer := Value;
end;

procedure TCustomClientDataSet.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;
  if (Operation = opRemove) and (AComponent = FConnectionBroker) then
    FConnectionBroker:= nil;
end;

procedure TCustomClientDataSet.DataEvent(Event: TDataEvent; Info: TObject);
begin
  case Event of
    deParentScroll: MasterChanged(Self);
    deDataSetScroll,
    deDataSetChange: SetAltRecBuffers(nil, nil, nil);
    deFieldListChange: FAggFieldsInit := False;
    deConnectChange:
      if not Boolean(Info) then
        AppServer := nil;
  end;
  inherited;
end;

function TCustomClientDataSet.GetDelta: OleVariant;
var
  FDeltaDS: IDSBase;
begin
  CheckBrowseMode;
  Check(DSBase.GetDelta(FDeltaDS));
  if Assigned(FDeltaPacket) then
    FreeDataPacket(FDeltaPacket.FPacket)
  else
    FDeltaPacket := TDataPacketWrapper.Create;
  Check(FDeltaDS.StreamDS(FDeltaPacket.FPacket));
  DataPacketToVariant(FDeltaPacket.FPacket, Result);
end;

procedure TCustomClientDataSet.Execute;
begin
  DoExecute(PackageParams(Params));
end;

function TCustomClientDataSet.DataRequest(Data: OleVariant): OleVariant;
begin
  Result := AppServer.AS_DataRequest(ProviderName, Data);
end;

function TCustomClientDataSet.ApplyUpdates(MaxErrors: Integer): Integer;
var
  RootDataset: TCustomClientDataset;
begin
  CheckBrowseMode;
  RootDataset := Self;
  while RootDataset.FParentDataSet <> nil do
    RootDataset := RootDataset.FParentDataset;
  with RootDataset do
    if ChangeCount = 0 then
      Result := 0
    else
      Reconcile(DoApplyUpdates(Delta, MaxErrors, Result));
end;

procedure TCustomClientDataSet.MergeChangeLog;
begin
  CheckBrowseMode;
  DSBase.AcceptChanges;
  UpdateCursorPos;
  Resync([]);
end;

procedure TCustomClientDataSet.SetAltRecBuffers(Old, New, Cur: TRecordBuffer);
begin
  FOldValueBuffer := Old;
  FNewValueBuffer := New;
  FCurValueBuffer := Cur;
end;

function TCustomClientDataSet.ReconcileCallback(
    iClientData   : Integer;      { Not used }
    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   : string;     { 1-level error context, if any (otherwise Null) }
    pRecUpd,                   { Record that failed update }
    pRecOrg,                   { Original record, if any }
    pRecConflict  : TRecordBuffer;   { Conflicting error, if any }
    iLevels       : Integer;   { Number of levels to error0level }
    piFieldIDs    : IntPtr   { Array of fieldIDS to navigate to error-dataset }
): dsCBRType;
var
  I: Integer;
  Action: TReconcileAction;
  UpdateKind: TUpdateKind;
  DataSet: TCustomClientDataSet;
  E: EReconcileError;
  ReconcileInfo: TReconcileInfo;
  F: TField;
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 := 0 to iLevels - 1 do
      begin
        F := DataSet.Fields.FieldByNumber(Marshal.ReadInt32(piFieldIDs, I));
         DataSet := TCustomClientDataSet((F as TDataSetField).NestedDataSet);
      end;
      if UpdateKind = ukDelete then
        DataSet.SetAltRecBuffers(pRecUpd, pRecOrg, pRecConflict) else
        DataSet.SetAltRecBuffers(pRecOrg, pRecUpd, pRecConflict);
      ReconcileInfo := TReconcileInfo.Create;
      ReconcileInfo.DataSet := Dataset;
      ReconcileInfo.UpdateKind := UpdateKind;
      ReconcileInfo.ReconcileError := E;
      ReconcileInfo.ActionRef := Action;
      DataEvent(deReconcileError, ReconcileInfo);
      if Assigned(FOnReconcileError) then
        FOnReconcileError(DataSet, E, UpdateKind, Action);
    finally
      E.Free;
    end;
  except
    if Assigned(Classes.ApplicationHandleException) then
      ApplicationHandleException(Self);
    Action := raAbort;
  end;
  Result := Ord(Action) + 1;
  FInReconcileCallback := False;
end;

function TCustomClientDataSet.Reconcile(const Results: OleVariant): Boolean;
var
  I: Integer;
  AField: TField;
begin
  if VarIsNull(Results) then MergeChangeLog else
  begin
    UpdateCursorPos;
    FReconcileDataSet := TCustomClientDataSet.Create(Self);
    try
      FReconcileDataSet.FDSBase := TDSBaseWrapper.Create;
      Check(DSBase.Clone(0, True, False, FReconcileDataSet.FDSBase.FDSBase));
      FReconcileDataSet.ObjectView := True;
      FReconcileDataSet.Open;
      for I := 0 to FReconcileDataSet.FieldCount - 1 do
      begin
        AField := Self.FindField(FReconcileDataSet.Fields[I].FieldName);
        if Assigned(AField) then
          FReconcileDataSet.Fields[I].DisplayLabel := AField.DisplayLabel;
      end;
      Check(DSBase.Reconcile_MD(FReconcileDataSet.DSBase, FDeltaPacket.FPacket,
        VarToDataPacket(Results), 0, @ReconcileCallback));
    finally
      FReconcileDataSet.Free;
      FReconcileDataSet := nil;
    end;
    Resync([]);
  end;
  Result := (ChangeCount = 0);
end;

procedure TCustomClientDataSet.NotifyCallback(ClientData: Integer);
begin
  try
    if State = dsBrowse then
    begin
      UpdateCursorPos;
      Resync([]);
      UpdateCursorPos;
    end;
  except
  end;
end;

procedure TCustomClientDataSet.SetNotifyCallback;
begin
  if not FNotifyCallback then
  begin
    Check(DSCursor.SetNotifyCallBack(0, @NotifyCallback));
    FNotifyCallback := True;
  end;
end;

procedure TCustomClientDataSet.CloneCursor(Source: TCustomClientDataSet; Reset, KeepSettings: Boolean);
begin
  Source.CheckActive;
  Close;
  DSBase := 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.ExprFilter <> nil then DSCursor.DropFilter(Source.ExprFilter);
    if Source.FuncFilter <> nil then DSCursor.DropFilter(Source.FuncFilter);
    Resync([]);
  end;
  SetNotifyCallback;
  Source.SetNotifyCallback;
end;

procedure TCustomClientDataSet.EncodeFieldDesc(var FieldDesc: DSFLDDesc;
  const Name: string; DataType: TFieldType; Size, Precision: Integer;
  Calculated: Boolean; Attributes: TFieldAttributes);
begin
  with FieldDesc do
  begin
    szName := Name;
    if DataType = ftWideString then
      iFldType := fldUNICODE else
      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:
        iUnits1 := Size;
      ftWideString:
        iUnits1 := Size * 2;
      ftBCD, ftFMTBcd:
        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 TCustomClientDataSet.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
    begin
      if Length(Name) >= MIDASNAMELEN then
        raise EDSWriter.CreateFmt(SFieldNameTooLarge,[Name, MIDASNAMELEN - 1]);
      if HasChildDefs then
        GetFieldDefCount(ChildDefs, Count);
    end;
  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(DSBase.CreateIndex(IndexDesc));
    end;
  end;

var
  FieldDefCount, DescNo: Integer;
  FieldDescs: TFieldDescList;
  Mem: IntPtr;
begin
  CheckInactive;
  InitFieldDefsFromFields;
  FieldDefCount := 0;
  GetFieldDefCount(FieldDefs, FieldDefCount);
  if FieldDefCount = 0 then
    DatabaseError(SCannotCreateDataSet);
  SetLength(FieldDescs, FieldDefCount);
  DescNo := 0;
  EncodeFieldDescs(FieldDefs, FieldDescs, DescNo);
  DSBase := CreateDSBase;
  try
    Mem := ArrayToNativeBuf(FieldDescs);
    try
      Check(DSBase.Create(FieldDefCount, Mem, Name));
    finally
      Marshal.FreeHGlobal(Mem);
    end;
    CreateIndexes;
  except
    DSBase := nil;
    FCloneSource := nil;
    raise;
  end;
  Open;
end;

procedure TCustomClientDataSet.EmptyDataSet;
begin
  CheckBrowseMode;
  Check(DSBase.Reset);
  DSBase.SetProp(dspropDATAHASCHANGED, Integer(True));
  ProviderEOF := True;
  Resync([]);
  InitRecord(ActiveBuffer);
end;

procedure TCustomClientDataSet.SetupInternalCalcFields(Add: Boolean);
var
  Precision, I: Integer;
  FieldDesc: DSFLDDesc;
  Mem: IntPtr;
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 in [ftBCD,ftFMTBcd] then
            Precision := TBCDField(Fields[I]).Precision else
            Precision := 0;
          EncodeFieldDesc(FieldDesc, FieldName, DataType, Size, Precision, True, []);
          Mem := Marshal.AllocHGlobal(Marshal.SizeOF(TypeOf(DSFLDDesc)));
          try
            Marshal.StructureToPtr(TObject(FieldDesc), Mem, False);
            Check(DSBase.AddField(Mem));
          finally
            Marshal.DestroyStructure(Mem, TypeOf(DSFLDDesc));
          end;
        end;
    end;
  end
  else if InternalCalcFields then
  begin
    Check(DSBase.SetFieldCalculation(0, nil));
    FDSBase.FCalcFields := False;
    Check(DSBase.AddField(nil));
  end;
end;

procedure TCustomClientDataSet.WriteDataPacket(Stream: TStream; WriteSize: Boolean;
  Format: TDataPacketFormat = dfBinary);
var
  Size: Integer;
  Data: OleVariant;
begin
  if Active then CheckBrowseMode;
  if IsCursorOpen then
  begin
    CheckProviderEOF;
    SaveDataPacket(Format);
  end;
  if Assigned(FSavedPacket) and Assigned(FSavedPacket.FPacket) then
  begin
    Size := DataPacketSize(FSavedPacket.FPacket);
    DataPacketToVariant(FSavedPacket.FPacket, Data);
      if WriteSize then
        Stream.Write(Size, SizeOf(Size));
      Stream.Write(TBytes(Data), Size);
    if Active then ClearSavedPacket;
  end;
end;

procedure TCustomClientDataSet.ReadDataPacket(Stream: TStream; ReadSize: Boolean);
var
  Size: Integer;
  B: TBytes;
begin
  if ReadSize then
    Stream.ReadBuffer(Size, SizeOf(Size)) else
    Size := Stream.Size - Stream.Position;
  if Size > 0 then
  begin
    ClearSavedPacket;
    SetLength(B, Size);
    Stream.Read(B, Size);
    if not Assigned(FSavedPacket) then
      FSavedPacket := TDataPacketWrapper.Create;
    FSavedPacket.FPacket := VarToDataPacket(B);
  end;
end;

procedure TCustomClientDataSet.LoadFromStream(Stream: TStream);
begin
  Close;
  ReadDataPacket(Stream, False);
  Open;
end;

procedure TCustomClientDataSet.SaveToStream(Stream: TStream; Format: TDataPacketFormat = dfBinary);
begin
  WriteDataPacket(Stream, False, Format);
end;

procedure TCustomClientDataSet.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 TCustomClientDataSet.SaveToFile(const FileName: string = '';
  Format: TDataPacketFormat = dfBinary);
var
  Stream: TStream;
  TempFileName: string;
begin
  // Prevent writing a zero-byte file
  if Active or (Assigned(DSBase) and (DatasetField = nil)) then
  begin
    if FileName = '' then
      TempFileName := Self.FileName else
      TempFileName := FileName;
    Stream := TFileStream.Create(TempFileName, fmCreate);
    try
      if (Format = dfBinary) and (LowerCase(ExtractFileExt(TempFileName)) = '.xml') then
        Format := dfXML;
      SaveToStream(Stream, Format);
    finally
      Stream.Free;
    end;
  end;
end;

procedure TCustomClientDataSet.SetLogChanges(Value: Boolean);
begin
  CheckBrowseMode;
  Check(DSBase.SetProp(dspropLOGCHANGES, Integer(Value)));
end;

function TCustomClientDataSet.GetLogChanges: Boolean;
var
  LogChanges: IntPtr;
begin
  CheckBrowseMode;
  LogChanges := Marshal.ALlocHGlobal(SizeOf(Integer));
  try
    Check(DSBase.GetProp(dspropLOGCHANGES, LogChanges));
    Result := Marshal.ReadInt32(LogChanges) <> 0;
  finally
    Marshal.FreeHGlobal(LogChanges);
  end;
end;

function TCustomClientDataSet.GetCanModify: Boolean;
begin
  Result := FCanModify and not ReadOnly;
end;

procedure TCustomClientDataSet.SetReadOnly(Value: Boolean);
begin
  FReadOnly := Value;
  if Assigned(DSBase) then
  begin
    Check(DSBase.SetProp(dspropREADONLY, Integer(Value)));
    FCanModify := not Value;
  end;
end;

function TCustomClientDataSet.ConstraintsDisabled: Boolean;
begin
  Result := FConstDisableCount > 0;
end;

procedure TCustomClientDataSet.DisableConstraints;
begin
  if FConstDisableCount = 0 then
    Check(DSBase.SetProp(dspropCONSTRAINTS_DISABLED, Longint(True)));
  Inc(FConstDisableCount);
end;

procedure TCustomClientDataSet.EnableConstraints;
begin
  if FConstDisableCount <> 0 then
  begin
    Dec(FConstDisableCount);
    if FConstDisableCount = 0 then
      Check(DSBase.SetProp(dspropCONSTRAINTS_DISABLED, Longint(False)));
  end;
end;

{ Record Functions }

procedure TCustomClientDataSet.InitBufferPointers(GetProps: Boolean);
var
  CursorProps: DSProps;
begin
  if GetProps then
  begin
    Check(DSCursor.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 TCustomClientDataSet.AllocRecordBuffer: TRecordBuffer;
begin
  Result := FBufferManager.AllocHGlobal(FRecBufSize);
end;

procedure TCustomClientDataSet.FreeRecordBuffer(var Buffer: TRecordBuffer);
begin
  FBufferManager.FreeHGlobal(Buffer);
  Buffer := nil;
end;

procedure TCustomClientDataSet.InternalInitRecord(Buffer: TRecordBuffer);
begin
  Check(DSCursor.InitRecord(Buffer));
end;

procedure TCustomClientDataSet.ClearCalcFields(Buffer: TRecordBuffer);
var
  I: Integer;
begin
  for I := 0 to CalcFieldsSize - 1 do
    Marshal.WriteByte(Buffer, FRecordSize + I, 0);
end;

procedure TCustomClientDataSet.InitRecord(Buffer: TRecordBuffer);
var
  RecInfo: TRecInfo;
begin
  inherited InitRecord(Buffer);
  RecInfo.BookmarkFlag := bfInserted;
  RecInfo.RecordNumber := -1;
  RecInfo.Attribute := dsRecNew;
  Marshal.StructureToPtr(TObject(RecInfo), IntPtr(Integer(Buffer) + FRecInfoOfs), False);
end;

function TCustomClientDataSet.GetRecord(Buffer: TRecordBuffer; GetMode: TGetMode;
  DoCheck: Boolean): TGetResult;
var
  Status: DBResult;
  TempRN: Longword;
  RecInfo: TRecInfo;
begin
  with DSCursor 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);
          RecInfo.BookmarkFlag := bfCurrent;
          GetSequenceNumber(TempRN);
          RecInfo.RecordNumber := TempRN;
          GetRecordAttribute(RecInfo.Attribute);
          Marshal.StructureToPtr(TObject(RecInfo),
            IntPtr(Integer(Buffer) + FRecInfoOfs), False);
          GetCalcFields(Buffer);
          Check(GetCurrentBookmark(TBookmark(Integer(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 TCustomClientDataSet.GetCurrentRecord(Buffer: TRecordBuffer): Boolean;
begin
  if not IsEmpty and (GetBookmarkFlag(ActiveBuffer) = bfCurrent) then
  begin
    UpdateCursorPos;
    Result := (DSCursor.GetCurrentRecord(Buffer) = DBERR_NONE);
  end else
    Result := False;
end;

function TCustomClientDataSet.GetRecordCount: Integer;
var
  Status: DBResult;
begin
  CheckActive;
  if (FParentDataSet <> nil) and (FParentDataSet.State <> dsInsert) then
    FParentDataSet.UpdateCursorPos;
  Status := DSCursor.GetRecordCount(Result);
  if Status <> DBERR_DETAILSNOTFETCHED then
    Check(Status);
end;

function TCustomClientDataSet.GetRecNo: Integer;
var
  BufPtr: TRecordBuffer;
  RecInfo: TRecInfo;
begin
  CheckActive;
  if State = dsInternalCalc then
    Result := -1
  else
  begin
    if State = dsCalcFields then
      BufPtr := CalcBuffer else
      BufPtr := ActiveBuffer;
    RecInfo := TRecInfo(Marshal.PtrToStructure(IntPtr(Integer(BufPtr) + FRecInfoOfs),
      TypeOf(TRecInfo)));
    Result := RecInfo.RecordNumber;
  end;
end;

procedure TCustomClientDataSet.SetRecNo(Value: Integer);
begin
  CheckBrowseMode;
  if Value <> RecNo then
  begin
    DoBeforeScroll;
    Check(DSCursor.MoveToSeqNo(Value));
    Resync([rmCenter]);
    DoAfterScroll;
  end;
end;

function TCustomClientDataSet.GetRecordSize: Word;
begin
  Result := FRecordSize;
end;

function TCustomClientDataSet.GetActiveRecBuf(var RecBuf: TRecordBuffer): Boolean;

  function GetOriginalBuffer: TRecordBuffer;
  begin
    UpdateCursorPos;
    Result := TempBuffer;
    if DSCursor.GetProp(curpropGETORG_RECBUF, Result) <> DBERR_NONE then
      GetCurrentRecord(Result);
  end;

begin
  case State of
    dsBlockRead,
    dsBrowse: if IsEmpty then RecBuf := nil else RecBuf := ActiveBuffer;
    dsEdit, dsInsert: RecBuf := ActiveBuffer;
    dsSetKey: RecBuf := FKeyBuffers[FKeyBuffer].Data;
    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 TCustomClientDataSet.GetChangeCount: Integer;
var
  Mem: IntPtr;
begin
  if Active then
  begin
    Mem := Marshal.AllocHGlobal(SizeOf(Integer));
    try
      Check(DSBase.GetProp(dspropNOOFCHANGES, Mem));
      Result := Marshal.ReadInt32(Mem);
    finally
      Marshal.FreeHGlobal(Mem);
    end;
  end else
    Result := 0;
end;

function TCustomClientDataSet.UpdateStatus: TUpdateStatus;
var
  BufPtr: TRecordBuffer;
  Attr: Byte;
  RecInfo: TRecInfo;
begin
  CheckActive;
  if State = dsInternalCalc then
    Result := usUnModified
  else
  begin
    if State = dsCalcFields then
      BufPtr := CalcBuffer else
      BufPtr := ActiveBuffer;
    RecInfo := TRecInfo(Marshal.PtrToStructure(IntPtr(Integer(BufPtr) + FRecInfoOfs),
      TypeOf(TRecInfo)));
    Attr := RecInfo.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 TCustomClientDataSet.AddFieldDesc(FieldDescs: TFieldDescList;
  var DescNo: Integer; var FieldID: Integer; FieldDefs: TFieldDefs);
var
  FPrecision, I: Integer;
  FType: TFieldType;
  FSize, NameLen: 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;
    FName := TrimRight(szName);
    NameLen := Length(FName);
    I := 0;
    while FieldDefs.IndexOf(FName) >= 0 do
    begin
      Inc(I);
      SetLength(FName, NameLen);
      FName := Format('%s_%d', [FName, 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;
      fldFMTBCD, fldBCD:
        begin
          FSize := Abs(iUnits2);
          FPrecision := iUnits1;
          if iFldType = fldFMTBCD then
            FType := ftFMTBcd;
        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 TCustomClientDataSet.GetFieldData(Field: TField; Buffer: TValueBuffer): Boolean;
var
  IsBlank: Integer;
  RecBuf: TRecordBuffer;
  I: Integer;
begin
  Result := False;
  if GetActiveRecBuf(RecBuf) then
    with Field do
      if FieldKind in [fkData, fkInternalCalc] then
      begin
        Check(DSCursor.GetField(RecBuf, FieldNo, Buffer, IsBlank));
        Result := IsBlank = 0;
      end else
        if State in [dsBrowse, dsEdit, dsInsert, dsCalcFields] then
        begin
          RecBuf := TRecordBuffer(Integer(RecBuf) + FRecordSize + Offset);
          Result := Marshal.ReadByte(RecBuf) <> 0;
          if Result and (Buffer <> nil) then
            for I := 1 to DataSize do
              Marshal.WriteByte(Buffer, I - 1, Marshal.ReadByte(RecBuf, I));
        end;
end;

function TCustomClientDataSet.GetFieldData(FieldNo: Integer; Buffer: TValueBuffer): Boolean;
var
  RecBuf: TRecordBuffer;
  IsBlank: Integer;
begin
  Result := GetActiveRecBuf(RecBuf);
  if Result then
  begin
    Check(DSCursor.GetField(RecBuf, FieldNo, Buffer, IsBlank));
    Result := IsBlank = 0;
  end;
end;

function TCustomClientDataSet.GetStateFieldValue(State: TDataSetState; Field: TField): Variant;

  function CheckNotChanged(Buffer: TRecordBuffer): Variant;
  var
    IsBlank: Integer;
  begin
    if (Buffer = nil) then
      IsBlank := BLANK_NOTCHANGED
    else
      Check(DSCursor.GetField(Buffer, Field.FieldNo, nil, 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 TCustomClientDataSet.SetFieldData(Field: TField; Buffer: TValueBuffer);
var
  RecBuf: TRecordBuffer;
  B: Boolean;
  I: Integer;
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(DSCursor.VerifyField(FieldNo, Buffer));
      Check(DSCursor.PutField(RecBuf, FieldNo, Buffer));
      if FAggFieldsUpdated <> nil then
        FAggFieldsUpdated[FieldNo-1] := True;
    end else
    begin
      if State = dsInternalCalc then Exit;
      RecBuf := TRecordBuffer(Integer(RecBuf) + FRecordSize + Offset);
      if Assigned(Buffer) then
        B := Marshal.ReadInt32(Buffer) <> 0;
      Marshal.WriteByte(RecBuf, Byte(B));
      if B then
        for I := 1 to DataSize do
          Marshal.WriteByte(RecBuf, I, Marshal.ReadByte(Buffer, I - 1));
    end;
    if not (State in [dsCalcFields, dsInternalCalc, dsFilter, dsNewValue]) then
      DataEvent(deFieldChange, Field);
  end;
end;

function TCustomClientDataSet.CreateBlobStream(Field: TField; Mode: TBlobStreamMode): TStream;
var
  Status: DBResult;
  BlobLen: DWord;
  Buffer: TRecordBuffer;
begin
  if GetActiveRecBuf(Buffer) then
  begin
    Status := DSCursor.GetBlobLen(Buffer, Field.FieldNo, BlobLen);
    if (Status = DBERR_BLOBNOTFETCHED) and FetchOnDemand then
      FetchBlobs;
  end;
  Result := TClientBlobStream.Create(Field as TBlobField, Mode);
end;

procedure TCustomClientDataSet.RefreshInternalCalcFields(Buffer: TRecordBuffer);
begin
  CalculateFields(Buffer);
end;

function TCustomClientDataSet.CalcFieldsCallBack(ClientData: Integer; RecBuf: TRecordBuffer): DBResult;
var
  SaveState: TDataSetState;
begin
  try
    SaveState := SetTempState(dsInternalCalc);
    try
      CalculateFields(RecBuf);
    finally
      RestoreState(SaveState);
    end;
  except
  end;
  Result := 0;
end;

procedure TCustomClientDataSet.DataConvert(Field: TField;
  Source, Dest: TValueBuffer; ToNative: Boolean);
var
  S: WideString;
  I: Integer;
  W: Integer;
begin
  if Field.DataType = ftWideString then
  begin
    if ToNative then
    begin
       S := Marshal.PtrToStringUni(Source);
      Marshal.WriteInt16(Dest, Length(S) * 2); // write the length
      for I := 1 to Length(S) do
        Marshal.WriteInt16(Dest, I * 2, Ord(S[I])); // copy the string;
    end else
    begin
      W := Marshal.ReadInt16(Source) div 2;
      // shift the characters down by one
      for I := 1 to W do
        Marshal.WriteInt16(Dest, 2 * (I - 1), Marshal.ReadInt16(Source, I * 2));
      Marshal.WriteInt16(Dest, W * 2, 0); // add null terminator
    end;
  end else
    inherited;
end;

{ Navigation / Editing }

procedure TCustomClientDataSet.InternalFirst;
begin
  Check(DSCursor.MoveToBOF);
end;

procedure TCustomClientDataSet.InternalLast;
begin
  CheckProviderEOF;
  Check(DSCursor.MoveToEOF);
end;

procedure TCustomClientDataSet.InternalPost;
begin
  inherited;
  if State = dsEdit then
    Check(DSCursor.ModifyRecord(ActiveBuffer)) else
    Check(DSCursor.InsertRecord(ActiveBuffer));
  if AggregatesActive then
    DoAggUpdates(State = dsEdit);
end;

procedure TCustomClientDataSet.InternalCancel;
begin
  if BlobFieldCount > 0 then
    DSBase.ReleaseBlobs(0);
end;

procedure TCustomClientDataSet.InternalDelete;
begin
  if doDisableDeletes in FDSOptions then
    DatabaseError(SNoDeletesAllowed, Self);
  Check(DSCursor.DeleteRecord);
  if AggregatesActive then
    DoAggUpdates(False);
end;

procedure TCustomClientDataSet.InternalEdit;
begin
  if doDisableEdits in FDSOptions then
    DatabaseError(SNoEditsAllowed, Self);
end;

procedure TCustomClientDataSet.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 TCustomClientDataSet.InternalInsert;
begin
  DSCursor.SetProp(curpropSETCRACK, 0);
  CursorPosChanged;
end;

procedure TCustomClientDataSet.Post;
var
  CursorProps: DSProps;
begin
  Check(DSBase.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 TCustomClientDataSet.Cancel;
begin
  inherited Cancel;
  if State = dsSetKey then
    PostKeyBuffer(False);
end;

procedure TCustomClientDataSet.InternalAddRecord(Buffer: TRecordBuffer; Append: Boolean);
begin
  if Append then DSCursor.MoveToEOF;
  Check(DSCursor.InsertRecord(Buffer));
end;

procedure TCustomClientDataSet.InternalGotoBookmark(const Bookmark: TBookmark);
begin
  Check(DSCursor.MoveToBookmark(Bookmark));
end;

procedure TCustomClientDataSet.InternalSetToRecord(Buffer: TRecordBuffer);
begin
  InternalGotoBookmark(TBookmark(Integer(Buffer) + FBookmarkOfs));
end;

function TCustomClientDataSet.GetBookmarkFlag(Buffer: TRecordBuffer): TBookmarkFlag;
var
  RecInfo: TRecInfo;
begin
  RecInfo := TRecInfo(Marshal.PtrToStructure(IntPtr(Integer(Buffer) + FRecInfoOfs),
      TypeOf(TRecInfo)));
  Result := RecInfo.BookmarkFlag;
end;

procedure TCustomClientDataSet.SetBookmarkFlag(Buffer: TRecordBuffer; Value: TBookmarkFlag);
var
  RecInfo: TRecInfo;
  RecInfoPtr: IntPtr;
begin
  RecInfoPtr := IntPtr(Integer(Buffer) + FRecInfoOfs);
  RecInfo := TRecInfo(Marshal.PtrToStructure(RecInfoPtr, TypeOf(TRecInfo)));
  RecInfo.BookmarkFlag := Value;
  Marshal.StructureToPtr(TObject(RecInfo), RecInfoPtr, False);
end;

procedure TCustomClientDataSet.GetBookmarkData(Buffer: TRecordBuffer; var Bookmark: TBookmark);
var
  I: Integer;
begin
  for I := 0 to BookmarkSize - 1 do
    Marshal.WriteByte(Bookmark, I, Marshal.ReadByte(Buffer, FBookmarkOfs + I));
end;

procedure TCustomClientDataSet.SetBookmarkData(Buffer: TRecordBuffer; const Bookmark: TBookmark);
var
  I: Integer;
begin
  for I := 0 to BookmarkSize - 1 do
    Marshal.WriteByte(ActiveBuffer, FBookmarkOfs + I, Marshal.ReadByte(Bookmark, I));
end;

function TCustomClientDataSet.CompareBookmarks(const 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(DSCursor.CompareBookmarks(Bookmark1, Bookmark2, Result));
    if Result = 2 then Result := 0;
  end;
end;

function TCustomClientDataSet.BookmarkValid(const Bookmark: TBookmark): Boolean;
begin
  Result := DSCursor <> nil;
  if Result then
  begin
    CursorPosChanged;
    Result := (DSCursor.MoveToBookmark(Bookmark) = DBERR_NONE) and
      (DSCursor.GetCurrentRecord(nil) = DBERR_NONE);
  end;
end;

procedure TCustomClientDataSet.SyncCursors(Cursor1, Cursor2: IDSCursor);
var
  BM: TBookmark; {DSBOOKMRK}
begin
  BM := Marshal.AllocHGlobal(Marshal.SizeOf(TypeOf(DSBOOKMRK)));
  try
    Cursor2.GetCurrentBookmark(BM);
    Cursor1.MoveToBookmark(BM);
  finally
    Marshal.FreeHGlobal(BM);
  end;
end;

function TCustomClientDataSet.UndoLastChange(FollowChange: Boolean): Boolean;
begin
  Cancel;
  CheckBrowseMode;
  UpdateCursorPos;
  Result := (DSCursor.UndoLastChange(FollowChange) = DBERR_NONE);
  if Result then
  begin
    if FollowChange then CursorPosChanged;
    Resync([]);
  end;
end;

procedure TCustomClientDataSet.RevertRecord;
begin
  Cancel;
  CheckBrowseMode;
  UpdateCursorPos;
  Check(DSCursor.RevertRecord);
  Resync([]);
end;

function TCustomClientDataSet.GetSavePoint: Integer;
var
  Mem: IntPtr;
begin
  CheckBrowseMode;
  Mem := Marshal.AllocHGlobal(SizeOf(Integer));
  try
    DSBase.GetProp(dspropGETSAVEPOINT, Mem);
    Result := Marshal.ReadInt32(Mem);
  finally
    Marshal.FreeHGlobal(Mem);
  end;
end;

procedure TCustomClientDataSet.SetSavePoint(Value: Integer);
begin
  Cancel;
  CheckBrowseMode;
  UpdateCursorPos;
  Check(DSBase.RollBack(Value));
  CursorPosChanged;
  Resync([]);
end;

procedure TCustomClientDataSet.CancelUpdates;
begin
  SetSavePoint(0);
end;

{ Indexes }

procedure TCustomClientDataSet.UpdateIndexDefs;
var
  I: Integer;
  CursorProps: DSProps;
  IndexDesc: DSIDXDesc;
  Opts: TIndexOptions;
  IdxName, Flds: string;
  DescFlds, CaseInsFlds: string;
  Mem, P: IntPtr;
begin
  if (csDesigning in ComponentState) and (IndexDefs.Count > 0) then Exit;
  if Active and not IndexDefs.Updated then
  begin
    FieldDefs.Update;
    Check(DSCursor.GetCursorProps(CursorProps));
    IndexDefs.Clear;
    Mem := Marshal.AllocHGlobal(CursorProps.iIndexes * Marshal.SizeOf(TypeOf(DSIDXDesc)));
    try
      Check(DSBase.GetIndexDescs(Mem));
      P := Mem;
      for I := 0 to CursorProps.iIndexes - 1 do
      begin
        IndexDesc := DSIDXDesc(Marshal.PtrToStructure(P, TypeOf(DSIDXDesc)));
        DecodeIndexDesc(IndexDesc, IdxName, Flds, DescFlds, CaseInsFlds, Opts);
        with IndexDefs.AddIndexDef do
        begin
          Name := IdxName;
          Fields := Flds;
          DescFields := DescFlds;
          CaseInsFields := CaseInsFlds;
          Options := Opts;
        end;
        P := IntPtr(Integer(P) + Marshal.SizeOf(TypeOf(DSIDXDesc)));
      end;
    finally
      Marshal.FreeHGlobal(Mem);
    end;
    IndexDefs.Updated := True;
  end;
end;

procedure TCustomClientDataSet.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 LongBool(bDescending[I]) then
        ConcatField(DescFields, FieldName);
      if LongBool(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 TCustomClientDataSet.GetIndexNames(List: TStrings);
begin
  IndexDefs.Update;
  IndexDefs.GetItemNames(List);
end;

function TCustomClientDataSet.GetIndexDefs: TIndexDefs;
begin
  if FIndexDefs = nil then
    FIndexDefs := TIndexDefs.Create(Self);
  Result := FIndexDefs;
end;

procedure TCustomClientDataSet.SetIndexDefs(Value: TIndexDefs);
begin
  IndexDefs.Assign(Value);
end;

procedure TCustomClientDataSet.GetIndexInfo(IndexName: string);
var
  Index: Integer;
  IndexDesc: DSIDXDesc;
begin
  if DSCursor.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 TCustomClientDataSet.SwitchToIndex(const IndexName: string);
var
  Status: DBResult;
  IndexDesc: DSIDXDesc;
  ptr: IntPtr;
begin
  ResetCursorRange;
  ptr := Marshal.StringToHGlobalAnsi(IndexName);
  try
    Status := DSCursor.UseIndexOrder(ptr);
  finally
    Marshal.FreeHGlobal(ptr);
  end;
  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(DSBase.CreateIndex(IndexDesc));
        ptr := Marshal.StringToHGlobalAnsi(IndexName);
        try
          Check(DSCursor.UseIndexOrder(ptr));
        finally
	        Marshal.FreeHGlobal(ptr);
        end;
      end;
    end else
      Check(Status);
  end;
  GetIndexInfo(IndexName);
end;

procedure TCustomClientDataSet.SetIndex(const Value: string; FieldsIndex: Boolean);
var
  SeqNo: DWord;
begin
  if Active then
  begin
    CheckBrowseMode;
    UpdateCursorPos;
    CheckProviderEOF;
    if (FIndexName <> Value) or (FFieldsIndex <> FieldsIndex) then
    begin
      if FieldsIndex then
        SortOnFields(DSCursor, Value, False, False) else
        SwitchToIndex(Value);
      FIndexName := Value;
      FFieldsIndex := FieldsIndex;
      if FParentDataSet <> nil then
        with FParentDataSet.DSCursor do begin
          GetSequenceNumber(SeqNo);
          MoveToBOF;
          MoveToSeqNo(SeqNo);
        end;
      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 TCustomClientDataSet.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: LongBool;
begin
//  FillChar(IndexDesc, SizeOf(IndexDesc), 0);
  with IndexDesc do
  begin
    szName := 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] := Integer(Descending);
      bCaseInsensitive[iFields] := Integer(CaseInsensitive);
      Inc(iFields);
    end;
    Pos := 1;
    while Pos <= Length(DescFields) do
      bDescending[IndexFieldOfs(ExtractFieldName(DescFields, Pos))] := Integer(True);
    Pos := 1;
    while Pos <= Length(CaseInsFields) do
      bCaseInsensitive[IndexFieldOfs(ExtractFieldName(CaseInsFields, Pos))] := Integer(True);
  end;
end;

procedure TCustomClientDataSet.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(DSBase.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 TCustomClientDataSet.DeleteIndex(const Name: string);
var
  ptr: IntPtr;
begin
  CheckBrowseMode;
  if AnsiCompareText(Name, IndexName) = 0 then IndexName := '';
  ptr := Marshal.StringToHGlobalAnsi(Name);
  try
    Check(DSBase.RemoveIndex(ptr));
  finally
    Marshal.FreeHGlobal(ptr);
  end;
  IndexDefs.Updated := False;
end;

function TCustomClientDataSet.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 TCustomClientDataSet.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 TCustomClientDataSet.GetIndexName: string;
begin
  if FFieldsIndex then Result := '' else Result := FIndexName;
end;

procedure TCustomClientDataSet.SetFileName(const Value: string);
begin
  FFileName := Value;
end;

procedure TCustomClientDataSet.SetIndexName(const Value: string);
begin
  SetIndex(Value, False);
end;

procedure TCustomClientDataSet.SetIndexField(Index: Integer; Value: TField);
begin
  GetIndexField(Index).Assign(Value);
end;

function TCustomClientDataSet.GetIndexFieldNames: string;
begin
  if FFieldsIndex then Result := FIndexName else Result := '';
end;

procedure TCustomClientDataSet.SetIndexFieldNames(const Value: string);
begin
  SetIndex(Value, Value <> '');
end;

function TCustomClientDataSet.GetIndexFieldCount: Integer;
begin
  Result := FIndexFieldCount;
end;

procedure TCustomClientDataSet.SortOnFields(Cursor: IDSCursor; const Fields: string;
  CaseInsensitive, Descending: Boolean);
var
  I: Integer;
  FieldList: TObjectList;
  DescFlags, CaseFlags: DSKEYBOOL;
  DFPtr, CFPtr, FieldNoPtr: IntPtr;

  function GetFlags(Flag: LongBool; var FlagArray: DSKEYBOOL): IntPtr;
  var
    J: Integer;
  begin
    if not Flag then Result := nil else
    begin
      Result := Marshal.AllocHGlobal(FieldList.Count * SizeOf(LongBool));
      try
        for J := 0 to FieldList.Count - 1 do
        begin
          FlagArray[J] := Integer(True);
          Marshal.WriteInt32(Result, J, 1);
        end;
      except
        Marshal.FreeHGlobal(Result);
        raise;
      end;
    end;
  end;

begin
  FieldList := TObjectList.Create;
  try
    GetFieldList(FieldList, Fields);
    FieldNoPtr := nil;
    if FieldList.Count > 0 then
      FieldNoPtr := Marshal.AllocHGlobal(FieldList.Count * sizeof(Integer));
    try
      for I := 0 to FieldList.Count - 1 do
        if TField(FieldList[I]).FieldNo > 0 then
          Marshal.WriteInt32(FieldNoPtr, I * sizeof(Integer),
            TField(FieldList[I]).FieldNo)
        else
          DatabaseError(SFieldIndexError, Self);
       DFPtr := GetFlags(Descending, DescFlags);
       try
         CFPtr := GetFlags(CaseInsensitive, CaseFlags);
         try
          Check(Cursor.SortOnFields(FieldList.Count, FieldNoPtr, DFPtr, CFPtr));
         finally
           if CFPtr <> nil then
             Marshal.FreeHGlobal(CFPtr);
         end;
       finally
         if DFPtr <> nil then
           Marshal.FreeHGlobal(DFPtr);
       end;
    finally
      if FieldNoPtr <> nil then
        Marshal.FreeHGlobal(FieldNoPtr);
    end;
    GetIndexInfo('');
  finally
    FieldList.Free;
  end;
end;

{ Ranges / Keys }

procedure TCustomClientDataSet.CopyKeyBuffer(Source: TKeyBuffer; var Dest: TKeyBuffer);
var
  I: Integer;
begin
  Dest.Modified := Source.Modified;
  Dest.Exclusive := Source.Exclusive;
  Dest.FieldCount := Source.FieldCount;
  for I := 0 to FRecordSize - 1 do
    Marshal.WriteByte(Dest.Data, I, Marshal.ReadByte(Source.Data, I));
end;

procedure TCustomClientDataSet.AllocKeyBuffers;
var
  KeyIndex: TKeyIndex;
begin
  try
    {first initialize the data pointers just in case}
    for KeyIndex := Low(TKeyIndex) to High(TKeyIndex) do
      FKeyBuffers[KeyIndex].Data := nil;
    for KeyIndex := Low(TKeyIndex) to High(TKeyIndex) do
      FKeyBuffers[KeyIndex].Data := FBufferManager.AllocHGlobal(FRecordSize);
    if Assigned(FCloneSource) then
      for KeyIndex := Low(TKeyIndex) to High(TKeyIndex) do
        CopyKeyBuffer(FCloneSource.FKeyBuffers[KeyIndex], FKeyBuffers[KeyIndex]);
  except
    FreeKeyBuffers;
    raise;
  end;
end;

procedure TCustomClientDataSet.FreeKeyBuffers;
var
  KeyIndex: TKeyIndex;
begin
  for KeyIndex := Low(TKeyIndex) to High(TKeyIndex) do
  begin
    if FKeyBuffers[KeyIndex].Data <> nil then
      FBufferManager.FreeHGlobal(FKeyBuffers[KeyIndex].Data);
    FKeyBuffers[KeyIndex].Data := nil;
  end;
end;

procedure TCustomClientDataSet.InitKeyBuffer(var Buffer: TKeyBuffer);
begin
  Buffer.Modified := False;
  Buffer.Exclusive := False;
  Buffer.FieldCount := 0;
  Check(DSCursor.InitRecord(Buffer.Data));
end;

procedure TCustomClientDataSet.CheckSetKeyMode;
begin
  if State <> dsSetKey then DatabaseError(SNotEditing, Self);
end;

function KeyBuffersEqual(B1, B2: TKeyBuffer; DataSize: Integer): Boolean;
begin
  Result := (B1.Exclusive = B2.Exclusive) and (B1.FieldCount = B2.FieldCount);
  if Result then
    Result := BuffersEqual(B1.Data, B2.Data, DataSize);
end;

function TCustomClientDataSet.SetCursorRange: Boolean;
var
  RangeStart, RangeEnd: TKeyBuffer;
  StartKey, EndKey: TRecordBuffer;
begin
  Result := False;
  if not (
    KeyBuffersEqual(FKeyBuffers[kiRangeStart], FKeyBuffers[kiCurRangeStart],
      FRecordSize) and
    KeyBuffersEqual(FKeyBuffers[kiRangeEnd], FKeyBuffers[kiCurRangeEnd],
      FRecordSize)) then
  begin
    CheckProviderEOF;
    RangeStart := FKeyBuffers[kiRangeStart];
    RangeEnd := FKeyBuffers[kiRangeEnd];
    StartKey := RangeStart.Data;
    if not RangeStart.Modified then
      StartKey := nil;
    EndKey := RangeEnd.Data;
    if not RangeEnd.Modified then
      EndKey := nil;
    Check(DSCursor.SetRange(RangeStart.FieldCount, StartKey,
      not RangeStart.Exclusive, EndKey, not RangeEnd.Exclusive));
    CopyKeyBuffer(FKeyBuffers[kiRangeStart], FKeyBuffers[kiCurRangeStart]);
    CopyKeyBuffer(FKeyBuffers[kiRangeEnd], FKeyBuffers[kiCurRangeEnd]);
    DestroyLookupCursor;
    Result := True;
  end;
end;

function TCustomClientDataSet.ResetCursorRange: Boolean;
begin
  Result := False;
  if FKeyBuffers[kiCurRangeStart].Modified or
    FKeyBuffers[kiCurRangeEnd].Modified then
  begin
    Check(DSCursor.DropRange);
    InitKeyBuffer(FKeyBuffers[kiCurRangeStart]);
    InitKeyBuffer(FKeyBuffers[kiCurRangeEnd]);
    DestroyLookupCursor;
    Result := True;
  end;
end;

procedure TCustomClientDataSet.SetLinkRanges(MasterFields: TList);
var
  I: Integer;
  SaveState: TDataSetState;
begin
  SaveState := SetTempState(dsSetKey);
  try
    FKeyBuffer := kiRangeStart;
    InitKeyBuffer(FKeyBuffers[FKeyBuffer]);
    FKeyBuffers[FKeyBuffer].Modified := True;
    for I := 0 to MasterFields.Count - 1 do
      GetIndexField(I).Assign(TField(MasterFields[I]));
    FKeyBuffers[FKeyBuffer].FieldCount := MasterFields.Count;
  finally
    RestoreState(SaveState);
  end;
  CopyKeyBuffer(FKeyBuffers[kiRangeStart], FKeyBuffers[kiRangeEnd]);
end;

procedure TCustomClientDataSet.SetKeyBuffer(KeyIndex: TKeyIndex; Clear: Boolean);
begin
  CheckBrowseMode;
  FKeyBuffer := KeyIndex;
  CopyKeyBuffer(FKeyBuffers[FKeyBuffer], FKeyBuffers[kiSave]);
  if Clear then InitKeyBuffer(FKeyBuffers[FKeyBuffer]);
  SetState(dsSetKey);
  SetModified(FKeyBuffers[FKeyBuffer].Modified);
  DataEvent(deDataSetChange, TObject(Integer(0)));
end;

procedure TCustomClientDataSet.PostKeyBuffer(Commit: Boolean);
begin
  DataEvent(deCheckBrowseMode, nil);
  if Commit then
    FKeyBuffers[FKeyBuffer].Modified := Modified else
    CopyKeyBuffer(FKeyBuffers[kiSave], FKeyBuffers[FKeyBuffer]);
  SetState(dsBrowse);
  DataEvent(deDataSetChange, TObject(Integer(0)));
end;

function TCustomClientDataSet.GetKeyExclusive: Boolean;
begin
  CheckSetKeyMode;
  Result := FKeyBuffers[FKeyBuffer].Exclusive;
end;

procedure TCustomClientDataSet.SetKeyExclusive(Value: Boolean);
begin
  CheckSetKeyMode;
  FKeyBuffers[FKeyBuffer].Exclusive := Value;
end;

function TCustomClientDataSet.GetKeyFieldCount: Integer;
begin
  CheckSetKeyMode;
  Result := FKeyBuffers[FKeyBuffer].FieldCount;
end;

procedure TCustomClientDataSet.SetKeyFieldCount(Value: Integer);
begin
  CheckSetKeyMode;
  FKeyBuffers[FKeyBuffer].FieldCount := Value;
end;

procedure TCustomClientDataSet.SetKeyFields(KeyIndex: TKeyIndex;
  const Values: array of const);
var
  I: Integer;
  SaveState: TDataSetState;
begin
  if FIndexFieldCount = 0 then DatabaseError(SNoFieldIndexes, Self);
  SaveState := SetTempState(dsSetKey);
  try
    FKeyBuffer := KeyIndex;
    InitKeyBuffer(FKeyBuffers[FKeyBuffer]);
    if FParentDataSet = nil then
    begin
      for I := 0 to High(Values) do GetIndexField(I).AssignValue(Variant(Values[I]));
      FKeyBuffers[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(Variant(Values[I]));
      FKeyBuffers[FKeyBuffer].FieldCount := High(Values) + 1;
    end;
    FKeyBuffers[FKeyBuffer].Modified := Modified;
  finally
    RestoreState(SaveState);
  end;
end;

function TCustomClientDataSet.FindKey(const KeyValues: array of const): Boolean;
begin
  CheckBrowseMode;
  SetKeyFields(kiLookup, KeyValues);
  Result := GotoKey;
end;

procedure TCustomClientDataSet.FindNearest(const KeyValues: array of const);
begin
  CheckBrowseMode;
  SetKeyFields(kiLookup, KeyValues);
  GotoNearest
end;

function TCustomClientDataSet.GotoKey: Boolean;
var
  RecBuffer: TRecordBuffer;
begin
  CheckBrowseMode;
  DoBeforeScroll;
  CursorPosChanged;
  CheckProviderEOF;
  RecBuffer := FKeyBuffers[kiLookup].Data;
  Result := DSCursor.GetRecordForKey(FKeyBuffers[kiLookup].FieldCount, 0, RecBuffer, nil) = 0;
  if Result then
  begin
    Resync([rmExact, rmCenter]);
    DoAfterScroll;
  end;
end;

procedure TCustomClientDataSet.GotoNearest;
var
  SearchCond: DBSearchCond;
  RecBuffer: TRecordBuffer;
begin
  CheckBrowseMode;
  DoBeforeScroll;
  CheckProviderEOF;
  RecBuffer := FKeyBuffers[kiLookup].Data;
  if FKeyBuffers[kiLookup].Exclusive then
    SearchCond := keySEARCHGT else
    SearchCond := keySEARCHGEQ;
  Check(DSCursor.MoveToKey(SearchCond, FKeyBuffers[kiLookup].FieldCount, 0, RecBuffer));
  Resync([rmCenter]);
  DoAfterScroll;
end;

procedure TCustomClientDataSet.SetKey;
begin
  SetKeyBuffer(kiLookup, True);
end;

procedure TCustomClientDataSet.EditKey;
begin
  SetKeyBuffer(kiLookup, False);
end;

procedure TCustomClientDataSet.ApplyRange;
begin
  CheckBrowseMode;
  if SetCursorRange then First;
end;

procedure TCustomClientDataSet.CancelRange;
begin
  CheckBrowseMode;
  UpdateCursorPos;
  if ResetCursorRange then Resync([]);
end;

procedure TCustomClientDataSet.SetRange(const StartValues, EndValues: array of const);
begin
  CheckBrowseMode;
  SetKeyFields(kiRangeStart, StartValues);
  SetKeyFields(kiRangeEnd, EndValues);
  ApplyRange;
end;

procedure TCustomClientDataSet.SetRangeEnd;
begin
  SetKeyBuffer(kiRangeEnd, True);
end;

procedure TCustomClientDataSet.SetRangeStart;
begin
  SetKeyBuffer(kiRangeStart, True);
end;

procedure TCustomClientDataSet.EditRangeEnd;
begin
  SetKeyBuffer(kiRangeEnd, False);
end;

procedure TCustomClientDataSet.EditRangeStart;
begin
  SetKeyBuffer(kiRangeStart, False);
end;

{ Master / Detail }

procedure TCustomClientDataSet.CheckDetailRecords;
var
  I, RecCount: Integer;
  MasterValues: OleVariant;
  Status: DBResult;
begin
  Status := DSCursor.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], varObject);
      for I := 0 to FMasterLink.Fields.Count - 1 do
        with TField(FMasterLink.Fields[I]) do
          MasterValues[I] := VarArrayOf([IndexFields[I].FieldName, Value, IndexFields[I].DataType]);
      AddDataPacket(DoGetRecords(-1, RecCount, 0, '', MasterValues), False);
      if Active then First;
    end;
  end;
end;

procedure TCustomClientDataSet.CheckMasterRange;
begin
  if FMasterLink.Active and (FMasterLink.Fields.Count > 0) then
  begin
    SetLinkRanges(FMasterLink.Fields);
    SetCursorRange;
    if FetchOnDemand then CheckDetailRecords;
  end;
end;

function SameBookmarkBits(BM1, BM2: TBytes): Boolean;
var
  I: Integer;
begin
  Result := False;
  if Length(BM1) <> Length(BM2) then Exit;
  for I := 0 to Length(BM1) - 1 do
    if BM1[I] <> BM2[I] then Exit;
  Result := True;
end;

procedure TCustomClientDataSet.MasterChanged(Sender: TObject);
var
  BM: TBytes;
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
    begin
      SetLength(BM, FParentDataSet.BookmarkSize);
      Marshal.Copy(IntPtr(Integer(FParentDataSet.ActiveBuffer) + FParentDataSet.FBookmarkOfs),
                   BM, 0, FParentDataSet.BookmarkSize);
      if not SameBookmarkBits(FLastParentBM, BM) then
      begin
        if FetchOnDemand then CheckDetailRecords;
        First;
        SetLength(FLastParentBM, FParentDataSet.BookmarkSize);
        Marshal.Copy(IntPtr(Integer(FParentDataSet.ActiveBuffer) + FParentDataSet.FBookmarkOfs),
                     FLastParentBM, 0, FParentDataSet.BookmarkSize);
      end else
      begin
        UpdateCursorPos;
        Resync([]);
      end;
    end;
  end;
end;

procedure TCustomClientDataSet.MasterDisabled(Sender: TObject);
begin
  CancelRange;
end;

procedure TCustomClientDataSet.SetDataSetField(const Value: TDataSetField);
begin
  if Assigned(Value) then
  begin
    Close;
    ProviderName := '';
    RemoteServer := nil;
    FileName := '';
  end;
  inherited;
end;

procedure TCustomClientDataSet.Loaded;
begin
  inherited Loaded;
  if Active and Assigned(FSavedPacket) and Assigned(FSavedPacket.FPacket) then
    Open;
end;

procedure TCustomClientDataSet.ReadData(Stream: TStream);
begin
  ReadDataPacket(Stream, True);
end;

procedure TCustomClientDataSet.WriteData(Stream: TStream);
begin
  WriteDataPacket(Stream, True);
end;

function TCustomClientDataSet.GetDataSource: TDataSource;
begin
  Result := FMasterLink.DataSource;
end;

procedure TCustomClientDataSet.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 TCustomClientDataSet.GetMasterFields: string;
begin
  Result := FMasterLink.FieldNames;
end;

procedure TCustomClientDataSet.SetMasterFields(const Value: string);
begin
  FMasterLink.FieldNames := Value;
end;

procedure TCustomClientDataSet.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 TCustomClientDataSet.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 TCustomClientDataSet(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 }

function TCustomClientDataSet.GetExprFilter: IntPtr;
begin
  if Assigned(FDSCursor) then
    Result := FDSCursor.FExprFilter
  else
    Result := nil;
end;

function TCustomClientDataSet.GetFuncFilter: IntPtr;
begin
  if Assigned(FDSCursor) then
    Result := FDSCursor.FFuncFilter
  else
    Result := nil;
end;

procedure TCustomClientDataSet.ActivateFilters;
begin
  if Filter <> '' then
    AddExprFilter(Filter, FilterOptions);
  if Assigned(OnFilterRecord) then
    AddFuncFilter;
end;

procedure TCustomClientDataSet.DeactivateFilters;
begin
  if FuncFilter <> nil then
  begin
    DSCursor.DropFilter(FuncFilter);
    FDSCursor.FFuncFilter := nil;
  end;
  if ExprFilter <> nil then
  begin
    DSCursor.DropFilter(ExprFilter);
    FDSCursor.FExprFilter := nil;
  end;
end;

procedure TCustomClientDataSet.AddExprFilter(const Expr: string; Options: TFilterOptions);
var
  Mem: IntPtr;
begin
  if ExprFilter <> nil then DSCursor.DropFilter(ExprFilter);
  if not Assigned(FDSCursor) then
    FDSCursor := TCursorWrapper.Create;
  if Expr <> '' then
    with TExprParser.Create(Self, Expr, Options, [poExtSyntax], '', nil, FieldTypeMap) do
    try
      CheckProviderEOF;
      Mem := Marshal.AllocHGlobal(DataSize);
      try
        Marshal.Copy(FilterData, 0, Mem, DataSize);
        Check(DSCursor.AddFilter(Mem, DataSize, FDSCursor.FExprFilter));
      finally
        Marshal.FreeHGlobal(Mem);
      end;
    finally
      Free;
    end;
end;

function TCustomClientDataSet.FilterCallback(ClientData: Integer; RecBuf: TRecordBuffer): LongBool;
var
  SaveState: TDataSetState;
  Accept: Boolean;
begin
  SaveState := SetTempState(dsFilter);
  FFilterBuffer := RecBuf;
  try
    Accept := True;
    OnFilterRecord(Self, Accept);
  except
    if Assigned(Classes.ApplicationHandleException) then
      ApplicationHandleException(Self);
  end;
  RestoreState(SaveState);
  Result := Accept;
end;

procedure TCustomClientDataSet.AddFuncFilter;
begin
  if FuncFilter <> nil then DSCursor.DropFilter(FuncFilter);
  CheckProviderEOF;
  if not Assigned(FDSCursor) then
    FDSCursor := TCursorWrapper.Create;
  Check(DSCursor.AddFilterCallBack(0, @FilterCallback, FDSCursor.FFuncFilter));
end;

procedure TCustomClientDataSet.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 TCustomClientDataSet.SetFilterText(const Value: string);
begin
  SetFilterData(Value, FilterOptions);
end;

procedure TCustomClientDataSet.SetFilterOptions(Value: TFilterOptions);
begin
  SetFilterData(Filter, Value);
end;

procedure TCustomClientDataSet.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
        if Assigned(FDSCursor) and (FDSCursor.FFuncFilter <> nil) then
        begin
          DSCursor.DropFilter(FuncFilter);
          FDSCursor.FFuncFilter := nil;
        end;
    end;
    DestroyLookupCursor;
    First;
  end;
  inherited SetOnFilterRecord(Value);
end;

procedure TCustomClientDataSet.SetFiltered(Value: Boolean);
begin
  if Active then
  begin
    CheckBrowseMode;
    if Filtered <> Value then
    begin
      DestroyLookupCursor;
      DSCursor.MoveToBOF;
      if Value then ActivateFilters else DeactivateFilters;
      inherited SetFiltered(Value);
    end;
    First;
  end else
    inherited SetFiltered(Value);
end;

procedure TCustomClientDataSet.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(DSBase.SetProp(dspropCHANGEINDEX_VIEW, StatusValues));
  end else
  begin
    if IndexName = szCHANGEINDEX then
      IndexName := szDEFAULT_ORDER;
    Check(DSBase.SetProp(dspropCHANGEINDEX_VIEW, 0));
  end;
  FStatusFilter := Value;
  Resync([]);
end;

function TCustomClientDataSet.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(DSCursor)
      finally
        DeactivateFilters;
      end;
    end else
      if not Restart then SyncCursors(FFindCursor, DSCursor);
    Cursor := FFindCursor;
  end else
    Cursor := DSCursor;
  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 <> DSCursor then
  begin
    SyncCursors(DSCursor, FFindCursor);
    Status := DSCursor.GetCurrentRecord(nil);
  end;
  if Status = DBERR_NONE then
  begin
    Resync([rmExact, rmCenter]);
    SetFound(True);
  end;
  Result := Found;
  if Result then DoAfterScroll;
end;

procedure TCustomClientDataSet.DestroyLookupCursor;
begin
  FFindCursor := nil;
end;

function TCustomClientDataSet.LocateRecord(const KeyFields: string;
  const KeyValues: Variant; Options: TLocateOptions;
  SyncCursor: Boolean): Boolean;
var
  Fields: TObjectList;
  I: Integer;
  Status: DBResult;
  FilterOptions: TFilterOptions;
  ExprParser: TExprParser;
  ValStr, Expr: string;
  Value: Variant;
  Mem: IntPtr;
begin
  CheckBrowseMode;
  UpdateCursorPos;
  CursorPosChanged;
  CheckProviderEOF;
  Fields := TObjectList.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, ftTimeStamp:
          ValStr := Format('''%s''',[VarToStr(Value)]);
        ftSmallint, ftInteger, ftWord, ftAutoInc, ftBoolean, ftFloat, ftCurrency, ftBCD, ftLargeInt, ftFMTBcd:
          ValStr := VarToStr(Value);
      else
        DatabaseErrorFmt(SBadFieldType, [TField(Fields[i]).FieldName]);
      end;
      if Expr <> '' then
        Expr := Expr + ' and ';    { Do not localize }
      if VarIsNull(Value) then
        Expr := Expr + Format('[%s] IS NULL',[TField(Fields[i]).FieldName])  { Do not localize }
      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
      DSCursor.MoveToBOF;
      Mem := Marshal.AllocHGlobal(ExprParser.DataSize);
      try
        Marshal.Copy(ExprParser.FilterData, 0, Mem, ExprParser.DataSize);
        Status := DSCursor.LocateWithFilter(Mem, ExprParser.DataSize);
      finally
        Marshal.FreeHGlobal(Mem);
      end;
      if Status = DBERR_NONE then
        DSCursor.GetCurrentRecord(TempBuffer);
    finally
      ExprParser.Free;
    end;
  finally
    Fields.Free;
  end;
  Result := Status = DBERR_NONE;
end;

function TCustomClientDataSet.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 TCustomClientDataSet.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 TCustomClientDataSet.GotoCurrent(DataSet: TCustomClientDataSet);
begin
  CheckBrowseMode;
  CheckProviderEOF;
  DataSet.CheckActive;
  BookMark := DataSet.BookMark;
end;

{ Aggregates }

function AggValueAsVariant(Agg: TAggregate; Buffer: TValueBuffer): Variant;
var
  B: TBytes;
  T: TTimeStamp;
  P: IntPtr;
begin
  case Agg.DataType of
    ftInteger:
      Result := Marshal.ReadInt32(Buffer);
    ftSmallInt:
      Result := Marshal.ReadInt16(Buffer);
    ftBoolean:
      Result := Marshal.ReadInt16(Buffer) <> 0;
    ftFloat:
      Result := BitConverter.Int64BitsToDouble(Marshal.ReadInt64(Buffer));
    ftBCD:
      begin
        SetLength(B, SizeOfTBcd);
        Marshal.Copy(Buffer, B, 0, Length(B));
        Result := Variant(TBcd.FromBytes(B));
      end;
    ftFMTBcd:
      begin
        SetLength(B, SizeOfTBcd);
        Marshal.Copy(Buffer, B, 0, Length(B));
        Result := VarFMTBcdCreate(TBcd.FromBytes(B));
      end;
    ftDate:
      begin
        T.Time := 0;
        T.Date := Marshal.ReadInt32(Buffer);
        Result := TimeStampToDateTime(T);
      end;
    ftTime:
      begin
        T.Time := Marshal.ReadInt32(Buffer);
        T.Date := DateDelta;
        Result := TimeStampToDateTime(T);
      end;
    ftDateTime:
      Result := TimeStampToDateTime(MSecsToTimeStamp(Trunc(
          BitConverter.Int64BitsToDouble(Marshal.ReadInt64(Buffer)))));
    ftTimeStamp:
      Result := VarSQLTimeStampCreate(TSQLTimeStamp(
        Marshal.PtrToStructure(Buffer, TypeOf(TSQLTimeStamp))));
    ftString, ftGUID:
      Result := Marshal.PtrToStringAnsi(Buffer);
    ftWideString:
      begin
        P := Marshal.ReadIntPtr(Buffer);
        Result := Marshal.PtrToStringUni(
          IntPtr(Integer(P) + sizeOf(Word)), Marshal.ReadInt16(P));
      end;
    else
      Result := Null;
  end;
end;

procedure TCustomClientDataSet.SetAggregates(Value: TAggregates);
begin
  FAggregates.Assign(Value);
end;

procedure TCustomClientDataSet.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 TCustomClientDataSet.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 TCustomClientDataSet.CloseAggs;
var
  I: Integer;
  Field: TAggregateField;
begin
  for I := 0 to FAggregates.Count - 1 do
    begin
      if (FAggregates[I].AggHandle <> 0) and (DSCursor <> nil) then
        DSCursor.DropAggregate(FAggregates[I].AggHandle);
      FAggregates[I].AggHandle := 0;
      FAggregates[I].FInUse := False;
    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 TCustomClientDataSet.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 TCustomClientDataSet.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
      DSCursor.DropAggregate(Agg.AggHandle);
      Agg.AggHandle := 0;
    end
  end;
  if FMaxAggGroupingLevel > GroupingLevel then
    FGroupingLevel := FMaxAggGroupingLevel;
end;

procedure TCustomClientDataSet.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 := 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 (FParentDataset <> nil) and (csLoading in FParentDataSet.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 TCustomClientDataSet.InternalGetGroupState(Level: Integer): TGroupPosInds;
var
  Status: DBResult;
  DSGrpState: GROUPSTATE;
begin
  Status := DSCursor.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 TCustomClientDataSet.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 TCustomClientDataSet.GetActiveAggs(Index: Integer): TList;
begin
  if Index < FActiveAggLists.Count then
    Result := TList(FActiveAggLists.Items[Index]) else
    Result := nil;
end;

procedure TCustomClientDataSet.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 TCustomClientDataSet.GetAggFieldData(Buffer: TRecordBuffer);
var
  I: Integer;
  Agg: TAggregate;
  Blank: LongBool;
  PAggData: IntPtr;
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 := IntPtr(Integer(Buffer) + FAggFieldsOfs + Agg.RecBufOfs);
      if Agg.InUse  and Agg.Active and (DSCursor.GetAggregateValue(Agg.AggHandle,
         IntPtr(Integer(PAggData)+1), Blank) = DBERR_NONE) and not Blank then
        Marshal.WriteByte(PAggData, 0) else
        Marshal.WriteByte(PAggData, 1);
    end;
  end;
end;

function TCustomClientDataSet.GetAggregateValue(Field: TField): Variant;
var
 Agg: TAggregate;
 RecBuf: TRecordBuffer;
begin
 Result := NULL;
 if FAggregatesActive and GetActiveRecBuf(RecBuf) then
 begin
   Agg := TAggregate(TAggregateField(Field).Handle);
   if Agg <> nil then
   begin
     if Agg.InUse then
     begin
       if Marshal.ReadInt16(RecBuf, FAggFieldsOfs + Agg.RecBufOfs) = 1 then
         Result := NULL
       else
         Result := AggValueAsVariant(Agg, TValueBuffer(Integer(RecBuf) + FAggFieldsOfs + Agg.RecBufOfs + 1))
     end else
       Result := NULL;
   end;
 end;
end;

procedure TCustomClientDataSet.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;

procedure TCustomClientDataSet.DoAfterApplyUpdates(var OwnerData: OleVariant);
begin
  if Assigned(FAfterApplyUpdates) then FAfterApplyUpdates(Self, OwnerData);
end;

procedure TCustomClientDataSet.DoBeforeApplyUpdates(var OwnerData: OleVariant);
begin
  if Assigned(FBeforeApplyUpdates) then FBeforeApplyUpdates(Self, OwnerData);
end;

function TCustomClientDataSet.DoApplyUpdates(Delta: OleVariant; MaxErrors: Integer;
  out ErrorCount: Integer): OleVariant;
var
  OwnerData: OleVariant;
begin
  DoBeforeApplyUpdates(OwnerData);
  Result := AppServer.AS_ApplyUpdates(ProviderName, Delta, MaxErrors, ErrorCount, OwnerData);
  DoAfterApplyUpdates(OwnerData);
end;

procedure TCustomClientDataSet.DoBeforeGetRecords(var OwnerData: OleVariant);
begin
  if Assigned(FBeforeGetRecords) then FBeforeGetRecords(Self, OwnerData);
end;

procedure TCustomClientDataSet.DoAfterGetRecords(var OwnerData: OleVariant);
begin
  if Assigned(FAfterGetRecords) then FAfterGetRecords(Self, OwnerData);
end;

function TCustomClientDataSet.DoGetRecords(Count: Integer; out RecsOut: Integer;
  Options: Integer; const CommandText: WideString; Params: OleVariant): TDataPacket;
var
  OwnerData: OleVariant;
begin
  DoBeforeGetRecords(OwnerData);
  if VarIsEmpty(Params) and (Self.Params.Count > 0) then
    Params := PackageParams(Self.Params);
  Result := VarArrayDataToDataPacket(AppServer.AS_GetRecords(ProviderName,
    Count, RecsOut, Options, CommandText, Params, OwnerData));
  UnPackParams(Params, Self.Params);
  DoAfterGetRecords(OwnerData);
end;

procedure TCustomClientDataSet.DoAfterRowRequest(var OwnerData: OleVariant);
begin
  if Assigned(FAfterRowRequest) then FAfterRowRequest(Self, OwnerData);
end;

procedure TCustomClientDataSet.DoBeforeRowRequest(var OwnerData: OleVariant);
begin
  if Assigned(FBeforeRowRequest) then FBeforeRowRequest(Self, OwnerData);
end;

function TCustomClientDataSet.DoRowRequest(Row: OleVariant; RequestType: Integer): OleVariant;
var
  OwnerData: OleVariant;
begin
  DoBeforeRowRequest(OwnerData);
  Result := AppServer.AS_RowRequest(ProviderName, Row, RequestType, OwnerData);
  DoAfterRowRequest(OwnerData);
end;

procedure TCustomClientDataSet.DoAfterExecute(var OwnerData: OleVariant);
begin
  if Assigned(FAfterExecute) then FAfterExecute(Self, OwnerData);
end;

procedure TCustomClientDataSet.DoBeforeExecute(var OwnerData: OleVariant);
begin
  if Assigned(FBeforeExecute) then FBeforeExecute(Self, OwnerData);
end;

procedure TCustomClientDataSet.DoExecute(Params: OleVariant);
var
  OwnerData: OleVariant;
begin
  DoBeforeExecute(OwnerData);
  AppServer.AS_Execute(ProviderName, CommandText, Params, OwnerData);
  UnPackParams(Params, Self.Params);
  DoAfterExecute(OwnerData);
end;

function TCustomClientDataSet.ConstraintsStored: Boolean;
begin
  Result := Constraints.Count > 0;
end;

type
  TSQLExprInfo = packed record
    iErrStrLen: Integer;
    iFldNum: Integer;
    bReqExpr: BYTE;
  end;

procedure AddSQLExprAttr(ExprParser: TExprParser; const ExprText, ExprErrMsg,
  FieldName: string; FieldIndex: Integer; ConstraintType: TConstraintType;
  Required: Boolean; Mem: IntPtr; var Size: Integer);
var
  ErrorStr: string;
  Len, Remaining: Integer;
  Options: TParserOptions;
  ExprInfoSize: Integer;
begin
  Remaining := Size;
  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);
  ExprInfoSize := Marshal.SizeOf(TypeOf(TSQLExprInfo));
  Dec(Remaining, ExprInfoSize);
  if Len > 0 then
  begin
    if Len >= Remaining then
    begin
      SetLength(ErrorStr, Remaining - 1);
      SetLength(ErrorStr, Remaining);
      Len := Remaining;
    end else
    begin
      Inc(Len);
      SetLength(ErrorStr, Len);
    end;
    Marshal.Copy(BytesOf(ErrorStr), 0, IntPtr(Integer(Mem) + ExprInfoSize), Len);
    Dec(Remaining, Len);
  end;
  {write the ExprInfo before the error string}
  Marshal.WriteInt32(Mem, Len);
  Marshal.WriteInt32(Mem, SizeOf(Len), FieldIndex);
  Marshal.WriteByte(Mem, 8, 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);
    if DataSize < Remaining then
      Remaining := DataSize;
    Marshal.Copy(FilterData, 0, IntPtr(Integer(Mem) + ExprInfoSize + Len),
      Remaining);
    Size := Remaining + ExprInfoSize + Len;
  end;
end;

procedure TCustomClientDataSet.SetupConstraints;

  procedure AddSQLExprParam(ExprParser: TExprParser; const ExprText, ExprErrMsg,
    FieldName: string; FieldIndex: Integer; ConstraintType: TConstraintType;
    Required: Boolean);
  const
    TypeStr: array[TConstraintType] of string = (szBDEDOMCL, szBDERECCL, szBDEDEFCL);
    Attr: Integer = dsVaryingFldType or SizeOf(Integer) or (dsfldBYTES shl dsSizeBitsLen);
  var
    AttrSize: Integer;
    Mem, AttrType: IntPtr;
    PN: TBytes;
  begin
    if ExprText = '' then Exit;
    AttrSize := 4096;
    Mem := Marshal.AllocHGlobal(AttrSize);
    try
      AddSQLExprAttr(ExprParser, ExprText, ExprErrMsg, FieldName, FieldIndex,
        ConstraintType, Required, Mem, AttrSize);
      PN := BytesOf(TypeStr[ConstraintType] + #0);
      AttrType := Marshal.AllocHGlobal(Length(PN));
      try
        Marshal.Copy(PN, 0, AttrType, Length(PN));
        Check(DSBase.AddOptParameter(0, AttrType, Attr, AttrSize {+ SizeOf(Integer)}, Mem));
      finally
        Marshal.FreeHGlobal(AttrType);
      end;
    finally
      Marshal.FreeHGlobal(Mem);
    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
            AddSQLExprParam(ExprParser, ImportedConstraint, ErrorMessage, '', 0,
              ctRecord, False);
            AddSQLExprParam(ExprParser, CustomConstraint, ErrorMessage, '', 0,
              ctRecord, False);
          end;
      except
        if Name <> '' then
          ErrMsg := Format('%s: %s',[Name, SRecConstFail])
        else
          ErrMsg := SRecConstFail;
        if ExceptObject is Exception then
          raise EDSWriter.CreateFmt(ErrMsg, [Exception(ExceptObject).Message])
        else
          raise EDSWriter.CreateFmt(ErrMsg, ['']);
      end;
    end;
    for i := 0 to FieldList.Count - 1 do
      with FieldList[i] do
      begin
        try
          AddSQLExprParam(ExprParser, DefaultExpression, '', FullName, FieldNo,
            ctDefault, False);
        except
          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]);
          if ExceptObject is Exception then
            raise EDSWriter.CreateFmt(ErrMsg, [Exception(ExceptObject).Message])
          else
            raise EDSWriter.CreateFmt(ErrMsg, ['']);
        end;
        try
          AddSQLExprParam(ExprParser, ImportedConstraint, ConstraintErrorMessage,
            FullName, FieldNo, ctField, False);
          AddSQLExprParam(ExprParser, CustomConstraint, ConstraintErrorMessage,
            FullName, FieldNo, ctField, False);
        except
          if Name <> '' then
            ErrMsg := Format('%s: %s',[Name, SFieldConstFail]) else
          if DataSet.Name <> '' then
            ErrMsg := Format('%s.%s: %s',[Name, FullName, SFieldConstFail]) else
            ErrMsg := Format('%s: %s',[FullName, SFieldConstFail]);
          if ExceptObject is Exception then
            raise EDSWriter.CreateFmt(ErrMsg, [Exception(ExceptObject).Message])
          else
            raise EDSWriter.CreateFmt(ErrMsg, ['']);
        end;
      end;
  finally
    ExprParser.Free;
  end;
end;

procedure TCustomClientDataSet.SetConnectionBroker(
  const Value: TConnectionBroker);
begin
  if Assigned(FConnectionBroker) then FConnectionBroker.UnRegisterClient(Self);
  FConnectionBroker := Value;
  if Assigned(Value) then
  begin
    Value.RegisterClient(Self);
    Value.FreeNotification(Self);
    SetRemoteServer(Nil);
  end;
end;

function TCustomClientDataSet.PSGetIndexDefs(IndexTypes: TIndexOptions): TIndexDefs;
begin
  Result := inherited GetIndexDefs(IndexDefs, IndexTypes);
end;

{ TClientBlobStream }

constructor TClientBlobStream.Create(Field: TBlobField; Mode: TBlobStreamMode);
begin
  inherited Create;
  FField := Field;
  FFieldNo := FField.FieldNo;
  FDataSet := FField.DataSet as TCustomClientDataSet;
  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;
var
  Mem: IntPtr;
begin
  if FModified then
  try
    Mem := Marshal.AllocHGlobal(Size);
    try
      if (Assigned(Memory)) then
        Marshal.Copy(Memory, 0, Mem, Size);
      FDataSet.Check(FDataSet.DSCursor.PutBlob(FBuffer, FFieldNo, 0, Mem, Size));
    finally
      Marshal.FreeHGlobal(Mem);
    end;
    FField.Modified := True;
    FDataSet.DataEvent(deFieldChange, FField);
  except
    if Assigned(Classes.ApplicationHandleException) then
      ApplicationHandleException(Self);
  end;
  inherited Destroy;
end;

procedure TClientBlobStream.ReadBlobData;
var
  BlobLen: DWord;
  Mem: IntPtr;
begin
  FDataSet.Check(FDataSet.DSCursor.GetBlobLen(FBuffer, FFieldNo, BlobLen));
  if BlobLen > 0 then
  begin
    Position := 0;
    Mem := Marshal.AllocHGlobal(BlobLen);
    try
      FDataSet.Check(FDataSet.DSCursor.GetBlob(FBuffer, FFieldNo, 0, Mem, BlobLen));
      SetSize(BlobLen);
      Marshal.Copy(Mem, Memory, 0, BlobLen);
    finally
      Marshal.FreeHGlobal(Mem);
    end;
  end;
end;

function TClientBlobStream.Write(const Buffer: array of Byte; Offset, Count: Longint): Longint;
begin
  Result := inherited Write(Buffer, Offset, 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 := TCustomClientDataSet(GetOwner);
end;

procedure TAggregates.Clear;
var
  DataSet: TCustomClientDataSet;
begin
  inherited Clear;
  DataSet := TCustomClientDataSet(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 }

procedure TAggCursor.Finalize;
begin
  if (FHAggregate <> 0) and Assigned(FDSCursor) and (FDSCursor.FCursor <> nil) then
    FDSCursor.FCursor.DropAggregate(FHAggregate);
  FHAggregate := 0;
end;

constructor TAggregate.Create(Aggregates: TAggregates; ADataSet: TCustomClientDataSet);
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 AggHandle <> 0 then
        begin
          FDataset.DSCursor.DropAggregate(AggHandle);
          AggHandle := 0;
        end;
        if Handle = Self then Handle := nil;
      end;
  if FDependentFields <> nil then
    FreeAndNil(FDependentFields);
  inherited Destroy;
end;

function TAggregate.GetAggHandle: hDSAggregate;
begin
  if Assigned(FAggHandle) then
    Result := FAggHandle.FHAggregate
  else
    Result := 0;
end;

procedure TAggregate.SetAggHandle(Value: hDSAggregate);
begin
  if not Assigned(FAggHandle) then
  begin
    if Value = 0 then Exit;
    FAggHandle :=  TAggCursor.Create;
    FAggHandle.FDSCursor := FDataSet.FDSCursor;
  end;
  FAggHandle.FHAggregate := Value;
end;

procedure TAggregate.Activate;
var
  Parser: TExprParser;
  Mem: IntPtr;
  Handle: hDSAggregate;
begin
  if FOutOfDate and (AggHandle <> 0) then
  begin
    FDataSet.Check(FDataSet.DSCursor.DropAggregate(AggHandle));
    AggHandle := 0;
  end;
  FOutOfDate := False;
  if AggHandle = 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
        Mem := Marshal.AllocHGlobal(Parser.DataSize);
        try
          Marshal.Copy(Parser.FilterData, 0, Mem, Parser.DataSize);
          FDataset.Check(FDataSet.DSCursor.AddAggregate(GroupingLevel,
            Parser.DataSize, Mem, Handle));
          AggHandle := Handle;
        finally
          Marshal.FreeHGlobal(Mem);
        end;
        FDataset.Check(FDataSet.DSCursor.GetAggregateDesc(Handle, FFldDesc));
        FBufferSize := 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
    begin
      FDataSet.Check(FDataSet.DSCursor.AddAggregate(GroupingLevel, 0, nil,
        Handle));
      AggHandle := Handle;
    end;
  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.DSCursor <> 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: LongBool;
  Mem: TValueBuffer;
begin
  Result := Null;
  if InUse and Active and Assigned(FDataSet) then
  begin
    FDataSet.UpdateCursorPos;
    Mem := Marshal.AllocHGlobal(FBufferSize);
    try
      FDataSet.DSCursor.GetAggregateValue(AggHandle, Mem, Blank);
      if Blank then
        Result := NULL else
        Result := AggValueAsVariant(Self, Mem);
    finally
      Marshal.FreeHGlobal(Mem);
    end;
  end;
end;

{ TConnectionBroker }

constructor TConnectionBroker.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
//  RCS;     {TODO: RCS}
end;

function TConnectionBroker.GetServer: IAppServer;
begin
  if Connection <> nil then
    Result := Connection.GetServer else
    Result := nil;
end;

function TConnectionBroker.GetAppServer: IAppServer;
begin
  if not Assigned(Connection) then
    raise Exception.Create(SNoParentConnection);
  Result := Connection.GetServer;
end;

function TConnectionBroker.GetConnected: Boolean;
begin
  Result := inherited GetConnected;
  if Assigned(Connection) then
    Result := Connection.Connected;
end;

procedure TConnectionBroker.Notification(AComponent: TComponent;
  Operation: TOperation);
begin
  inherited Notification(AComponent, Operation);
  if (Operation = opRemove) and (AComponent = FConnection) then
    Connection:= nil;
end;

procedure TConnectionBroker.Loaded;
begin
  inherited Loaded;
  if StreamedConnected then
    SetConnected(True);
end;

procedure TConnectionBroker.SetConnected(Value: Boolean);
begin
  if csLoading in ComponentState then
  begin
    FStreamedConnected := True;
    exit;
  end;
  if Value and (Connection = nil) then
    raise Exception.Create(SConnectionMissing);
  FStreamedConnected := False;
  inherited SetConnected(Value);
  if Assigned(Connection) then
    Connection.Connected := Value;
end;

procedure TConnectionBroker.SetConnection(
  const Value: TCustomRemoteServer);
begin
  if FConnection <> Value then
  begin
    if Value = Self then
      raise Exception.Create(SNoCircularConnection)
    else
      if Assigned(Value) and (Value is TConnectionBroker) then
        raise Exception.Create(SNoConnectToBroker);
    FConnection := Value;
  end;
end;

end.
