home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Delphi Anthology
/
aDELPHI.iso
/
Runimage
/
Delphi50
/
Source
/
Vcl
/
provider.pas
< prev
next >
Wrap
Pascal/Delphi Source File
|
1999-08-11
|
126KB
|
3,828 lines
{*******************************************************}
{ }
{ Borland Delphi Visual Component Library }
{ Provider - Resolver framework }
{ }
{ Copyright (c) 1997,99 Inprise Corporation }
{ }
{*******************************************************}
unit Provider;
{$T-,H+,X+}
interface
uses Windows, SysUtils, Classes, DbClient, DB, DsIntf, ActiveX, Midas;
type
{ EDSWriter }
EDSWriter = class(Exception)
private
FErrorCode: Integer;
public
constructor Create(ErrMsg: string; Status: Integer);
property ErrorCode: Integer read FErrorCode;
end;
{$EXTERNALSYM EDSWriter}
(*$HPPEMIT 'namespace Provider' *)
(*$HPPEMIT '{' *)
(*$HPPEMIT 'class DELPHICLASS EDSWriter;' *)
(*$HPPEMIT '#pragma pack(push, 4)' *)
(*$HPPEMIT 'class PASCALIMPLEMENTATION EDSWriter : public Sysutils::Exception' *)
(*$HPPEMIT '{' *)
(*$HPPEMIT ' typedef Sysutils::Exception inherited;' *)
(*$HPPEMIT '' *)
(*$HPPEMIT 'private:' *)
(*$HPPEMIT ' int FErrorCode;' *)
(*$HPPEMIT '' *)
(*$HPPEMIT 'public:' *)
(*$HPPEMIT ' __fastcall EDSWriter(AnsiString ErrMsg, long Status);' *)
(*$HPPEMIT ' __property int ErrorCode = {read=FErrorCode, nodefault};' *)
(*$HPPEMIT 'public:' *)
(*$HPPEMIT ' /* Exception.CreateFmt */ inline __fastcall EDSWriter(const AnsiString Msg, const System::TVarRec * Args, const int Args_Size) : Sysutils::Exception(Msg, Args, Args_Size) { }' *)
(*$HPPEMIT ' /* Exception.CreateRes */ inline __fastcall EDSWriter(int Ident, Extended Dummy) : Sysutils::Exception(Ident, Dummy) { }' *)
(*$HPPEMIT ' /* Exception.CreateResFmt */ inline __fastcall EDSWriter(int Ident, const System::TVarRec * Args, const int Args_Size) : Sysutils::Exception(Ident, Args, Args_Size) { }' *)
(*$HPPEMIT ' /* Exception.CreateHelp */ inline __fastcall EDSWriter(const AnsiString Msg, int AHelpContext) : Sysutils::Exception(Msg, AHelpContext) { }' *)
(*$HPPEMIT ' /* Exception.CreateFmtHelp */ inline __fastcall EDSWriter(const AnsiString Msg, const System::TVarRec * Args, const int Args_Size, int AHelpContext) : Sysutils::Exception(Msg, Args, Args_Size, AHelpContext) { }' *)
(*$HPPEMIT ' /* Exception.CreateResHelp */ inline __fastcall EDSWriter(int Ident, int AHelpContext) : Sysutils::Exception(Ident, AHelpContext) { }' *)
(*$HPPEMIT ' /* Exception.CreateResFmtHelp */ inline __fastcall EDSWriter(int Ident, const System::TVarRec * Args, const int Args_Size, int AHelpContext) : Sysutils::Exception(Ident, Args, Args_Size, AHelpContext) { }' *)
(*$HPPEMIT '' *)
(*$HPPEMIT 'public:' *)
(*$HPPEMIT ' /* TObject.Destroy */ inline __fastcall virtual ~EDSWriter(void) { }' *)
(*$HPPEMIT '' *)
(*$HPPEMIT '};' *)
(*$HPPEMIT '' *)
(*$HPPEMIT '#pragma pack(pop)' *)
(*$HPPEMIT '}' *)
{ TCustomPacketWriter }
TCustomPacketWriter = class(TObject)
private
FIDSWriter: IDSWriter;
FBuffer: array of Byte;
protected
procedure AddAttribute(Area: TPcktAttrArea; const ParamName: string;
const Value: OleVariant; IncludeInDelta: Boolean);
procedure Check(Status: Integer);
property DSWriter: IDSWriter read FIDSWriter;
public
constructor Create; virtual;
destructor Destroy; override;
end;
{ TDataPacketWriter }
type
TGetRecordOption = (grMetaData, grReset, grXML);
TGetRecordOptions = set of TGetRecordOption;
TDataRequestEvent = function(Sender: TObject; Input: OleVariant): OleVariant of object;
TProviderOption = (poFetchBlobsOnDemand, poFetchDetailsOnDemand,
poIncFieldProps, poCascadeDeletes, poCascadeUpdates, poReadOnly,
poAllowMultiRecordUpdates, poDisableInserts, poDisableEdits,
poDisableDeletes, poNoReset, poAutoRefresh, poPropogateChanges,
poAllowCommandText);
TProviderOptions = set of TProviderOption;
PPutFieldInfo = ^TPutFieldInfo;
TPutFieldProc = procedure(Info: PPutFieldInfo) of object;
TPutFieldInfo = record
FieldNo: Integer;
Field: TField;
DataSet: TDataSet;
Size: Integer;
IsDetail: Boolean;
Opened: Boolean;
PutProc: TPutFieldProc;
LocalFieldIndex: Integer;
FieldInfos: Pointer;
end;
TInfoArray = array of TPutFieldInfo;
TGetParamsEvent = procedure(DataSet: TDataSet; Params: TList) of object;
TDataPacketWriter = class(TCustomPacketWriter)
private
FConstraints: Boolean;
FPutFieldInfo: TInfoArray;
FOptions: TProviderOptions;
FPacketOptions: TGetRecordOptions;
FOnGetParams: TGetParamsEvent;
procedure FreeInfoRecords(var Info: TInfoArray);
function GetFieldIdx(const FieldName: string; const Info: TInfoArray): Integer;
procedure AddExtraFieldProps(Field: TField);
function InitPutProcs(ADataSet: TDataSet; var GlobalIdx: Integer): TInfoArray;
procedure RefreshPutProcs(ADataSet: TDataSet; var Info: TInfoArray);
protected
procedure AddColumn(const Info: TPutFieldInfo);
procedure AddConstraints(DataSet: TDataSet);
procedure AddDataSetAttributes(DataSet: TDataSet);
procedure AddFieldLinks(const Info: TInfoArray);
procedure AddIndexDefs(DataSet: TDataSet; const Info: TInfoArray);
procedure PutADTField(Info: PPutFieldInfo);
procedure PutArrayField(Info: PPutFieldInfo);
procedure PutBlobField(Info: PPutFieldInfo);
procedure PutCalcField(Info: PPutFieldInfo);
procedure PutDataSetField(Info: PPutFieldInfo);
procedure PutField(Info: PPutFieldInfo);
procedure PutStringField(Info: PPutFieldInfo);
procedure PutWideStringField(Info: PPutFieldInfo);
procedure PutVarBytesField(Info: PPutFieldInfo);
procedure WriteMetaData(DataSet: TDataSet; const Info: TInfoArray;
IsReference: Boolean = False);
function WriteDataSet(DataSet: TDataSet; var Info: TInfoArray;
RecsOut: Integer): Integer;
property OnGetParams: TGetParamsEvent read FOnGetParams write FOnGetParams;
public
destructor Destroy; override;
procedure GetDataPacket(DataSet: TDataSet; var RecsOut: Integer;
out Data: OleVariant);
property Constraints: Boolean read FConstraints write FConstraints;
property PacketOptions: TGetRecordOptions read FPacketOptions write FPacketOptions;
property Options: TProviderOptions read FOptions write FOptions;
end;
{ TPacketDataSet }
TPacketDataSet = class(TClientDataSet)
private
FOldRecBuf: PChar;
FCurRecBuf: PChar;
FCurValues: PChar;
FUseCurValues: Boolean;
FWritingCurValues: Boolean;
FNewValuesModified: Boolean;
function GetStreamMetaData: Boolean;
procedure SetStreamMetaData(Value: Boolean);
procedure SetWritingCurValues(const Value: Boolean);
protected
procedure DataEvent(Event: TDataEvent; Info: Longint); override;
function GetStateFieldValue(State: TDataSetState; Field: TField): Variant; override;
procedure InternalClose; override;
procedure InternalOpen; override;
procedure InternalInitRecord(Buffer: PChar); override;
procedure SetFieldData(Field: TField; Buffer: Pointer); override;
property WritingCurValues: Boolean read FWritingCurValues write SetWritingCurValues;
public
constructor Create(AOwner: TComponent); override;
procedure AssignCurValues(Source: TDataSet); overload;
procedure AssignCurValues(const CurValues: Variant); overload;
procedure CreateFromDelta(Source: TPacketDataSet);
function HasCurValues: Boolean;
function HasMergeConflicts: Boolean;
procedure InitAltRecBuffers(CheckModified: Boolean = True);
function UpdateKind: TUpdateKind;
property NewValuesModified: Boolean read FNewValuesModified;
property StreamMetaData: Boolean read GetStreamMetaData write SetStreamMetaData;
property UseCurValues: Boolean read FUseCurValues write FUseCurValues;
end;
{ TCustomProvider }
TCustomProvider = class(TComponent)
private
FExported: Boolean;
FOnDataRequest: TDataRequestEvent;
FBeforeApplyUpdates: TRemoteEvent;
FAfterApplyUpdates: TRemoteEvent;
FBeforeGetRecords: TRemoteEvent;
FAfterGetRecords: TRemoteEvent;
FBeforeRowRequest: TRemoteEvent;
FAfterRowRequest: TRemoteEvent;
FBeforeExecute: TRemoteEvent;
FAfterExecute: TRemoteEvent;
FBeforeGetParams: TRemoteEvent;
FAfterGetParams: TRemoteEvent;
function GetData: OleVariant;
protected
function InternalApplyUpdates(const Delta: OleVariant; MaxErrors: Integer;
out ErrorCount: Integer): OleVariant; virtual; abstract;
function InternalGetRecords(Count: Integer; out RecsOut: Integer;
Options: TGetRecordOptions; const CommandText: WideString;
var Params: OleVariant): OleVariant; virtual;
function InternalRowRequest(const Row: OleVariant; RequestType: TFetchOptions): OleVariant; virtual;
procedure InternalExecute(const CommandText: WideString; var Params: OleVariant); virtual;
function InternalGetParams(Types: TParamTypes = AllParamTypes): OleVariant; virtual;
{ Event overrides }
procedure DoBeforeGetRecords(Count: Integer; Options: Integer;
const CommandText: WideString; var Params, OwnerData: OleVariant); virtual;
procedure DoBeforeExecute(const CommandText: WideString; var Params,
OwnerData: OleVariant); virtual;
{ Events }
property OnDataRequest: TDataRequestEvent read FOnDataRequest write FOnDataRequest;
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;
function ApplyUpdates(const Delta: OleVariant; MaxErrors: Integer;
out ErrorCount: Integer): OleVariant; overload;
function ApplyUpdates(const Delta: OleVariant; MaxErrors: Integer;
out ErrorCount: Integer; var OwnerData: OleVariant): OleVariant; overload;
function GetRecords(Count: Integer; out RecsOut: Integer;
Options: Integer): OleVariant; overload;
function GetRecords(Count: Integer; out RecsOut: Integer; Options: Integer;
const CommandText: WideString; var Params,
OwnerData: OleVariant): OleVariant; overload;
function RowRequest(const Row: OleVariant; RequestType: Integer;
var OwnerData: OleVariant): OleVariant;
procedure Execute(const CommandText: WideString; var Params,
OwnerData: OleVariant);
function GetParams(var OwnerData: OleVariant): OleVariant;
function DataRequest(Input: OleVariant): OleVariant; virtual;
property Data: OleVariant read GetData;
property Exported: Boolean read FExported write FExported default True;
end;
{ TBaseProvider }
type
TCustomResolver = class;
TResolverResponse = (rrSkip, rrAbort, rrMerge, rrApply, rrIgnore);
TProviderDataEvent = procedure(Sender: TObject; DataSet: TClientDataSet) of object;
TBeforeUpdateRecordEvent = procedure(Sender: TObject; SourceDS: TDataSet;
DeltaDS: TClientDataSet; UpdateKind: TUpdateKind; var Applied: Boolean) of object;
TAfterUpdateRecordEvent = procedure(Sender: TObject; SourceDS: TDataSet;
DeltaDS: TClientDataSet; UpdateKind: TUpdateKind) of object;
TResolverErrorEvent = procedure(Sender: TObject; DataSet: TClientDataSet;
E: EUpdateError; UpdateKind: TUpdateKind; var Response: TResolverResponse) of object;
TBaseProvider = class(TCustomProvider)
private
FDataDS: TPacketDataSet;
FUpdateMode: TUpdateMode;
FResolver: TCustomResolver;
FOnGetData: TProviderDataEvent;
FOnUpdateData: TProviderDataEvent;
FOnUpdateError: TResolverErrorEvent;
FBeforeUpdateRecord: TBeforeUpdateRecordEvent;
FAfterUpdateRecord: TAfterUpdateRecordEvent;
FProviderOptions: TProviderOptions;
protected
procedure CheckResolver;
function CreateResolver: TCustomResolver; virtual;
procedure FreeResolver;
procedure CreateDataPacket(PacketOpts: TGetRecordOptions;
ProvOpts: TProviderOptions; var RecsOut: Integer; var Data: OleVariant); virtual;
procedure DoOnGetData(var Data: OleVariant);
procedure DoOnUpdateData(Delta: TPacketDataSet);
procedure LocateRecord(Source, Delta: TDataSet); virtual;
procedure UpdateRecord(Source, Delta: TDataSet; BlobsOnly, KeyOnly: Boolean); virtual;
procedure FetchDetails(Source, Delta: TDataSet); virtual;
function InternalRowRequest(const Row: OleVariant;
RequestType: TFetchOptions): OleVariant; override;
function InternalApplyUpdates(const Delta: OleVariant; MaxErrors: Integer;
out ErrorCount: Integer): OleVariant; override;
function InternalGetRecords(Count: Integer; out RecsOut: Integer;
Options: TGetRecordOptions; const CommandText: WideString;
var Params: OleVariant): OleVariant; override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
property Resolver: TCustomResolver read FResolver;
property Options: TProviderOptions read FProviderOptions
write FProviderOptions default [];
property UpdateMode: TUpdateMode read FUpdateMode write FUpdateMode default upWhereAll;
property OnDataRequest;
property OnGetData: TProviderDataEvent read FOnGetData write FOnGetData;
property OnUpdateData: TProviderDataEvent read FOnUpdateData write FOnUpdateData;
property OnUpdateError: TResolverErrorEvent read FOnUpdateError write FOnUpdateError;
property BeforeUpdateRecord: TBeforeUpdateRecordEvent read FBeforeUpdateRecord
write FBeforeUpdateRecord;
property AfterUpdateRecord: TAfterUpdateRecordEvent read FAfterUpdateRecord
write FAfterUpdateRecord;
end;
{ TDataSetProvider }
TGetTableNameEvent = procedure(Sender: TObject; DataSet: TDataSet; var TableName: string) of object;
TGetDSProps = procedure(Sender: TObject; DataSet: TDataSet;
out Properties: OleVariant) of object;
TDataSetProvider = class(TBaseProvider)
private
FDataSet: TDataSet;
FDataSetOpened: Boolean;
FDSWriter: TDataPacketWriter;
FGetDSProps: TGetDSProps;
FParams: TParams;
FResolveToDataSet: Boolean;
FRecordsSent: Integer;
FConstraints: Boolean;
FGetTableName: TGetTableNameEvent;
procedure CheckDataSet;
function FindRecord(Source, Delta: TDataSet; UpdateMode: TUpdateMode): Boolean;
procedure Reset;
procedure SetCommandText(const CommandText: string);
procedure SetDataSet(ADataSet: TDataSet);
procedure SetParams(Values: OleVariant);
procedure SetResolveToDataSet(Value: Boolean);
protected
{ SQL Resolver support methods }
procedure DoGetTableName(DataSet: TDataSet; var TableName: string); virtual;
protected
{ Event overrides }
procedure DoBeforeGetRecords(Count: Integer; Options: Integer;
const CommandText: WideString; var Params, OwnerData: OleVariant); override;
procedure DoBeforeExecute(const CommandText: WideString; var Params,
OwnerData: OleVariant); override;
protected
procedure DoGetProviderAttributes(DataSet: TDataSet; List: TList); virtual;
function CreateResolver: TCustomResolver; override;
procedure CreateDataPacket(PacketOpts: TGetRecordOptions;
ProvOpts: TProviderOptions; var RecsOut: Integer; var Data: OleVariant); override;
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
procedure LocateRecord(Source, Delta: TDataSet); override;
procedure UpdateRecord(Source, Delta: TDataSet; BlobsOnly, KeyOnly: Boolean); override;
procedure FetchDetails(Source, Delta: TDataSet); override;
function InternalRowRequest(const Row: OleVariant; Options: TFetchOptions): OleVariant; override;
function InternalGetParams(Types: TParamTypes = AllParamTypes): OleVariant; override;
procedure InternalExecute(const CommandText: WideString; var Params: OleVariant); override;
function InternalGetRecords(Count: Integer; out RecsOut: Integer;
Options: TGetRecordOptions; const CommandText: WideString;
var Params: OleVariant): OleVariant; override;
function InternalApplyUpdates(const Delta: OleVariant; MaxErrors: Integer;
out ErrorCount: Integer): OleVariant; override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
property DataSet: TDataSet read FDataSet write SetDataSet;
property Constraints: Boolean read FConstraints write FConstraints;
property ResolveToDataSet: Boolean read FResolveToDataSet write SetResolveToDataSet default False;
property Exported;
property Options;
property UpdateMode;
property OnDataRequest;
property OnGetData;
property OnUpdateData;
property OnUpdateError;
property AfterUpdateRecord;
property BeforeUpdateRecord;
property BeforeApplyUpdates;
property AfterApplyUpdates;
property BeforeGetRecords;
property AfterGetRecords;
property BeforeRowRequest;
property AfterRowRequest;
property BeforeExecute;
property AfterExecute;
property BeforeGetParams;
property AfterGetParams;
property OnGetTableName: TGetTableNameEvent read FGetTableName write FGetTableName;
property OnGetDataSetProperties: TGetDSProps read FGetDSProps write FGetDSProps;
end;
{ TProvider - deprecated }
TProvider = class(TDataSetProvider)
end;
{ TUpdateTree }
TUpdateTree = class(TObject)
private
FDeltaDS: TPacketDataSet;
FErrorDS: TPacketDataSet;
FOpened: Boolean;
FSourceDS: TDataSet;
FParent: TUpdateTree;
FDetails: TList;
FData: Pointer;
FResolver: TCustomResolver;
FName: string;
function GetDetailCount: Integer;
function GetDetail(Index: Integer): TUpdateTree;
function GetErrorDS: TPacketDataSet;
function GetHasErrors: Boolean;
function GetIsNested: Boolean;
function GetTree(const AName: string): TUpdateTree;
protected
procedure Clear;
function DoUpdates: Boolean;
procedure RefreshData(Options: TFetchOptions);
procedure InitErrorPacket(E: EUpdateError; Response: TResolverResponse);
procedure InitData(ASource: TDataSet);
procedure InitDelta(const ADelta: OleVariant); overload;
procedure InitDelta(ADelta: TPacketDataSet); overload;
property Data: Pointer read FData write FData;
property Delta: TPacketDataSet read FDeltaDS;
property DetailCount: Integer read GetDetailCount;
property Details[Index: Integer]: TUpdateTree read GetDetail;
property ErrorDS: TPacketDataSet read GetErrorDS;
property HasErrors: Boolean read GetHasErrors;
property Name: string read FName write FName;
property Parent: TUpdateTree read FParent;
property Source: TDataSet read FSourceDS;
property IsNested: Boolean read GetIsNested;
public
constructor Create(AParent: TUpdateTree; AResolver: TCustomResolver);
destructor Destroy; override;
end;
{ TCustomResolver }
TCustomResolver = class(TComponent)
private
FProvider: TBaseProvider;
FPrevResponse: TResolverResponse;
FErrorCount: Integer;
FMaxErrors: Integer;
FUpdateTree: TUpdateTree;
protected
property Provider: TBaseProvider read FProvider;
function HandleUpdateError(Tree: TUpdateTree; E: EUpdateError;
var MaxErrors, ErrorCount: Integer): Boolean;
procedure LogUpdateRecord(Tree: TUpdateTree);
procedure LogUpdateError(Tree: TUpdateTree; E: EUpdateError;
Response: TResolverResponse);
procedure InitKeyFields(Tree: TUpdateTree; ADelta: TPacketDataSet);
procedure InternalBeforeResolve(Tree: TUpdateTree); virtual;
function InternalUpdateRecord(Tree: TUpdateTree): Boolean;
procedure BeginUpdate; virtual;
procedure EndUpdate; virtual;
procedure InitTreeData(Tree: TUpdateTree); virtual;
procedure FreeTreeData(Tree: TUpdateTree); virtual;
procedure InitializeConflictBuffer(Tree: TUpdateTree); virtual; abstract;
procedure DoUpdate(Tree: TUpdateTree); virtual; abstract;
procedure DoDelete(Tree: TUpdateTree); virtual; abstract;
procedure DoInsert(Tree: TUpdateTree); virtual; abstract;
function RowRequest(Row: OleVariant; Options: TFetchOptions): OleVariant; virtual;
function ApplyUpdates(const Delta: OleVariant; MaxErrors: Integer;
out ErrorCount: Integer): OleVariant; virtual;
public
constructor Create(AProvider: TBaseProvider); reintroduce;
destructor Destroy; override;
end;
{ TDataSetResolver }
TDataSetResolver = class(TCustomResolver)
private
FBookmark: TBookmarkStr;
FOpened: Boolean;
function GetProvider: TDataSetProvider;
procedure PutRecord(Tree: TUpdateTree);
protected
property Provider: TDataSetProvider read GetProvider;
procedure BeginUpdate; override;
procedure DoUpdate(Tree: TUpdateTree); override;
procedure DoDelete(Tree: TUpdateTree); override;
procedure DoInsert(Tree: TUpdateTree); override;
procedure EndUpdate; override;
procedure InternalBeforeResolve(Tree: TUpdateTree); override;
procedure InitializeConflictBuffer(Tree: TUpdateTree); override;
public
constructor Create(AProvider: TDataSetProvider); reintroduce;
end;
{ TSQLResolver }
TSQLResolver = class(TCustomResolver)
private
FSQL: TStringList;
FParams: TParams;
function GetProvider: TDataSetProvider;
procedure GenWhereSQL(Tree: TUpdateTree; SQL: TStrings; Params: TParams;
GenUpdateMode: TUpdateMode; Alias: string);
procedure GenInsertSQL(Tree: TUpdateTree; SQL: TStrings; Params: TParams);
procedure GenDeleteSQL(Tree: TUpdateTree; SQL: TStrings; Params: TParams;
Alias: string);
procedure GenUpdateSQL(Tree: TUpdateTree; SQL: TStrings; Params: TParams;
Alias: string);
procedure GenSelectSQL(Tree: TUpdateTree; SQL: TStrings; Params: TParams;
Alias: string);
function UseFieldInUpdate(Field: TField): Boolean;
function UseFieldInWhere(Field: TField; Mode: TUpdateMode): Boolean;
procedure InternalDoUpdate(Tree: TUpdateTree; UpdateKind: TUpdateKind);
protected
property Provider: TDataSetProvider read GetProvider;
procedure InitializeConflictBuffer(Tree: TUpdateTree); override;
procedure DoExecSQL(SQL: TStringList; Params: TParams); virtual;
procedure DoGetValues(SQL: TStringList; Params: TParams;
DataSet: TDataSet); virtual;
procedure InitTreeData(Tree: TUpdateTree); override;
procedure FreeTreeData(Tree: TUpdateTree); override;
procedure DoUpdate(Tree: TUpdateTree); override;
procedure DoDelete(Tree: TUpdateTree); override;
procedure DoInsert(Tree: TUpdateTree); override;
public
constructor Create(AProvider: TDataSetProvider); reintroduce;
destructor Destroy; override;
end;
{ TLocalAppServer }
TLocalAppServer = class(TInterfacedObject, IAppServer, ISupportErrorInfo)
private
FProvider: TCustomProvider;
FProviderCreated: Boolean;
protected
{ IDispatch }
function GetTypeInfoCount(out Count: Integer): HResult; stdcall;
function GetTypeInfo(Index, LocaleID: Integer; out TypeInfo): HResult; stdcall;
function GetIDsOfNames(const IID: TGUID; Names: Pointer;
NameCount, LocaleID: Integer; DispIDs: Pointer): HResult; stdcall;
function Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer;
Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult; stdcall;
{ IAppServer }
function AS_ApplyUpdates(const ProviderName: WideString; Delta: OleVariant; MaxErrors: Integer;
out ErrorCount: Integer; var OwnerData: OleVariant): OleVariant; safecall;
function AS_GetRecords(const ProviderName: WideString; Count: Integer; out RecsOut: Integer;
Options: Integer; const CommandText: WideString;
var Params: OleVariant; var OwnerData: OleVariant): OleVariant; safecall;
function AS_DataRequest(const ProviderName: WideString; Data: OleVariant): OleVariant; safecall;
function AS_GetProviderNames: OleVariant; safecall;
function AS_GetParams(const ProviderName: WideString; var OwnerData: OleVariant): OleVariant; safecall;
function AS_RowRequest(const ProviderName: WideString; Row: OleVariant; RequestType: Integer;
var OwnerData: OleVariant): OleVariant; safecall;
procedure AS_Execute(const ProviderName: WideString; const CommandText: WideString;
var Params, OwnerData: OleVariant); safecall;
{ ISupportErrorInfo }
function InterfaceSupportsErrorInfo(const iid: TIID): HResult; stdcall;
public
constructor Create(AProvider: TCustomProvider); overload;
constructor Create(ADataset: TDataset); overload;
destructor Destroy; override;
function SafeCallException(ExceptObject: TObject;
ExceptAddr: Pointer): HResult; override;
end;
{ Utility functions }
function GetObjectProperty(Instance: TPersistent; const PropName: string): TObject;
function GetStringProperty(Instance: TPersistent; const PropName: string): string;
implementation
uses MidConst, DBConsts, DBCommon, TypInfo, DataBkr, ComObj;
const
DEFBUFSIZE = 8192; { Default size for field data buffer }
DefAlias = 'A';
NestAlias = 'B';
tagSERVERCALC = 1;
PacketTypeMap: array [TFieldType] of Integer =
(dsfldUNKNOWN, dsfldZSTRING, dsfldINT, dsfldINT, dsfldINT, dsfldBOOL,
dsfldFLOATIEEE, dsfldFLOATIEEE, dsfldBCD, dsfldDATE, dsfldTIME,
dsfldTIMESTAMP, dsfldBYTES, dsfldBYTES, dsfldINT, dsfldBYTES, dsfldBYTES,
dsfldBYTES, dsfldBYTES, dsfldBYTES, dsfldBYTES, dsfldBYTES, dsfldUNKNOWN,
dsfldZSTRING, dsfldUNICODE, dsfldINT, dsfldADT, dsfldARRAY, dsfldEMBEDDEDTBL,
dsfldEMBEDDEDTBL, dsfldBYTES, dsfldBYTES, dsfldUNKNOWN, dsfldUNKNOWN,
dsfldUNKNOWN, dsfldZSTRING);
ExtraFieldProps: array [0..10] of string = ('Alignment', 'DisplayLabel',
'DisplayWidth', 'Visible', 'EditMask', 'DisplayFormat', 'EditFormat',
'MinValue', 'MaxValue', 'currency', 'DisplayValues');
{ Utility functions }
function GetObjectProperty(Instance: TPersistent; const PropName: string): TObject;
var
PropInfo: PPropInfo;
begin
Result := nil;
PropInfo := TypInfo.GetPropInfo(Instance.ClassInfo, PropName);
if (PropInfo <> nil) and (PropInfo^.PropType^.Kind = tkClass) then
Result := TObject(GetOrdProp(Instance, PropInfo));
end;
function GetStringProperty(Instance: TPersistent; const PropName: string): string;
var
PropInfo: PPropInfo;
begin
Result := '';
PropInfo := TypInfo.GetPropInfo(Instance.ClassInfo, PropName);
if (PropInfo <> nil) and (PropInfo^.PropType^.Kind in [tkString, tkLString, tkWString]) then
Result := GetStrProp(Instance, PropInfo);
end;
{ EDSWriter }
constructor EDSWriter.Create(ErrMsg: string; Status: Integer);
begin
FErrorCode := Status;
inherited Create(ErrMsg);
end;
{ TCustomPacketWriter }
constructor TCustomPacketWriter.Create;
begin
SetLength(FBuffer, DEFBUFSIZE);
end;
destructor TCustomPacketWriter.Destroy;
begin
FIDSWriter := nil;
FBuffer := nil;
inherited Destroy;
end;
procedure TCustomPacketWriter.Check(Status: Integer);
var
ErrMsg: array[0..2048] of Char;
begin
if Status <> 0 then
begin
FIDSWriter.GetErrorString(Status, ErrMsg);
raise EDSWriter.Create(ErrMsg, Status);
end;
end;
procedure TCustomPacketWriter.AddAttribute(Area: TPcktAttrArea; const ParamName: string;
const Value: OleVariant; IncludeInDelta: Boolean);
const
ParamTypeMap: array[varSmallInt..varByte] of Integer =
( dsfldINT, dsfldINT, dsfldFLOATIEEE, dsfldFLOATIEEE, dsfldBCD,
dsfldFLOATIEEE, dsfldZSTRING, 0, 0, dsfldBOOL, 0, 0, 0, 0, 0, dsfldINT);
ParamTypeSize: array[varSmallInt..varByte] of Integer =
( SizeOf(SmallInt), SizeOf(Integer), SizeOf(Single), SizeOf(Double),
SizeOf(Currency), SizeOf(TDateTime), 0, 0, 0, SizeOf(WordBool), 0, 0, 0,
0, 0, SizeOf(Byte));
var
ParamType, ParamLen, ElemSize, ElemCount: DWord;
P: Pointer;
DateRec: TDateTimeRec;
TimeStamp: TTimeStamp;
begin
if ((VarType(Value) and varTypeMask) in [varSmallInt, varInteger, varSingle,
varDouble, varCurrency, varDate, varOleStr, varBoolean, varByte]) then
begin
ParamType := ParamTypeMap[VarType(Value) and varTypeMask];
ParamLen := ParamTypeSize[VarType(Value) and varTypeMask];
if ParamType = dsfldZSTRING then
begin
ParamType := (dsfldZSTRING shl dsSizeBitsLen) or dsVaryingFldType or SizeOf(Word);
ParamLen := Length(Value) + 1;
PWord(FBuffer)^ := ParamLen;
Inc(ParamLen, SizeOf(Word));
StrPCopy(@FBuffer[SizeOf(Word)], Value);
end else
if ParamType = dsfldTIMESTAMP then
begin
TimeStamp := DateTimeToTimeStamp(Value);
DateRec.DateTime := TimeStampToMSecs(TimeStamp);
Move(DateRec, PChar(FBuffer)^, ParamLen);
ParamType := ParamType shl dsSizeBitsLen or SizeOf(TDateTimeRec);
end else
if VarIsArray(Value) then
begin
if ParamLen = 0 then
raise EDSWriter.Create(SInvalidOptParamType, 0);
ElemCount := VarArrayHighBound(Value, 1) + 1;
ElemSize := ParamLen;
ParamType := (dsfldUINT shl dsSizeBitsLen) or dsArrayFldType or ElemSize;
PInteger(FBuffer)^ := ElemCount;
ParamLen := ElemCount * ElemSize;
P := VarArrayLock(Value);
try
Move(P^, FBuffer[SizeOf(Integer)], ParamLen);
Inc(ParamLen, SizeOf(Integer));
finally
VarArrayUnlock(Value);
end;
end else
begin
if (VarType(Value) and varByRef) = varByRef then
P := TVarData(Value).VPointer else
P := @TVarData(Value).VPointer;
Move(P^, PByte(FBuffer)^, ParamLen);
ParamType := ParamType shl dsSizeBitsLen or ParamLen;
end;
if IncludeInDelta then
ParamType := ParamType or dsIncInDelta;
Check(FIDSWriter.AddAttribute(Area, PChar(ParamName), ParamType, ParamLen, PByte(FBuffer)));
end else
raise EDSWriter.Create(SInvalidOptParamType, 0);
end;
{ TDataPacketWriter }
destructor TDataPacketWriter.Destroy;
begin
FreeInfoRecords(FPutFieldInfo);
FPutFieldInfo := nil;
inherited Destroy;
end;
procedure TDataPacketWriter.FreeInfoRecords(var Info: TInfoArray);
var
i: Integer;
begin
for i := 0 to High(Info) do
if Info[i].FieldInfos <> nil then
begin
FreeInfoRecords(TInfoArray(Info[i].FieldInfos));
TInfoArray(Info[i].FieldInfos) := nil;
end;
end;
{ Writing data }
procedure TDataPacketWriter.PutBlobField(Info: PPutFieldInfo);
begin
if not (poFetchBlobsOnDemand in Options) then
begin
Info.Size := Info.DataSet.GetBlobFieldData(Info.FieldNo, TBlobByteData(FBuffer));
if Info.Size <> 0 then
begin
if Length(FBuffer) > Info.Size then
FBuffer[Info.Size] := 0;
if TBlobField(Info.Field).Transliterate then
Info.Size := Info.DataSet.Translate(PChar(FBuffer), PChar(FBuffer), False);
FIDSWriter.PutField(fldIsChanged, Info.Size, PByte(FBuffer))
end else
FIDSWriter.PutField(fldIsNull, 0, nil);
end else
FIDSWriter.PutField(fldIsChanged, dsDELAYEDBIT or 1, @Info.Size);
end;
procedure TDataPacketWriter.PutCalcField(Info: PPutFieldInfo);
begin
if Info.DataSet.GetFieldData(Info.Field, FBuffer) then
begin
if (Info.Field is TStringField) then
if TStringField(Info.Field).Transliterate then
Info.Size := Info.DataSet.Translate(PChar(FBuffer), PChar(FBuffer), False) else
Info.Size := StrLen(PChar(FBuffer));
FIDSWriter.PutField(fldIsChanged, Info.Size, PByte(FBuffer));
end else
FIDSWriter.PutField(fldIsNull, 0, nil);
end;
procedure TDataPacketWriter.PutField(Info: PPutFieldInfo);
begin
if Info.DataSet.GetFieldData(Info.FieldNo, FBuffer) then
FIDSWriter.PutField(fldIsChanged, Info.Size, PByte(FBuffer)) else
FIDSWriter.PutField(fldIsNull, 0, nil);
end;
procedure TDataPacketWriter.PutStringField(Info: PPutFieldInfo);
begin
if Info.DataSet.GetFieldData(Info.FieldNo, FBuffer) then
begin
if TStringField(Info.Field).Transliterate then
Info.Size := Info.DataSet.Translate(PChar(FBuffer), PChar(FBuffer), False) else
Info.Size := StrLen(PChar(FBuffer));
FIDSWriter.PutField(fldIsChanged, Info.Size, PByte(FBuffer));
end else
FIDSWriter.PutField(fldIsNull, 0, nil);
end;
procedure TDataPacketWriter.PutWideStringField(Info: PPutFieldInfo);
var
W: WideString;
begin
if Info.DataSet.GetFieldData(Info.FieldNo, @W) then
begin
Info.Size := Length(W);
FIDSWriter.PutField(fldIsChanged, Info.Size * 2, PByte(W));
end else
FIDSWriter.PutField(fldIsNull, 0, nil);
end;
procedure TDataPacketWriter.PutVarBytesField(Info: PPutFieldInfo);
begin
if Info.DataSet.GetFieldData(Info.FieldNo, FBuffer) then
FIDSWriter.PutField(fldIsChanged, PWord(FBuffer)^, @FBuffer[SizeOf(Word)]) else
FIDSWriter.PutField(fldIsNull, 0, nil);
end;
procedure TDataPacketWriter.PutADTField(Info: PPutFieldInfo);
var
i: Integer;
begin
if Info.Field.IsNull then
FIDSWriter.PutField(fldIsNull, 0, nil) else
FIDSWriter.PutField(fldIsChanged, 0, nil);
for i := 0 to High(TInfoArray(Info.FieldInfos)) do
with TInfoArray(Info^.FieldInfos)[i] do
PutProc(@TInfoArray(Info.FieldInfos)[i]);
end;
procedure TDataPacketWriter.PutArrayField(Info: PPutFieldInfo);
procedure RefreshInfos(Src: TField; Dest: PPutFieldInfo);
var
i: Integer;
begin
with Dest^ do
begin
Field := Src;
FieldNo := Src.FieldNo;
if (FieldInfos <> nil) then { Must be an ADT }
begin
if not (Src is TADTField) then
raise EDSWriter.CreateFmt(SArrayElementError,[Src.ClassName]);
with (Src as TADTField) do
for i := 0 to FieldCount - 1 do
RefreshInfos(Fields[i], @TInfoArray(FieldInfos)[i]);
end;
end;
end;
var
i: Integer;
begin
if Info.Field.IsNull then
FIDSWriter.PutField(fldIsNull, 0, nil) else
FIDSWriter.PutField(fldIsChanged, 0, nil);
for i := 0 to TArrayField(Info.Field).FieldCount - 1 do
with TInfoArray(Info^.FieldInfos)[0] do
begin
RefreshInfos(TArrayField(Info.Field).Fields[i], @TInfoArray(Info.FieldInfos)[0]);
PutProc(@TInfoArray(Info.FieldInfos)[0]);
end;
end;
procedure TDataPacketWriter.PutDataSetField(Info: PPutFieldInfo);
var
Count: DWord;
DataSet: TDataSet;
begin
if Info.Field <> nil then
begin
if Info.Field.IsNull then
begin
FIDSWriter.PutField(fldIsNull, 0, nil);
Exit;
end;
DataSet := TDataSetField(Info.Field).NestedDataSet;
end else
DataSet := Info.DataSet;
if (poFetchDetailsOnDemand in Options) then
Count := dsDELAYEDBIT else
Count := DWord(-1);
FIDSWriter.PutField(fldIsChanged, SizeOf(Count), @Count);
if (not (poFetchDetailsOnDemand in Options)) and (Count = DWord(-1)) then
begin
DataSet.UpdateCursorPos;
DataSet.First;
DataSet.BlockReadSize := MaxInt;
try
WriteDataSet(DataSet, TInfoArray(Info.FieldInfos), -1);
FIDSWriter.EndOfNestedRows;
finally
DataSet.BlockReadSize := 0;
end;
end;
end;
function TDataPacketWriter.WriteDataSet(DataSet: TDataSet; var Info: TInfoArray;
RecsOut: Integer): Integer;
const
B: Byte = 0;
var
i: Integer;
begin
Result := 0;
if RecsOut = AllRecords then
RecsOut := High(Integer);
if DataSet.DefaultFields then
RefreshPutProcs(DataSet, Info);
while (not DataSet.EOF) and (Result < RecsOut) do
begin
FIDSWriter.PutField(fldIsChanged, 1, @B);
for i := 0 to High(Info) do
Info[i].PutProc(@Info[i]);
Inc(Result);
if Result < RecsOut then
DataSet.Next;
end;
end;
{ Writing meta data }
procedure TDataPacketWriter.AddDataSetAttributes(DataSet: TDataSet);
var
i: Integer;
List: TList;
begin
if Assigned(FOnGetParams) then
begin
List := TList.Create;
try
FOnGetParams(DataSet, List);
for i := 0 to List.Count - 1 do
with PPacketAttribute(List[i])^ do
begin
AddAttribute(pcktAttrArea, Name, Value, IncludeInDelta);
Dispose(PPacketAttribute(List[i]));
end;
finally
List.Free;
end;
end;
end;
function TDataPacketWriter.GetFieldIdx(const FieldName: string; const Info: TInfoArray): Integer;
var
i: Integer;
begin
Result := -1;
for i := 0 to High(Info) do
if (Info[i].Field <> nil) and (Info[i].Field.FieldName = FieldName) then
begin
Result := Info[i].LocalFieldIndex;
break;
end;
end;
type
TPropWriter = class(TWriter);
procedure TDataPacketWriter.AddExtraFieldProps(Field: TField);
procedure WriteProp(Instance: TPersistent; const PropName: string;
Writer: TPropWriter);
var
PropInfo: PPropInfo;
begin
PropInfo := TypInfo.GetPropInfo(Instance.ClassInfo, PropName);
if (PropInfo <> nil) and IsStoredProp(Instance, PropInfo) then
Writer.WriteProperty(Instance, PropInfo);
end;
var
Writer: TPropWriter;
Stream: TMemoryStream;
i, Attr: Integer;
begin
Stream := TMemoryStream.Create;
try
Writer := TPropWriter.Create(Stream, 1024);
try
Writer.WriteListBegin;
for i := 0 to High(ExtraFieldProps) do
WriteProp(Field, ExtraFieldProps[i], Writer);
Writer.WriteListEnd;
Writer.FlushBuffer;
if Stream.Size > 2 then
begin
Attr := (dsfldBYTES shl dsSizeBitsLen) or dsArrayFldType or SizeOf(Byte);
PInteger(FBuffer)^ := Stream.Size;
Move(Stream.Memory^, FBuffer[SizeOf(Integer)], Stream.Size);
Check(FIDSWriter.AddAttribute(fldAttrArea, szFIELDPROPS, Attr,
Stream.Size + SizeOf(Integer), FBuffer));
end;
finally
Writer.Free;
end;
finally
Stream.Free;
end;
end;
procedure TDataPacketWriter.AddColumn(const Info: TPutFieldInfo);
procedure AddFieldDesc(const FldName: string; FldType, Attributes: Integer);
var
FldDesc: TDSDataPacketFldDesc;
begin
if Length(FldName) > SizeOf(FldDesc.szFieldName) then
raise EDSWriter.CreateFmt(SFieldNameTooLong,[SizeOf(FldDesc.szFieldName) - 1]);
FillChar(FldDesc, SizeOf(FldDesc), 0);
StrCopy(FldDesc.szFieldName, PChar(FldName));
FldDesc.iFieldType := FldType;
FldDesc.iAttributes := Attributes;
Check(FIDSWriter.AddColumnDesc(FldDesc));
end;
function ComputeInfoCount(Info: TInfoArray): Integer;
var
i: Integer;
begin
Result := Length(Info);
for i := 0 to High(Info) do
if Info[i].FieldInfos <> nil then
Inc(Result, ComputeInfoCount(Info[i].FieldInfos));
end;
procedure AddMinMax(AField: TField);
begin
case AField.DataType of
ftInteger, ftSmallInt:
if (TIntegerField(AField).MinValue <> 0) or
(TIntegerField(AField).MaxValue <> 0) then
begin
AddAttribute(fldAttrArea, szMINVALUE,
TIntegerField(AField).MinValue, False);
AddAttribute(fldAttrArea, szMAXVALUE,
TIntegerField(AField).MaxValue, False);
end;
ftCurrency, ftFloat:
if (TFloatField(AField).MinValue <> 0 ) or
(TFloatField(AField).MaxValue <> 0 ) then
begin
AddAttribute(fldAttrArea, szMINVALUE,
TFloatField(AField).MinValue, False);
AddAttribute(fldAttrArea, szMAXVALUE,
TFloatField(AField).MaxValue, False);
end;
ftBCD:
if (TBCDField(AField).MinValue <> 0 ) or
(TIntegerField(AField).MaxValue <> 0 ) then
begin
AddAttribute(fldAttrArea, szMINVALUE,
TBCDField(AField).MinValue, False);
AddAttribute(fldAttrArea, szMAXVALUE,
TBCDField(AField).MaxValue, False);
end;
end;
end;
var
FldType, Prec, Attr, i, Width: Integer;
TempStr: string;
begin
if Info.IsDetail and (Info.Field = nil) then
begin
FldType := (dsfldEMBEDDEDTBL shl dsSizeBitsLen) or
ComputeInfoCount(Info.FieldInfos) or dsPseudoFldType;
AddFieldDesc(Info.DataSet.Name, FldType, 0);
WriteMetaData(Info.DataSet, TInfoArray(Info.FieldInfos));
end else
begin
Width := 0;
Attr := 0;
if Info.Field.ReadOnly or (Info.Field.FieldKind <> fkData) then Attr := Attr or fldAttrREADONLY;
if Info.Field.Required and (Info.Field.DataType <> ftAutoInc) then Attr := Attr or fldAttrREQUIRED;
if (pfHidden in Info.Field.ProviderFlags) then Attr := Attr or fldAttrHIDDEN or fldAttrREADONLY;
FldType := PacketTypeMap[Info.Field.DataType];
case Info.Field.DataType of
ftString, ftFixedChar, ftVarBytes, ftGUID, ftWideString:
begin
FldType := FldType shl dsSizeBitsLen or dsVaryingFldType;
if Info.Size < 255 then
FldType := FldType or SizeOf(Byte) else
FldType := FldType or SizeOf(Word);
Width := Info.Size;
end;
ftBCD:
begin
if TBCDField(Info.Field).Precision = 0 then
Width := 32 else
Width := TBCDField(Info.Field).Precision;
Prec := Width shr 1;
Inc(Prec, Prec and 1); { Make an even number }
FldType := (FldType shl dsSizeBitsLen) or (Prec + 2);
end;
ftArray:
FldType := (FldType shl dsSizeBitsLen) or dsPseudoFldType or
dsCompArrayFldType or TObjectField(Info.Field).Size;
ftADT:
FldType := (FldType shl dsSizeBitsLen) or dsPseudoFldType or
TObjectField(Info.Field).FieldCount;
ftDataSet, ftReference:
FldType := (FldType shl dsSizeBitsLen) or dsPseudoFldType or
dsEmbeddedFldType or ComputeInfoCount(TInfoArray(Info.FieldInfos));
else
if Info.Field.IsBlob then
begin
FldType := (FldType shl dsSizeBitsLen) or dsVaryingFldType or SizeOf(Integer);
Width := Info.Field.Size;
end else
FldType := (FldType shl dsSizeBitsLen) or Info.Size;
end;
AddFieldDesc(Info.Field.FieldName, FldType, Attr);
if (Info.Field.FieldKind <> fkData) then
AddAttribute(fldAttrArea, szSERVERCALC, True, True);
if Info.Field.ProviderFlags <> [pfInWhere, pfInUpdate] then
AddAttribute(fldAttrArea, szPROVFLAGS, Byte(Info.Field.ProviderFlags), True);
if Info.Field.Origin <> '' then
AddAttribute(fldAttrArea, szORIGIN, Info.Field.Origin, True);
if Width > 0 then
AddAttribute(fldAttrArea, szWIDTH, Width, False);
if (Info.Field is TBCDField) and (TBCDField(Info.Field).Size <> 0) then
AddAttribute(fldAttrArea, szDECIMALS, TBCDField(Info.Field).Size, False);
AddMinMax(Info.Field);
case Info.Field.DataType of
ftCurrency: TempStr := szstMONEY;
ftAutoInc: TempStr := szstAUTOINC;
ftVarBytes, ftBlob: TempStr := szstBINARY;
ftMemo: TempStr := szstMEMO;
ftFmtMemo: TempStr := szstFMTMEMO;
ftParadoxOle: TempStr := szstOLEOBJ;
ftGraphic: TempStr := szstGRAPHIC;
ftDBaseOle: TempStr := szstDBSOLEOBJ;
ftTypedBinary: TempStr := szstTYPEDBINARY;
ftADT:
if (Info.Field.ParentField <> nil) and
(Info.Field.ParentField.DataType in [ftDataSet, ftReference]) then
TempStr := szstADTNESTEDTABLE;
ftReference: TempStr := szstREFNESTEDTABLE;
ftString:
if TStringField(Info.Field).FixedChar then
TempStr := szstFIXEDCHAR else
TempStr := '';
ftGUID: TempStr := szstGUID;
ftOraClob: TempStr := szstHMEMO;
ftOraBlob: TempStr := szstHBINARY;
else
TempStr := '';
end;
if TempStr <> '' then
AddAttribute(fldAttrArea, szSUBTYPE, TempStr, False);
if Info.Field is TObjectField then
AddAttribute(fldAttrArea, szTYPENAME, TObjectField(Info.Field).ObjectType, False);
if poIncFieldProps in Options then
AddExtraFieldProps(Info.Field);
case Info.Field.DataType of
ftADT, ftArray: { Array will only have 1 child field }
for i := 0 to High(TInfoArray(Info.FieldInfos)) do
AddColumn(TInfoArray(Info.FieldInfos)[i]);
ftDataSet, ftReference:
with TDataSetField(Info.Field) do
WriteMetaData(NestedDataSet, TInfoArray(Info.FieldInfos),
Info.Field.DataType = ftReference);
end;
end;
end;
procedure TDataPacketWriter.AddConstraints(DataSet: TDataSet);
type
TConstraintType = (ctField, ctRecord, ctDefault);
procedure AddSQLExprAttr(ExprParser: TExprParser; const ExprText, ExprErrMsg,
FieldName: string; FieldIndex: Integer; ConstraintType: TConstraintType;
Required: Boolean);
type
PSQLExprInfo = ^TSQLExprInfo;
TSQLExprInfo = packed record
iErrStrLen: Integer;
iFldNum: Integer;
bReqExpr: BYTE;
end;
const
TypeStr: array[TConstraintType] of PChar = (szBDEDOMX, szBDERECX, szBDEDEFX);
Attr: Integer = dsVaryingFldType or SizeOf(Integer) or (dsfldBYTES shl dsSizeBitsLen);
var
ErrorStr: string;
AttrType: PChar;
Len, AttrSize: Integer;
SQLExprInfo: PSQLExprInfo;
Options: TParserOptions;
begin
if ExprText = '' then Exit;
if (ConstraintType <> ctDefault) and (ExprErrMsg = '') then
begin
if (ConstraintType = ctField) and (FieldName <> '') then
ErrorStr := Format('%s %s: %s %s',[SConstraintFailed, SField, FieldName, ExprText]) else
ErrorStr := Format('%s %s',[SConstraintFailed, ExprText]);
end else
ErrorStr := ExprErrMsg;
Len := Length(ErrorStr);
if (Len > 0) then Inc(Len);
SQLExprInfo := @FBuffer[SizeOf(Integer)];
SQLExprInfo.iErrStrLen := Len;
SQLExprInfo.iFldNum := FieldIndex;
SQLExprInfo.bReqExpr := Ord(Required);
Options := [poExtSyntax];
if ConstraintType = ctDefault then Include(Options, poDefaultExpr);
if ConstraintType = ctRecord then Include(Options, poUseOrigNames);
if FieldName <> '' then Include(Options, poFieldNameGiven);
with ExprParser do
begin
SetExprParams(ExprText, [], Options, FieldName);
Move(FilterData[0], FBuffer[SizeOf(TSQLExprInfo) + Len + SizeOf(Integer)], DataSize);
AttrSize := DataSize + SizeOf(TSQLExprInfo) + Len;
end;
PInteger(FBuffer)^ := AttrSize;
if Len > 0 then
StrCopy(@FBuffer[SizeOf(TSQLExprInfo) + SizeOf(Integer)], PChar(ErrorStr));
AttrType := TypeStr[ConstraintType];
Check(FIDSWriter.AddAttribute(pcktAttrArea, AttrType, Attr, AttrSize + SizeOf(Integer), PByte(FBuffer)));
end;
var
i: Integer;
ExprParser: TExprParser;
Constraints: TCheckConstraints;
Obj: TObject;
ErrMsg: string;
begin
ExprParser := TExprParser.Create(DataSet, '', [], [], '', nil, FieldTypeMap);
try
Obj := GetObjectProperty(DataSet, 'Constraints'); { Do not localize }
if (Obj <> nil) and (Obj is TCheckConstraints) then
begin
Constraints := Obj as TCheckConstraints;
try
for i := 0 to Constraints.Count - 1 do
with Constraints[i] do
begin
AddSQLExprAttr(ExprParser, ImportedConstraint, ErrorMessage, '', 0,
ctRecord, False);
AddSQLExprAttr(ExprParser, CustomConstraint, ErrorMessage, '', 0,
ctRecord, False);
end;
except
on E: Exception do
begin
if DataSet.Name <> '' then
ErrMsg := Format('%s: %s',[DataSet.Name, SRecConstFail]) else
ErrMsg := SRecConstFail;
raise EDSWriter.CreateFmt(ErrMsg, [E.Message]);
end;
end;
end;
for i := 0 to DataSet.FieldList.Count - 1 do
with DataSet.FieldList[i] do
begin
try
AddSQLExprAttr(ExprParser, DefaultExpression, '', FullName, i + 1,
ctDefault, False);
except
on E: Exception do
begin
if Name <> '' then
ErrMsg := Format('%s: %s',[Name, SDefExprFail]) else
if DataSet.Name <> '' then
ErrMsg := Format('%s.%s: %s',[DataSet.Name, FullName, SDefExprFail]) else
ErrMsg := Format('%s: %s',[FullName, SDefExprFail]);
raise EDSWriter.CreateFmt(ErrMsg, [E.Message]);
end;
end;
try
AddSQLExprAttr(ExprParser, ImportedConstraint, ConstraintErrorMessage,
FullName, i + 1, ctField, False);
AddSQLExprAttr(ExprParser, CustomConstraint, ConstraintErrorMessage,
FullName, i + 1, ctField, False);
except
on E: Exception do
begin
if Name <> '' then
ErrMsg := Format('%s: %s',[Name, SFieldConstFail]) else
if DataSet.Name <> '' then
ErrMsg := Format('%s.%s: %s',[DataSet.Name, FullName, SFieldConstFail]) else
ErrMsg := Format('%s: %s',[FullName, SFieldConstFail]);
raise EDSWriter.CreateFmt(ErrMsg, [E.Message]);
end;
end;
end;
finally
ExprParser.Free;
end;
end;
procedure TDataPacketWriter.AddIndexDefs(DataSet: TDataSet; const Info: TInfoArray);
var
FieldList, CaseList, DescList: TList;
function GetKeyData(Index: TIndexDef): OleVariant;
var
i: Integer;
x: SmallInt;
begin
with Index do
begin
FieldList.Clear;
CaseList.Clear;
DescList.Clear;
DataSet.GetFieldList(FieldList, Fields);
DataSet.GetFieldList(CaseList, CaseInsFields);
DataSet.GetFieldList(DescList, DescFields);
Result := VarArrayCreate([0, FieldList.Count - 1], varSmallInt);
for i := 0 to FieldList.Count - 1 do
begin
x := GetFieldIdx(TField(FieldList[i]).FieldName, Info);
if (CaseList.IndexOf(FieldList[i]) <> -1) or
((i = 0) and (FieldList.Count = 1) and (ixCaseInSensitive in Options)) then
x := x or dskeyCASEINSENSITIVE;
if (DescList.IndexOf(FieldList[i]) <> -1) or
((i = 0) and (FieldList.Count = 1) and (ixDescending in Options)) then
x := x or SmallInt(dskeyDESCENDING);
Result[i] := x;
end;
end;
end;
var
i: Integer;
DefIdx, KeyIndex: TIndexDef;
IndexDefs: TIndexDefs;
KeyList: OleVariant;
KeyFields: string;
begin
FieldList := TList.Create;
try
CaseList := TList.Create;
try
DescList := TList.Create;
try
{ Get the DEFAULT_ORDER }
DefIdx := IProviderSupport(DataSet).PSGetDefaultOrder;
if Assigned(DefIdx) then
try
KeyList := GetKeyData(DefIdx);
AddAttribute(pcktAttrArea, szDEFAULT_ORDER, KeyList, False);
finally
DefIdx.Free;
end;
KeyFields := IProviderSupport(DataSet).PSGetKeyFields;
IndexDefs := IProviderSupport(DataSet).PSGetIndexDefs([ixUnique]);
try
if KeyFields <> '' then
begin
{ PRIMARY_KEY is used to define the keyfields }
KeyList := NULL;
if Assigned(IndexDefs) then
begin
KeyIndex := IndexDefs.GetIndexForFields(KeyFields, False);
if Assigned(KeyIndex) then
begin
KeyList := GetKeyData(KeyIndex);
KeyIndex.Free;{ KeyIndex is already used, remove it from the list }
end;
end;
if VarIsNull(KeyList) then
begin
DataSet.GetFieldList(FieldList, KeyFields);
KeyList := VarArrayCreate([0, FieldList.Count - 1], varSmallInt);
for i := 0 to FieldList.Count - 1 do
KeyList[i] := GetFieldIdx(TField(FieldList[i]).FieldName, Info);
end;
if not VarIsNull(KeyList) then
AddAttribute(pcktAttrArea, szPRIMARY_KEY, KeyList, False);
end;
if Assigned(IndexDefs) then
for i := 0 to IndexDefs.Count - 1 do
with IndexDefs[i] do
begin
KeyList := GetKeyData(IndexDefs[i]);
AddAttribute(pcktAttrArea, szUNIQUE_KEY, KeyList, False);
end;
finally
IndexDefs.Free;
end;
finally
DescList.Free;
end;
finally
CaseList.Free;
end;
finally
FieldList.Free;
end;
end;
procedure TDataPacketWriter.AddFieldLinks(const Info: TInfoArray);
var
MasterFields, DetailFields: TList;
i, j: Integer;
LinkFields: Variant;
begin
MasterFields := TList.Create;
try
DetailFields := TList.Create;
try
for i := 0 to High(Info) do
if Info[i].IsDetail and (Info[i].Field = nil) then
begin
Info[i].DataSet.GetDetailLinkFields(MasterFields, DetailFields);
if (MasterFields.Count > 0) and (MasterFields.Count <= DetailFields.Count) then
begin
LinkFields := VarArrayCreate([0, MasterFields.Count * 2], varSmallInt);
LinkFields[0] := Info[i].LocalFieldIndex;
for j := 0 to MasterFields.Count - 1 do
LinkFields[j + 1] := GetFieldIdx(TField(MasterFields[j]).FieldName,
Info);
for j := 0 to MasterFields.Count - 1 do
LinkFields[j + MasterFields.Count + 1] :=
GetFieldIdx(TField(DetailFields[j]).FieldName, TInfoArray(Info[i].FieldInfos));
AddAttribute(pcktAttrArea, szMD_FIELDLINKS, LinkFields, False);
end;
end;
finally
DetailFields.Free;
end;
finally
MasterFields.Free;
end;
end;
procedure TDataPacketWriter.WriteMetaData(DataSet: TDataSet; const Info: TInfoArray;
IsReference: Boolean);
var
i, MDOptions: Integer;
begin
for i := 0 to High(Info) do
AddColumn(Info[i]);
if (poReadOnly in Options) or IsReference then
AddAttribute(pcktAttrArea, szREADONLY, True, False);
if (poDisableEdits in Options) then
AddAttribute(pcktAttrArea, szDISABLE_EDITS, True, False);
if (poDisableInserts in Options) then
AddAttribute(pcktAttrArea, szDISABLE_INSERTS, True, False);
if (poDisableDeletes in Options) then
AddAttribute(pcktAttrArea, szDISABLE_DELETES, True, False);
if (poNoReset in Options) then
AddAttribute(pcktAttrArea, szNO_RESET_CALL, True, False);
if Constraints then
AddConstraints(DataSet);
AddIndexDefs(DataSet, Info);
AddFieldLinks(Info);
MDOptions := 0;
if poCascadeDeletes in Options then MDOptions := dsCASCADEDELETES;
if poCascadeUpdates in Options then MDOptions := MDOptions or dsCASCADEUPDATES;
if MDOptions <> 0 then
AddAttribute(pcktAttrArea, szMD_SEMANTICS, MDOptions, True);
AddDataSetAttributes(DataSet);
if Info <> FPutFieldInfo then
Check(FIDSWriter.AddAttribute(pcktAttrArea, nil, 0, 0, nil));
end;
procedure TDataPacketWriter.RefreshPutProcs(ADataSet: TDataSet; var Info: TInfoArray);
procedure RefreshInfo(ADataSet: TDataSet; AField: TField; var Info: TPutFieldInfo);
var
j: Integer;
begin
Info.Field := AField;
if AField = nil then
Info.DataSet := ADataSet
else
begin
Info.DataSet := AField.DataSet;
if AField.DataType = ftADT then
begin
with TADTField(AField) do
for j := 0 to FieldCount - 1 do
RefreshInfo(ADataSet, Fields[j], TInfoArray(Info.FieldInfos)[j]);
end;
end;
end;
var
i: Integer;
List: TList;
begin
List := TList.Create;
try
ADataSet.GetDetailDataSets(List);
for i := 0 to ADataSet.FieldCount - 1 do
RefreshInfo(ADataSet, ADataSet.Fields[i], Info[i]);
for i := 0 to List.Count - 1 do
RefreshInfo(TDataSet(List[i]), nil, Info[ADataSet.FieldCount + i]);
finally
List.Free;
end;
end;
function TDataPacketWriter.InitPutProcs(ADataSet: TDataSet;
var GlobalIdx: Integer): TInfoArray;
procedure InitInfoStruct(var Info: TPutFieldInfo; AField: TField;
var GlobalIdx, LocalIdx: Integer);
begin
FillChar(Info, SizeOf(Info), 0);
with Info do
begin
IsDetail := AField = nil;
Field := AField;
Inc(GlobalIdx);
LocalFieldIndex := LocalIdx;
Inc(LocalIdx);
if Field <> nil then
begin
FieldNo := Field.FieldNo;
Size := Field.DataSize;
DataSet := Field.DataSet;
end;
end;
end;
procedure InitFieldProc(ADataSet: TDataSet; AField: TField;
var Info: TPutFieldInfo; var GlobalIdx, LocalIdx: Integer);
var
i: Integer;
NestedIdx: Integer;
begin
with Info do
begin
InitInfoStruct(Info, AField, GlobalIdx, LocalIdx);
if AField = nil then { Linked dataset }
begin
Opened := not ADataSet.Active;
if Opened then ADataSet.Open;
DataSet := ADataSet;
PutProc := PutDataSetField;
TInfoArray(FieldInfos) := InitPutProcs(DataSet, GlobalIdx);
end else
begin
case Field.DataType of
ftString, ftFixedChar, ftGUID:
begin
PutProc := PutStringField;
Dec(Size); { Don't count the null terminator }
end;
ftWideString:
begin
PutProc := PutWideStringField;
Size := AField.Size * 2;
end;
ftVarBytes:
begin
PutProc := PutVarBytesField;
Dec(Size, 2); { Don't write size bytes }
end;
ftADT:
with TADTField(Field) do
begin
PutProc := PutADTField;
SetLength(TInfoArray(FieldInfos), FieldCount);
for i := 0 to FieldCount - 1 do
InitFieldProc(ADataSet, Fields[i], TInfoArray(FieldInfos)[i],
GlobalIdx, LocalIdx);
end;
ftArray:
with TArrayField(Field) do
begin
PutProc := PutArrayField;
SetLength(TInfoArray(FieldInfos), 1);
NestedIdx := LocalIdx;
InitFieldProc(ADataSet, Fields[0], TInfoArray(FieldInfos)[0],
GlobalIdx, LocalIdx);
LocalIdx := (LocalIdx - NestedIdx) * (FieldCount - 1) + LocalIdx;
end;
ftDataSet, ftReference:
with TDataSetField(Field).NestedDataSet do
begin
PutProc := PutDataSetField;
NestedIdx := 1;
SetLength(TInfoArray(FieldInfos), FieldCount);
for i := 0 to FieldCount - 1 do
InitFieldProc(TDataSetField(Field).NestedDataSet, Fields[i],
TInfoArray(FieldInfos)[i], GlobalIdx, NestedIdx);
end;
ftSmallint, ftInteger, ftWord, ftBoolean, ftFloat, ftCurrency, ftBCD,
ftDate, ftTime, ftDateTime, ftAutoInc, ftLargeint, ftBytes:
PutProc := PutField;
ftBlob, ftMemo, ftGraphic, ftFmtMemo, ftParadoxOle, ftDBaseOle, ftTypedBinary, ftOraBlob, ftOraClob:
PutProc := PutBlobField;
else
DatabaseErrorFmt(SUnknownFieldType, [Field.FieldName]);
end;
if Field.FieldKind <> fkData then
PutProc := PutCalcField;
end;
end;
end;
var
i, LocalIdx: Integer;
List: TList;
begin
LocalIdx := 1;
List := TList.Create;
try
ADataSet.GetDetailDataSets(List);
SetLength(Result, ADataSet.FieldCount + List.Count);
for i := 0 to ADataSet.FieldCount - 1 do
InitFieldProc(ADataSet, ADataSet.Fields[i], Result[i], GlobalIdx, LocalIdx);
for i := 0 to List.Count - 1 do
InitFieldProc(TDataSet(List[i]), nil, Result[ADataSet.FieldCount + i],
GlobalIdx, LocalIdx);
finally
List.Free;
end;
end;
procedure TDataPacketWriter.GetDataPacket(DataSet: TDataSet;
var RecsOut: Integer; out Data: OleVariant);
procedure CheckMetaData(DataSet: TDataSet);
var
Idx: Integer;
TempPacket: TDataPacket;
Version: Integer;
begin
Idx := 1;
if (FPutFieldInfo = nil) or (grMetaData in PacketOptions) then
begin
CreateDBClientObject(CLSID_DSWriter, IDSWriter, FIDSWriter);
if FPutFieldInfo <> nil then
begin
FreeInfoRecords(FPutFieldInfo);
FPutFieldInfo := nil;
end;
FPutFieldInfo := InitPutProcs(DataSet, Idx);
if poFetchBlobsOnDemand in Options then
Version := PACKETVERSION_2 else
Version := PACKETVERSION_1;
if grXML in PacketOptions then
FIDSWriter.SetXMLMode(xmlON) else
FIDSWriter.SetXMLMode(0);
Check(FIDSWriter.Init_Sequential(Version, Idx - 1));
WriteMetaData(DataSet, FPutFieldInfo);
if not (grMetaData in PacketOptions) then
begin
FIDSWriter.GetDataPacket(TempPacket);
SafeArrayDestroy(TempPacket);
TempPacket := nil;
end;
end;
if not (grMetaData in PacketOptions) then
Check(FIDSWriter.Reset);
end;
var
DataPacket: TDataPacket;
begin
CheckMetaData(DataSet);
RecsOut := WriteDataSet(DataSet, FPutFieldInfo, RecsOut);
FIDSWriter.GetDataPacket(DataPacket);
DataPacketToVariant(DataPacket, Data);
end;
{ TPacketDataSet }
constructor TPacketDataSet.Create(AOwner: TComponent);
begin
inherited;
FetchOnDemand := False;
end;
procedure TPacketDataSet.CreateFromDelta(Source: TPacketDataSet);
var
TempBase: IDSBase;
begin
Source.Check(Source.DSBase.Clone(2, True, False, TempBase));
DSBase := TempBase;
Open;
end;
procedure TPacketDataSet.InternalInitRecord(Buffer: PChar);
var
I: Integer;
begin
inherited InternalInitRecord(Buffer);
{ Initialize new records in the error result dataset to unchanged values }
for I := 1 to FieldCount do
DSBase.PutBlank(PByte(Buffer), 0, I, BLANK_NOTCHANGED);
end;
procedure TPacketDataSet.InternalOpen;
var
MDSem: DWord;
begin
inherited InternalOpen;
FOldRecBuf := AllocRecordBuffer;
FCurRecBuf := AllocRecordBuffer;
DSBase.GetProp(dspropMD_SEMANTICS, @MDSem);
MDSem := MDSem and mdCASCADEMOD;
DSBase.SetProp(dspropMD_SEMANTICS, MDSem);
end;
procedure TPacketDataSet.InternalClose;
begin
inherited InternalClose;
FreeRecordBuffer(FOldRecBuf);
FreeRecordBuffer(FCurRecBuf);
end;
function TPacketDataSet.GetStateFieldValue(State: TDataSetState; Field: TField): Variant;
begin
{ When reading an OldValue, return the CurValue instead if we have one }
if FUseCurValues and (State = dsOldValue) and HasCurValues then
begin
Result := inherited GetStateFieldValue(dsCurValue, Field);
if not VarIsEmpty(Result) then Exit;
end;
Result := inherited GetStateFieldValue(State, Field);
end;
function TPacketDataSet.GetStreamMetaData: Boolean;
var
Value: Integer;
begin
DSBase.GetProp(DSProp(dspropDONTINCLMETADATA), @Value);
Result := Value <> 0;
end;
procedure TPacketDataSet.SetStreamMetaData(Value: Boolean);
begin
DSBase.SetProp(DSProp(dspropDONTINCLMETADATA), Integer(not Value));
end;
function TPacketDataSet.UpdateKind: TUpdateKind;
begin
case UpdateStatus of
usInserted: Result := ukInsert;
usDeleted: Result := ukDelete;
else
Result := ukModify;
end;
end;
procedure TPacketDataSet.DataEvent(Event: TDataEvent; Info: Integer);
begin
if Event in [deDataSetScroll, deDataSetChange] then
begin
FNewValuesModified := False;
FCurValues := nil;
end;
inherited DataEvent(Event, Info);
end;
function TPacketDataSet.HasCurValues: Boolean;
begin
Result := FCurValues <> nil;
end;
procedure TPacketDataSet.InitAltRecBuffers(CheckModified: Boolean);
var
No: Integer;
begin
if UpdateStatus in [usUnmodified, usDeleted] then
GetCurrentRecord(FOldRecBuf);
if CheckModified and (UpdateStatus = usUnmodified) then
begin
No := RecNo;
Next;
if UpdateStatus <> usModified then
RecNo := No;
end;
if UpdateStatus = usInserted then
SetAltRecBuffers(ActiveBuffer, ActiveBuffer, FCurRecBuf) else
SetAltRecBuffers(FOldRecBuf, ActiveBuffer, FCurRecBuf);
end;
procedure TPacketDataSet.SetFieldData(Field: TField; Buffer: Pointer);
begin
{ Set a flag when any of the field's NewValue properties are modified }
if State = dsNewValue then
FNewValuesModified := True;
if FWritingCurValues then
Check(DSCursor.PutField(FCurRecBuf, Field.FieldNo, Buffer)) else
inherited SetFieldData(Field, Buffer);
end;
procedure TPacketDataSet.SetWritingCurValues(const Value: Boolean);
begin
if Value then
begin
FCurValues := FCurRecBuf;
InitRecord(FCurValues);
end else
InitAltRecBuffers;
FWritingCurValues := Value;
end;
procedure TPacketDataSet.AssignCurValues(Source: TDataSet);
var
I: Integer;
NewValue: Variant;
Field, SourceField: TField;
begin
WritingCurValues := True;
try
for i := 0 to FieldCount - 1 do
begin
Field := Fields[i];
SourceField := Source.FindField(Field.FieldName);
if (SourceField <> nil) and not Field.IsBlob and
not (Field.DataType in [ftBytes, ftVarBytes]) and
(Field.OldValue <> SourceField.Value) then
begin
NewValue := Field.NewValue;
if VarIsEmpty(Field.NewValue) or
(NewValue <> SourceField.Value) then
Field.Assign(SourceField);
end;
end;
finally
WritingCurValues := False;
end;
end;
procedure TPacketDataSet.AssignCurValues(const CurValues: Variant);
var
I: Integer;
Field: TField;
CurValue: Variant;
begin
WritingCurValues := True;
try
if VarIsNull(CurValues) then
FCurValues := nil
else
for I := VarArrayLowBound(CurValues, 1) to VarArrayHighBound(CurValues, 1) do
begin
if VarIsArray(CurValues[I]) then
begin
CurValue := CurValues[I][1];
Field := FieldByName(CurValues[I][0])
end else
begin
CurValue := CurValues[I];
Field := Fields[I];
end;
if not VarIsEmpty(CurValue) then
if (Field.OldValue <> CurValue) then
Fields[I].Value := CurValue;
end;
finally
WritingCurValues := False;
end;
end;
function TPacketDataSet.HasMergeConflicts: Boolean;
var
I: Integer;
CurVal, NewVal: Variant;
begin
Result := False;
for I := 0 to FieldCount - 1 do
with Fields[I] do
begin
CurVal := CurValue;
if VarIsEmpty(CurVal) then Continue;
NewVal := NewValue;
if VarIsEmpty(NewVal) then Continue;
if CurVal = NewVal then Continue;
Result := True;
Break;
end;
end;
{ TCustomProvider }
constructor TCustomProvider.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FExported := True;
if AOwner is TRemoteDataModule then
TRemoteDataModule(AOwner).RegisterProvider(Self);
end;
destructor TCustomProvider.Destroy;
begin
if Owner is TRemoteDataModule then
TRemoteDataModule(Owner).UnRegisterProvider(Self);
inherited Destroy;
end;
function TCustomProvider.GetData: OleVariant;
var
Recs: Integer;
Options: TGetRecordOptions;
begin
Options := [grMetaData];
Result := GetRecords(-1, Recs, Byte(Options));
end;
function TCustomProvider.ApplyUpdates(Const Delta: OleVariant; MaxErrors: Integer;
out ErrorCount: Integer): OleVariant;
var
OwnerData: OleVariant;
begin
ApplyUpdates(Delta, MaxErrors, ErrorCount, OwnerData);
end;
function TCustomProvider.ApplyUpdates(const Delta: OleVariant; MaxErrors: Integer;
out ErrorCount: Integer; var OwnerData: OleVariant): OleVariant;
begin
if Assigned(FBeforeApplyUpdates) then FBeforeApplyUpdates(Self, OwnerData);
Result := InternalApplyUpdates(Delta, MaxErrors, ErrorCount);
if Assigned(FAfterApplyUpdates) then FAfterApplyUpdates(Self, OwnerData);
end;
procedure TCustomProvider.DoBeforeGetRecords(Count: Integer; Options: Integer;
const CommandText: WideString; var Params, OwnerData: OleVariant);
begin
if Assigned(FBeforeGetRecords) then FBeforeGetRecords(Self, OwnerData);
end;
function TCustomProvider.GetRecords(Count: Integer; out RecsOut: Integer; Options: Integer): OleVariant;
var
Params, OwnerData: OleVariant;
begin
Result := GetRecords(Count, RecsOut, Options, '', Params, OwnerData);
end;
function TCustomProvider.GetRecords(Count: Integer; out RecsOut: Integer; Options: Integer;
const CommandText: WideString; var Params, OwnerData: OleVariant): OleVariant;
begin
DoBeforeGetRecords(Count, Options, CommandText, Params, OwnerData);
Result := InternalGetRecords(Count, RecsOut, TGetRecordOptions(Byte(Options)),
CommandText, Params);
if Assigned(FAfterGetRecords) then FAfterGetRecords(Self, OwnerData);
Params := InternalGetParams([ptOutput, ptInputOutput]);
end;
function TCustomProvider.RowRequest(const Row: OleVariant; RequestType: Integer;
var OwnerData: OleVariant): OleVariant;
begin
if Assigned(FBeforeRowRequest) then FBeforeRowRequest(Self, OwnerData);
Result := InternalRowRequest(Row, TFetchOptions(Byte(RequestType)));
if Assigned(FAfterRowRequest) then FAfterRowRequest(Self, OwnerData);
end;
procedure TCustomProvider.DoBeforeExecute(const CommandText: WideString;
var Params, OwnerData: OleVariant);
begin
if Assigned(FBeforeExecute) then FBeforeExecute(Self, OwnerData);
end;
procedure TCustomProvider.Execute(const CommandText: WideString;
var Params, OwnerData: OleVariant);
begin
DoBeforeExecute(CommandText, Params, OwnerData);
InternalExecute(CommandText, Params);
if Assigned(FAfterExecute) then FAfterExecute(Self, OwnerData);
Params := InternalGetParams([ptOutput, ptInputOutput]);
end;
function TCustomProvider.GetParams(var OwnerData: OleVariant): OleVariant;
begin
if Assigned(FBeforeGetParams) then FBeforeGetParams(Self, OwnerData);
Result := InternalGetParams;
if Assigned(FAfterGetParams) then FAfterGetParams(Self, OwnerData);
end;
function TCustomProvider.InternalGetParams(Types: TParamTypes = AllParamTypes): OleVariant;
begin
Result := NULL;
end;
procedure TCustomProvider.InternalExecute(const CommandText: WideString; var Params: OleVariant);
begin
end;
function TCustomProvider.InternalGetRecords(Count: Integer; out RecsOut: Integer;
Options: TGetRecordOptions; const CommandText: WideString;
var Params: OleVariant): OleVariant;
begin
Result := NULL;
end;
function TCustomProvider.InternalRowRequest(const Row: OleVariant; RequestType: TFetchOptions): OleVariant;
begin
Result := NULL;
end;
function TCustomProvider.DataRequest(Input: OleVariant): OleVariant;
begin
if Assigned(FOnDataRequest) then
Result := FOnDataRequest(Self, Input) else
Result := NULL;
end;
{ TBaseProvider }
constructor TBaseProvider.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FProviderOptions := [];
end;
destructor TBaseProvider.Destroy;
begin
FResolver.Free;
inherited Destroy;
end;
procedure TBaseProvider.LocateRecord(Source, Delta: TDataSet);
begin
end;
procedure TBaseProvider.UpdateRecord(Source, Delta: TDataSet; BlobsOnly, KeyOnly: Boolean);
begin
end;
procedure TBaseProvider.FetchDetails(Source, Delta: TDataSet);
begin
end;
procedure TBaseProvider.CheckResolver;
begin
if not Assigned(FResolver) then
FResolver := CreateResolver;
end;
procedure TBaseProvider.FreeResolver;
begin
FResolver.Free;
FResolver := nil;
end;
function TBaseProvider.InternalApplyUpdates(const Delta: OleVariant; MaxErrors: Integer;
out ErrorCount: Integer): OleVariant;
begin
if poReadOnly in Options then DatabaseError(SReadOnlyProvider);
CheckResolver;
Result := Resolver.ApplyUpdates(Delta, MaxErrors, ErrorCount);
end;
function TBaseProvider.InternalRowRequest(const Row: OleVariant; RequestType: TFetchOptions): OleVariant;
begin
CheckResolver;
Result := Resolver.RowRequest(Row, RequestType);
end;
function TBaseProvider.InternalGetRecords(Count: Integer; out RecsOut: Integer;
Options: TGetRecordOptions; const CommandText: WideString;
var Params: OleVariant): OleVariant;
begin
if (Count = 0) then
Include(Options, grMetaData);
RecsOut := Count;
CreateDataPacket(Options, Self.Options, RecsOut, Result);
DoOnGetData(Result);
end;
procedure TBaseProvider.DoOnGetData(var Data: OleVariant);
begin
if Assigned(OnGetData) then
begin
if not Assigned(FDataDS) then
FDataDS := TPacketDataSet.Create(Self) else
FDataDS.StreamMetaData := False;
FDataDS.AppendData(Data, False);
OnGetData(Self, FDataDS);
if FDataDS.ChangeCount > 0 then
begin
FDataDS.MergeChangeLog;
Data := FDataDS.Data;
end;
FDataDS.EmptyDataSet;
end;
end;
procedure TBaseProvider.DoOnUpdateData(Delta: TPacketDataSet);
begin
if Assigned(FOnUpdateData) then
begin
Delta.LogChanges := False;
FOnUpdateData(Self, Delta);
end;
end;
function TBaseProvider.CreateResolver: TCustomResolver;
begin
Result := nil;
end;
procedure TBaseProvider.CreateDataPacket(PacketOpts: TGetRecordOptions;
ProvOpts: TProviderOptions; var RecsOut: Integer; var Data: OleVariant);
begin
RecsOut := 0;
Data := NULL;
end;
{ TDataSetProvider }
constructor TDataSetProvider.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FResolveToDataSet := False;
FUpdateMode := upWhereAll;
FDSWriter := nil;
FConstraints := True;
FRecordsSent := 0;
end;
destructor TDataSetProvider.Destroy;
begin
FDSWriter.Free;
if Assigned(FParams) then
FParams.Free;
inherited Destroy;
end;
procedure TDataSetProvider.LocateRecord(Source, Delta: TDataSet);
begin
if not FindRecord(Source, Delta, UpdateMode) then
DatabaseError(SRecordChanged);
end;
function TDataSetProvider.FindRecord(Source, Delta: TDataSet;
UpdateMode: TUpdateMode): Boolean;
procedure GetFieldList(DataSet: TDataSet; UpdateMode: TUpdateMode; List: TList);
var
i: Integer;
begin
for i := 0 to DataSet.FieldCount - 1 do
with DataSet.Fields[i] do
begin
if (DataType in [ftBytes, ftVarBytes]) or IsBlob or
(DataSet.Fields[i] is TObjectField) then continue;
case UpdateMode of
upWhereKeyOnly:
if pfInKey in ProviderFlags then List.Add(DataSet.Fields[i]);
upWhereAll:
if pfInWhere in ProviderFlags then List.Add(DataSet.Fields[i]);
upWhereChanged:
if (pfInKey in ProviderFlags) or (not VarIsEmpty(NewValue)) then
List.Add(DataSet.Fields[i]);
end;
end;
end;
var
i: Integer;
KeyValues: Variant;
Fields: string;
FieldList: TList;
IsDelta: BOOL;
begin
Result := False;
TPacketDataSet(Delta).DSBase.GetProp(dspropISDELTA, @IsDelta);
FieldList := TList.Create;
try
GetFieldList(Delta, UpdateMode, FieldList);
if FieldList.Count > 1 then
begin
KeyValues := VarArrayCreate([0, FieldList.Count - 1], varVariant);
Fields := '';
for i := 0 to FieldList.Count - 1 do
with TField(FieldList[i]) do
begin
if IsDelta then
KeyValues[i] := OldValue else
KeyValues[i] := Value;
if Fields <> '' then Fields := Fields + ';';
Fields := Fields + FieldName;
end;
Result := Source.Locate(Fields, KeyValues, []);
end
else if FieldList.Count = 1 then
begin
with TField(FieldList[0]) do
if IsDelta then
Result := Source.Locate(FieldName, OldValue, []) else
Result := Source.Locate(FieldName, Value, []);
end else
DatabaseError(SNoKeySpecified);
finally
FieldList.Free;
end;
end;
procedure TDataSetProvider.FetchDetails(Source, Delta: TDataSet);
var
i: Integer;
Field: TField;
begin
Source.First;
while not Source.EOF do
begin
Delta.Insert;
for i := 0 to Delta.FieldCount - 1 do
begin
Field := Source.FindField(Delta.Fields[i].FieldName);
if Field <> nil then
Delta.Fields[i].Assign(Field);
end;
Delta.Post;
Source.Next;
end;
end;
procedure TDataSetProvider.UpdateRecord(Source, Delta: TDataSet; BlobsOnly, KeyOnly: Boolean);
var
Field: TField;
i: Integer;
UseUpMode: TUpdateMode;
begin
if KeyOnly then
UseUpMode := upWhereKeyOnly
else
UseUpMode := UpdateMode;
if not FindRecord(Source, Delta, UseUpMode) then
DatabaseError(SRecordChanged);
begin
if not FindRecord(Source, Delta, upWhereKeyOnly) then
DatabaseError(SRecordChanged);
with Delta do
begin
Edit;
for i := 0 to FieldCount - 1 do
begin
Field := Source.FindField(Fields[i].FieldName);
if (Field <> nil) and (not BlobsOnly or (Field.IsBlob and VarIsNull(Fields[i].NewValue))) then
Fields[i].Assign(Field);
end;
Post;
end;
end;
end;
procedure TDataSetProvider.DoBeforeExecute(const CommandText: WideString;
var Params, OwnerData: OleVariant);
begin
SetCommandText(CommandText);
SetParams(Params);
inherited DoBeforeExecute(CommandText, Params, OwnerData);
end;
procedure TDataSetProvider.InternalExecute(const CommandText: WideString;
var Params: OleVariant);
begin
CheckDataSet;
IProviderSupport(DataSet).PSExecute;
end;
procedure TDataSetProvider.DoGetTableName(DataSet: TDataSet; var TableName: string);
begin
if Assigned(OnGetTableName) then
OnGetTableName(Self, DataSet, TableName);
end;
procedure TDataSetProvider.Reset;
begin
CheckDataSet;
if FDataSetOpened then
begin
DataSet.Close;
FDataSetOpened := False;
end;
IProviderSupport(DataSet).PSReset;
if DataSet.Active then
DataSet.First;
FRecordsSent := 0;
end;
procedure TDataSetProvider.SetCommandText(const CommandText: string);
begin
if CommandText = '' then Exit;
if not (poAllowCommandText in Options) then
DatabaseError(SCannotChangeCommandText);
CheckDataSet;
IProviderSupport(DataSet).PSSetCommandText(CommandText);
end;
procedure TDataSetProvider.SetParams(Values: OleVariant);
begin
if VarIsEmpty(Values) then Exit;
CheckDataSet;
if not Assigned(FParams) then
FParams := TParams.Create;
FParams.Clear;
UnpackParams(Values, FParams);
IProviderSupport(DataSet).PSSetParams(FParams);
end;
function TDataSetProvider.InternalGetParams(Types: TParamTypes = AllParamTypes): OleVariant;
var
Params: TParams;
begin
CheckDataSet;
Params := IProviderSupport(DataSet).PSGetParams;
if (Params = nil) or (Params.Count = 0) then
Result := NULL else
Result := PackageParams(Params, Types);
end;
function TDataSetProvider.InternalRowRequest(const Row: OleVariant; Options: TFetchOptions): OleVariant;
begin
CheckResolver;
CheckDataSet;
Resolver.FUpdateTree.InitData(DataSet);
try
if not DataSet.Active then
begin
DataSet.Open;
FDataSetOpened := True;
end;
Result := inherited InternalRowRequest(Row, Options);
finally
Resolver.FUpdateTree.InitData(nil);
if FDataSetOpened then
begin
DataSet.Close;
FDataSetOpened := False;
end;
end;
end;
function TDataSetProvider.InternalApplyUpdates(const Delta: OleVariant; MaxErrors: Integer;
out ErrorCount: Integer): OleVariant;
var
TransactionStarted: Boolean;
begin
CheckDataSet;
TransactionStarted := not IProviderSupport(DataSet).PSInTransaction;
if TransactionStarted then
IProviderSupport(DataSet).PSStartTransaction;
try
CheckResolver;
Resolver.FUpdateTree.InitData(DataSet);
try
Result := inherited InternalApplyUpdates(Delta, MaxErrors, ErrorCount);
finally
Resolver.FUpdateTree.InitData(nil);
end;
finally
if TransactionStarted then
IProviderSupport(DataSet).PSEndTransaction((ErrorCount <= MaxErrors) or (MaxErrors = -1));
end;
end;
procedure TDataSetProvider.SetDataSet(ADataSet: TDataSet);
begin
FDataSet := ADataSet;
end;
procedure TDataSetProvider.SetResolveToDataSet(Value: Boolean);
begin
if (Value <> FResolveToDataSet) and Assigned(Resolver) then
FreeResolver;
FResolveToDataSet := Value;
end;
function TDataSetProvider.CreateResolver: TCustomResolver;
begin
if ResolveToDataSet then
Result := TDataSetResolver.Create(Self) else
Result := TSQLResolver.Create(Self);
end;
procedure TDataSetProvider.CheckDataSet;
begin
if not Assigned(DataSet) then DatabaseError(SMissingDataSet);
end;
procedure TDataSetProvider.DoBeforeGetRecords(Count: Integer; Options: Integer;
const CommandText: WideString; var Params, OwnerData: OleVariant);
begin
SetCommandText(CommandText);
SetParams(Params);
inherited;
end;
function TDataSetProvider.InternalGetRecords(Count: Integer; out RecsOut: Integer;
Options: TGetRecordOptions; const CommandText: WideString;
var Params: OleVariant): OleVariant;
begin
try
if grReset in Options then
begin
Reset;
{ When doing only a reset and not getting more data then exit }
if Count = 0 then Exit;
end;
if not DataSet.Active then
begin
DataSet.Open;
FDataSetOpened := True;
end;
if (Count = 0) or (grMetaData in Options) then
begin
FDataDS.Free;
FDataDS := nil;
FRecordsSent := 0;
end;
DataSet.CheckBrowseMode;
DataSet.BlockReadSize := Count;
try
Result := inherited InternalGetRecords(Count, RecsOut, Options,
CommandText, Params);
Inc(FRecordsSent, RecsOut);
if (RecsOut <> Count) then Reset;
finally
if DataSet.Active then
begin
DataSet.BlockReadSize := 0;
if (Count <> 0) and (RecsOut = Count) then
DataSet.Next;
end;
end;
except
Reset;
raise;
end;
end;
procedure TDataSetProvider.DoGetProviderAttributes(DataSet: TDataSet; List: TList);
var
CustParams: OleVariant;
Attr: PPacketAttribute;
i, j: Integer;
begin
IProviderSupport(DataSet).PSGetAttributes(List);
if Assigned(FGetDSProps) then
begin
FGetDSProps(Self, DataSet, CustParams);
if VarIsArray(CustParams) then
begin
for i := VarArrayLowBound(CustParams, 1) to VarArrayHighBound(CustParams, 1) do
begin
if VarIsArray(CustParams[i]) and
(VarArrayHighBound(CustParams[i], 1) - VarArrayLowBound(CustParams[i], 1) = 2) then
begin
j := VarArrayLowBound(CustParams[i], 1);
New(Attr);
List.Add(Attr);
with Attr^ do
begin
Name := CustParams[i][j];
Value := CustParams[i][j + 1];
IncludeInDelta := CustParams[i][j + 2];
end;
end;
end;
end;
end;
end;
procedure TDataSetProvider.CreateDataPacket(PacketOpts: TGetRecordOptions;
ProvOpts: TProviderOptions; var RecsOut: Integer; var Data: OleVariant);
begin
if not Assigned(FDSWriter) then
FDSWriter := TDataPacketWriter.Create;
FDSWriter.Constraints := Constraints;
FDSWriter.OnGetParams := DoGetProviderAttributes;
FDSWriter.PacketOptions := PacketOpts;
FDSWriter.Options := ProvOpts;
FDSWriter.GetDataPacket(DataSet, RecsOut, Data);
end;
procedure TDataSetProvider.Notification(AComponent: TComponent; Operation: TOperation);
begin
inherited Notification(AComponent, Operation);
if (Operation = opRemove) and (FDataSet <> nil) and
(AComponent = FDataSet) then FDataSet := nil;
end;
{ TUpdateTree }
constructor TUpdateTree.Create(AParent: TUpdateTree; AResolver: TCustomResolver);
begin
FResolver := AResolver;
FParent := AParent;
FDeltaDS := TPacketDataSet.Create(nil);
FDeltaDS.ObjectView := True;
FDeltaDS.FieldDefs.HiddenFields := True;
FDetails := TList.Create;
FName := '';
end;
destructor TUpdateTree.Destroy;
begin
if Assigned(FResolver) then
FResolver.FreeTreeData(Self);
Clear;
FDetails.Free;
if not Assigned(Parent) then
FDeltaDS.Free;
inherited Destroy;
end;
function TUpdateTree.GetIsNested: Boolean;
begin
Result := Assigned(Source) and Assigned(Source.DataSetField);
end;
procedure TUpdateTree.Clear;
var
i: Integer;
begin
for i := 0 to DetailCount - 1 do
Details[i].Free;
FDetails.Clear;
FDeltaDS.Data := NULL;
if not Assigned(Parent) then
begin
FErrorDS.Free;
FErrorDS := nil;
end;
end;
function TUpdateTree.GetTree(const AName: string): TUpdateTree;
var
i: Integer;
begin
for i := 0 to DetailCount - 1 do
if AnsiCompareText(Details[i].Name, AName) = 0 then
begin
Result := Details[i];
Exit;
end;
Result := TUpdateTree.Create(Self, FResolver);
Result.Name := AName;
FDetails.Add(Result);
end;
procedure TUpdateTree.InitData(ASource: TDataSet);
var
i: Integer;
Tree: TUpdateTree;
List: TList;
begin
if ASource = nil then
begin
for i := 0 to FDetails.Count - 1 do
TUpdateTree(FDetails[i]).InitData(nil);
if FOpened then FSourceDS.Close;
FOpened := False;
end else
begin
FSourceDS := ASource;
FOpened := (FSourceDS.FieldCount = 0) and FSourceDS.ObjectView;
if FOpened then FSourceDS.Open;
if FSourceDS.ObjectView then
for i := 0 to FSourceDS.FieldCount - 1 do
if FSourceDS.Fields[i].DataType in [ftDataSet] then
with TDataSetField(FSourceDS.Fields[i]) do
begin
Tree := GetTree(FSourceDS.Fields[i].FieldName);
Tree.InitData(NestedDataSet);
end;
List := TList.Create;
try
FSourceDS.GetDetailDataSets(List);
for i := 0 to List.Count - 1 do
begin
Tree := GetTree(TDataSet(List[i]).Name);
Tree.InitData(TDataSet(List[i]));
end;
finally
List.Free;
end;
end;
end;
procedure TUpdateTree.InitDelta(ADelta: TPacketDataSet);
var
i: Integer;
Attr: Variant;
KeySet: Boolean;
Tree: TUpdateTree;
FieldInfo: TFieldInfo;
begin
if (FDeltaDS <> nil) and (FDeltaDS <> ADelta) then
FDeltaDS.Free;
FDeltaDS := ADelta;
FDeltaDS.LogChanges := False;
KeySet := False;
for i := 0 to FDeltaDS.FieldCount - 1 do
begin
Attr := FDeltaDS.InternalGetOptionalParam(szPROVFLAGS, FDeltaDS.Fields[i].FieldNo);
if not (VarIsNull(Attr) or VarIsEmpty(Attr)) then
FDeltaDS.Fields[i].ProviderFlags := TProviderFlags(Byte(Attr));
Attr := FDeltaDS.InternalGetOptionalParam(szORIGIN, FDeltaDS.Fields[i].FieldNo);
if not (VarIsNull(Attr) or VarIsEmpty(Attr)) then
FDeltaDS.Fields[i].Origin := Attr;
Attr := FDeltaDS.InternalGetOptionalParam(szSERVERCALC, FDeltaDS.Fields[i].FieldNo);
if not (VarIsEmpty(Attr) or VarIsNull(Attr)) and
(VarType(Attr) = varBoolean) and Boolean(Attr) then
FDeltaDS.Fields[i].Tag := tagSERVERCALC;
if GetFieldInfo(FDeltaDS.Fields[i].Origin, FieldInfo) then
FDeltaDS.Fields[i].Origin := FieldInfo.OriginalFieldName else
FDeltaDS.Fields[i].Origin := FDeltaDS.Fields[i].FieldName;
if pfInKey in FDeltaDS.Fields[i].ProviderFlags then
KeySet := True;
if Delta.Fields[i].DataType = ftDataSet then
with TDataSetField(Delta.Fields[i]) do
begin
Tree := GetTree(Delta.Fields[i].FieldName);
Tree.InitDelta(TPacketDataSet(NestedDataSet));
end;
end;
FResolver.InitTreeData(Self);
if not KeySet then
FResolver.InitKeyFields(Self, FDeltaDS);
end;
procedure TUpdateTree.InitDelta(const ADelta: OleVariant);
begin
if FDeltaDS.Active then Clear;
FDeltaDS.Data := ADelta;
InitDelta(FDeltaDS);
end;
function TUpdateTree.GetDetailCount: Integer;
begin
Result := FDetails.Count;
end;
function TUpdateTree.GetDetail(Index: Integer): TUpdateTree;
begin
Result := TUpdateTree(FDetails[Index]);
end;
procedure TUpdateTree.RefreshData(Options: TFetchOptions);
function NeedsUpdate(DataSet: TDataSet): Boolean;
var
i: Integer;
Field: TField;
begin
Result := False;
if DataSet.RecordCount = 0 then Exit;
for i := 0 to DataSet.FieldCount - 1 do
begin
Field := DataSet.Fields[i];
Result := (Field is TDataSetField) and
(VarIsNull(Field.NewValue) or
NeedsUpdate(TDataSetField(Field).NestedDataSet));
if Result then Exit;
end;
end;
var
i: Integer;
Tree: TUpdateTree;
Field: TField;
Updated: Boolean;
begin
Updated := False;
if (foRecord in Options) and (Delta.RecordCount > 0) then
begin
Updated := True;
FResolver.Provider.UpdateRecord(Source, Delta, False, True);
end;
for i := 0 to Delta.FieldCount - 1 do
begin
Field := Delta.Fields[i];
if (not Updated) and (foBlobs in Options) and Field.IsBlob and
VarIsNull(Field.NewValue) then
begin
Updated := True;
FResolver.Provider.UpdateRecord(Source, Delta, True, False);
end;
if (Field is TDataSetField) then
begin
if not Updated then
FResolver.Provider.LocateRecord(Source, Delta);
Tree := GetTree(Field.FieldName);
if Assigned(Tree) then
begin
if not VarIsNull(Field.NewValue) then
begin
if Tree.Delta.RecordCount > 0 then
Tree.RefreshData(Options);
end else
FResolver.Provider.FetchDetails(Tree.Source, Tree.Delta);
end;
end;
end;
end;
function TUpdateTree.DoUpdates: Boolean;
var
i: Integer;
begin
Result := True;
Delta.First;
while not Delta.EOF do
begin
Delta.InitAltRecBuffers(False);
FResolver.InternalBeforeResolve(Self);
if (Delta.UpdateStatus = usInserted) then
begin
Result := FResolver.InternalUpdateRecord(Self);
if not Result then Exit;
end;
for i := 0 to DetailCount - 1 do
begin
Result := Details[i].DoUpdates;
if not Result then Exit;
end;
if Delta.UpdateStatus = usUnmodified then
Delta.InitAltRecBuffers(True);
if (Delta.UpdateStatus = usModified) then
Result := FResolver.InternalUpdateRecord(Self);
if (Delta.UpdateStatus = usDeleted) then
Result := FResolver.InternalUpdateRecord(Self);
if not Result then Exit;
Delta.Next;
end;
end;
function TUpdateTree.GetErrorDS: TPacketDataSet;
var
Field: TField;
begin
if not Assigned(FErrorDS) then
begin
if not Assigned(Parent) then
begin
FErrorDS := TPacketDataSet.Create(nil);
FErrorDS.ObjectView := True;
FErrorDS.CreateFromDelta(Delta);
end else
begin
Field := Parent.ErrorDS.FieldByName(Delta.DataSetField.FieldName);
FErrorDS := (Field as TDataSetField).NestedDataSet as TPacketDataSet;
end;
FErrorDS.LogChanges := False;
FErrorDS.DSBase.SetProp(DSProp(dspropAUTOINC_DISABLED), Integer(True));
end;
Result := FErrorDS;
end;
function TUpdateTree.GetHasErrors: Boolean;
begin
Result := Assigned(FErrorDS);
end;
procedure TUpdateTree.InitErrorPacket(E: EUpdateError; Response: TResolverResponse);
var
TrueRecNo: DWord;
begin
with ErrorDS do
begin
if Assigned(Parent) then Parent.InitErrorPacket(nil, rrSkip);
Self.Delta.UpdateCursorPos;
Self.Delta.DSCursor.GetRecordNumber(TrueRecNo);
if not Locate('ERROR_RECORDNO', Integer(TrueRecNo), []) then
Append else
Edit;
if not Assigned(E) then
begin
if Response = rrSkip then
begin
SetFields([TrueRecNo]);
Post;
end else
SetFields([TrueRecNo, 0, '', '', 0, 0]);
end else
SetFields([TrueRecNo, Ord(Response)+1, E.Message, '', 1, E.ErrorCode]);
end;
end;
{ TCustomResolver }
constructor TCustomResolver.Create(AProvider: TBaseProvider);
begin
FProvider := AProvider;
FUpdateTree := TUpdateTree.Create(nil, Self);
end;
destructor TCustomResolver.Destroy;
begin
FUpdateTree.Free;
inherited Destroy;
end;
{ Updates }
procedure TCustomResolver.BeginUpdate;
begin
end;
procedure TCustomResolver.EndUpdate;
begin
end;
procedure TCustomResolver.InitKeyFields(Tree: TUpdateTree; ADelta: TPacketDataSet);
var
Pos, i: Integer;
KeyFields, FieldName: string;
begin
KeyFields := IProviderSupport(Tree.Source).PSGetKeyFields;
Pos := 1;
while Pos <= Length(KeyFields) do
begin
FieldName := ExtractFieldName(KeyFields, Pos);
for i := 0 to ADelta.FieldCount - 1 do
if AnsiCompareText(FieldName, ADelta.Fields[i].Origin) = 0 then
begin
ADelta.Fields[i].ProviderFlags := ADelta.Fields[i].ProviderFlags + [pfInKey];
break;
end;
end;
end;
procedure TCustomResolver.InitTreeData(Tree: TUpdateTree);
begin
end;
procedure TCustomResolver.FreeTreeData(Tree: TUpdateTree);
begin
end;
procedure TCustomResolver.InternalBeforeResolve(Tree: TUpdateTree);
begin
end;
function TCustomResolver.InternalUpdateRecord(Tree: TUpdateTree): Boolean;
type
PRaiseFrame = ^TRaiseFrame;
TRaiseFrame = record
NextRaise: PRaiseFrame;
ExceptAddr: Pointer;
ExceptObject: TObject;
ExceptionRecord: PExceptionRecord;
end;
var
RecNoSave: Integer;
Applied: Boolean;
UpdateKind: TUpdateKind;
E: Exception;
PrevErr, Err: EUpdateError;
begin
PrevErr := nil;
Err := nil;
Tree.Delta.UseCurValues := False;
while True do
try
UpdateKind := Tree.Delta.UpdateKind;
if ((UpdateKind = ukInsert) and (FPrevResponse in [rrMerge, rrApply])) or
((FPrevResponse = rrMerge) and Tree.Delta.HasMergeConflicts) then
DatabaseError(SInvalidResponse);
Applied := False;
RecNoSave := Tree.Delta.RecNo;
try
if Assigned(Provider.BeforeUpdateRecord) then
Provider.BeforeUpdateRecord(Provider, Tree.Source, Tree.Delta, UpdateKind, Applied);
finally
if Tree.Delta.RecNo <> RecNoSave then
Tree.Delta.RecNo := RecNoSave;
end;
if not Applied then
case UpdateKind of
ukModify:
begin
if poDisableEdits in Provider.Options then
raise Exception.CreateRes(@SNoEditsAllowed);
DoUpdate(Tree);
end;
ukDelete:
begin
if poDisableDeletes in Provider.Options then
raise Exception.CreateRes(@SNoDeletesAllowed);
DoDelete(Tree);
end;
ukInsert:
begin
if poDisableInserts in Provider.Options then
raise Exception.CreateRes(@SNoInsertsAllowed);
DoInsert(Tree);
end;
end;
if Assigned(Provider.AfterUpdateRecord) then
Provider.AfterUpdateRecord(Provider, Tree.Source, Tree.Delta, UpdateKind);
if (poPropogateChanges in Provider.Options) and Tree.Delta.NewValuesModified then
LogUpdateRecord(Tree);
Break;
except
if RaiseList <> nil then
begin
{ Remove the exception }
E := Exception(PRaiseFrame(RaiseList)^.ExceptObject);
PRaiseFrame(RaiseList)^.ExceptObject := nil;
PrevErr.Free;
PrevErr := Err;
Err := IProviderSupport(Tree.Source).PSGetUpdateException(E, PrevErr);
if HandleUpdateError(Tree, Err, FMaxErrors, FErrorCount) then
begin
Tree.Delta.UseCurValues := True;
Continue;
end else
break;
end;
end;
PrevErr.Free;
Err.Free;
FPrevResponse := rrSkip;
Result := FErrorCount <= FMaxErrors;
end;
function TCustomResolver.RowRequest(Row: OleVariant; Options: TFetchOptions): OleVariant;
begin
BeginUpdate;
try
FUpdateTree.InitDelta(Row);
try
FUpdateTree.RefreshData(Options);
Result := FUpdateTree.Delta.Data;
finally
FUpdateTree.Clear;
end;
finally
EndUpdate;
end;
end;
function TCustomResolver.ApplyUpdates(const Delta: OleVariant; MaxErrors: Integer;
out ErrorCount: Integer): OleVariant;
begin
BeginUpdate;
try
FUpdateTree.InitDelta(Delta);
try
Provider.DoOnUpdateData(FUpdateTree.Delta);
FPrevResponse := rrSkip;
if MaxErrors = -1 then MaxErrors := MaxInt;
FMaxErrors := MaxErrors;
FErrorCount := 0;
FUpdateTree.DoUpdates;
ErrorCount := FErrorCount;
if FUpdateTree.HasErrors then
Result := FUpdateTree.ErrorDS.Data else
Result := Null;
finally
FUpdateTree.Clear;
end;
finally
EndUpdate;
end;
end;
{ Update error handling }
function TCustomResolver.HandleUpdateError(Tree: TUpdateTree;
E: EUpdateError; var MaxErrors, ErrorCount: Integer): Boolean;
var
Response: TResolverResponse;
UpdateKind: TUpdateKind;
begin
UpdateKind := Tree.Delta.UpdateKind;
if ErrorCount < MaxErrors then
Response := rrSkip else
Response := rrAbort;
try
InitializeConflictBuffer(Tree);
except
{ Ignore errors that occur when initializing the conflict buffer }
end;
if Assigned(Provider.OnUpdateError) then
Provider.OnUpdateError(Provider, Tree.Delta, E, UpdateKind, Response);
if Response in [rrSkip, rrAbort] then
begin
Inc(ErrorCount);
if ErrorCount > MaxErrors then
Response := rrAbort;
if (Response = rrAbort) then
MaxErrors := ErrorCount - 1;
if Response in [rrSkip, rrAbort] then
LogUpdateError(Tree, E, Response);
end;
FPrevResponse := Response;
Result := Response in [rrMerge, rrApply];
end;
procedure TCustomResolver.LogUpdateRecord(Tree: TUpdateTree);
var
I: Integer;
CurVal: Variant;
begin
Tree.InitErrorPacket(nil, rrApply);
for I := 0 to Tree.Delta.FieldCount - 1 do
begin
{ Blobs, Bytes and VarBytes are not included in result packet }
if (Tree.Delta.Fields[I].IsBlob) or
(Tree.Delta.Fields[I].DataType in [ftBytes, ftVarBytes]) then
continue;
CurVal := Tree.Delta.Fields[I].NewValue;
if not VarIsEmpty(CurVal) then
Tree.ErrorDS.FieldByName(Tree.Delta.Fields[I].FieldName).Value := CurVal;
end;
Tree.ErrorDS.Post;
end;
procedure TCustomResolver.LogUpdateError(Tree: TUpdateTree;
E: EUpdateError; Response: TResolverResponse);
var
I: Integer;
CurVal: Variant;
begin
Tree.InitErrorPacket(E, Response);
if Tree.Delta.HasCurValues then
for I := 0 to Tree.Delta.FieldCount - 1 do
begin
{ Blobs, Bytes and VarBytes are not included in result packet }
if (Tree.Delta.Fields[I].IsBlob) or
(Tree.Delta.Fields[I].DataType in [ftBytes, ftVarBytes]) then
continue;
CurVal := Tree.Delta.Fields[I].CurValue;
if not VarIsEmpty(CurVal) then
Tree.ErrorDS.FieldByName(Tree.Delta.Fields[I].FieldName).Value := CurVal;
end;
Tree.ErrorDS.Post;
end;
{ TDataSetResolver }
constructor TDataSetResolver.Create(AProvider: TDataSetProvider);
begin
inherited Create(AProvider);
FOpened := False;
end;
function TDataSetResolver.GetProvider: TDataSetProvider;
begin
Result := TDataSetProvider(inherited Provider);
end;
procedure TDataSetResolver.BeginUpdate;
begin
FOpened := not Provider.DataSet.Active;
if FOpened then
begin
Provider.DataSet.Open;
FBookmark := '';
end else
FBookmark := Provider.DataSet.Bookmark;
end;
procedure TDataSetResolver.EndUpdate;
begin
if FOpened then
begin
Provider.DataSet.Close;
FOpened := False;
end else
begin
if (Length(FBookmark) > 0) and
Provider.DataSet.BookmarkValid(@FBookmark[1]) then
Provider.DataSet.Bookmark := FBookmark;
end;
end;
procedure TDataSetResolver.InitializeConflictBuffer(Tree: TUpdateTree);
begin
{ Set the conflict buffer to the current values of the data }
if Provider.FindRecord(Tree.Source, Tree.Delta, upWhereKeyOnly) then
Tree.Delta.AssignCurValues(Tree.Source);
end;
procedure TDataSetResolver.InternalBeforeResolve(Tree: TUpdateTree);
begin
Provider.FindRecord(Tree.Source, Tree.Delta, Provider.UpdateMode);
end;
procedure TDataSetResolver.PutRecord(Tree: TUpdateTree);
procedure PutField(Src, Dest: TField); forward;
procedure PutObjectField(Src, Dest: TObjectField);
var
i: Integer;
begin
if VarIsNull(Src.NewValue) then
Dest.Clear else
for i := 0 to Src.FieldCount - 1 do
if (not VarIsEmpty(Src.Fields[i].NewValue)) and
(pfInUpdate in Src.Fields[i].ProviderFlags) then
PutField(Src.Fields[i], Dest.Fields[i]);
end;
procedure PutField(Src, Dest: TField);
begin
if (Src.DataType in [ftArray, ftADT]) then
PutObjectField(TObjectField(Src), TObjectField(Dest)) else
if (Src.DataType in [ftDataSet, ftReference]) then
raise Exception.CreateRes(@SNoDataSets) else
if (not VarIsEmpty(Src.NewValue)) and
(pfInUpdate in Src.ProviderFlags) then
Dest.Assign(Src);
end;
var
i: Integer;
Field: TField;
begin
with Tree do
try
for i := 0 to Delta.FieldCount - 1 do
begin
Field := Source.FindField(Delta.Fields[i].FieldName);
if (Field <> nil) then
PutField(Delta.Fields[i], Field);
end;
Source.Post;
except
Source.Cancel;
raise;
end;
end;
procedure TDataSetResolver.DoUpdate(Tree: TUpdateTree);
begin
with Tree do
begin
if not Provider.FindRecord(Source, Delta, Provider.UpdateMode) then
DatabaseError(SRecordChanged);
Source.Edit;
PutRecord(Tree);
end;
end;
procedure TDataSetResolver.DoDelete(Tree: TUpdateTree);
begin
with Tree do
begin
if Provider.FindRecord(Tree.Source, Tree.Delta, Provider.UpdateMode) then
Source.Delete else
DatabaseError(SRecordChanged);
end;
end;
procedure TDataSetResolver.DoInsert(Tree: TUpdateTree);
begin
Tree.Source.Append;
PutRecord(Tree);
end;
{ TSQLResolver }
type
PSQLInfo = ^TSQLInfo;
TSQLInfo = record
IsSQLBased: Boolean;
QuoteChar: string;
QuotedTable: string;
QuotedTableDot: string;
Opened: Boolean;
HasObjects: Boolean;
end;
constructor TSQLResolver.Create(AProvider: TDataSetProvider);
begin
inherited Create(AProvider);
FSQL := TStringList.Create;
FParams := TParams.Create(nil);
end;
destructor TSQLResolver.Destroy;
begin
FSQL.Free;
FParams.Free;
inherited Destroy;
end;
function TSQLResolver.GetProvider: TDataSetProvider;
begin
Result := TDataSetProvider(inherited Provider);
end;
procedure TSQLResolver.InitTreeData(Tree: TUpdateTree);
function GetQuotedTableName(SQLBased: Boolean;
const QuoteChar, TableName: string): string;
var
DotPos: Integer;
begin
Result := '';
if Length(TableName) > 0 then
begin
if TableName[1] in ['''','"','`'] then
Result := TableName else
begin
if SQLBased then
begin
Result := TableName;
DotPos := Pos('.', Result);
if DotPos <> 0 then
begin
System.Insert(QuoteChar, Result, DotPos + 1);
System.Insert(QuoteChar, Result, DotPos);
end;
Result := QuoteChar + Result + QuoteChar;
end else
Result := QuoteChar + TableName + QuoteChar;
end;
end;
end;
var
Info: PSQLInfo;
i: Integer;
TableName: string;
begin
if Tree.Data <> nil then
Dispose(PSQLInfo(Tree.Data));
New(Info);
Tree.Data := Info;
Info.IsSQLBased := IProviderSupport(Tree.Source).PSIsSQLBased;
Info.QuoteChar := IProviderSupport(Tree.Source).PSGetQuoteChar;
TableName := VarToStr(Tree.Delta.GetOptionalParam(szTABLE_NAME));
if TableName = '' then
TableName := IProviderSupport(Tree.Source).PSGetTableName;
Provider.DoGetTableName(Tree.Source, TableName);
if TableName <> '' then
Info.QuotedTable := GetQuotedTableName(Info.IsSQLBased, Info.QuoteChar, TableName);
if Info.IsSQLBased then
Info.QuotedTableDot := '' else
Info.QuotedTableDot := Info.QuotedTable + '.';
Info.HasObjects := False;
for i := 0 to Tree.Delta.FieldCount - 1 do
if (Tree.Delta.Fields[i] is TObjectField) and
(TObjectField(Tree.Delta.Fields[i]).ObjectType <> '') then
begin
Info.HasObjects := True;
break;
end;
end;
procedure TSQLResolver.FreeTreeData(Tree: TUpdateTree);
begin
Dispose(PSQLInfo(Tree.Data));
Tree.Data := nil;
end;
procedure TSQLResolver.DoExecSQL(SQL: TStringList; Params: TParams);
var
RowsAffected: Integer;
begin
RowsAffected := IProviderSupport(Provider.DataSet).PSExecuteStatement(SQL.Text, Params);
if not (poAllowMultiRecordUpdates in Provider.Options) and (RowsAffected > 1) then
DatabaseError(STooManyRecordsModified);
if RowsAffected < 1 then
DatabaseError(SRecordChanged);
end;
procedure TSQLResolver.DoGetValues(SQL: TStringList; Params: TParams;
DataSet: TDataSet);
var
DS: TDataSet;
begin
DS := nil;
IProviderSupport(Provider.DataSet).PSExecuteStatement(SQL.Text, Params, @DS);
if Assigned(DS) then
try
TPacketDataSet(DataSet).AssignCurValues(DS)
finally
DS.Free;
end;
end;
procedure TSQLResolver.InitializeConflictBuffer(Tree: TUpdateTree);
var
Alias: string;
begin
if PSQLInfo(Tree.Data)^.HasObjects then Alias := DefAlias else Alias := '';
FSQL.Clear;
FParams.Clear;
GenSelectSQL(Tree, FSQL, FParams, Alias);
DoGetValues(FSQL, FParams, Tree.Delta);
end;
procedure TSQLResolver.InternalDoUpdate(Tree: TUpdateTree; UpdateKind: TUpdateKind);
var
Alias: string;
begin
if not IProviderSupport(Tree.Source).PSUpdateRecord(UpdateKind, Tree.Delta) then
begin
if (PSQLInfo(Tree.Data)^.QuotedTable = '') and not Tree.IsNested then
DatabaseError(SNoTableName);
if PSQLInfo(Tree.Data)^.HasObjects then Alias := DefAlias else Alias := '';
FSQL.Clear;
FParams.Clear;
case UpdateKind of
ukModify: GenUpdateSQL(Tree, FSQL, FParams, Alias);
ukInsert: GenInsertSQL(Tree, FSQL, FParams);
ukDelete: GenDeleteSQL(Tree, FSQL, FParams, Alias);
end;
DoExecSQL(FSQL, FParams);
end;
end;
procedure TSQLResolver.DoUpdate(Tree: TUpdateTree);
begin
InternalDoUpdate(Tree, ukModify);
end;
procedure TSQLResolver.DoDelete(Tree: TUpdateTree);
begin
InternalDoUpdate(Tree, ukDelete);
end;
procedure TSQLResolver.DoInsert(Tree: TUpdateTree);
begin
InternalDoUpdate(Tree, ukInsert);
end;
{ SQL generation }
function QuoteFullName(const FullName, QuoteChar: string): string;
var
i: Integer;
p: PChar;
begin
if (Length(FullName) > 1) and (FullName[1] in [#0, #1]) then
p := @FullName[2] else
p := PChar(FullName);
Result := Format('%s%s%0:s',[QuoteChar, p]);
for i := Length(Result) downto 1 do
if Result[i] = '.' then
begin
System.Insert(QuoteChar, Result, i + 1);
System.Insert(QuoteChar, Result, i);
end;
end;
function TSQLResolver.UseFieldInUpdate(Field: TField): Boolean;
const
ExcludedTypes = [ftAutoInc, ftDataSet, ftADT, ftArray, ftReference, ftCursor, ftUnknown];
begin
with Field do
begin
Result := (pfInUpdate in ProviderFlags) and not (DataType in ExcludedTypes) and
not ReadOnly and (FieldKind = fkData) and not (pfHidden in ProviderFlags) and
not VarIsEmpty(NewValue) and (Tag <> tagSERVERCALC);
end;
end;
function TSQLResolver.UseFieldInWhere(Field: TField; Mode: TUpdateMode): Boolean;
const
ExcludedTypes = [ftDataSet, ftADT, ftArray, ftReference, ftCursor, ftUnknown];
begin
with Field do
begin
Result := not (DataType in ExcludedTypes) and not IsBlob and
(FieldKind = fkData) and (Tag <> tagSERVERCALC);
if Result then
case Mode of
upWhereAll:
Result := pfInWhere in ProviderFlags;
upWhereChanged:
Result := ((pfInWhere in ProviderFlags) and not VarIsEmpty(NewValue)) or
(pfInKey in ProviderFlags);
upWhereKeyOnly:
Result := pfInKey in ProviderFlags;
end;
end;
end;
procedure TSQLResolver.GenWhereSQL(Tree: TUpdateTree; SQL: TStrings; Params: TParams;
GenUpdateMode: TUpdateMode; Alias: string);
function AddField(Field: TField; InObject: Boolean): Boolean;
var
i: Integer;
BindText: string;
begin
Result := False;
with PSQLInfo(Tree.Data)^ do
begin
if Field.DataType = ftADT then
begin
for i := 0 to TObjectField(Field).FieldCount - 1 do
if AddField(TObjectField(Field).Fields[i], True) then
Result := True;
end else
if UseFieldInWhere(Field, GenUpdateMode) then
begin
Result := True;
if InObject then
begin
if VarIsNull(Field.OldValue) then
BindText := Format(' %s.%s is null and', [Alias,
QuoteFullName(Field.FullName, QuoteChar)])
else
begin
BindText := Format(' %s.%s = ? and',[Alias,
QuoteFullName(Field.FullName, QuoteChar)]);
TParam(Params.Add).AssignFieldValue(Field, Field.OldValue);
end;
end else
begin
if VarIsNull(Field.OldValue) or (not IsSQLBased and
(Field.DataType = ftString) and (Length(Field.OldValue) = 0)) then
BindText := Format(' %s%s%s%1:s is null and', [PSQLInfo(Tree.Data)^.QuotedTableDot,
QuoteChar, Field.Origin])
else
begin
BindText := Format(' %s%s%s%1:s = ? and',
[PSQLInfo(Tree.Data)^.QuotedTableDot, QuoteChar, Field.Origin]);
TParam(Params.Add).AssignFieldValue(Field, Field.OldValue);
end;
end;
SQL.Add(BindText);
end;
end;
end;
var
I: Integer;
TempStr: string;
Added: Boolean;
begin
with PSQLInfo(Tree.Data)^ do
begin
SQL.Add('where');
Added := False;
for I := 0 to Tree.Delta.FieldCount - 1 do
if AddField(Tree.Delta.Fields[I], Alias = NestAlias) then
Added := True;
if not Added then
DatabaseError(SNoKeySpecified);
{ Remove last ' and'}
TempStr := SQL[SQL.Count-1];
SQL[SQL.Count-1] := Copy(TempStr, 1, Length(TempStr) - 4);
end;
end;
procedure TSQLResolver.GenInsertSQL(Tree: TUpdateTree; SQL: TStrings;
Params: TParams);
procedure AddField(Field: TField; var FieldLine, ParamLine: string);
var
i: Integer;
TempStr: string;
Value: Variant;
begin
with PSQLInfo(Tree.Data)^ do
begin
if Field.DataType in [ftADT, ftArray] then
begin
FieldLine := Format('%s%s%s%s%2:s, ', [FieldLine, PSQLInfo(Tree.Data)^.QuotedTableDot,
QuoteChar, Field.Origin]);
ParamLine := Format('%s%s(', [ParamLine, TObjectField(Field).ObjectType]);
for i := 0 to TObjectField(Field).FieldCount - 1 do
AddField(TObjectField(Field).Fields[i], TempStr, ParamLine);
ParamLine := Copy(ParamLine, 1, Length(ParamLine) - 2) + '), ';
end else
if (Field.DataType = ftDataSet) and (TObjectField(Field).ObjectType <> '') then
begin
FieldLine := Format('%s%s%s%s%2:s, ', [FieldLine, PSQLInfo(Tree.Data)^.QuotedTableDot,
QuoteChar, Field.Origin]);
ParamLine := Format('%s%s(), ', [ParamLine, TDataSetField(Field).ObjectType]);
end else
if UseFieldInUpdate(Field) then
begin
if Field.DataType = ftOraBlob then
begin
FieldLine := Format('%s%s%s%s%2:s, ', [FieldLine, PSQLInfo(Tree.Data)^.QuotedTableDot,
QuoteChar, Field.Origin]);
ParamLine := ParamLine + 'EMPTY_BLOB(), '
end
else if Field.DataType = ftOraClob then
begin
FieldLine := Format('%s%s%s%s%2:s, ', [FieldLine, PSQLInfo(Tree.Data)^.QuotedTableDot,
QuoteChar, Field.Origin]);
ParamLine := ParamLine + 'EMPTY_CLOB(), '
end else
begin
FieldLine := Format('%s%s%s%s%2:s, ', [FieldLine, PSQLInfo(Tree.Data)^.QuotedTableDot,
QuoteChar, Field.Origin]);
ParamLine := ParamLine + '?, ';
Value := Field.NewValue;
if VarIsEmpty(Value) then Value := Field.OldValue;
TParam(Params.Add).AssignFieldValue(Field, Value);
end;
end;
end;
end;
var
I, J: Integer;
FieldLine, ParamLine: string;
OraLobs: Integer;
Value: Variant;
begin
OraLobs := 0;
with PSQLInfo(Tree.Data)^ do
begin
SQL.Clear;
if Tree.IsNested then
begin
SQL.Add(Format('insert into the (select %s FROM %s %s',[QuoteFullName(Tree.Name, QuoteChar),
PSQLInfo(Tree.Parent.Data).QuotedTable, DefAlias]));
GenWhereSQL(Tree.Parent, SQL, Params, upWhereKeyOnly, DefAlias);
SQL.Add(')');
end else
SQL.Add(Format('insert into %s', [QuotedTable]));
FieldLine := ' (';
ParamLine := FieldLine;
for I := 0 to Tree.Delta.FieldCount - 1 do
begin
AddField(Tree.Delta.Fields[I], FieldLine, ParamLine);
if Tree.Delta.Fields[I].DataType in [ftOraClob, ftOraBlob] then
Inc(OraLobs);
end;
if not Tree.IsNested then
SQL.Add(Copy(FieldLine, 1, Length(FieldLine)-2)+')');
SQL.Add('values');
SQL.Add(Copy(ParamLine, 1, Length(ParamLine)-2)+')');
if OraLobs > 0 then
begin
SQL.Add(' RETURNING ');
J := OraLobs;
for I := 0 to Tree.Delta.FieldCount - 1 do
if (Tree.Delta.Fields[I].DataType in [ftOraClob, ftOraBlob] )
and UseFieldInUpdate(Tree.Delta.Fields[I]) then
begin
Dec(J);
SQL.Add(Format('%s ', [Tree.Delta.Fields[I].FullName]));
if J > 0 then SQL.Add(', ');
Value := Tree.Delta.Fields[I].NewValue;
if VarIsEmpty(Value) then Value := Tree.Delta.Fields[I].OldValue;
TParam(Params.Add).AssignFieldValue(Tree.Delta.Fields[I], Value)
end;
SQL.Add('INTO ');
while OraLobs > 0 do
begin
SQL.Add('? ');
Dec(OraLobs);
if OraLobs > 0 then SQL.Add(', ');
end;
end;
end;
end;
procedure TSQLResolver.GenDeleteSQL(Tree: TUpdateTree; SQL: TStrings;
Params: TParams; Alias: string);
begin
with PSQLInfo(Tree.Data)^ do
begin
SQL.Clear;
if Tree.IsNested then
begin
Alias := NestAlias;
SQL.Add(Format('delete the (select %s FROM %s %s',[QuoteFullName(Tree.Name, QuoteChar),
PSQLInfo(Tree.Parent.Data).QuotedTable, DefAlias]));
GenWhereSQL(Tree.Parent, SQL, Params, upWhereKeyOnly, DefAlias);
SQL.Add(Format(') %s',[Alias]));
end else
SQL.Add(Format('delete from %s %s', [QuotedTable, Alias]));
GenWhereSQL(Tree, SQL, Params, Provider.UpdateMode, Alias);
end;
end;
procedure TSQLResolver.GenUpdateSQL(Tree: TUpdateTree; SQL: TStrings;
Params: TParams; Alias: string);
procedure AddField(Field: TField; InObject, InArray: Boolean);
var
i: Integer;
TempStr: string;
Value: Variant;
NoParam: Boolean;
begin
NoParam := False;
with PSQLInfo(Tree.Data)^ do
begin
if Field.DataType = ftADT then
begin
if InArray then
SQL.Add(Format(' %s(',[TObjectField(Field).ObjectType]));
for i := 0 to TObjectField(Field).FieldCount - 1 do
AddField(TObjectField(Field).Fields[i], True, InArray);
if InArray then
begin
TempStr := SQL[SQL.Count-1];
SQL[SQL.Count-1] := Copy(TempStr, 1, Length(TempStr) - 1);
SQL.Add('),');
end;
end
else if Field.DataType = ftArray then
begin
SQL.Add(Format('%s = %s(',[Field.FullName, TObjectField(Field).ObjectType]));
for i := 0 to TObjectField(Field).FieldCount - 1 do
AddField(TObjectField(Field).Fields[i], InObject, True);
TempStr := SQL[SQL.Count-1];
SQL[SQL.Count-1] := Copy(TempStr, 1, Length(TempStr) - 1);
SQL.Add('),');
end
else if InArray then
begin
SQL.Add(' ?,');
Value := Field.NewValue;
if VarIsEmpty(Value) then Value := Field.OldValue;
TParam(Params.Add).AssignFieldValue(Field, Value);
end
else if UseFieldInUpdate(Field) then
begin
if Field.DataType = ftOraClob then
begin
NoParam := True;
if InObject then
SQL.Add(Format(' %s.%s = EMPTY_CLOB(),', [Alias, QuoteFullName(Field.FullName, QuoteChar),
Field.FullName])) else
SQL.Add(Format(' %s%s%s%1:s = EMPTY_CLOB(),', [PSQLInfo(Tree.Data)^.QuotedTableDot,
QuoteChar, Field.Origin]));
end
else if Field.DataType = ftOraBlob then
begin
NoParam := True;
if InObject then
SQL.Add(Format(' %s.%s = EMPTY_BLOB(),', [Alias, QuoteFullName(Field.FullName, QuoteChar),
Field.FullName])) else
SQL.Add(Format(' %s%s%s%1:s = EMPTY_BLOB(),', [PSQLInfo(Tree.Data)^.QuotedTableDot,
QuoteChar, Field.Origin]));
end
else if InObject then
SQL.Add(Format(' %s.%s = ?,', [Alias, QuoteFullName(Field.FullName, QuoteChar),
Field.FullName])) else
SQL.Add(Format(' %s%s%s%1:s = ?,', [PSQLInfo(Tree.Data)^.QuotedTableDot,
QuoteChar, Field.Origin]));
if not NoParam then
begin
Value := Field.NewValue;
if VarIsEmpty(Value) then Value := Field.OldValue;
TParam(Params.Add).AssignFieldValue(Field, Value);
end;
end;
end;
end;
var
I, J: integer;
TempStr: string;
OraLobs: Integer;
Value: Variant;
begin
OraLobs := 0;
with PSQLInfo(Tree.Data)^ do
begin
if Tree.IsNested then
begin
Alias := NestAlias;
SQL.Add(Format('update the (select %s FROM %s %s',[QuoteFullName(Tree.Name, QuoteChar),
PSQLInfo(Tree.Parent.Data).QuotedTable, DefAlias]));
GenWhereSQL(Tree.Parent, SQL, Params, upWhereKeyOnly, DefAlias);
SQL.Add(Format(') %s set',[Alias]));
end else
SQL.Add(Format('update %s %s set', [QuotedTable, Alias]));
for I := 0 to Tree.Delta.FieldCount - 1 do
begin
if (Tree.Delta.Fields[i].DataType in [ftOraClob, ftOraBlob]) and
UseFieldInUpdate(Tree.Delta.Fields[I]) then
Inc(OraLobs);
AddField(Tree.Delta.Fields[i], Alias = NestAlias, False);
end;
{ Remove last ',' }
TempStr := SQL[SQL.Count-1];
SQL[SQL.Count-1] := Copy(TempStr, 1, Length(TempStr) - 1);
GenWhereSQL(Tree, SQL, Params, Provider.UpdateMode, Alias);
if OraLobs > 0 then
begin
SQL.Add(' RETURNING ');
J := OraLobs;
for I := 0 to Tree.Delta.FieldCount - 1 do
if (Tree.Delta.Fields[I].DataType in [ftOraClob, ftOraBlob])
and UseFieldInUpdate(Tree.Delta.Fields[I]) then
begin
Dec(J);
SQL.Add(Format('%s ', [Tree.Delta.Fields[I].FullName]));
if J > 0 then SQL.Add(', ');
Value := Tree.Delta.Fields[I].NewValue;
if VarIsEmpty(Value) then Value := Tree.Delta.Fields[I].OldValue;
TParam(Params.Add).AssignFieldValue(Tree.Delta.Fields[I], Value)
end;
SQL.Add('INTO ');
while OraLobs > 0 do
begin
SQL.Add('? ');
Dec(OraLobs);
if OraLobs > 0 then SQL.Add(', ');
end;
end;
end;
end;
procedure TSQLResolver.GenSelectSQL(Tree: TUpdateTree; SQL: TStrings;
Params: TParams; Alias: string);
var
i: Integer;
Temp: string;
begin
with PSQLInfo(Tree.Data)^ do
begin
SQL.Add('select');
for i := 0 to Tree.Delta.FieldCount - 1 do
with Tree.Delta.Fields[i] do
if not (DataType in [ftDataSet, ftReference]) and (FieldKind = fkData) then
SQL.Add(Format(' %s%s%s%1:s,',[QuotedTableDot, QuoteChar, Origin]));
{ Remove last ',' }
Temp := SQL[SQL.Count-1];
SQL[SQL.Count-1] := Copy(Temp, 1, Length(Temp) - 1);
SQL.Add(Format(' from %s %s',[QuotedTable, Alias]));
GenWhereSQL(Tree, SQL, Params, upWhereKeyOnly, Alias);
end;
end;
{ TLocalAppServer }
constructor TLocalAppServer.Create(AProvider: TCustomProvider);
begin
inherited Create;
FProvider := AProvider;
end;
destructor TLocalAppServer.Destroy;
begin
if FProviderCreated then FProvider.Free;
inherited Destroy;
end;
constructor TLocalAppServer.Create(ADataset: TDataset);
begin
inherited Create;
FProvider := TDatasetProvider.Create(nil);
TDatasetProvider(FProvider).Dataset := ADataset;
FProviderCreated := True;
end;
function TLocalAppServer.GetTypeInfoCount(out Count: Integer): HResult;
begin
Result := E_NOTIMPL;
end;
function TLocalAppServer.GetTypeInfo(Index, LocaleID: Integer; out TypeInfo): HResult;
begin
Result := E_NOTIMPL;
end;
function TLocalAppServer.GetIDsOfNames(const IID: TGUID; Names: Pointer;
NameCount, LocaleID: Integer; DispIDs: Pointer): HResult;
begin
Result := E_NOTIMPL;
end;
function TLocalAppServer.Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer;
Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult;
begin
Result := E_NOTIMPL;
end;
function TLocalAppServer.AS_ApplyUpdates(const ProviderName: WideString;
Delta: OleVariant; MaxErrors: Integer; out ErrorCount: Integer;
var OwnerData: OleVariant): OleVariant;
begin
Result := FProvider.ApplyUpdates(Delta, MaxErrors, ErrorCount, OwnerData);
end;
function TLocalAppServer.AS_GetRecords(const ProviderName: WideString; Count: Integer;
out RecsOut: Integer; Options: Integer; const CommandText: WideString;
var Params, OwnerData: OleVariant): OleVariant;
begin
Result := FProvider.GetRecords(Count, RecsOut, Options, CommandText, Params,
OwnerData);
end;
function TLocalAppServer.AS_GetProviderNames: OleVariant;
begin
Result := NULL;
end;
function TLocalAppServer.AS_DataRequest(const ProviderName: WideString;
Data: OleVariant): OleVariant;
begin
Result := FProvider.DataRequest(Data);
end;
function TLocalAppServer.AS_GetParams(const ProviderName: WideString;
var OwnerData: OleVariant): OleVariant;
begin
Result := FProvider.GetParams(OwnerData);
end;
function TLocalAppServer.AS_RowRequest(const ProviderName: WideString;
Row: OleVariant; RequestType: Integer; var OwnerData: OleVariant): OleVariant;
begin
Result := FProvider.RowRequest(Row, RequestType, OwnerData);
end;
procedure TLocalAppServer.AS_Execute(const ProviderName: WideString;
const CommandText: WideString; var Params, OwnerData: OleVariant);
begin
FProvider.Execute(CommandText, Params, OwnerData);
end;
function TLocalAppServer.InterfaceSupportsErrorInfo(const iid: TIID): HResult;
begin
if IsEqualGUID(IAppServer, iid) then
Result := S_OK else
Result := S_FALSE;
end;
function TLocalAppServer.SafeCallException(ExceptObject: TObject;
ExceptAddr: Pointer): HResult;
begin
Result := HandleSafeCallException(ExceptObject, ExceptAddr, IAppServer, '', '');
end;
end.