2146 lines
78 KiB
ObjectPascal
2146 lines
78 KiB
ObjectPascal
unit uDABusinessProcessor;
|
|
|
|
{----------------------------------------------------------------------------}
|
|
{ Data Abstract Library - Core Library }
|
|
{ }
|
|
{ compiler: Delphi 6 and up, Kylix 3 and up }
|
|
{ platform: Win32, Linux }
|
|
{ }
|
|
{ (c)opyright RemObjects Software. all rights reserved. }
|
|
{ }
|
|
{ Using this code requires a valid license of the Data Abstract }
|
|
{ which can be obtained at http://www.remobjects.com. }
|
|
{----------------------------------------------------------------------------}
|
|
|
|
{$I DataAbstract.inc}
|
|
|
|
interface
|
|
|
|
{ ToDo: for DA3, refactor the Oracle specific stuff OUT of here again }
|
|
|
|
uses
|
|
Classes, DB, Contnrs, SysUtils,
|
|
uDAInterfaces, uDADataTable, uDAClasses, uDAOracleInterfaces,
|
|
uDAScriptingProvider, uDADelta
|
|
, uDASupportClasses
|
|
;
|
|
|
|
type
|
|
// These structs are to speed up mapping between command parameters and delta fields.
|
|
// See TDABusinessProcessor.ProcessDelta
|
|
TDAMappingType = (mtNewValue, mtOldValue{, mtNullCheck});
|
|
TDAParamMapping = record
|
|
CommandIndex,
|
|
DeltaIndex: integer;
|
|
MappingType: TDAMappingType;
|
|
|
|
GeneratorName : string;
|
|
GeneratorValue : integer;
|
|
end;
|
|
|
|
TDAParamMappingArray = array of TDAParamMapping;
|
|
|
|
const
|
|
MappingPrefix: array[TDAMappingType] of string = ('', 'OLD_'{, 'NULL_'});
|
|
|
|
type
|
|
TDABusinessProcessor = class;
|
|
TDABusinessProcessorRules = class;
|
|
|
|
TDADeltaProcessorItemCollection = class;
|
|
|
|
|
|
{ Events }
|
|
TDAProcessDeltaEvent = procedure(Sender: TDABusinessProcessor; const aDelta: IDADelta) of object;
|
|
|
|
TDABeforeProcessChangeEvent = procedure(Sender: TDABusinessProcessor; aChangeType: TDAChangeType; aChange: TDADeltaChange; var ProcessChange: boolean) of object;
|
|
|
|
TDAAfterProcessChangeEvent = procedure(Sender: TDABusinessProcessor; aChange: TDADeltaChange; Processed: boolean; var CanRemoveFromDelta: boolean) of object;
|
|
|
|
TDAProcessChangeEvent = procedure(Sender: TDABusinessProcessor; aChangeType: TDAChangeType; aChange: TDADeltaChange; const aCommand: IDASQLCommand) of object;
|
|
|
|
TDARefreshDeltaChangeEvent = procedure(Sender: TDABusinessProcessor; aChangeType: TDAChangeType; aChange: TDADeltaChange; const aRefreshDataSet: IDADataSet) of object;
|
|
|
|
TDAProcessErrorEvent = procedure(Sender: TDABusinessProcessor;
|
|
aChangeType: TDAChangeType;
|
|
aChange: TDADeltaChange;
|
|
const aCommand: IDASQLCommand;
|
|
var CanRemoveFromDelta: boolean;
|
|
Error: Exception) of object;
|
|
|
|
TDAGenerateSQLEvent = procedure(Sender: TDABusinessProcessor;
|
|
ChangeType: TDAChangeType;
|
|
const ReferencedStatement: TDAStatement;
|
|
const aDelta: IDADelta;
|
|
var SQL: string) of object;
|
|
|
|
{ Misc }
|
|
TDAProcessorOption = (poAutoGenerateInsert,
|
|
poAutoGenerateUpdate,
|
|
poAutoGenerateDelete,
|
|
poAutoGenerateRefreshDataset,
|
|
poPrepareCommands,
|
|
poIgnoreRowsAffected);
|
|
TDAProcessorOptions = set of TDAProcessorOption;
|
|
|
|
TDAUpdateMode = (updWhereKeyOnly, updWhereAll, updWhereKeyAndUserDefined, updWhereUserDefined);
|
|
|
|
{ TDADeltaStruct }
|
|
TDADeltaStruct = class
|
|
private
|
|
fDelta : IDADelta;
|
|
fBusinessProcessor : TDABusinessProcessor;
|
|
fDetailDeltas : TDADeltaList;
|
|
fRelationShips: TDADatasetRelationshipList;
|
|
|
|
public
|
|
constructor Create(const aDelta : IDADelta; aBusinessProcessor : TDABusinessProcessor);
|
|
destructor Destroy; override;
|
|
|
|
property Delta : IDADelta read fDelta;
|
|
property BusinessProcessor : TDABusinessProcessor read fBusinessProcessor;
|
|
property DetailDeltas : TDADeltaList read fDetailDeltas;
|
|
property RelationShips : TDADatasetRelationshipList read fRelationShips;
|
|
end;
|
|
|
|
{ TDADeltaStructList }
|
|
TDADeltaStructList = class(TObjectList)
|
|
private
|
|
function GetDADeltaStructs(Index: integer): TDADeltaStruct;
|
|
protected
|
|
public
|
|
function Add(const aDelta : IDADelta; aBusinessProcessor : TDABusinessProcessor): TDADeltaStruct;
|
|
|
|
function FindStruct(const aLogicalName : string) : TDADeltaStruct;
|
|
function StructByLogicalName(const aLogicalName : string) : TDADeltaStruct;
|
|
|
|
property DeltaStructs[Index : integer] : TDADeltaStruct read GetDADeltaStructs; default;
|
|
end;
|
|
|
|
IDABusinessProcessorScriptingProvider = interface(IDAScriptingProvider)
|
|
['{7BF0D886-51D7-4E91-8073-0FBA78CC11F3}']
|
|
procedure RunBusinessProcessorScript(aBusinessProcessor: TDABusinessProcessor; const aScript: string; const aMethod: string; aLanguage:TROSEScriptLanguage);
|
|
end;
|
|
|
|
{ IDASQLGenerator }
|
|
IDASQLGenerator = interface
|
|
['{EDE4E068-C300-4991-89CF-F9A81D207930}']
|
|
function GenerateSQL(aChangeType: TDAChangeType;
|
|
aChange: TDADeltaChange;
|
|
aDataset: TDADataset;
|
|
const aDelta: IDADelta;
|
|
aDatasetStatement: TDAStatement;
|
|
aConnection: IDAConnection): string;
|
|
|
|
procedure UpdateSQLForOracle(aChangeType: TDAChangeType;
|
|
aDataset: TDADataset; const aDelta: IDADelta;
|
|
aDatasetStatement: TDAStatement; aConnection: IOracleConnection;
|
|
var OriginalSQL: string);
|
|
|
|
function GenerateRefreshDataset(aDataset: TDADataset; const aDelta: IDADelta;
|
|
aDatasetStatement: TDAStatement; aConnection: IDAConnection): IDADataset;
|
|
|
|
end;
|
|
|
|
{ TDABusinessProcessor }
|
|
TDABusinessProcessor = class(TScriptableComponent, IDASQLGenerator)
|
|
private
|
|
fOnBeforeProcessChange: TDABeforeProcessChangeEvent;
|
|
fOnAfterProcessChange: TDAAfterProcessChangeEvent;
|
|
|
|
fInsertCommandName: string;
|
|
fDeleteCommandName: string;
|
|
fUpdateCommandName: string;
|
|
|
|
fSchema: TDASchema;
|
|
|
|
fOnAfterProcessDelta: TDAProcessDeltaEvent;
|
|
fOnBeforeProcessDelta: TDAProcessDeltaEvent;
|
|
fOnProcessChange: TDAProcessChangeEvent;
|
|
fOnProcessError: TDAProcessErrorEvent;
|
|
fOnRefreshDeltaChange: TDARefreshDeltaChangeEvent;
|
|
|
|
fReferencedDataset: string;
|
|
fProcessorOptions: TDAProcessorOptions;
|
|
fUserUpdateFields: TStringList;
|
|
fUpdateMode: TDAUpdateMode;
|
|
fOnGenerateSQL: TDAGenerateSQLEvent;
|
|
|
|
fBusinessRules: TDABusinessProcessorRules;
|
|
fCurrentChange: integer;
|
|
fCurrentDelta: TDADelta;
|
|
fBusinessRulesID: string;
|
|
fRefreshDataset: string;
|
|
FRaiseExceptionAtError: boolean;
|
|
fHasReducedDelta: Boolean;
|
|
FDynamicWhereInRefreshDataset: Boolean;
|
|
|
|
procedure SetSchema(const Value: TDASchema);
|
|
procedure SetDeleteCommandName(const Value: string);
|
|
procedure SetInsertCommandName(const Value: string);
|
|
procedure SetUpdateCommandName(const Value: string);
|
|
procedure SetReferencedDataset(const Value: string);
|
|
function GetUserUpdateFields: TStrings;
|
|
procedure SetUserUpdateFields(Value: TStrings);
|
|
function NeedsReferencedDataset: boolean;
|
|
function GetCurrentChange: TDADeltaChange;
|
|
procedure SetBusinessRulesID(const Value: string);
|
|
procedure SetupParameters(const aCommand: IDASQLCommand; aReferencedDataset : TDADataset);
|
|
procedure SetRefreshDataset(const Value: string);
|
|
|
|
procedure RefreshDeltaChange(const aConnection : IDAConnection; const aRefreshDataset: IDADataset;
|
|
const aDelta: IDADelta; aDeltaChange: TDADeltaChange; GenAutoIncValue : integer = -1);
|
|
protected
|
|
procedure SetupCommands(const aConnection: IDAConnection; const aDelta: IDADelta;
|
|
out anInsertCmd, anUpdateCmd, aDeleteCmd: IDASQLCommand;
|
|
out aRefreshDs : IDADataset); overload;dynamic;
|
|
procedure SetupCommands(const aConnection: IDAConnection; const aChange: TDADeltaChange;
|
|
out anCmd: IDASQLCommand); overload;dynamic;
|
|
// For unions
|
|
procedure SetupCommands(const aConnection: IDAConnection; const aDelta: IDADelta;
|
|
out aCommandsList : TDADeltaProcessorItemCollection); overload;dynamic;
|
|
procedure SetupCommandsWithMapping(const aConnection: IDAConnection; const aChange: TDADeltaChange;
|
|
out anCmd: IDASQLCommand; out aParamMapping: TDAParamMappingArray);
|
|
|
|
|
|
procedure CreateMappings(const aDelta: IDADelta; var MappingArray: TDAParamMappingArray; const aCommand: IDASQLCommand; lAdditionalMapping: TDAColumnMappingCollection = nil; AValidateCommand: Boolean = False);
|
|
|
|
{ IDASQLGenerator }
|
|
function GenerateSQL(aChangeType: TDAChangeType;
|
|
aChange: TDADeltaChange;
|
|
aDataset: TDADataset;
|
|
const aDelta: IDADelta;
|
|
aDatasetStatement: TDAStatement;
|
|
aConnection: IDAConnection): string; dynamic;
|
|
|
|
procedure UpdateSQLForOracle(aChangeType: TDAChangeType;
|
|
aDataset: TDADataset; const aDelta: IDADelta;
|
|
aDatasetStatement: TDAStatement; aConnection: IOracleConnection;
|
|
var OriginalSQL: string);
|
|
|
|
function GenerateRefreshDataset(aDataset: TDADataset; const aDelta: IDADelta;
|
|
aDatasetStatement: TDAStatement; aConnection: IDAConnection): IDADataset; dynamic;
|
|
|
|
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
|
|
|
|
// IInterface
|
|
function QueryInterface(const IID: TGUID; out Obj): HResult; override;
|
|
|
|
public
|
|
constructor Create(aOwner: TComponent); override;
|
|
destructor Destroy; override;
|
|
|
|
procedure SynchronizeAutoIncs(const aMasterDelta, aDetailDelta : IDADelta;
|
|
const aRelationship : TDADatasetRelationship);
|
|
|
|
procedure ProcessDelta(const aConnection: IDAConnection;
|
|
const aDelta: IDADelta;
|
|
ChangeTypes: TDAChangeTypes = AllChanges); overload;
|
|
|
|
procedure ProcessDelta(aDataTable: TDADataTable;
|
|
ChangeTypes: TDAChangeTypes; const aConnection : IDAConnection = NIL); overload;
|
|
|
|
procedure ProcessDeltaForUnion(const aConnection: IDAConnection;
|
|
const aDelta: IDADelta; ChangeTypes: TDAChangeTypes = AllChanges);
|
|
|
|
|
|
property CurrentDelta: TDADelta read fCurrentDelta;
|
|
property CurrentChange: TDADeltaChange read GetCurrentChange;
|
|
property HasReducedDelta: Boolean read fHasReducedDelta write fHasReducedDelta;
|
|
procedure CheckProperties;
|
|
published
|
|
property OnBeforeProcessDelta: TDAProcessDeltaEvent read fOnBeforeProcessDelta write fOnBeforeProcessDelta;
|
|
property OnAfterProcessDelta: TDAProcessDeltaEvent read fOnAfterProcessDelta write fOnAfterProcessDelta;
|
|
property OnBeforeProcessChange: TDABeforeProcessChangeEvent read fOnBeforeProcessChange write fOnBeforeProcessChange;
|
|
property OnAfterProcessChange: TDAAfterProcessChangeEvent read fOnAfterProcessChange write fOnAfterProcessChange;
|
|
property OnProcessChange: TDAProcessChangeEvent read fOnProcessChange write fOnProcessChange;
|
|
property OnProcessError: TDAProcessErrorEvent read fOnProcessError write fOnProcessError;
|
|
property OnGenerateSQL: TDAGenerateSQLEvent read fOnGenerateSQL write fOnGenerateSQL;
|
|
property OnRefreshDeltaChange: TDARefreshDeltaChangeEvent read fOnRefreshDeltaChange write fOnRefreshDeltaChange;
|
|
|
|
property Schema: TDASchema read fSchema write SetSchema;
|
|
|
|
property InsertCommandName: string read fInsertCommandName write SetInsertCommandName;
|
|
property DeleteCommandName: string read fDeleteCommandName write SetDeleteCommandName;
|
|
property UpdateCommandName: string read fUpdateCommandName write SetUpdateCommandName;
|
|
property RefreshDatasetName: string read fRefreshDataset write SetRefreshDataset;
|
|
|
|
property ReferencedDataset: string read fReferencedDataset write SetReferencedDataset;
|
|
property ProcessorOptions: TDAProcessorOptions read fProcessorOptions write fProcessorOptions;
|
|
|
|
property UpdateMode: TDAUpdateMode read fUpdateMode write fUpdateMode;
|
|
property UserUpdateFields: TStrings read GetUserUpdateFields write SetUserUpdateFields;
|
|
|
|
property BusinessRulesID: string read fBusinessRulesID write SetBusinessRulesID;
|
|
property RaiseExceptionAtError: boolean read FRaiseExceptionAtError write FRaiseExceptionAtError default False;
|
|
end;
|
|
|
|
{ TDABusinessProcessorRules }
|
|
TDABusinessProcessorRules = class(TDABusinessRules, IDAStronglyTypedDataTable)
|
|
private
|
|
fBusinessProcessor: TDABusinessProcessor;
|
|
protected
|
|
property BusinessProcessor: TDABusinessProcessor read fBusinessProcessor;
|
|
|
|
// Misc
|
|
procedure Attach(aBusinessProcessor: TDABusinessProcessor); virtual;
|
|
procedure Detach(aBusinessProcessor: TDABusinessProcessor); virtual;
|
|
|
|
// Business events
|
|
procedure BeforeProcessDelta(Sender: TDABusinessProcessor; const aDelta: IDADelta); virtual;
|
|
procedure AfterProcessDelta(Sender: TDABusinessProcessor; const aDelta: IDADelta); virtual;
|
|
procedure BeforeProcessChange(Sender: TDABusinessProcessor; aChangeType: TDAChangeType; aChange: TDADeltaChange; var ProcessChange: boolean); virtual;
|
|
procedure AfterProcessChange(Sender: TDABusinessProcessor; aChange: TDADeltaChange; Processed: boolean; var CanRemoveFromDelta: boolean); virtual;
|
|
procedure ProcessChange(Sender: TDABusinessProcessor; aChangeType: TDAChangeType; aChange: TDADeltaChange; const aCommand: IDASQLCommand); virtual;
|
|
procedure ProcessError(Sender: TDABusinessProcessor; aChangeType: TDAChangeType; aChange: TDADeltaChange;
|
|
const aCommand: IDASQLCommand; var CanRemoveFromDelta: boolean; Error: Exception); virtual;
|
|
procedure GenerateSQL(Sender: TDABusinessProcessor; ChangeType: TDAChangeType; const ReferencedStatement: TDAStatement;
|
|
const aDelta: IDADelta; var SQL: string); virtual;
|
|
|
|
procedure NotSupportedByBusinessProcessor;
|
|
procedure Open;
|
|
procedure Close;
|
|
|
|
function GetActive: boolean;
|
|
procedure SetActive(const Value: boolean);
|
|
|
|
procedure Append;
|
|
procedure Cancel;
|
|
procedure Delete;
|
|
procedure Edit;
|
|
procedure First;
|
|
procedure Insert;
|
|
procedure Last;
|
|
procedure Next;
|
|
procedure Post;
|
|
procedure Prior;
|
|
function GetBOF: Boolean;
|
|
function GetEOF: Boolean;
|
|
function GetRecordCount: Integer;
|
|
function Locate(const aKeyFields: String; const aKeyValues: Variant; aOptions: TLocateOptions = []): Boolean;
|
|
function Lookup(const KeyFields: string; const KeyValues: Variant; const ResultFields: string): Variant;
|
|
|
|
function GetMasterOptions : TDAMasterOptions;
|
|
procedure SetMasterOptions(Value : TDAMasterOptions);
|
|
function GetDetailOptions : TDADetailOptions;
|
|
procedure SetDetailOptions(Value : TDADetailOptions);
|
|
function GetState : TDatasetState;
|
|
function GetIsEmpty : boolean;
|
|
|
|
function IsFieldNull(const FieldIndexOrName : Variant) : boolean;
|
|
procedure ClearField(const FieldIndexOrName : Variant);
|
|
|
|
function GetDataTable : TDADataTable;
|
|
|
|
function GetRecNo: integer;
|
|
procedure SetRecNo(Value: integer);
|
|
|
|
public
|
|
constructor Create(aBusinessProcessor: TDABusinessProcessor); reintroduce; virtual;
|
|
end;
|
|
|
|
TDABusinessProcessorRulesClass = class of TDABusinessProcessorRules;
|
|
|
|
TDADeltaProcessorItem = class(TCollectionItem)
|
|
private
|
|
fName: String;
|
|
fRefreshDataset: IDADataset;
|
|
|
|
fInsertCommand: IDASQLCommand;
|
|
fUpdateCommand: IDASQLCommand;
|
|
fDeleteCommand: IDASQLCommand;
|
|
|
|
fInsertCommandMapping: TDAParamMappingArray;
|
|
fUpdateCommandMapping: TDAParamMappingArray;
|
|
fDeleteCommandMapping: TDAParamMappingArray;
|
|
|
|
public
|
|
property RefreshDataset: IDADataset read fRefreshDataset write fRefreshDataset;
|
|
|
|
property InsertCommand: IDASQLCommand read fInsertCommand write fInsertCommand;
|
|
property UpdateCommand: IDASQLCommand read fUpdateCommand write fUpdateCommand;
|
|
property DeleteCommand: IDASQLCommand read fDeleteCommand write fDeleteCommand;
|
|
|
|
property InsertCommandMapping: TDAParamMappingArray read fInsertCommandMapping write fInsertCommandMapping;
|
|
property UpdateCommandMapping: TDAParamMappingArray read fUpdateCommandMapping write fUpdateCommandMapping;
|
|
property DeleteCommandMapping: TDAParamMappingArray read fDeleteCommandMapping write fDeleteCommandMapping;
|
|
published
|
|
property Name: String read fName write fName;
|
|
end;
|
|
|
|
TDADeltaProcessorItemCollection = class(TSearcheableCollection)
|
|
private
|
|
function GetItem(Index: integer): TDADeltaProcessorItem;
|
|
procedure SetItem(Index: integer; const Value: TDADeltaProcessorItem);
|
|
public
|
|
constructor Create(aOwner: TComponent);
|
|
function Add: TDADeltaProcessorItem; reintroduce;
|
|
function ItemByName(const aName: String): TDADeltaProcessorItem;
|
|
property Items[Index: integer]: TDADeltaProcessorItem read GetItem write SetItem; default;
|
|
end;
|
|
|
|
|
|
// Registration routines
|
|
procedure RegisterBusinessProcessorRules(const anID: string; const aDeltaChangeClass: TDABusinessProcessorRulesClass);
|
|
function FindBusinessProcessorRules(const anID: string; out aDeltaChangeClass: TDABusinessProcessorRulesClass): boolean;
|
|
|
|
implementation
|
|
|
|
uses
|
|
{$IFDEF DEBUG_DATAABSTRACT_SQL}
|
|
eDebugServer,
|
|
{$ENDIF}
|
|
uROClasses, Variants, TypInfo, Types;
|
|
|
|
{$IFDEF DEBUG_DATAABSTRACT_SQL}
|
|
const
|
|
cat_SQL = 'SQL Generation';
|
|
{$ENDIF}
|
|
|
|
var
|
|
_bizdeltachanges: TStringList;
|
|
|
|
procedure RegisterBusinessProcessorRules(const anID: string; const aDeltaChangeClass: TDABusinessProcessorRulesClass);
|
|
var
|
|
idx: integer;
|
|
begin
|
|
idx := _bizdeltachanges.IndexOf(anID);
|
|
|
|
if (idx >= 0) then
|
|
_bizdeltachanges.Objects[idx] := TObject(aDeltaChangeClass)
|
|
else
|
|
_bizdeltachanges.AddObject(anID, TObject(aDeltaChangeClass));
|
|
end;
|
|
|
|
function FindBusinessProcessorRules(const anID: string; out aDeltaChangeClass: TDABusinessProcessorRulesClass): boolean;
|
|
var
|
|
idx: integer;
|
|
begin
|
|
result := FALSE;
|
|
idx := _bizdeltachanges.IndexOf(anID);
|
|
if (idx >= 0) then begin
|
|
aDeltaChangeClass := TDABusinessProcessorRulesClass(_bizdeltachanges.Objects[idx]);
|
|
result := TRUE;
|
|
end
|
|
else
|
|
aDeltaChangeClass := nil;
|
|
end;
|
|
|
|
function QuoteIdentifier(const aConnection : IDAConnection; const aFieldName: string): string;
|
|
begin
|
|
{if (Pos(' ', aFieldName) > 0) then
|
|
result := QuoteChars[0] + aFieldName + QuoteChars[1]
|
|
else
|
|
result := aFieldName;}
|
|
result := aConnection.QuoteIdentifierIfNeeded(aFieldName);
|
|
end;
|
|
|
|
function QuoteFieldName(const aConnection : IDAConnection; const aTableName,aFieldName: string): string;
|
|
begin
|
|
result := aConnection.QuoteFieldNameIfNeeded(aTableName, aFieldName);
|
|
end;
|
|
|
|
function QuoteParamName(const aConnection : IDAConnection; const aParamName: string): string;
|
|
begin
|
|
if aConnection.IdentifierNeedsQuoting(aParamName) then
|
|
result := AnsiQuotedStr(aParamName,'"')
|
|
else
|
|
Result:= aParamName;
|
|
end;
|
|
|
|
|
|
{ TDADeltaStruct }
|
|
|
|
constructor TDADeltaStruct.Create(const aDelta : IDADelta; aBusinessProcessor : TDABusinessProcessor);
|
|
begin
|
|
inherited Create;
|
|
|
|
fDelta := aDelta;
|
|
fBusinessProcessor := aBusinessProcessor;
|
|
fDetailDeltas := TDADeltaList.Create;
|
|
fRelationShips := TDADatasetRelationshipList.Create;
|
|
end;
|
|
|
|
destructor TDADeltaStruct.Destroy;
|
|
begin
|
|
inherited;
|
|
|
|
FreeAndNIL(fDetailDeltas);
|
|
FreeAndNIL(fRelationShips);
|
|
end;
|
|
|
|
{ TDADeltaStructList }
|
|
|
|
function TDADeltaStructList.Add(const aDelta : IDADelta; aBusinessProcessor : TDABusinessProcessor): TDADeltaStruct;
|
|
begin
|
|
result := TDADeltaStruct.Create(aDelta, aBusinessProcessor);
|
|
inherited Add(result);
|
|
end;
|
|
|
|
function TDADeltaStructList.FindStruct(
|
|
const aLogicalName: string): TDADeltaStruct;
|
|
var i : integer;
|
|
begin
|
|
result := NIL;
|
|
for i := 0 to (Count-1) do
|
|
if SameText(DeltaStructs[i].Delta.LogicalName, aLogicalName) then begin
|
|
result := DeltaStructs[i];
|
|
Exit;
|
|
end;
|
|
end;
|
|
|
|
function TDADeltaStructList.GetDADeltaStructs(
|
|
Index: integer): TDADeltaStruct;
|
|
begin
|
|
result := TDADeltaStruct(inherited Items[Index])
|
|
end;
|
|
|
|
function TDADeltaStructList.StructByLogicalName(
|
|
const aLogicalName: string): TDADeltaStruct;
|
|
begin
|
|
result := FindStruct(aLogicalName);
|
|
if result=NIL then raise Exception.Create('Cannot find struct '+aLogicalName);
|
|
end;
|
|
|
|
{ TDABusinessProcessor }
|
|
|
|
constructor TDABusinessProcessor.Create(aOwner: TComponent);
|
|
begin
|
|
inherited;
|
|
|
|
fUserUpdateFields := TStringList.Create;
|
|
fProcessorOptions := [poAutoGenerateInsert, poAutoGenerateUpdate, poAutoGenerateDelete, poPrepareCommands, poAutoGenerateRefreshDataset];
|
|
fUpdateMode := updWhereKeyOnly;
|
|
end;
|
|
|
|
destructor TDABusinessProcessor.Destroy;
|
|
begin
|
|
FreeAndNIL(fUserUpdateFields);
|
|
|
|
if (fBusinessRules <> nil) then begin
|
|
fBusinessRules.Detach(Self);
|
|
fBusinessRules.Free;
|
|
end;
|
|
|
|
inherited;
|
|
end;
|
|
|
|
function TDABusinessProcessor.GetUserUpdateFields: TStrings;
|
|
begin
|
|
result := fUserUpdateFields
|
|
end;
|
|
|
|
procedure TDABusinessProcessor.SetUserUpdateFields(Value: TStrings);
|
|
begin
|
|
fUserUpdateFields.Assign((Value));
|
|
end;
|
|
|
|
procedure TDABusinessProcessor.CreateMappings(const aDelta: IDADelta; var MappingArray: TDAParamMappingArray; const aCommand: IDASQLCommand; lAdditionalMapping: TDAColumnMappingCollection = nil; AValidateCommand: Boolean = False);
|
|
var
|
|
i,j: integer;
|
|
lParamName : string;
|
|
lDeltaIndex : integer;
|
|
lFieldName: String;
|
|
lFound: boolean;
|
|
begin
|
|
if aCommand = nil then Exit;
|
|
|
|
// The command is what determines how many mappings will be done
|
|
SetLength(MappingArray, aCommand.Params.Count);
|
|
|
|
for i := 0 to (aCommand.Params.Count - 1) do begin
|
|
MappingArray[i].CommandIndex := 0;
|
|
|
|
lParamName := UpperCase(aCommand.Params[i].Name);
|
|
|
|
// Determines the mapping type
|
|
if (Pos(MappingPrefix[mtOldValue], lParamName) = 1)
|
|
then MappingArray[i].MappingType := mtOldValue
|
|
|
|
{else if (Pos(MappingPrefix[mtNullCheck], lParamName) = 1)
|
|
then MappingArray[i].MappingType := mtNullCheck}
|
|
|
|
else MappingArray[i].MappingType := mtNewValue;
|
|
|
|
if (MappingArray[i].MappingType = mtOldValue) then
|
|
lFieldName := Copy(aCommand.Params[i].Name, 5, MaxInt)
|
|
else
|
|
lFieldName := aCommand.Params[i].Name;
|
|
|
|
if AValidateCommand then begin
|
|
lFound:=False;
|
|
for j:=0 to aDelta.LoggedFieldCount-1 do
|
|
if SameText(aDelta.LoggedFieldNames[j],lFieldName) then begin
|
|
lFound:=True;
|
|
Break;
|
|
end;
|
|
if not lFound then raise Exception.CreateFmt('%s command can''t be used as delta command for %s datatable: parameters mismatch',[aCommand.Name,aDelta.LogicalName]);
|
|
end;
|
|
|
|
// if lAdditionalMapping is assigned (usually incase delta for union table)
|
|
// then translate it
|
|
if Assigned(lAdditionalMapping) then begin
|
|
lFieldName := lAdditionalMapping.MappingByTableField(lFieldName).DatasetField;
|
|
end;
|
|
|
|
// Finds the index for this value in the delta
|
|
lDeltaIndex := aDelta.IndexOfLoggedField(lFieldName);
|
|
|
|
MappingArray[i].DeltaIndex := lDeltaIndex;
|
|
|
|
MappingArray[i].GeneratorName := aCommand.ParamByName(lParamName).GeneratorName;
|
|
MappingArray[i].GeneratorValue := -1;
|
|
end;
|
|
end;
|
|
|
|
function TDABusinessProcessor.GenerateRefreshDataset(
|
|
aDataset: TDADataset; const aDelta: IDADelta; aDatasetStatement: TDAStatement;
|
|
aConnection: IDAConnection): IDADataset;
|
|
var
|
|
i: integer;
|
|
namesstr,
|
|
fullsql,
|
|
fieldsstr,
|
|
keystr,
|
|
//remotename,
|
|
localname: string;
|
|
fld: TDAField;
|
|
begin
|
|
result := aConnection.NewDataset('');
|
|
fieldsstr := '';
|
|
namesstr := '';
|
|
keystr := '';
|
|
|
|
with aDataset do try
|
|
// Generates the SELECT
|
|
for i := 0 to Fields.Count-1 do begin
|
|
if (not Fields[i].ServerAutoRefresh) and (Fields[i].DataType <> datAutoInc)and (Fields[i].DataType <> datLargeAutoInc) then Continue;
|
|
|
|
localname := QuoteFieldName(aConnection, aDatasetStatement.TargetTable, aDatasetStatement.ColumnMappings.MappingByDatasetField(Fields[i].Name).TableField);
|
|
fieldsstr := fieldsstr+localname+','+#13;
|
|
|
|
result.Fields.Add.AssignField(Fields[i]);
|
|
end;
|
|
if (fieldsstr='') then Exit;
|
|
fieldsstr := Copy(fieldsstr,1, Length(fieldsstr)-2)+#13;
|
|
|
|
// Generates the WHERE using the primary key fields
|
|
for i := 0 to Fields.Count-1 do begin
|
|
if not Fields[i].InPrimaryKey then Continue;
|
|
|
|
localname := QuoteFieldName(aConnection,aDatasetStatement.TargetTable, aDatasetStatement.ColumnMappings.MappingByDatasetField(Fields[i].Name).TableField);
|
|
keystr := keystr+Format('%s=:%s', [localname, Fields[i].Name])+' AND '+#13;
|
|
end;
|
|
if (keystr='') then Exit;
|
|
keystr := Copy(keystr,1, Length(keystr)-5)+#13;
|
|
|
|
// Combines the two
|
|
fullsql := 'SELECT'#13+fieldsstr+'FROM '+ QuoteIdentifier(aConnection, aDatasetStatement.TargetTable) +#13+'WHERE'+#13+keystr;
|
|
result.SQL := fullsql;
|
|
|
|
{$IFDEF DEBUG_DATAABSTRACT_SQL}DebugServer.Write([cat_SQL], result.SQL);{$ENDIF}
|
|
|
|
with aDatasetStatement do
|
|
for i := 0 to (ColumnMappings.Count - 1) do begin
|
|
fld := result.FindField(ColumnMappings[i].DatasetField);
|
|
if Assigned(fld) then begin
|
|
fld.TableField := ColumnMappings[i].TableField;
|
|
fld.SQLOrigin := ColumnMappings[i].SQLOrigin;
|
|
end;
|
|
end;
|
|
|
|
finally
|
|
if (result.SQL='') then result := NIL;
|
|
end;
|
|
end;
|
|
|
|
function TDABusinessProcessor.GenerateSQL(aChangeType: TDAChangeType;
|
|
aChange: TDADeltaChange;
|
|
aDataset: TDADataset;
|
|
const aDelta: IDADelta;
|
|
aDatasetStatement: TDAStatement;
|
|
aConnection: IDAConnection): string;
|
|
var
|
|
i: integer;
|
|
namesstr,
|
|
valuesstr,
|
|
keystr,
|
|
remotename,
|
|
localname: string;
|
|
oraconn : IOracleConnection;
|
|
fld : TDAField;
|
|
usegenerators : boolean;
|
|
//lIsUnionTable : Boolean;
|
|
lUnionTable: TDAUnionDataTable;
|
|
lSrcTable: TDAUnionSourceTable;
|
|
lUpdateMode: TDAUpdateMode;
|
|
begin
|
|
result := '';
|
|
valuesstr := '';
|
|
namesstr := '';
|
|
keystr := '';
|
|
lSrcTable := nil;
|
|
if aDatasetStatement.TargetTable = '' then raise Exception.Create(aDelta.LogicalName+'. Can''t generate a delta''s SQL. TargetTable isn''t assigned.');
|
|
// If passed dataset is a source table for uniontable then lSrcTable will be assigned (used for distinguish unionsourcetables)
|
|
lUnionTable := fSchema.UnionDataTables.FindItem(fReferencedDataset) as TDAUnionDataTable;
|
|
if assigned(lUnionTable) then begin
|
|
lSrcTable := lUnionTable.SourceTables.UnionSourceTableByName(aDataset.Name);
|
|
end;
|
|
|
|
usegenerators := Supports(aConnection, IDAUseGenerators);
|
|
lUpdateMode:=fUpdateMode;
|
|
if (aDelta.KeyFieldCount=0) and (lUpdateMode = updWhereKeyOnly) then
|
|
lUpdateMode:=updWhereAll;
|
|
with aDatasetStatement, aDelta do begin
|
|
// Generates the WHERE conditions. Done here because used by both deletes and updates
|
|
if (aChangeType <> ctInsert) then begin
|
|
case lUpdateMode of
|
|
updWhereKeyOnly, updWhereKeyAndUserDefined: begin
|
|
for i := 0 to (KeyFieldCount - 1) do begin
|
|
remotename := KeyFieldNames[i];
|
|
|
|
// If this is UnionSourceDataTable then we should do fields remapping
|
|
if Assigned(lSrcTable) then begin
|
|
if SameText(remotename, def_SourceTableFieldName) then Continue;
|
|
remotename := lSrcTable.ColumnMappings.MappingByDatasetField(remotename).TableField;
|
|
end;
|
|
|
|
localname := QuoteFieldName(aConnection,aDatasetStatement.TargetTable, ColumnMappings.MappingByDatasetField(remotename).TableField);
|
|
keystr := keystr + #13'(' + localname + '=:'+QuoteParamName(aConnection,'OLD_' + remotename)+ ') AND ';
|
|
end;
|
|
end;
|
|
|
|
updWhereAll: begin
|
|
for i := 0 to (LoggedFieldCount - 1) do begin
|
|
remotename := LoggedFieldNames[i];
|
|
|
|
// If this is UnionSourceDataTable then we should do fields remapping
|
|
if Assigned(lSrcTable) then begin
|
|
if SameText(remotename, def_SourceTableFieldName) then Continue;
|
|
remotename := lSrcTable.ColumnMappings.MappingByDatasetField(remotename).TableField;
|
|
end;
|
|
|
|
localname := QuoteFieldName(aConnection,aDatasetStatement.TargetTable, ColumnMappings.MappingByDatasetField(remotename).TableField);
|
|
if (aDataset.FieldByName(remotename).DataType in [datBlob, datMemo]) then Continue;
|
|
keystr := keystr + #13'((' + localname + '=:'+QuoteParamName(aConnection, 'OLD_' + remotename) + ') OR (:'+QuoteParamName(aConnection, 'OLD_' + remotename) + ' IS NULL AND ' + localname + ' IS NULL)) AND ';
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
if (lUpdateMode in [updWhereKeyAndUserDefined, updWhereUserDefined]) then begin
|
|
for i := 0 to (fUserUpdateFields.Count - 1) do begin
|
|
remotename := fUserUpdateFields[i];
|
|
|
|
// If this is UnionSourceDataTable then we should do fields remapping
|
|
if Assigned(lSrcTable) then begin
|
|
if SameText(remotename, def_SourceTableFieldName) then Continue;
|
|
remotename := lSrcTable.ColumnMappings.MappingByDatasetField(remotename).TableField;
|
|
end;
|
|
|
|
localname := QuoteFieldName(aConnection, aDatasetStatement.TargetTable,ColumnMappings.MappingByDatasetField(remotename).TableField);
|
|
if assigned(aChange) and ROVariantsEqual(aChange.OldValueByName[remotename],aChange.NewValueByName[remotename]) then Continue;
|
|
keystr := keystr + #13'((' + localname + '=:'+QuoteParamName(aConnection, 'OLD_' + remotename) + ') OR (:'+QuoteParamName(aConnection,'OLD_' + remotename) + ' IS NULL AND ' + localname + ' IS NULL)) AND ';
|
|
end;
|
|
end;
|
|
|
|
keystr := Copy(keystr, 1, Length(keystr) - 5);
|
|
end;
|
|
|
|
// Remaining part of the SQL command
|
|
case aChangeType of
|
|
// Insert
|
|
ctInsert: begin
|
|
result := 'INSERT INTO ' + QuoteIdentifier(aConnection, aDatasetStatement.TargetTable) + ' ('#13;
|
|
|
|
for i := 0 to (LoggedFieldCount - 1) do begin
|
|
remotename := LoggedFieldNames[i];
|
|
|
|
// If this is UnionSourceDataTable then we should do fields remapping
|
|
if Assigned(lSrcTable) then begin
|
|
if SameText(remotename, def_SourceTableFieldName) then Continue;
|
|
remotename := lSrcTable.ColumnMappings.MappingByDatasetField(remotename).TableField;
|
|
end;
|
|
|
|
|
|
fld := aDataset.Fields.FieldByName(remotename);
|
|
if (((fld.DataType = datAutoInc) or (fld.DataType = datlargeAutoInc)) and not usegenerators) // Skips autoincs on DBs like MSSQL
|
|
or (fld.Calculated) or (fld.ReadOnly) or (fld.ServerCalculated)
|
|
then Continue;
|
|
if (Assigned(aChange) and VarIsNull(aChange.NewValues[i])) then Continue;
|
|
localname := QuoteFieldName(aConnection, aDatasetStatement.TargetTable,ColumnMappings.MappingByDatasetField(remotename).TableField);
|
|
|
|
namesstr := namesstr + localname + ', ';
|
|
valuesstr := valuesstr + ':' +QuoteParamName(aConnection, remotename) + ', ';
|
|
end;
|
|
|
|
namesstr := Copy(namesstr, 1, Length(namesstr) - 2) + ')';
|
|
valuesstr := Copy(valuesstr, 1, Length(valuesstr) - 2) + ')';
|
|
|
|
result := result + namesstr + #13' VALUES (' + valuesstr;
|
|
end;
|
|
|
|
// Delete
|
|
ctDelete: result := 'DELETE FROM ' + QuoteIdentifier(aConnection, aDatasetStatement.TargetTable) + #13' WHERE ' + keystr;
|
|
|
|
// Update
|
|
ctUpdate: begin
|
|
result := '';
|
|
|
|
for i := 0 to (LoggedFieldCount - 1) do begin
|
|
remotename := LoggedFieldNames[i];
|
|
|
|
// If this is UnionSourceDataTable then we should do fields remapping
|
|
if Assigned(lSrcTable) then begin
|
|
if SameText(remotename, def_SourceTableFieldName) then Continue;
|
|
remotename := lSrcTable.ColumnMappings.MappingByDatasetField(remotename).TableField;
|
|
end;
|
|
|
|
|
|
fld := aDataset.Fields.FieldByName(remotename);
|
|
if ((fld.DataType = datAutoInc) or (fld.DataType = datlargeAutoInc)) then Continue; // Skips autoincs
|
|
if (fld.InPrimaryKey and not fld.LogChanges) then Continue;
|
|
if (fld.Calculated) or (fld.ReadOnly) or (fld.ServerCalculated) then Continue;
|
|
if assigned(aChange) and ROVariantsEqual(aChange.OldValues[i],aChange.NewValues[i]) then Continue;
|
|
localname := QuoteFieldName(aConnection, aDatasetStatement.TargetTable,ColumnMappings.MappingByDatasetField(remotename).TableField);
|
|
|
|
if (result<>'') then result := result + ', ';
|
|
result := result + #13 + localname + '= :' + QuoteParamName(aConnection,remotename);
|
|
//if (i < LoggedFieldCount - 1) then result := result + ', ';
|
|
end;
|
|
|
|
result := 'UPDATE '+QuoteIdentifier(aConnection, aDatasetStatement.TargetTable)+' SET '+result+#13' WHERE '+keystr;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
// Exception for Oracle's SQL
|
|
if Supports(aConnection, IOracleConnection, oraconn)
|
|
then UpdateSQLForOracle(aChangeType, aDataset, aDelta, aDatasetStatement, oraconn, result);
|
|
|
|
// Events
|
|
if Assigned(fOnGenerateSQL) then
|
|
fOnGenerateSQL(Self, aChangeType, aDatasetStatement, aDelta, result)
|
|
else if Assigned(fBusinessRules) then
|
|
fBusinessRules.GenerateSQL(Self, aChangeType, aDatasetStatement, aDelta, result);
|
|
|
|
{$IFDEF DEBUG_DATAABSTRACT_SQL}DebugServer.Write([cat_SQL], Result);{$ENDIF}
|
|
end;
|
|
|
|
procedure TDABusinessProcessor.UpdateSQLForOracle(aChangeType: TDAChangeType;
|
|
aDataset: TDADataset;
|
|
const aDelta: IDADelta;
|
|
aDatasetStatement: TDAStatement;
|
|
aConnection: IOracleConnection;
|
|
var OriginalSQL: string);
|
|
var i : integer;
|
|
returningstr, intostr : string;
|
|
begin
|
|
returningstr := '';
|
|
intostr := '';
|
|
|
|
with aDataset do begin
|
|
for i := 0 to (Fields.Count-1) do begin
|
|
|
|
if not aDataset.Fields[i].LogChanges then Continue;
|
|
|
|
case Fields[i].BlobType of
|
|
dabtOraBlob : begin
|
|
OriginalSQL := StringReplace(OriginalSQL, ':'+Fields[i].Name, 'empty_blob()', [rfIgnoreCase])
|
|
end;
|
|
|
|
dabtOraClob : begin
|
|
OriginalSQL := StringReplace(OriginalSQL, ':'+Fields[i].Name, 'empty_clob()', [rfIgnoreCase])
|
|
end;
|
|
|
|
else Continue;
|
|
end;
|
|
|
|
returningstr := returningstr+Fields[i].Name+',';
|
|
intostr := intostr+':'+Fields[i].Name+',';
|
|
end;
|
|
end;
|
|
|
|
if (returningstr<>'') then begin
|
|
Delete(returningstr, Length(returningstr), 1);
|
|
returningstr := 'RETURNING '+#13#10+returningstr+#13#10;
|
|
|
|
Delete(intostr, Length(intostr), 1);
|
|
intostr := 'INTO '+#13#10+intostr;
|
|
|
|
OriginalSQL := OriginalSQL+#13#10+returningstr+intostr;
|
|
end;
|
|
|
|
{$IFDEF DEBUG_DATAABSTRACT_SQL}DebugServer.Write([cat_SQL], OriginalSQL);{$ENDIF}
|
|
end;
|
|
|
|
function TDABusinessProcessor.NeedsReferencedDataset: boolean;
|
|
begin
|
|
result :=
|
|
(
|
|
((poAutoGenerateInsert in fProcessorOptions) and (InsertCommandName = '')) or
|
|
((poAutoGenerateUpdate in fProcessorOptions) and (UpdateCommandName = '')) or
|
|
((poAutoGenerateDelete in fProcessorOptions) and (DeleteCommandName = '')) or
|
|
((poAutoGenerateRefreshDataset in fProcessorOptions) and (RefreshDatasetName = ''))
|
|
);
|
|
end;
|
|
|
|
procedure TDABusinessProcessor.SetupParameters(const aCommand : IDASQLCommand;
|
|
aReferencedDataset : TDADataset);
|
|
var lParams: TParams;
|
|
fldname, lSQL: string;
|
|
i: integer;
|
|
par: TDAParam;
|
|
fld : TDAField;
|
|
begin
|
|
lParams := TParams.Create;
|
|
try
|
|
|
|
{ Bug in ParseSQL modified passed in string; use UniqueString to prevent
|
|
corrupting the original! }
|
|
lSQL := aCommand.SQL;
|
|
UniqueString(lSQL);
|
|
lParams.ParseSQL(lSQL, TRUE);
|
|
|
|
with aCommand do begin
|
|
Params.Clear; // Just in case
|
|
|
|
for i := 0 to (lParams.Count - 1) do begin
|
|
fldname := lParams[i].Name;
|
|
|
|
// Checks if it's one of the autogenerated params
|
|
if (Pos(MappingPrefix[mtOldValue], fldname)=1)
|
|
then Delete(fldname, 1, Length(MappingPrefix[mtOldValue]));
|
|
|
|
{else if (Pos(MappingPrefix[mtNullCheck], fldname)=1)
|
|
then Delete(fldname, 1, Length(MappingPrefix[mtNullCheck]));}
|
|
|
|
// Looks up the field and completes the param definition
|
|
fld := aReferencedDataset.Fields.FieldByName(fldname);
|
|
|
|
par := aCommand.Params.Add;
|
|
par.Name := lParams[i].Name;
|
|
par.DataType := fld.DataType;
|
|
par.BlobType := fld.BlobType;
|
|
par.ParamType := daptInput; // ODAC Blobs require it and this is not covered by AssignField below
|
|
{par.Size := fld.Size;
|
|
par.GeneratorName := fld.GeneratorName;}
|
|
par.AssignField(fld);
|
|
par.Name := lParams[i].Name; // Leave this here! Must override the Assign
|
|
end;
|
|
end;
|
|
finally
|
|
lParams.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TDABusinessProcessor.SetupCommands(const aConnection: IDAConnection; const aDelta: IDADelta;
|
|
out anInsertCmd, anUpdateCmd, aDeleteCmd: IDASQLCommand; out aRefreshDs : IDADataset);
|
|
var
|
|
sql: string;
|
|
refstmt: TDAStatement;
|
|
ds: TDADataset;
|
|
needsref: boolean;
|
|
i: integer;
|
|
begin
|
|
anInsertCmd := nil;
|
|
aDeleteCmd := nil;
|
|
anUpdateCmd := nil;
|
|
ds := nil;
|
|
refstmt := nil;
|
|
CheckProperties;
|
|
// Looks for the referenced statement which contains field mappings and TargetTableName
|
|
// in case the user specifies any AutoGenerateXXX option
|
|
needsref := NeedsReferencedDataset;
|
|
if needsref then begin
|
|
ds := TDADataset(fSchema.Datasets.FindItem(fReferencedDataset));
|
|
if (ds <> nil) then begin
|
|
refstmt := fSchema.FindCommandStatement(aConnection,ds);
|
|
// refstmt := TDAStatement(ds.Statements.FindItem(aConnection.Name));
|
|
// if (refstmt = nil) then refstmt := ds.Statements.StatementByName(Schema.ConnectionManager.GetDefaultConnectionName);
|
|
end
|
|
else
|
|
RaiseError(Name + '''s referenced dataset is not speficied or does not exist. Cannot generate SQL');
|
|
end;
|
|
|
|
|
|
for i:= 0 to aDelta.Count-1 do
|
|
if aDelta.Changes[i].ChangeType = ctInsert then begin
|
|
// Tries to locate the specified commands
|
|
if (fInsertCommandName <> '') then anInsertCmd := fSchema.NewCommand(aConnection, fInsertCommandName);
|
|
// Auto generates the SQL for the undefined commands
|
|
if (anInsertCmd = nil) and (poAutoGenerateInsert in fProcessorOptions) then begin
|
|
sql := GenerateSQL(ctInsert, nil, ds, aDelta, refstmt, aConnection);
|
|
anInsertCmd := aConnection.NewCommand(sql, stSQL);
|
|
SetupParameters(anInsertCmd, ds);
|
|
end;
|
|
Break;
|
|
end;
|
|
|
|
for i:= 0 to aDelta.Count-1 do
|
|
if aDelta.Changes[i].ChangeType = ctUpdate then begin
|
|
// Tries to locate the specified commands
|
|
if (fUpdateCommandName <> '') then anUpdateCmd := fSchema.NewCommand(aConnection, fUpdateCommandName);
|
|
// Auto generates the SQL for the undefined commands
|
|
if (anUpdateCmd = nil) and (poAutoGenerateUpdate in fProcessorOptions) then begin
|
|
sql := GenerateSQL(ctUpdate, nil, ds, aDelta, refstmt, aConnection);
|
|
anUpdateCmd := aConnection.NewCommand(sql, stSQL);
|
|
SetupParameters(anUpdateCmd, ds);
|
|
end;
|
|
Break;
|
|
end;
|
|
|
|
for i:= 0 to aDelta.Count-1 do
|
|
if aDelta.Changes[i].ChangeType = ctDelete then begin
|
|
// Tries to locate the specified commands
|
|
if (fDeleteCommandName <> '') then aDeleteCmd := fSchema.NewCommand(aConnection, fDeleteCommandName);
|
|
// Auto generates the SQL for the undefined commands
|
|
if (aDeleteCmd = nil) and (poAutoGenerateDelete in fProcessorOptions) then begin
|
|
sql := GenerateSQL(ctDelete, nil, ds, aDelta, refstmt, aConnection);
|
|
aDeleteCmd := aConnection.NewCommand(sql, stSQL);
|
|
SetupParameters(aDeleteCmd, ds);
|
|
end;
|
|
Break;
|
|
end;
|
|
|
|
if (fRefreshDataset <> '')
|
|
then aRefreshDs := fSchema.NewDataset(aConnection, fRefreshDataset,[],'','',False,True);
|
|
|
|
if (aRefreshDs = nil) and (poAutoGenerateRefreshDataset in fProcessorOptions) then begin
|
|
aRefreshDs := GenerateRefreshDataset(ds, aDelta, refstmt, aConnection);
|
|
if (aRefreshDs<>NIL) then SetupParameters(aRefreshDs, ds);
|
|
end;
|
|
|
|
// Finally prepares them
|
|
if (poPrepareCommands in fProcessorOptions) then begin
|
|
if (anInsertCmd <> nil) then anInsertCmd.Prepared := TRUE;
|
|
if (anUpdateCmd <> nil) then anUpdateCmd.Prepared := TRUE;
|
|
if (aDeleteCmd <> nil) then aDeleteCmd.Prepared := TRUE;
|
|
if (aRefreshDs <> NIL) then aRefreshDs.Prepared := TRUE;
|
|
end;
|
|
end;
|
|
|
|
function TDABusinessProcessor.GetCurrentChange: TDADeltaChange;
|
|
begin
|
|
result := fCurrentDelta.Changes[fCurrentChange]
|
|
end;
|
|
|
|
const
|
|
lDynWhereParamPrefix = 'dynwhere';
|
|
|
|
procedure TDABusinessProcessor.RefreshDeltaChange(const aConnection : IDAConnection;
|
|
const aRefreshDataset : IDADataset; const aDelta : IDADelta; aDeltaChange : TDADeltaChange; GenAutoIncValue : integer = -1);
|
|
var i : integer;
|
|
par : TDAParam;
|
|
nam: string;
|
|
val : Variant;
|
|
fld: TDAField;
|
|
// genname : string;
|
|
begin
|
|
// Sets the parameters of the dataset
|
|
for i := 0 to aRefreshDataset.Params.Count-1 do begin
|
|
par := aRefreshDataset.Params[i];
|
|
|
|
if (aDelta.IndexOfLoggedField(par.Name) < 0) then
|
|
Continue;
|
|
|
|
if (aDeltaChange.ChangeType=ctInsert) then begin
|
|
if (par.DataType=datAutoInc) or (Par.DataType = datLargeAutoInc) then begin
|
|
if (GenAutoIncValue<>-1)
|
|
then val := GenAutoIncValue
|
|
else val := aConnection.GetLastAutoInc(par.GeneratorName);
|
|
end
|
|
else val := aDeltaChange.NewValueByName[par.Name]; // Fix for Schuff
|
|
end
|
|
else begin
|
|
val := aDeltaChange.NewValueByName[par.Name];
|
|
end;
|
|
|
|
par.Value := val;
|
|
end;
|
|
|
|
if FDynamicWhereInRefreshDataset then
|
|
for i := 0 to aDelta.KeyFieldCount - 1 do begin
|
|
par:=aRefreshDataset.ParamByName(lDynWhereParamPrefix+intToStr(i));
|
|
par.Value := aDeltaChange.NewValueByName[aDelta.KeyFieldNames[i]];
|
|
end;
|
|
|
|
if Assigned(fOnRefreshDeltaChange) then
|
|
fOnRefreshDeltaChange(Self, aDeltaChange.ChangeType, aDeltaChange, aRefreshDataset);
|
|
|
|
// Opens the dataset
|
|
aRefreshDataset.Open;
|
|
try
|
|
case aDeltaChange.ChangeType of
|
|
ctInsert, ctUpdate : begin
|
|
// ctInsert:
|
|
// Swaps the new values the client sent in the old values then updates the
|
|
// new values. This way the client can locate the record it sent and also
|
|
// read what's new. This could probabily be optimized
|
|
|
|
// ctUpdate:
|
|
// Leaves the old values alone because they are needed on the client
|
|
// for merge purposes. This could probabily be optimized
|
|
for I := 0 to aDelta.LoggedFieldCount - 1 do begin
|
|
nam := aDelta.LoggedFieldNames[i];
|
|
fld := aRefreshDataset.FindField(nam);
|
|
if fld = nil then Continue;
|
|
aDeltaChange.OldValueByName[nam] := aDeltaChange.NewValueByName[nam];
|
|
aDeltaChange.NewValueByName[nam] := fld.Value;
|
|
end;
|
|
end;
|
|
end;
|
|
finally
|
|
aRefreshDataset.Close;
|
|
end;
|
|
end;
|
|
|
|
procedure TDABusinessProcessor.SynchronizeAutoIncs(const aMasterDelta, aDetailDelta : IDADelta;
|
|
const aRelationship : TDADatasetRelationship);
|
|
var x, k, z: integer;
|
|
masterds : TDADataset;
|
|
masterfields, detailfields : TStringList;
|
|
oldmasterval, masterval, detailval : Variant;
|
|
begin
|
|
CheckProperties;
|
|
masterfields := TStringList.Create;
|
|
masterfields.Delimiter := ';';
|
|
|
|
detailfields := TStringList.Create;
|
|
detailfields.Delimiter := ';';
|
|
|
|
try
|
|
masterds := Schema.Datasets.DatasetByName(aMasterDelta.LogicalName);
|
|
|
|
masterfields.DelimitedText := aRelationship.MasterFields;
|
|
detailfields.DelimitedText := aRelationship.DetailFields;
|
|
|
|
for x := 0 to (aMasterDelta.Count-1) do begin
|
|
if not (aMasterDelta[x].Status=csResolved) then Continue;
|
|
|
|
for k := 0 to (masterfields.Count-1) do begin
|
|
if not (masterds.FieldByName(masterfields[k]).DataType in [datAutoInc, datLargeAutoInc, datInteger, datLargeInt]) then Continue;
|
|
|
|
masterval := aMasterDelta[x].NewValueByName[masterfields[k]];
|
|
oldmasterval := aMasterDelta[x].OldValueByName[masterfields[k]];
|
|
|
|
for z := 0 to (aDetailDelta.Count-1) do begin
|
|
if (aDetailDelta[z].ChangeType<>ctInsert) then Continue;
|
|
|
|
detailval := aDetailDelta[z].NewValueByName[detailfields[k]];
|
|
if (detailval<>oldmasterval) then Continue;
|
|
|
|
aDetailDelta[z].NewValueByName[detailfields[k]] := masterval;
|
|
aDetailDelta[z].RefreshedByServer := TRUE;
|
|
end;
|
|
end;
|
|
end;
|
|
finally
|
|
masterfields.Free;
|
|
detailfields.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TDABusinessProcessor.ProcessDelta(const aConnection: IDAConnection;
|
|
const aDelta: IDADelta;
|
|
ChangeTypes: TDAChangeTypes = AllChanges);
|
|
var
|
|
i, x, rowsaffected: integer;
|
|
canremove, ok: boolean;
|
|
currcmd, inscmd, delcmd, updcmd: IDASQLCommand;
|
|
refds : IDADataset;
|
|
insmap, delmap, updmap, refmap: TDAParamMappingArray;
|
|
mapptr: ^TDAParamMappingArray;
|
|
change: TDADeltaChange;
|
|
todelete: TList;
|
|
val : variant;
|
|
parname : string;
|
|
autoincvalue: integer;
|
|
usegenerators : boolean;
|
|
lexpr: TDAWhereExpression;
|
|
begin
|
|
Check(not Assigned(aDelta), 'Cannot process a NIL delta');
|
|
if (aDelta.Count=0) then Exit;
|
|
|
|
|
|
if Assigned(fSchema.UnionDataTables.FindItem(fReferencedDataset)) then begin
|
|
ProcessDeltaForUnion(aConnection, aDelta, ChangeTypes);
|
|
exit;
|
|
end;
|
|
|
|
fCurrentDelta := aDelta.GetDelta;
|
|
fCurrentChange := 0;
|
|
|
|
usegenerators := Supports(aConnection, IDAUseGenerators);
|
|
|
|
// Fires the "before" events
|
|
if Assigned(fOnBeforeProcessDelta)
|
|
then fOnBeforeProcessDelta(Self, aDelta)
|
|
|
|
else if Assigned(fBusinessRules)
|
|
then fBusinessRules.BeforeProcessDelta(Self, aDelta);
|
|
|
|
// Prepares the commands
|
|
if not fHasReducedDelta then begin
|
|
SetupCommands(aConnection, aDelta, inscmd, updcmd, delcmd, refds);
|
|
|
|
CreateMappings(aDelta, insmap, inscmd, nil, True);
|
|
CreateMappings(aDelta, updmap, updcmd, nil, True);
|
|
CreateMappings(aDelta, delmap, delcmd, nil, True);
|
|
CreateMappings(aDelta, refmap, refds);
|
|
|
|
FDynamicWhereInRefreshDataset:=False;
|
|
// dynwhere
|
|
if (refds <> nil) and refds.SQLContainsDynamicWhere then begin
|
|
FDynamicWhereInRefreshDataset := True;
|
|
refds.DynamicWhere.Expression:=nil;
|
|
for i := 0 to aDelta.KeyFieldCount - 1 do begin
|
|
with refds.Params.Add do begin
|
|
ParamType:=daptInput;
|
|
Name:=lDynWhereParamPrefix+IntToStr(i);
|
|
end;
|
|
with refds.DynamicWhere do begin
|
|
lexpr:= NewBinaryExpression(NewField('',aDelta.KeyFieldNames[i]),NewParameter(lDynWhereParamPrefix+IntToStr(i)),dboEqual);
|
|
if Expression = nil then
|
|
Expression := lexpr
|
|
else
|
|
Expression:= NewBinaryExpression(Expression,lexpr,dboEqual);
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
// Processes the delta
|
|
todelete := TList.Create;
|
|
try
|
|
for i := 0 to (aDelta.Count - 1) do begin
|
|
change := aDelta[i];
|
|
fCurrentChange := i; // Do NOT remove!!!
|
|
canremove := false;
|
|
autoincvalue := -1;
|
|
|
|
ok := change.ChangeType in ChangeTypes; // Filters
|
|
try
|
|
|
|
// Even if there might not be a command associated, the user might want to do something with this
|
|
// We just give an override chance here
|
|
if Assigned(fOnBeforeProcessChange) then
|
|
fOnBeforeProcessChange(Self, change.ChangeType, aDelta[i], ok)
|
|
|
|
else if Assigned(fBusinessRules) then
|
|
fBusinessRules.BeforeProcessChange(Self, change.ChangeType, aDelta[i], ok);
|
|
|
|
if ok then begin
|
|
// Selects the right command
|
|
mapptr := nil;
|
|
if not fHasReducedDelta then begin
|
|
case change.ChangeType of
|
|
ctInsert: begin
|
|
currcmd := inscmd;
|
|
mapptr := @insmap;
|
|
end;
|
|
ctUpdate: begin
|
|
currcmd := updcmd;
|
|
mapptr := @updmap;
|
|
end;
|
|
ctDelete: begin
|
|
currcmd := delcmd;
|
|
mapptr := @delmap;
|
|
end;
|
|
end;
|
|
|
|
end else begin
|
|
SetupCommands(aConnection, Change, currCmd);
|
|
CreateMappings(aDelta, insmap, currCmd, nil, True);
|
|
mapptr := @insmap;
|
|
end;
|
|
// Assigns the values of the current change
|
|
if (currcmd <> nil) then
|
|
for x := 0 to (currcmd.Params.Count - 1) do
|
|
if (mapptr^[x].DeltaIndex >= 0) then begin
|
|
case mapptr^[x].MappingType of
|
|
mtOldValue: val := change.OldValues[mapptr^[x].DeltaIndex];
|
|
else val := change.NewValues[mapptr^[x].DeltaIndex];
|
|
end;
|
|
|
|
if (currcmd.Params[x].DataType in [datAutoinc, datLargeAutoInc]) and (usegenerators) and (change.ChangeType=ctInsert) and (mapptr^[x].MappingType=mtNewValue) then begin
|
|
// Gets the next generator values from the DB. This is for DBs such as IB or Oracle which lack autoinc fields
|
|
if (autoincvalue<>-1) then raise Exception.Create('Multiple auto incremental fields not supported');
|
|
parname := currcmd.Params[x].Name;
|
|
change.OldValueByName[parname] := val;
|
|
autoincvalue := (aConnection as IDAUseGenerators).GetNextAutoinc(currcmd.Params[x].GeneratorName);
|
|
currcmd.Params[x].Value := autoincvalue;
|
|
change.RefreshedByServer := TRUE;
|
|
if not Assigned(refds) then begin
|
|
change.NewValueByName[parname] := autoincvalue;
|
|
end;
|
|
end
|
|
else currcmd.Params[x].Value := val;
|
|
end;
|
|
|
|
// Gives the user a chance to modify it before execution
|
|
if Assigned(fOnProcessChange) then
|
|
fOnProcessChange(Self, change.ChangeType, change, currcmd)
|
|
else if Assigned(fBusinessRules) then
|
|
fBusinessRules.ProcessChange(Self, change.ChangeType, change, currcmd);
|
|
|
|
// Executes it
|
|
if (currcmd <> nil)
|
|
then rowsaffected := currcmd.Execute
|
|
else rowsaffected := 0;
|
|
|
|
canremove := FALSE;
|
|
|
|
// IBX returns -1 even if updates are successful! This started to happen after I plugged the GetNextAutoinc call above.
|
|
// If records fail, an exception is usually generated so this check for <>0 should be sufficient for all cases...
|
|
// DO NOT CHANGE this to >0 !!!!
|
|
if (rowsaffected<>0) or (poIgnoreRowsAffected in fProcessorOptions) then begin
|
|
if (change.ChangeType<>ctDelete) and (refds<>NIL) then begin
|
|
RefreshDeltaChange(aConnection, refds, aDelta, change, autoincvalue);
|
|
change.RefreshedByServer := TRUE;
|
|
end;
|
|
|
|
canremove := not change.RefreshedByServer;
|
|
change.Status := csResolved;
|
|
end
|
|
else begin
|
|
canremove := FALSE;
|
|
change.Status := csFailed;
|
|
change.Message := 'No rows were affected by this update';
|
|
end;
|
|
|
|
// After processing gives the user a last chance to update the change and
|
|
// optionally set it so that it goes back to the client (i.e. updated values)
|
|
if Assigned(fOnAfterProcessChange)
|
|
then fOnAfterProcessChange(Self, change, ok, canremove)
|
|
|
|
else if Assigned(fBusinessRules)
|
|
then fBusinessRules.AfterProcessChange(Self, change, ok, canremove)
|
|
end;
|
|
|
|
except
|
|
on E: Exception do begin
|
|
change.Status := csFailed;
|
|
change.Message := E.Message;
|
|
canremove := FALSE;
|
|
|
|
if Assigned(fOnProcessError) then
|
|
fOnProcessError(Self, change.ChangeType, change, currcmd, canremove, E)
|
|
else if Assigned(fBusinessRules) then begin
|
|
fBusinessRules.ProcessError(Self, change.ChangeType, change, currcmd, canremove, E);
|
|
if FRaiseExceptionAtError then raise;
|
|
end
|
|
else
|
|
raise EDAApplyUpdateFailed.Create(change, E);
|
|
end;
|
|
end;
|
|
|
|
if canremove then todelete.Add(change);
|
|
end;
|
|
|
|
for i := 0 to todelete.Count - 1 do
|
|
aDelta.RemoveChange(TDADeltaChange(todelete[i]));
|
|
|
|
if Assigned(fOnAfterProcessDelta) then
|
|
fOnAfterProcessDelta(Self, aDelta)
|
|
else if Assigned(fBusinessRules) then
|
|
fBusinessRules.AfterProcessDelta(Self, aDelta)
|
|
|
|
finally
|
|
todelete.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TDABusinessProcessor.SetDeleteCommandName(const Value: string);
|
|
begin
|
|
fDeleteCommandName := Trim(Value);
|
|
end;
|
|
|
|
procedure TDABusinessProcessor.SetInsertCommandName(const Value: string);
|
|
begin
|
|
fInsertCommandName := Trim(Value);
|
|
end;
|
|
|
|
procedure TDABusinessProcessor.SetUpdateCommandName(const Value: string);
|
|
begin
|
|
fUpdateCommandName := Trim(Value);
|
|
end;
|
|
|
|
procedure TDABusinessProcessor.SetSchema(const Value: TDASchema);
|
|
begin
|
|
fSchema := Value;
|
|
|
|
if (Value <> nil) then fSchema.FreeNotification(Self);
|
|
end;
|
|
|
|
procedure TDABusinessProcessor.SetReferencedDataset(const Value: string);
|
|
begin
|
|
fReferencedDataset := Value;
|
|
end;
|
|
|
|
procedure TDABusinessProcessor.ProcessDelta(aDataTable: TDADataTable;
|
|
ChangeTypes: TDAChangeTypes; const aConnection : IDAConnection = NIL);
|
|
var
|
|
conn: IDAConnection;
|
|
begin
|
|
with aDataTable do begin
|
|
Check(LocalSchema = nil, 'Datatable doesn''t reference any schema');
|
|
Check(LocalSchema.ConnectionManager = nil, 'Datatable''s schema doesn''t reference a connection manager');
|
|
|
|
if aConnection<>NIL
|
|
then conn := aConnection
|
|
else conn := LocalSchema.ConnectionManager.NewConnection(LocalConnection);
|
|
|
|
ProcessDelta(conn, aDataTable.Delta, ChangeTypes);
|
|
end;
|
|
end;
|
|
|
|
function TDABusinessProcessor.QueryInterface(const IID: TGUID; out Obj): HResult;
|
|
begin
|
|
result := inherited QueryInterface(IID, Obj);
|
|
|
|
if (result <> S_OK) and Assigned(fBusinessRules) then begin
|
|
// Users might introduce specific interfaces at the business rule level
|
|
// This allows to type cast the data table to any additional business oriented interface
|
|
// they decide to create.
|
|
result := fBusinessRules.QueryInterface(IID, Obj);
|
|
end;
|
|
end;
|
|
|
|
procedure TDABusinessProcessor.SetBusinessRulesID(const Value: string);
|
|
var
|
|
bizclass: TDABusinessProcessorRulesClass;
|
|
begin
|
|
if (Value = fBusinessRulesID) then Exit;
|
|
|
|
if Assigned(fBusinessRules) then begin
|
|
fBusinessRules.Detach(Self);
|
|
FreeAndNIL(fBusinessRules);
|
|
end;
|
|
|
|
fBusinessRulesID := Trim(Value);
|
|
|
|
if (fBusinessRulesID <> '') and not (csDesigning in ComponentState) then begin
|
|
Check(not FindBusinessProcessorRules(Value, bizclass), Name+'. Invalid BusinessRulesID "%s"', [Value]);
|
|
|
|
fBusinessRules := bizclass.Create(Self);
|
|
fBusinessRules.Attach(Self);
|
|
end;
|
|
end;
|
|
|
|
procedure TDABusinessProcessor.SetRefreshDataset(const Value: string);
|
|
begin
|
|
fRefreshDataset := Trim(Value);
|
|
end;
|
|
|
|
procedure TDABusinessProcessor.Notification(AComponent: TComponent;
|
|
Operation: TOperation);
|
|
begin
|
|
inherited;
|
|
if (Operation = opRemove) then begin
|
|
if (AComponent = fSchema) then fSchema := nil;
|
|
end;
|
|
end;
|
|
|
|
procedure TDABusinessProcessor.CheckProperties;
|
|
begin
|
|
Check(Schema = nil, Name + '.Schema must be assinged.');
|
|
end;
|
|
|
|
procedure TDABusinessProcessor.SetupCommandsWithMapping(
|
|
const aConnection: IDAConnection;
|
|
const aChange: TDADeltaChange;
|
|
out anCmd: IDASQLCommand;
|
|
out aParamMapping: TDAParamMappingArray);
|
|
var
|
|
lSql: string;
|
|
lDataset: TDADataset;
|
|
lRefStatement: TDAStatement;
|
|
lUnionTable: TDAUnionDataTable;
|
|
lVar: Variant;
|
|
|
|
lSourceTableIdx: Integer;
|
|
lUnionSourceTable : TDAUnionSourceTable;
|
|
lSourceTable : TDADataset;
|
|
lSourceTableName: String;
|
|
lSourceTableMapping: TDAColumnMappingCollection;
|
|
begin
|
|
anCmd := nil;
|
|
lRefStatement := nil;
|
|
lSourceTableMapping := nil;
|
|
CheckProperties;
|
|
lDataset := TDADataset(fSchema.FindDataset(fReferencedDataset));
|
|
|
|
// If Union table ...
|
|
if lDataset is TDAUnionDataTable then begin
|
|
lUnionTable := TDAUnionDataTable(lDataset);
|
|
lVar := aChange.NewValueByName[def_SourceTableFieldName];
|
|
if (VarIsNull(lVar)) then begin
|
|
lSourceTableName := lUnionTable.DefaultSourceTable;
|
|
lUnionSourceTable := TDAUnionSourceTable(lUnionTable.SourceTables.ItemByName(lSourceTableName));
|
|
end else begin
|
|
lSourceTableIdx := lVar;
|
|
lUnionSourceTable := lUnionTable.SourceTables[lSourceTableIdx];
|
|
lSourceTableName := lUnionSourceTable.Name;
|
|
end;
|
|
lSourceTableMapping := lUnionSourceTable.ColumnMappings;
|
|
|
|
lSourceTable := TDADataset(fSchema.Datasets.FindItem(lSourceTableName));
|
|
if Assigned(lSourceTable) then begin
|
|
|
|
lDataset := lSourceTable;
|
|
|
|
lRefStatement := TDAStatement(lSourceTable.Statements.FindItem(aConnection.Name));
|
|
if (lRefStatement = nil) then lRefStatement := lSourceTable.Statements.StatementByName(Schema.ConnectionManager.GetDefaultConnectionName);
|
|
end;
|
|
|
|
|
|
|
|
end else
|
|
|
|
// If plain table ...
|
|
if lDataset is TDADataset then begin
|
|
lRefStatement := TDAStatement(lDataset.Statements.FindItem(aConnection.Name));
|
|
if (lRefStatement = nil) then lRefStatement := lDataset.Statements.StatementByName(Schema.ConnectionManager.GetDefaultConnectionName);
|
|
|
|
// If no dataset found
|
|
end else
|
|
RaiseError(Name + '''s referenced dataset is not speficied or does not exist. Cannot generate SQL');
|
|
|
|
lSql := GenerateSQL(aChange.ChangeType, aChange, lDataset, aChange.Delta, lRefStatement, aConnection);
|
|
anCmd := aConnection.NewCommand(lSql, stSQL);
|
|
SetupParameters(anCmd, lDataset);
|
|
if anCmd <> nil then anCmd.Prepared:=True;
|
|
CreateMappings(aChange.Delta, aParamMapping, anCmd, lSourceTableMapping, True);
|
|
end;
|
|
|
|
procedure TDABusinessProcessor.SetupCommands(
|
|
const aConnection: IDAConnection; const aChange: TDADeltaChange;
|
|
out anCmd: IDASQLCommand);
|
|
var
|
|
sql: string;
|
|
ds: TDADataset;
|
|
refstmt: TDAStatement;
|
|
begin
|
|
anCmd:=nil;
|
|
refstmt := nil;
|
|
CheckProperties;
|
|
ds := TDADataset(fSchema.Datasets.FindItem(fReferencedDataset));
|
|
if (ds <> nil) then begin
|
|
refstmt := fSchema.FindCommandStatement(aConnection,ds,aConnection.Name);
|
|
// refstmt := TDAStatement(ds.Statements.FindItem(aConnection.Name));
|
|
// if (refstmt = nil) then refstmt := ds.Statements.StatementByName(Schema.ConnectionManager.GetDefaultConnectionName);
|
|
end
|
|
else
|
|
RaiseError(Name + '''s referenced dataset is not speficied or does not exist. Cannot generate SQL');
|
|
sql := GenerateSQL(aChange.ChangeType, aChange, ds,aChange.Delta ,refstmt, aConnection);
|
|
anCmd := aConnection.NewCommand(sql, stSQL);
|
|
SetupParameters(anCmd, ds);
|
|
if anCmd <> nil then anCmd.Prepared:=True;
|
|
end;
|
|
|
|
|
|
|
|
{ TDABusinessProcessorRules }
|
|
|
|
procedure TDABusinessProcessorRules.Attach(
|
|
aBusinessProcessor: TDABusinessProcessor);
|
|
begin
|
|
|
|
end;
|
|
|
|
constructor TDABusinessProcessorRules.Create(aBusinessProcessor: TDABusinessProcessor);
|
|
begin
|
|
inherited Create;
|
|
fBusinessProcessor := aBusinessProcessor;
|
|
end;
|
|
|
|
procedure TDABusinessProcessorRules.Detach(
|
|
aBusinessProcessor: TDABusinessProcessor);
|
|
begin
|
|
|
|
end;
|
|
|
|
procedure TDABusinessProcessorRules.AfterProcessChange(
|
|
Sender: TDABusinessProcessor; aChange: TDADeltaChange;
|
|
Processed: boolean; var CanRemoveFromDelta: boolean);
|
|
begin
|
|
|
|
end;
|
|
|
|
procedure TDABusinessProcessorRules.AfterProcessDelta(
|
|
Sender: TDABusinessProcessor; const aDelta: IDADelta);
|
|
begin
|
|
|
|
end;
|
|
|
|
procedure TDABusinessProcessorRules.BeforeProcessChange(
|
|
Sender: TDABusinessProcessor; aChangeType: TDAChangeType;
|
|
aChange: TDADeltaChange; var ProcessChange: boolean);
|
|
begin
|
|
|
|
end;
|
|
|
|
procedure TDABusinessProcessorRules.BeforeProcessDelta(
|
|
Sender: TDABusinessProcessor; const aDelta: IDADelta);
|
|
begin
|
|
|
|
end;
|
|
|
|
procedure TDABusinessProcessorRules.GenerateSQL(
|
|
Sender: TDABusinessProcessor; ChangeType: TDAChangeType;
|
|
const ReferencedStatement: TDAStatement; const aDelta: IDADelta;
|
|
var SQL: string);
|
|
begin
|
|
|
|
end;
|
|
|
|
procedure TDABusinessProcessorRules.ProcessChange(
|
|
Sender: TDABusinessProcessor; aChangeType: TDAChangeType;
|
|
aChange: TDADeltaChange; const aCommand: IDASQLCommand);
|
|
begin
|
|
|
|
end;
|
|
|
|
procedure TDABusinessProcessorRules.ProcessError(
|
|
Sender: TDABusinessProcessor; aChangeType: TDAChangeType;
|
|
aChange: TDADeltaChange; const aCommand: IDASQLCommand;
|
|
var CanRemoveFromDelta: boolean; Error: Exception);
|
|
begin
|
|
|
|
end;
|
|
|
|
//----------
|
|
|
|
procedure TDABusinessProcessorRules.NotSupportedByBusinessProcessor;
|
|
begin
|
|
RaiseError('This method is not supported on the Business Processor Rule, but only on the client.');
|
|
end;
|
|
|
|
procedure TDABusinessProcessorRules.Append;
|
|
begin
|
|
NotSupportedByBusinessProcessor();
|
|
end;
|
|
|
|
procedure TDABusinessProcessorRules.Cancel;
|
|
begin
|
|
NotSupportedByBusinessProcessor();
|
|
end;
|
|
|
|
procedure TDABusinessProcessorRules.Delete;
|
|
begin
|
|
NotSupportedByBusinessProcessor();
|
|
end;
|
|
|
|
procedure TDABusinessProcessorRules.Edit;
|
|
begin
|
|
NotSupportedByBusinessProcessor();
|
|
end;
|
|
|
|
procedure TDABusinessProcessorRules.First;
|
|
begin
|
|
NotSupportedByBusinessProcessor();
|
|
end;
|
|
|
|
function TDABusinessProcessorRules.GetBOF: Boolean;
|
|
begin
|
|
result := false; { to avoid warning; }
|
|
NotSupportedByBusinessProcessor();
|
|
end;
|
|
|
|
function TDABusinessProcessorRules.GetEOF: Boolean;
|
|
begin
|
|
result := false; { to avoid warning; }
|
|
NotSupportedByBusinessProcessor();
|
|
end;
|
|
|
|
function TDABusinessProcessorRules.GetRecordCount: Integer;
|
|
begin
|
|
result := -1; { to avoid warning; }
|
|
NotSupportedByBusinessProcessor();
|
|
end;
|
|
|
|
procedure TDABusinessProcessorRules.Insert;
|
|
begin
|
|
NotSupportedByBusinessProcessor();
|
|
end;
|
|
|
|
procedure TDABusinessProcessorRules.Last;
|
|
begin
|
|
NotSupportedByBusinessProcessor();
|
|
end;
|
|
|
|
function TDABusinessProcessorRules.Locate(const aKeyFields: String; const aKeyValues: Variant; aOptions: TLocateOptions): Boolean;
|
|
begin
|
|
result := false; { to avoid warning; }
|
|
NotSupportedByBusinessProcessor();
|
|
end;
|
|
|
|
procedure TDABusinessProcessorRules.Next;
|
|
begin
|
|
NotSupportedByBusinessProcessor();
|
|
end;
|
|
|
|
procedure TDABusinessProcessorRules.Post;
|
|
begin
|
|
NotSupportedByBusinessProcessor();
|
|
end;
|
|
|
|
procedure TDABusinessProcessorRules.Prior;
|
|
begin
|
|
NotSupportedByBusinessProcessor();
|
|
end;
|
|
|
|
function TDABusinessProcessorRules.GetDetailOptions: TDADetailOptions;
|
|
begin
|
|
result := []
|
|
end;
|
|
|
|
function TDABusinessProcessorRules.GetMasterOptions: TDAMasterOptions;
|
|
begin
|
|
result := []
|
|
end;
|
|
|
|
procedure TDABusinessProcessorRules.SetDetailOptions(
|
|
Value: TDADetailOptions);
|
|
begin
|
|
NotSupportedByBusinessProcessor();
|
|
end;
|
|
|
|
procedure TDABusinessProcessorRules.SetMasterOptions(
|
|
Value: TDAMasterOptions);
|
|
begin
|
|
NotSupportedByBusinessProcessor();
|
|
end;
|
|
|
|
function TDABusinessProcessorRules.GetRecNo: integer;
|
|
begin
|
|
result := -1; { to avoid warning; }
|
|
NotSupportedByBusinessProcessor();
|
|
end;
|
|
|
|
procedure TDABusinessProcessorRules.SetRecNo(Value: integer);
|
|
begin
|
|
NotSupportedByBusinessProcessor();
|
|
end;
|
|
|
|
function TDABusinessProcessorRules.GetIsEmpty: boolean;
|
|
begin
|
|
result := FALSE;
|
|
NotSupportedByBusinessProcessor();
|
|
end;
|
|
|
|
function TDABusinessProcessorRules.GetState: TDatasetState;
|
|
begin
|
|
result := dsBrowse
|
|
end;
|
|
|
|
function TDABusinessProcessorRules.Lookup(const KeyFields: string;
|
|
const KeyValues: Variant; const ResultFields: string): Variant;
|
|
begin
|
|
Result:=Null;
|
|
NotSupportedByBusinessProcessor();
|
|
end;
|
|
|
|
procedure TDABusinessProcessorRules.ClearField(
|
|
const FieldIndexOrName: Variant);
|
|
begin
|
|
NotSupportedByBusinessProcessor();
|
|
end;
|
|
|
|
function TDABusinessProcessorRules.IsFieldNull(
|
|
const FieldIndexOrName: Variant): boolean;
|
|
begin
|
|
result := FALSE;
|
|
NotSupportedByBusinessProcessor();
|
|
end;
|
|
|
|
function TDABusinessProcessorRules.GetDataTable: TDADataTable;
|
|
begin
|
|
result := NIL;
|
|
NotSupportedByBusinessProcessor();
|
|
end;
|
|
|
|
procedure TDABusinessProcessorRules.Close;
|
|
begin
|
|
NotSupportedByBusinessProcessor();
|
|
end;
|
|
|
|
procedure TDABusinessProcessorRules.Open;
|
|
begin
|
|
NotSupportedByBusinessProcessor();
|
|
end;
|
|
|
|
function TDABusinessProcessorRules.GetActive: boolean;
|
|
begin
|
|
NotSupportedByBusinessProcessor();
|
|
result:=FALSE
|
|
end;
|
|
|
|
procedure TDABusinessProcessorRules.SetActive(const Value: boolean);
|
|
begin
|
|
NotSupportedByBusinessProcessor();
|
|
end;
|
|
|
|
procedure TDABusinessProcessor.ProcessDeltaForUnion(const aConnection: IDAConnection;
|
|
const aDelta: IDADelta;
|
|
ChangeTypes: TDAChangeTypes = AllChanges);
|
|
var
|
|
i, x, rowsaffected: integer;
|
|
canremove, ok: boolean;
|
|
refds : IDADataset;
|
|
insmap: TDAParamMappingArray;
|
|
mapptr: ^TDAParamMappingArray;
|
|
change: TDADeltaChange;
|
|
todelete: TList;
|
|
val : variant;
|
|
parname : string;
|
|
autoincvalue: integer;
|
|
usegenerators : boolean;
|
|
currcmd: IDASQLCommand;
|
|
lCommands: TDADeltaProcessorItemCollection;
|
|
lUnionTable: TDAUnionDataTable;
|
|
lSourceTableName: String;
|
|
lProcessorItem: TDADeltaProcessorItem;
|
|
lVar: Variant;
|
|
begin
|
|
Check(not Assigned(aDelta), 'Cannot process a NIL delta');
|
|
if (aDelta.Count=0) then Exit;
|
|
|
|
fCurrentDelta := aDelta.GetDelta;
|
|
fCurrentChange := 0;
|
|
|
|
usegenerators := Supports(aConnection, IDAUseGenerators);
|
|
|
|
// Fires the "before" events
|
|
if Assigned(fOnBeforeProcessDelta)
|
|
then fOnBeforeProcessDelta(Self, aDelta)
|
|
|
|
else if Assigned(fBusinessRules)
|
|
then fBusinessRules.BeforeProcessDelta(Self, aDelta);
|
|
|
|
lUnionTable := fSchema.UnionDataTables.ItemByName(fReferencedDataset) as TDAUnionDataTable;
|
|
//lSourceTableFieldIdx := lUnionTable.Fields.FieldByName(def_SourceTableFieldName).Index;
|
|
|
|
try
|
|
// Prepares the commands
|
|
if not fHasReducedDelta then begin
|
|
SetupCommands(aConnection, aDelta, lCommands);
|
|
end;
|
|
// Processes the delta
|
|
todelete := TList.Create;
|
|
try
|
|
for i := 0 to (aDelta.Count - 1) do begin
|
|
change := aDelta[i];
|
|
fCurrentChange := i; // Do NOT remove!!!
|
|
canremove := false;
|
|
autoincvalue := -1;
|
|
|
|
ok := change.ChangeType in ChangeTypes; // Filters
|
|
try
|
|
|
|
// Even if there might not be a command associated, the user might want to do something with this
|
|
// We just give an override chance here
|
|
if Assigned(fOnBeforeProcessChange) then
|
|
fOnBeforeProcessChange(Self, change.ChangeType, aDelta[i], ok)
|
|
|
|
else if Assigned(fBusinessRules) then
|
|
fBusinessRules.BeforeProcessChange(Self, change.ChangeType, aDelta[i], ok);
|
|
|
|
if ok then begin
|
|
// Selects the right command
|
|
mapptr := nil;
|
|
if not fHasReducedDelta then begin
|
|
|
|
// Define appripriate source table and get its commands
|
|
|
|
//lVar := aDelta.Changes[i].NewValues[lSourceTableFieldIdx];
|
|
if (aDelta.Changes[i].ChangeType = ctInsert) then
|
|
lVar := aDelta.Changes[i].NewValueByName[def_SourceTableFieldName]
|
|
else
|
|
lVar := aDelta.Changes[i].OldValueByName[def_SourceTableFieldName];
|
|
|
|
if (VarIsNull(lVar)) then
|
|
lSourceTableName := lUnionTable.DefaultSourceTable
|
|
else
|
|
lSourceTableName := lUnionTable.SourceTables[lVar].Name;
|
|
|
|
lProcessorItem := TDADeltaProcessorItem(lCommands.FindItem(lSourceTableName));
|
|
|
|
case change.ChangeType of
|
|
ctInsert: begin
|
|
currcmd := lProcessorItem.fInsertCommand;
|
|
mapptr := @lProcessorItem.fInsertCommandMapping;
|
|
end;
|
|
ctUpdate: begin
|
|
currcmd := lProcessorItem.fUpdateCommand;
|
|
mapptr := @lProcessorItem.fUpdateCommandMapping;
|
|
end;
|
|
ctDelete: begin
|
|
currcmd := lProcessorItem.fDeleteCommand;
|
|
mapptr := @lProcessorItem.fDeleteCommandMapping;
|
|
|
|
end;
|
|
end;
|
|
end else begin
|
|
SetupCommandsWithMapping(aConnection, Change, currCmd, insmap);
|
|
mapptr := @insmap;
|
|
end;
|
|
// Assigns the values of the current change
|
|
if (currcmd <> nil) then
|
|
for x := 0 to (currcmd.Params.Count - 1) do
|
|
if (mapptr^[x].DeltaIndex >= 0) then begin
|
|
case mapptr^[x].MappingType of
|
|
mtOldValue: val := change.OldValues[mapptr^[x].DeltaIndex];
|
|
else val := change.NewValues[mapptr^[x].DeltaIndex];
|
|
end;
|
|
|
|
if (currcmd.Params[x].DataType in [datAutoinc, datLargeAutoInc]) and (usegenerators) and (change.ChangeType=ctInsert) and (mapptr^[x].MappingType=mtNewValue) then begin
|
|
// Gets the next generator values from the DB. This is for DBs such as IB or Oracle which lack autoinc fields
|
|
if (autoincvalue<>-1) then raise Exception.Create('Multiple auto incremental fields not supported');
|
|
parname := currcmd.Params[x].Name;
|
|
change.OldValueByName[parname] := val;
|
|
autoincvalue := (aConnection as IDAUseGenerators).GetNextAutoinc(currcmd.Params[x].GeneratorName);
|
|
currcmd.Params[x].Value := autoincvalue;
|
|
change.RefreshedByServer := TRUE;
|
|
if not Assigned(refds) then begin
|
|
change.NewValueByName[parname] := autoincvalue;
|
|
end;
|
|
end
|
|
else currcmd.Params[x].Value := val;
|
|
end;
|
|
|
|
// Gives the user a chance to modify it before execution
|
|
if Assigned(fOnProcessChange) then
|
|
fOnProcessChange(Self, change.ChangeType, change, currcmd)
|
|
else if Assigned(fBusinessRules) then
|
|
fBusinessRules.ProcessChange(Self, change.ChangeType, change, currcmd);
|
|
|
|
// Executes it
|
|
if (currcmd <> nil)
|
|
then rowsaffected := currcmd.Execute
|
|
else rowsaffected := 0;
|
|
|
|
canremove := FALSE;
|
|
|
|
// IBX returns -1 even if updates are successful! This started to happen after I plugged the GetNextAutoinc call above.
|
|
// If records fail, an exception is usually generated so this check for <>0 should be sufficient for all cases...
|
|
// DO NOT CHANGE this to >0 !!!!
|
|
if (rowsaffected<>0) or (poIgnoreRowsAffected in fProcessorOptions) then begin
|
|
if (change.ChangeType<>ctDelete) and (refds<>NIL) then begin
|
|
RefreshDeltaChange(aConnection, refds, aDelta, change, autoincvalue);
|
|
change.RefreshedByServer := TRUE;
|
|
end;
|
|
|
|
canremove := not change.RefreshedByServer;
|
|
change.Status := csResolved;
|
|
end
|
|
else begin
|
|
canremove := FALSE;
|
|
change.Status := csFailed;
|
|
change.Message := 'No rows were affected by this update';
|
|
end;
|
|
|
|
// After processing gives the user a last chance to update the change and
|
|
// optionally set it so that it goes back to the client (i.e. updated values)
|
|
if Assigned(fOnAfterProcessChange)
|
|
then fOnAfterProcessChange(Self, change, ok, canremove)
|
|
|
|
else if Assigned(fBusinessRules)
|
|
then fBusinessRules.AfterProcessChange(Self, change, ok, canremove)
|
|
end;
|
|
|
|
except
|
|
on E: Exception do begin
|
|
change.Status := csFailed;
|
|
change.Message := E.Message;
|
|
canremove := FALSE;
|
|
|
|
if Assigned(fOnProcessError) then
|
|
fOnProcessError(Self, change.ChangeType, change, currcmd, canremove, E)
|
|
else if Assigned(fBusinessRules) then begin
|
|
fBusinessRules.ProcessError(Self, change.ChangeType, change, currcmd, canremove, E);
|
|
if FRaiseExceptionAtError then raise;
|
|
end
|
|
else
|
|
raise EDAApplyUpdateFailed.Create(change, E);
|
|
end;
|
|
end;
|
|
|
|
if canremove then todelete.Add(change);
|
|
end;
|
|
|
|
for i := 0 to todelete.Count - 1 do
|
|
aDelta.RemoveChange(TDADeltaChange(todelete[i]));
|
|
|
|
if Assigned(fOnAfterProcessDelta) then
|
|
fOnAfterProcessDelta(Self, aDelta)
|
|
else if Assigned(fBusinessRules) then
|
|
fBusinessRules.AfterProcessDelta(Self, aDelta)
|
|
|
|
finally
|
|
todelete.Free;
|
|
end;
|
|
finally
|
|
lCommands.Free();
|
|
end;
|
|
|
|
end;
|
|
|
|
procedure TDABusinessProcessor.SetupCommands(const aConnection: IDAConnection;
|
|
const aDelta: IDADelta; out aCommandsList : TDADeltaProcessorItemCollection);
|
|
var i: Integer;
|
|
refstmt: TDAStatement;
|
|
lUnionTable: TDAUnionDataTable;
|
|
lSourceTable: TDADataset;
|
|
lSourceTableName: String;
|
|
lSourceTableIdx: integer;
|
|
lSourceTableMapping: TDAColumnMappingCollection;
|
|
lProcessorItem: TDADeltaProcessorItem;
|
|
lSql: String;
|
|
lVar: Variant;
|
|
begin
|
|
//lUnionTable := nil;
|
|
refstmt := nil;
|
|
CheckProperties;
|
|
|
|
aCommandsList := TDADeltaProcessorItemCollection.Create(Self);
|
|
|
|
lUnionTable := fSchema.UnionDataTables.ItemByName(fReferencedDataset) as TDAUnionDataTable;
|
|
|
|
if not Assigned(lUnionTable) then
|
|
RaiseError(Name + '''s referenced dataset is not speficied or does not exist. Cannot generate SQL');
|
|
|
|
// for each change in delta
|
|
for i:= 0 to aDelta.Count-1 do begin
|
|
case aDelta.Changes[i].ChangeType of
|
|
ctInsert: lVar := aDelta.Changes[i].NewValueByName[def_SourceTableFieldName];
|
|
ctUpdate, ctDelete: lVar := aDelta.Changes[i].OldValueByName[def_SourceTableFieldName];
|
|
end;
|
|
|
|
if (VarIsNull(lVar)) then begin
|
|
lSourceTableName := lUnionTable.DefaultSourceTable;
|
|
lSourceTableIdx := lUnionTable.SourceTables.ItemByName(lSourceTableName).Index;
|
|
end else begin
|
|
lSourceTableIdx := lVar;
|
|
lSourceTableName := lUnionTable.SourceTables[lSourceTableIdx].Name;
|
|
end;
|
|
lSourceTableMapping := lUnionTable.SourceTables[lSourceTableIdx].ColumnMappings;
|
|
|
|
lSourceTable := TDADataset(fSchema.Datasets.FindItem(lSourceTableName));
|
|
if Assigned(lSourceTable) then begin
|
|
refstmt := TDAStatement(lSourceTable.Statements.FindItem(aConnection.Name));
|
|
if (refstmt = nil) then refstmt := lSourceTable.Statements.StatementByName(Schema.ConnectionManager.GetDefaultConnectionName);
|
|
end;
|
|
|
|
lProcessorItem := TDADeltaProcessorItem(aCommandsList.FindItem(lSourceTableName));
|
|
|
|
if not Assigned(lProcessorItem) then begin
|
|
lProcessorItem := aCommandsList.Add();
|
|
lProcessorItem.fName := lSourceTableName;
|
|
end;
|
|
|
|
// Assemble Refresh dataset ...
|
|
if (fRefreshDataset <> '') and (not Assigned(lProcessorItem.fRefreshDataset)) then begin
|
|
lProcessorItem.fRefreshDataset := fSchema.NewDataset(aConnection, fRefreshDataset, [], [], FALSE);
|
|
|
|
if (lProcessorItem.fRefreshDataset = nil) and (poAutoGenerateRefreshDataset in fProcessorOptions) then begin
|
|
lProcessorItem.fRefreshDataset := GenerateRefreshDataset(lSourceTable, aDelta, refstmt, aConnection);
|
|
if (lProcessorItem.fRefreshDataset <> NIL) then SetupParameters(lProcessorItem.fRefreshDataset, lSourceTable);
|
|
end;
|
|
|
|
if Assigned(lProcessorItem.fRefreshDataset) then
|
|
lProcessorItem.fRefreshDataset.Prepared := True;
|
|
end;
|
|
|
|
// Assemble Insert command ...
|
|
if ((aDelta.Changes[i].ChangeType = ctInsert) and (not Assigned(lProcessorItem.fInsertCommand))) then begin
|
|
// Tries to locate the specified commands
|
|
if (fInsertCommandName <> '') then
|
|
lProcessorItem.fInsertCommand := fSchema.NewCommand(aConnection, fInsertCommandName);
|
|
// Auto generates the SQL for the undefined commands
|
|
if (lProcessorItem.fInsertCommand = nil) and (poAutoGenerateInsert in fProcessorOptions) then begin
|
|
lSql := GenerateSQL(ctInsert, nil, lSourceTable, aDelta, refstmt, aConnection);
|
|
lProcessorItem.fInsertCommand := aConnection.NewCommand(lSql, stSQL);
|
|
SetupParameters(lProcessorItem.fInsertCommand, lSourceTable);
|
|
end;
|
|
|
|
if Assigned(lProcessorItem.fInsertCommand) then begin
|
|
CreateMappings(aDelta, lProcessorItem.fInsertCommandMapping , lProcessorItem.fInsertCommand, lSourceTableMapping, True);
|
|
lProcessorItem.fInsertCommand.Prepared := True;
|
|
end;
|
|
end;
|
|
|
|
// Assemble Update command ...
|
|
if ((aDelta.Changes[i].ChangeType = ctUpdate) and (not Assigned(lProcessorItem.fUpdateCommand))) then begin
|
|
// Tries to locate the specified commands
|
|
if (fUpdateCommandName <> '') then
|
|
lProcessorItem.fUpdateCommand := fSchema.NewCommand(aConnection, fUpdateCommandName);
|
|
// Auto generates the SQL for the undefined commands
|
|
if (lProcessorItem.fUpdateCommand = nil) and (poAutoGenerateUpdate in fProcessorOptions) then begin
|
|
lSql := GenerateSQL(ctUpdate, nil, lSourceTable, aDelta, refstmt, aConnection);
|
|
lProcessorItem.fUpdateCommand := aConnection.NewCommand(lSql, stSQL);
|
|
SetupParameters(lProcessorItem.fUpdateCommand, lSourceTable);
|
|
end;
|
|
if Assigned(lProcessorItem.fUpdateCommand) then begin
|
|
CreateMappings(aDelta, lProcessorItem.fUpdateCommandMapping , lProcessorItem.fUpdateCommand, lSourceTableMapping, True);
|
|
lProcessorItem.fUpdateCommand.Prepared := True;
|
|
end;
|
|
|
|
end;
|
|
|
|
// Assemble Delete command ...
|
|
if ((aDelta.Changes[i].ChangeType = ctDelete) and (not Assigned(lProcessorItem.fDeleteCommand))) then begin
|
|
// Tries to locate the specified commands
|
|
if (fDeleteCommandName <> '') then
|
|
lProcessorItem.fDeleteCommand := fSchema.NewCommand(aConnection, fDeleteCommandName);
|
|
// Auto generates the SQL for the undefined commands
|
|
if (lProcessorItem.fDeleteCommand = nil) and (poAutoGenerateDelete in fProcessorOptions) then begin
|
|
lSql := GenerateSQL(ctDelete, nil, lSourceTable, aDelta, refstmt, aConnection);
|
|
lProcessorItem.fDeleteCommand := aConnection.NewCommand(lSql, stSQL);
|
|
SetupParameters(lProcessorItem.fDeleteCommand, lSourceTable);
|
|
end;
|
|
if Assigned(lProcessorItem.fDeleteCommand) then begin
|
|
CreateMappings(aDelta, lProcessorItem.fDeleteCommandMapping , lProcessorItem.fDeleteCommand, lSourceTableMapping, True);
|
|
lProcessorItem.fDeleteCommand.Prepared := True;
|
|
end;
|
|
end;
|
|
|
|
end;
|
|
end;
|
|
|
|
{ TDADeltaProcessorItemCollection }
|
|
constructor TDADeltaProcessorItemCollection.Create(aOwner: TComponent);
|
|
begin
|
|
inherited Create(aOwner, TDADeltaProcessorItem);
|
|
end;
|
|
|
|
function TDADeltaProcessorItemCollection.GetItem(Index: integer): TDADeltaProcessorItem;
|
|
begin
|
|
result := TDADeltaProcessorItem(inherited Items[Index])
|
|
end;
|
|
|
|
procedure TDADeltaProcessorItemCollection.SetItem(Index: integer; const Value: TDADeltaProcessorItem);
|
|
begin
|
|
Items[Index].Assign(Value);
|
|
end;
|
|
|
|
function TDADeltaProcessorItemCollection.Add: TDADeltaProcessorItem;
|
|
begin
|
|
result := TDADeltaProcessorItem(inherited Add);
|
|
end;
|
|
|
|
function TDADeltaProcessorItemCollection.ItemByName(const aName: String): TDADeltaProcessorItem;
|
|
begin
|
|
result := TDADeltaProcessorItem(inherited ItemByName(aName));
|
|
end;
|
|
|
|
|
|
initialization
|
|
_bizdeltachanges := TStringList.Create;
|
|
_bizdeltachanges.Sorted := TRUE;
|
|
|
|
finalization
|
|
_bizdeltachanges.Free;
|
|
|
|
end.
|
|
|