1031 lines
32 KiB
ObjectPascal
1031 lines
32 KiB
ObjectPascal
unit uDADelta;
|
|
|
|
{----------------------------------------------------------------------------}
|
|
{ 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
|
|
|
|
uses
|
|
Classes, DB, SysUtils,
|
|
// {$IFDEF MSWINDOWS}ActiveX,{$ENDIF}
|
|
uRODL, uROTypes, uROClasses, uROClientIntf,
|
|
uDAInterfaces, uDAEngine,
|
|
DataAbstract3_Intf, DataAbstract4_Intf;
|
|
|
|
const
|
|
UndefinedRecordID = -1;
|
|
|
|
type
|
|
IDADelta = interface;
|
|
TDADelta = class;
|
|
TDADeltaChange = class;
|
|
|
|
TVariantArray = array of variant;
|
|
|
|
{ Exceptions }
|
|
EDAApplyUpdateFailed = class(EROException)
|
|
private
|
|
fRecID: integer;
|
|
fDeltaName: string;
|
|
|
|
public
|
|
constructor Create(aChange: TDADeltaChange; anOriginalException: Exception);
|
|
|
|
published
|
|
property RecID: integer read fRecID write fRecID;
|
|
property DeltaName: string read fDeltaName write fDeltaName;
|
|
end;
|
|
|
|
IDADataTable = interface
|
|
['{BC6CD610-6D6E-4CD7-B181-73B3A5F9DE4F}']
|
|
end;
|
|
|
|
{ IDADeltaOwner }
|
|
IDADeltaOwner = interface
|
|
['{A92ECD00-14B2-4147-AE49-9493C56763A0}']
|
|
function GetDelta: IDADelta; safecall;
|
|
end;
|
|
|
|
{ TDADeltaChange }
|
|
TDADeltaChange = class
|
|
private
|
|
fDelta: TDADelta;
|
|
fRecID: integer;
|
|
fNewValues: TVariantArray;
|
|
fOldValues: TVariantArray;
|
|
fChangeType: TDAChangeType;
|
|
fStatus: TDAChangeStatus;
|
|
fMessage: string;
|
|
fRefreshedByServer: boolean;
|
|
|
|
procedure SetNewValue(const aName: string; const Value: Variant);
|
|
procedure SetOldValue(const aName: string; const Value: Variant);
|
|
function GetNewValue(const aName: string): Variant;
|
|
function GetOldValue(const aName: string): Variant;
|
|
function GetDelta: IDADelta;
|
|
protected
|
|
|
|
public
|
|
constructor Create(aDelta: TDADelta;
|
|
aRecID: integer;
|
|
aChangeType: TDAChangeType;
|
|
aStatus: TDAChangeStatus = csPending;
|
|
aMessage: string = '');
|
|
destructor Destroy; override;
|
|
|
|
property RecID: integer read fRecID write fRecID;
|
|
property ChangeType: TDAChangeType read fChangeType write fChangeType;
|
|
property OldValues: TVariantArray read fOldValues write fOldValues;
|
|
property NewValues: TVariantArray read fNewValues write fNewValues;
|
|
|
|
property OldValueByName[const aName: string]: Variant read GetOldValue write SetOldValue;
|
|
property NewValueByName[const aName: string]: Variant read GetNewValue write SetNewValue;
|
|
|
|
property Status: TDAChangeStatus read fStatus write fStatus;
|
|
property Message: string read fMessage write fMessage;
|
|
|
|
property Delta : IDADelta read GetDelta;
|
|
|
|
property RefreshedByServer : boolean read fRefreshedByServer write fRefreshedByServer;
|
|
end;
|
|
|
|
{ IDADelta }
|
|
IDADelta = interface
|
|
['{0FD17DDB-3C34-4520-9106-4D3D540BA3D3}']
|
|
// Property readers/writers
|
|
function GetLoggedFieldCount: integer;
|
|
function GetInChange: boolean;
|
|
function GetCount: integer;
|
|
function GetChange(Index: integer): TDADeltaChange;
|
|
function GetLoggedFieldNames(Index: integer): string;
|
|
function GetKeyFieldCount: integer;
|
|
function GetKeyFieldNames(Index: integer): string;
|
|
function GetLoggedFieldTypes(Index : integer): TDADataType;
|
|
procedure SetLoggedFieldTypes(anIndex : integer; aFieldType : TDADataType);
|
|
function GetLogicalName : string;
|
|
procedure SetLogicalName(const aName : string);
|
|
|
|
// Methods
|
|
procedure AssignDataTable(aDataTable : TComponent);
|
|
|
|
function FindChange(aRecID: integer): TDADeltaChange;
|
|
procedure RemoveChange(aChange: TDADeltaChange);
|
|
|
|
procedure Clear(DoClearFieldNames: boolean = FALSE; DoClearKeyFieldNames: boolean = FALSE);
|
|
|
|
procedure AddFieldName(const aFieldName: string);
|
|
procedure AddKeyFieldName(const aKeyFieldName: string);
|
|
|
|
procedure ClearFieldNames;
|
|
procedure ClearKeyFieldNames;
|
|
|
|
procedure StartChange(aChangeType: TDAChangeType);
|
|
procedure CancelChange;
|
|
procedure EndChange;
|
|
procedure RestoreLastChange;
|
|
|
|
function IsNewRecord(aRecordID: integer = -1): boolean;
|
|
|
|
procedure Add(aChange: TDADeltaChange); overload;
|
|
function Add(aRecordID: integer; aChangeType: TDAChangeType;
|
|
aStatus: TDAChangeStatus = csPending; const aMessage: string = ''): TDADeltaChange; overload;
|
|
procedure Delete(Index: integer);
|
|
|
|
function GetCountByStatus(aChangeStatus : TDAChangeStatus) : integer;
|
|
|
|
function IndexOfLoggedField(const aName: string): integer;
|
|
property LoggedFieldNames[Index: integer]: string read GetLoggedFieldNames;
|
|
property LoggedFieldCount: integer read GetLoggedFieldCount;
|
|
|
|
property KeyFieldNames[Index: integer]: string read GetKeyFieldNames;
|
|
property KeyFieldCount: integer read GetKeyFieldCount;
|
|
|
|
property LoggedFieldTypes[Index : integer]: TDADataType read GetLoggedFieldTypes write SetLoggedFieldTypes;
|
|
|
|
function GetDelta: TDADelta;
|
|
|
|
property LogicalName : string read GetLogicalName write SetLogicalName;
|
|
property Changes[Index: integer]: TDADeltaChange read GetChange; default;
|
|
property Count: integer read GetCount;
|
|
end;
|
|
|
|
{ IDADataReader }
|
|
IDADataReader = interface
|
|
['{7D2FC996-7A04-4ECE-91B0-4F17EFEC4985}']
|
|
function GetFieldNames(Index: integer): string; safecall;
|
|
function GetFieldIndexes(const aName: string): integer; safecall;
|
|
function GetFieldCount: integer; safecall;
|
|
function GetRecordCount: integer; safecall;
|
|
|
|
function GetAsBoolean(Index: integer): boolean; overload; safecall;
|
|
function GetAsCurrency(Index: integer): currency; overload; {$IFNDEF FPC_SAFECALL_BUG}safecall;{$ENDIF}
|
|
function GetAsDateTime(Index: integer): TDateTime; overload; {$IFNDEF FPC_SAFECALL_BUG}safecall;{$ENDIF}
|
|
function GetAsFloat(Index: integer): double; overload; {$IFNDEF FPC_SAFECALL_BUG}safecall;{$ENDIF}
|
|
function GetAsInteger(Index: integer): integer; overload; safecall;
|
|
function GetAsString(Index: integer): string; overload; safecall;
|
|
function GetAsVariant(Index: integer): variant; overload; safecall;
|
|
|
|
function GetAsBoolean(const FieldName: string): boolean; overload; safecall;
|
|
function GetAsCurrency(const FieldName: string): currency; overload; {$IFNDEF FPC_SAFECALL_BUG}safecall;{$ENDIF}
|
|
function GetAsDateTime(const FieldName: string): TDateTime; overload; {$IFNDEF FPC_SAFECALL_BUG}safecall;{$ENDIF}
|
|
function GetAsFloat(const FieldName: string): double; overload; {$IFNDEF FPC_SAFECALL_BUG}safecall;{$ENDIF}
|
|
function GetAsInteger(const FieldName: string): integer; overload; safecall;
|
|
function GetAsString(const FieldName: string): string; overload; safecall;
|
|
function GetAsVariant(const FieldName: string): variant; overload; safecall;
|
|
|
|
function First: boolean; safecall;
|
|
function Next: boolean; safecall;
|
|
|
|
function Locate(const KeyFields: string; const KeyValues: Variant; Options: TLocateOptions): Boolean; safecall;
|
|
|
|
property FieldNames[Index: integer]: string read GetFieldNames;
|
|
property FieldIndexes[const aFieldName: string]: integer read GetFieldIndexes;
|
|
property FieldCount: integer read GetFieldCount;
|
|
|
|
property RecordCount: integer read GetRecordCount;
|
|
end;
|
|
|
|
{ TDADeltaList }
|
|
TDADeltaList = class(TInterfaceList)
|
|
private
|
|
function GetDeltas(Index: integer): IDADelta;
|
|
|
|
protected
|
|
public
|
|
function Add(const aDelta : IDADelta) : integer; reintroduce;
|
|
procedure Insert(Index: Integer; const aDelta: IDADelta); reintroduce;
|
|
|
|
function DeltaByName(const aDeltaName : string) : IDADelta;
|
|
function FindDelta(const aDeltaName : string) : IDADelta;
|
|
|
|
property Deltas[Index : integer] : IDADelta read GetDeltas; default;
|
|
end;
|
|
|
|
{ TDADelta }
|
|
TDADelta = class(TDAEngineBaseObject, IDADelta, IDADataReader)
|
|
private
|
|
fDataTable: TComponent;
|
|
fChanges: TStringList;
|
|
fLastChange,
|
|
fCurrentChange: TDADeltaChange;
|
|
fNewChange: boolean;
|
|
fCurrentChangeType: TDAChangeType;
|
|
fKeyFields,
|
|
fFieldNames: TStringList;
|
|
fCurrPosition: integer;
|
|
fLogicalName : string;
|
|
|
|
function GetLoggedFieldCount: integer;
|
|
function GetInChange: boolean;
|
|
function GetCount: integer;
|
|
function GetChange(Index: integer): TDADeltaChange;
|
|
function GetLoggedFieldNames(Index: integer): string;
|
|
function GetKeyFieldCount: integer;
|
|
function GetKeyFieldNames(Index: integer): string;
|
|
function GetLoggedFieldTypes(Index : integer): TDADataType;
|
|
procedure SetLoggedFieldTypes(anIndex : integer; aFieldType : TDADataType);
|
|
|
|
protected
|
|
function GetDelta: TDADelta;
|
|
function GetLogicalName : string;
|
|
procedure SetLogicalName(const aName : string);
|
|
|
|
// IDADataReader
|
|
function GetFieldNames(Index: integer): string; safecall;
|
|
function GetFieldIndexes(const aName: string): integer; safecall;
|
|
function GetFieldCount: integer; safecall;
|
|
function GetAsBoolean(Index: integer): boolean; overload; safecall;
|
|
function GetAsCurrency(Index: integer): currency; overload; {$IFNDEF FPC_SAFECALL_BUG}safecall;{$ENDIF}
|
|
function GetAsDateTime(Index: integer): TDateTime; overload; {$IFNDEF FPC_SAFECALL_BUG}safecall;{$ENDIF}
|
|
function GetAsFloat(Index: integer): double; overload; {$IFNDEF FPC_SAFECALL_BUG}safecall;{$ENDIF}
|
|
function GetAsInteger(Index: integer): integer; overload; safecall;
|
|
function GetAsString(Index: integer): string; overload; safecall;
|
|
function GetAsVariant(Index: integer): variant; overload; safecall;
|
|
function GetAsBoolean(const FieldName: string): boolean; overload; safecall;
|
|
function GetAsCurrency(const FieldName: string): currency; overload; {$IFNDEF FPC_SAFECALL_BUG}safecall;{$ENDIF}
|
|
function GetAsDateTime(const FieldName: string): TDateTime; overload; {$IFNDEF FPC_SAFECALL_BUG}safecall;{$ENDIF}
|
|
function GetAsFloat(const FieldName: string): double; overload; {$IFNDEF FPC_SAFECALL_BUG}safecall;{$ENDIF}
|
|
function GetAsInteger(const FieldName: string): integer; overload; safecall;
|
|
function GetAsString(const FieldName: string): string; overload; safecall;
|
|
function GetAsVariant(const FieldName: string): variant; overload; safecall;
|
|
function GetRecordCount: integer; safecall;
|
|
function First: boolean; safecall;
|
|
function Next: boolean; safecall;
|
|
function Locate(const KeyFields: string; const KeyValues: Variant; Options: TLocateOptions): Boolean; safecall;
|
|
|
|
public
|
|
constructor Create(aDataTable: TComponent); overload;
|
|
constructor Create(const aLogicalName : string); overload;
|
|
constructor Create; overload;
|
|
destructor Destroy; override;
|
|
|
|
function FindChange(aRecID: integer): TDADeltaChange;
|
|
procedure RemoveChange(aChange: TDADeltaChange);
|
|
|
|
procedure Clear(DoClearFieldNames: boolean = FALSE; DoClearKeyFieldNames: boolean = FALSE);
|
|
|
|
procedure AssignDataTable(aDataTable : TComponent);
|
|
|
|
procedure AddFieldName(const aFieldName: string);
|
|
procedure AddKeyFieldName(const aKeyFieldName: string);
|
|
procedure ClearFieldNames;
|
|
procedure ClearKeyFieldNames;
|
|
|
|
procedure Add(aChange: TDADeltaChange); overload;
|
|
function Add(aRecordID: integer; aChangeType: TDAChangeType;
|
|
aStatus: TDAChangeStatus = csPending; const aMessage: string = ''): TDADeltaChange; overload;
|
|
procedure Delete(Index: integer);
|
|
|
|
procedure StartChange(aChangeType: TDAChangeType);
|
|
procedure CancelChange;
|
|
procedure EndChange;
|
|
procedure RestoreLastChange;
|
|
|
|
function IsNewRecord(aRecordID: integer = -1): boolean;
|
|
|
|
function IndexOfLoggedField(const aName: string): integer;
|
|
|
|
function GetCountByStatus(aChangeStatus : TDAChangeStatus) : integer;
|
|
|
|
property CurrentChange: TDADeltaChange read fCurrentChange;
|
|
|
|
property KeyFieldNames[Index: integer]: string read GetKeyFieldNames;
|
|
property KeyFieldCount: integer read GetKeyFieldCount;
|
|
|
|
property LoggedFieldNames[Index: integer]: string read GetLoggedFieldNames;
|
|
property LoggedFieldCount: integer read GetLoggedFieldCount;
|
|
property LoggedFieldTypes[Index : integer]: TDADataType read GetLoggedFieldTypes write SetLoggedFieldTypes;
|
|
property InChange: boolean read GetInChange;
|
|
|
|
property Changes[Index: integer]: TDADeltaChange read GetChange; default;
|
|
property Count: integer read GetCount;
|
|
//property DataTable: TComponent read fDataTable;
|
|
end;
|
|
|
|
function FormatRecIDString(aRecID: integer): string;
|
|
function NewDelta(aDeltaName: string): IDADelta; overload;
|
|
|
|
function FieldValueToVariant(aSourceField : TDAField) : Variant;
|
|
procedure VariantToFieldValue(const aSourceVariant : Variant; aField : TDAField);
|
|
|
|
|
|
implementation
|
|
|
|
uses
|
|
Variants, TypInfo,
|
|
uROClient, uROSessions, uROXMLIntf,
|
|
uDARes, uDARegExpr, uDADataTable;
|
|
|
|
function FormatRecIDString(aRecID: integer): string;
|
|
begin
|
|
result := FormatFloat('0000000000', aRecID);
|
|
end;
|
|
|
|
function NewDelta(aDeltaName: string): IDADelta;
|
|
begin
|
|
result := TDADelta.Create(aDeltaName);
|
|
end;
|
|
|
|
{ TDADeltaList }
|
|
|
|
function TDADeltaList.Add(const aDelta: IDADelta): integer;
|
|
begin
|
|
result := inherited Add(aDelta);
|
|
end;
|
|
|
|
function TDADeltaList.DeltaByName(const aDeltaName: string): IDADelta;
|
|
begin
|
|
result := FindDelta(aDeltaName);
|
|
if (result=NIL) then raise Exception.Create('Cannot find delta '+aDeltaName);
|
|
end;
|
|
|
|
function TDADeltaList.FindDelta(const aDeltaName: string): IDADelta;
|
|
var i : integer;
|
|
begin
|
|
result := NIL;
|
|
for i := 0 to (Count-1) do
|
|
if SameText(Deltas[i].LogicalName, aDeltaName) then begin
|
|
result := Deltas[i];
|
|
Exit;
|
|
end;
|
|
end;
|
|
|
|
function TDADeltaList.GetDeltas(Index: integer): IDADelta;
|
|
begin
|
|
result := inherited Items[Index] as IDADelta;
|
|
end;
|
|
|
|
procedure TDADeltaList.Insert(Index: Integer; const aDelta: IDADelta);
|
|
begin
|
|
inherited Insert(Index, aDelta);
|
|
end;
|
|
|
|
{ TDADelta }
|
|
|
|
constructor TDADelta.Create(const aLogicalName: string);
|
|
begin
|
|
inherited Create;
|
|
|
|
// This error is often generated if the user forgets to set LogicalName in the data table
|
|
if (aLogicalName='') then raise Exception.Create('Cannot create a delta without a name');
|
|
|
|
fLogicalName := aLogicalName;
|
|
|
|
fKeyFields := TStringList.Create;
|
|
fFieldNames := TStringList.Create;
|
|
|
|
fChanges := TStringList.Create;
|
|
fChanges.Duplicates := dupError;
|
|
fChanges.Sorted := TRUE;
|
|
end;
|
|
|
|
constructor TDADelta.Create;
|
|
begin
|
|
Create(NewGuidAsString);
|
|
end;
|
|
|
|
constructor TDADelta.Create(aDataTable: TComponent);
|
|
var i: integer;
|
|
dnme : string;
|
|
dt: TDADataTable;
|
|
begin
|
|
{ AleF: Changed the logic of how delta creation works. The name of the delta is required and, if using
|
|
this overloaded version of create, it will be taken from the datatable.LogicalName.
|
|
This was made to optimize the adapters which, right noiw, require a list of delta names and
|
|
one of deltas. Because of this change, adapters can find the name from the delta itself.
|
|
}
|
|
if (aDataTable=NIL) then raise Exception.Create('Datatable must be assigned');
|
|
dt:=aDataTable as TDADataTable;
|
|
if (dt.LogicalName<>'') then
|
|
dnme := dt.LogicalName
|
|
else
|
|
dnme := dt.Name;
|
|
|
|
Create(dnme); // Calls the constructor above;
|
|
fDataTable := aDataTable;
|
|
|
|
for i := 0 to (dt.Fields.Count - 1) do
|
|
with dt.Fields[i] do begin
|
|
if LogChanges or InPrimaryKey then fFieldNames.Add(Name);
|
|
if InPrimaryKey then fKeyFields.Add(Name);
|
|
end;
|
|
|
|
for i := 0 to (fFieldNames.Count-1) do
|
|
fFieldNames.Objects[i] := TObject(ord(dt.FieldByName(fFieldNames[i]).DataType));
|
|
end;
|
|
|
|
destructor TDADelta.Destroy;
|
|
begin
|
|
// Removes the current change (if new and not yet in the changes list)
|
|
if (fCurrentChange<>NIL) then begin
|
|
if (FindChange(fCurrentChange.RecID)=NIL)
|
|
then FreeAndNIL(fCurrentChange);
|
|
end;
|
|
|
|
if (fChanges<>NIL) then begin
|
|
Clear;
|
|
fChanges.Free;
|
|
end;
|
|
|
|
fFieldNames.Free;
|
|
fKeyFields.Free;
|
|
|
|
inherited;
|
|
end;
|
|
|
|
function TDADelta.GetLoggedFieldCount: integer;
|
|
begin
|
|
result := fFieldNames.Count;
|
|
end;
|
|
|
|
function FieldValueToVariant(aSourceField : TDAField) : Variant;
|
|
var stream : IROStream;
|
|
p : pointer;
|
|
sze : cardinal;
|
|
begin
|
|
case aSourceField.DataType of
|
|
datBlob : begin
|
|
if aSourceField.IsNull {or (aSourceField.BlobSize = 0)} then result := Unassigned
|
|
else begin
|
|
stream := NewROStream;
|
|
aSourceField.SaveToStream(stream);
|
|
sze := stream.Size;
|
|
if stream.Size = 0 then
|
|
result := Unassigned
|
|
else begin
|
|
stream.Position := 0;
|
|
result := VarArrayCreate([0, sze-1], varByte);
|
|
p := VarArrayLock(result);
|
|
try
|
|
stream.Read(p^, sze);
|
|
finally
|
|
VarArrayUnlock(result);
|
|
end;
|
|
stream.Position := 0;
|
|
end;
|
|
end;
|
|
end;
|
|
{$IFDEF DELPHI10UP}
|
|
datWideMemo: Result:= aSourceField.AsWideString;
|
|
{$ENDIF DELPHI10UP}
|
|
else result := aSourceField.Value;
|
|
end;
|
|
end;
|
|
|
|
procedure VariantToFieldValue(const aSourceVariant : Variant; aField : TDAField);
|
|
var stream : IROStream;
|
|
p : pointer;
|
|
readcount, sze : cardinal;
|
|
begin
|
|
case aField.DataType of
|
|
datBlob:
|
|
if VarIsEmpty(aSourceVariant) then begin
|
|
aField.Value := Unassigned;
|
|
end
|
|
else begin
|
|
stream := NewROStream;
|
|
|
|
sze := VarArrayHighBound(aSourceVariant, 1) - VarArrayLowBound(aSourceVariant, 1) + 1;
|
|
p := VarArrayLock(aSourceVariant);
|
|
try
|
|
readcount := stream.Write(p^, sze);
|
|
finally
|
|
VarArrayUnlock(aSourceVariant);
|
|
end;
|
|
|
|
if (readcount<>sze)
|
|
then raise Exception.CreateFmt('Couldn''t read all data. Expected %d read %d', [sze, readcount]);
|
|
|
|
stream.Position := 0;
|
|
aField.LoadFromStream(stream);
|
|
end;
|
|
{$IFDEF DELPHI10UP}
|
|
datWideMemo: aField.AsWideString:=aSourceVariant;
|
|
{$ENDIF DELPHI10UP}
|
|
else aField.Value := aSourceVariant;
|
|
end;
|
|
end;
|
|
|
|
procedure TDADelta.StartChange(aChangeType: TDAChangeType);
|
|
var
|
|
recid: integer;
|
|
x, i: integer;
|
|
begin
|
|
if InChange then RaiseError(err_ChangeLogAlreadyStarted);
|
|
|
|
if not Assigned(fDataTable) then RaiseError(err_NotAttachedToDataTable);
|
|
|
|
fCurrentChangeType := aChangeType;
|
|
|
|
// Checks to see if this is the first time this record was changed
|
|
if (aChangeType <> ctInsert) then begin
|
|
recid := (fDataTable as TDADataTable).RecIDField.AsInteger; // CurrRecId;
|
|
fCurrentChange := FindChange(recid);
|
|
fNewChange := (fCurrentChange = nil);
|
|
end
|
|
else begin
|
|
recid := UndefinedRecordID;
|
|
fNewChange := TRUE;
|
|
end;
|
|
|
|
if fNewChange then begin
|
|
fCurrentChange := TDADeltaChange.Create(Self, recid, aChangeType);
|
|
|
|
// For deletes and edit, we want to store the original values.
|
|
// This happens only once.
|
|
case aChangeType of
|
|
ctDelete, ctUpdate: begin
|
|
with (fDataTable as TDADataTable).Fields do begin
|
|
x := 0;
|
|
for i := 0 to ((fDataTable as TDADataTable).FieldCount - 1) do begin
|
|
if Fields[i].LogChanges or Fields[i].InPrimaryKey then begin
|
|
fCurrentChange.OldValues[x] := FieldValueToVariant(Fields[i]);
|
|
Inc(x);
|
|
end;
|
|
end;
|
|
end;
|
|
end
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TDADelta.CancelChange;
|
|
begin
|
|
//if not Assigned(fDataTable) then RaiseError(err_NotAttachedToDataTable);
|
|
|
|
if fNewChange
|
|
then FreeAndNIL(fCurrentChange); // if it is not a new change (i.e. cancelling an edit) should not free!
|
|
fCurrentChange := nil; // nil it in either case!
|
|
end;
|
|
|
|
procedure TDADelta.EndChange;
|
|
var
|
|
x, i: integer;
|
|
lWasChanged: Boolean;
|
|
lLoggedFieldName: string;
|
|
lNewValue, lOldValue: Variant;
|
|
lDataTable: TDAdataTable;
|
|
begin
|
|
if not Assigned(fDataTable) then RaiseError(err_NotAttachedToDataTable);
|
|
|
|
lDataTable := (fDataTable as TDADataTable);
|
|
|
|
try
|
|
if (fCurrentChange.RecID = UndefinedRecordID)
|
|
then fCurrentChange.RecID := lDataTable.RecIDField.AsInteger // Happens on inserts and first time changes (except deletes)
|
|
|
|
else if (fCurrentChange.RecID <> lDataTable.RecIDField.AsInteger)
|
|
then raise Exception.Create('Record pointer changed!');
|
|
|
|
// Doing a new operation on the same record
|
|
if (fCurrentChange.ChangeType <> fCurrentChangeType) then begin
|
|
case fCurrentChangeType of
|
|
ctDelete: begin
|
|
if (fCurrentChange.ChangeType = ctInsert) then begin // No need to track new records that have been deleted
|
|
RemoveChange(fCurrentChange);
|
|
Exit; // Done!
|
|
end
|
|
else begin
|
|
fCurrentChange.ChangeType := fCurrentChangeType; // Deletes have precedence over anything else
|
|
for i := 0 to (LoggedFieldCount - 1) do
|
|
fCurrentChange.NewValues[i] := UnAssigned;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
case fCurrentChange.ChangeType of
|
|
ctUpdate, ctInsert: begin
|
|
with lDataTable.Fields do begin
|
|
lWasChanged := False;
|
|
x := 0;
|
|
for i := 0 to (lDataTable.FieldCount - 1) do begin
|
|
if Fields[i].LogChanges or Fields[i].InPrimaryKey then begin
|
|
fCurrentChange.NewValues[x] := FieldValueToVariant(Fields[i]);
|
|
|
|
lOldValue := fCurrentChange.OldValues[x];
|
|
lNewValue := fCurrentChange.NewValues[x];
|
|
if fCurrentChange.ChangeType = ctUpdate then begin
|
|
lWasChanged := lWasChanged or not ROVariantsEqual(lOldValue, lNewValue);
|
|
end else begin
|
|
lLoggedFieldName := fCurrentChange.Delta.LoggedFieldNames[x];
|
|
if FieldByName(lLoggedFieldName).InPrimaryKey and VarIsEmpty(lOldValue) then begin
|
|
fCurrentChange.OldValues[x] := lNewValue;
|
|
end;
|
|
end;
|
|
Inc(x);
|
|
end;
|
|
end;
|
|
if (fCurrentChange.ChangeType = ctUpdate) and (not lWasChanged) then begin //The change doesn't affect any fields with LogChanges
|
|
CancelChange;
|
|
Exit; // Done!
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
if fNewChange then fChanges.AddObject(FormatRecIDString(fCurrentChange.RecID), fCurrentChange);
|
|
|
|
finally
|
|
// ALEF: added to keep a log of the last change in case a post fails. It will be restored in such case
|
|
// via the InternalOnPostError through a call to RestoreLastChange
|
|
fLastChange := fCurrentChange;
|
|
|
|
fCurrentChange := nil;
|
|
end;
|
|
end;
|
|
|
|
function TDADelta.GetInChange: boolean;
|
|
begin
|
|
result := fCurrentChange <> nil
|
|
end;
|
|
|
|
function TDADelta.FindChange(aRecID: integer): TDADeltaChange;
|
|
var
|
|
idx: integer;
|
|
begin
|
|
result := nil;
|
|
idx := fChanges.IndexOf(FormatRecIDString(aRecID));
|
|
if (idx >= 0) then result := TDADeltaChange(fChanges.Objects[idx]);
|
|
end;
|
|
|
|
function TDADelta.GetCount: integer;
|
|
begin
|
|
result := fChanges.Count;
|
|
end;
|
|
|
|
procedure TDADelta.Clear(DoClearFieldNames: boolean = FALSE; DoClearKeyFieldNames: boolean = FALSE);
|
|
var
|
|
i: integer;
|
|
begin
|
|
for i := 0 to (fChanges.Count - 1) do
|
|
if (fChanges.Objects[i]<>NIL)
|
|
then fChanges.Objects[i].Free;
|
|
|
|
fChanges.Clear;
|
|
|
|
if DoClearFieldNames then ClearFieldNames;
|
|
if DoClearKeyFieldNames then ClearKeyFieldNames;
|
|
|
|
fCurrPosition := 0;
|
|
end;
|
|
|
|
function TDADelta.GetChange(Index: integer): TDADeltaChange;
|
|
begin
|
|
result := TDADeltaChange(fChanges.Objects[Index]);
|
|
end;
|
|
|
|
procedure TDADelta.RemoveChange(aChange: TDADeltaChange);
|
|
var
|
|
idx: integer;
|
|
begin
|
|
idx := fChanges.IndexOfObject(aChange);
|
|
if (idx >= 0) then begin
|
|
fChanges.Objects[idx].Free;
|
|
fChanges.Delete(idx);
|
|
end;
|
|
end;
|
|
|
|
function TDADelta.GetLoggedFieldNames(Index: integer): string;
|
|
begin
|
|
result := fFieldNames[Index]
|
|
end;
|
|
|
|
procedure TDADelta.AddFieldName(const aFieldName: string);
|
|
begin
|
|
fFieldNames.Add(aFieldName);
|
|
end;
|
|
|
|
procedure TDADelta.ClearFieldNames;
|
|
begin
|
|
fFieldNames.Clear;
|
|
end;
|
|
|
|
procedure TDADelta.Add(aChange: TDADeltaChange);
|
|
begin
|
|
fChanges.AddObject(FormatRecIDString(aChange.RecId), aChange);
|
|
end;
|
|
|
|
procedure TDADelta.Delete(Index: integer);
|
|
begin
|
|
fChanges.Objects[Index].Free;
|
|
fChanges.Delete(Index);
|
|
end;
|
|
|
|
function TDADelta.IndexOfLoggedField(const aName: string): integer;
|
|
begin
|
|
result := fFieldNames.IndexOf(aName)
|
|
end;
|
|
|
|
function TDADelta.GetDelta: TDADelta;
|
|
begin
|
|
result := Self;
|
|
end;
|
|
|
|
function TDADelta.IsNewRecord(aRecordID: integer = -1): boolean;
|
|
var
|
|
recid, i: integer;
|
|
begin
|
|
result := FALSE;
|
|
recid := aRecordID;
|
|
|
|
if (aRecordID = -1) then begin
|
|
if ((fDataTable as TDADataTable).RecordCount > 0) then
|
|
recid := (fDataTable as TDADataTable).GetRowRecIDValue
|
|
else
|
|
Exit;
|
|
end;
|
|
|
|
for i := 0 to Count - 1 do
|
|
if (Changes[i].RecID = recid) then begin
|
|
result := Changes[i].ChangeType = ctInsert;
|
|
Exit;
|
|
end;
|
|
end;
|
|
|
|
function TDADelta.GetKeyFieldCount: integer;
|
|
begin
|
|
result := fKeyFields.Count
|
|
end;
|
|
|
|
function TDADelta.GetKeyFieldNames(Index: integer): string;
|
|
begin
|
|
result := fKeyFields[Index]
|
|
end;
|
|
|
|
procedure TDADelta.ClearKeyFieldNames;
|
|
begin
|
|
fKeyFields.Clear;
|
|
end;
|
|
|
|
procedure TDADelta.AddKeyFieldName(const aKeyFieldName: string);
|
|
begin
|
|
fKeyFields.Add(aKeyFieldName)
|
|
end;
|
|
|
|
function TDADelta.Add(aRecordID: integer; aChangeType: TDAChangeType;
|
|
aStatus: TDAChangeStatus; const aMessage: string): TDADeltaChange;
|
|
begin
|
|
result := TDADeltaChange.Create(Self, aRecordID, aChangeType, aStatus, aMessage);
|
|
Add(result);
|
|
end;
|
|
|
|
function TDADelta.GetFieldCount: integer;
|
|
begin
|
|
result := fFieldNames.Count;
|
|
end;
|
|
|
|
function TDADelta.GetFieldIndexes(const aName: string): integer;
|
|
begin
|
|
result := fFieldNames.IndexOf(aName);
|
|
end;
|
|
|
|
function TDADelta.GetFieldNames(Index: integer): string;
|
|
begin
|
|
result := fFieldNames[Index];
|
|
end;
|
|
|
|
function TDADelta.GetAsBoolean(const FieldName: string): boolean;
|
|
begin
|
|
result := GetVarBoolean(Changes[fCurrPosition].NewValueByName[FieldName]);
|
|
end;
|
|
|
|
function TDADelta.GetAsBoolean(Index: integer): boolean;
|
|
begin
|
|
result := GetVarBoolean(Changes[fCurrPosition].NewValues[Index]);
|
|
end;
|
|
|
|
function TDADelta.GetAsCurrency(Index: integer): currency;
|
|
begin
|
|
result := GetVarCurrency(Changes[fCurrPosition].NewValues[Index]);
|
|
end;
|
|
|
|
function TDADelta.GetAsCurrency(const FieldName: string): currency;
|
|
begin
|
|
result := GetVarCurrency(Changes[fCurrPosition].NewValueByName[FieldName]);
|
|
end;
|
|
|
|
function TDADelta.GetAsDateTime(Index: integer): TDateTime;
|
|
begin
|
|
result := GetVarDateTime(Changes[fCurrPosition].NewValues[Index]);
|
|
end;
|
|
|
|
function TDADelta.GetAsDateTime(const FieldName: string): TDateTime;
|
|
begin
|
|
result := GetVarDateTime(Changes[fCurrPosition].NewValueByName[FieldName]);
|
|
end;
|
|
|
|
function TDADelta.GetAsFloat(Index: integer): double;
|
|
begin
|
|
result := GetVarFloat(Changes[fCurrPosition].NewValues[Index]);
|
|
end;
|
|
|
|
function TDADelta.GetAsFloat(const FieldName: string): double;
|
|
begin
|
|
result := GetVarFloat(Changes[fCurrPosition].NewValueByName[FieldName]);
|
|
end;
|
|
|
|
function TDADelta.GetAsInteger(Index: integer): integer;
|
|
begin
|
|
result := GetVarInteger(Changes[fCurrPosition].NewValues[Index]);
|
|
end;
|
|
|
|
function TDADelta.GetAsInteger(const FieldName: string): integer;
|
|
begin
|
|
result := GetVarInteger(Changes[fCurrPosition].NewValueByName[FieldName]);
|
|
end;
|
|
|
|
function TDADelta.GetAsString(const FieldName: string): string;
|
|
begin
|
|
result := GetVarString(Changes[fCurrPosition].NewValueByName[FieldName]);
|
|
end;
|
|
|
|
function TDADelta.GetAsString(Index: integer): string;
|
|
begin
|
|
result := GetVarString(Changes[fCurrPosition].NewValues[Index]);
|
|
end;
|
|
|
|
function TDADelta.GetAsVariant(Index: integer): variant;
|
|
begin
|
|
result := Changes[fCurrPosition].NewValues[Index];
|
|
end;
|
|
|
|
function TDADelta.GetAsVariant(const FieldName: string): variant;
|
|
begin
|
|
result := Changes[fCurrPosition].NewValueByName[FieldName];
|
|
end;
|
|
|
|
function TDADelta.First: boolean;
|
|
begin
|
|
result := (fChanges.Count > 0);
|
|
if result then fCurrPosition := 0;
|
|
end;
|
|
|
|
function TDADelta.GetRecordCount: integer;
|
|
begin
|
|
result := fChanges.Count;
|
|
end;
|
|
|
|
function TDADelta.Next: boolean;
|
|
begin
|
|
result := (fCurrPosition < fChanges.Count - 1);
|
|
|
|
if result then Inc(fCurrPosition);
|
|
end;
|
|
|
|
function TDADelta.Locate(const KeyFields: string; const KeyValues: Variant;
|
|
Options: TLocateOptions): Boolean;
|
|
begin
|
|
result := FALSE; // Deltas don't support searches for now
|
|
end;
|
|
|
|
function TDADelta.GetLoggedFieldTypes(Index: integer): TDADataType;
|
|
begin
|
|
result := TDADataType(Cardinal(fFieldNames.Objects[Index]));
|
|
end;
|
|
|
|
procedure TDADelta.SetLoggedFieldTypes(anIndex: integer;
|
|
aFieldType: TDADataType);
|
|
begin
|
|
fFieldNames.Objects[anIndex] := TObject(ord(aFieldType));
|
|
end;
|
|
|
|
procedure TDADelta.RestoreLastChange;
|
|
var
|
|
i: integer;
|
|
begin
|
|
fCurrentChange := fLastChange;
|
|
if fNewChange then begin
|
|
i:=fChanges.IndexOf(FormatRecIDString(fCurrentChange.RecID));
|
|
if i <> -1 then fChanges.Delete(i);
|
|
fCurrentChange.RecID := UndefinedRecordID;
|
|
// basically nulls this --> fChanges.AddObject(FormatRecIDString(fCurrentChange.RecID), fCurrentChange);
|
|
end;
|
|
end;
|
|
|
|
function TDADelta.GetCountByStatus(
|
|
aChangeStatus: TDAChangeStatus): integer;
|
|
var i : integer;
|
|
begin
|
|
result := 0;
|
|
for i := 0 to fChanges.Count-1 do begin
|
|
if (Changes[i].Status=aChangeStatus) then Inc(result);
|
|
end;
|
|
end;
|
|
|
|
function TDADelta.GetLogicalName: string;
|
|
begin
|
|
result := fLogicalName;
|
|
end;
|
|
|
|
procedure TDADelta.SetLogicalName(const aName: string);
|
|
begin
|
|
fLogicalName := aName;
|
|
end;
|
|
|
|
procedure TDADelta.AssignDataTable(aDataTable: TComponent);
|
|
begin
|
|
fDataTable := aDataTable as TDADataTable;
|
|
end;
|
|
|
|
{ TDADeltaChange }
|
|
|
|
constructor TDADeltaChange.Create(aDelta: TDADelta;
|
|
aRecID: integer;
|
|
aChangeType: TDAChangeType;
|
|
aStatus: TDAChangeStatus = csPending;
|
|
aMessage: string = '');
|
|
begin
|
|
inherited Create;
|
|
|
|
fRefreshedByServer := FALSE;
|
|
fStatus := aStatus;
|
|
fDelta := aDelta;
|
|
fRecID := aRecID;
|
|
fChangeType := aChangeType;
|
|
fMessage := aMessage;
|
|
|
|
SetLength(fOldValues, aDelta.LoggedFieldCount);
|
|
SetLength(fNewValues, aDelta.LoggedFieldCount);
|
|
end;
|
|
|
|
destructor TDADeltaChange.Destroy;
|
|
begin
|
|
inherited;
|
|
end;
|
|
|
|
function TDADeltaChange.GetDelta: IDADelta;
|
|
begin
|
|
result := fDelta as IDADelta;
|
|
end;
|
|
|
|
function TDADeltaChange.GetNewValue(const aName: string): Variant;
|
|
var
|
|
idx: integer;
|
|
begin
|
|
idx := fDelta.IndexOfLoggedField(aName);
|
|
if (idx < 0) then RaiseError(err_CannotFindField, [aName]);
|
|
result := fNewValues[idx];
|
|
end;
|
|
|
|
function TDADeltaChange.GetOldValue(const aName: string): Variant;
|
|
var
|
|
idx: integer;
|
|
begin
|
|
idx := fDelta.IndexOfLoggedField(aName);
|
|
if (idx < 0) then RaiseError(err_CannotFindField, [aName]);
|
|
result := fOldValues[idx];
|
|
end;
|
|
|
|
procedure TDADeltaChange.SetNewValue(const aName: string; const Value: Variant);
|
|
var
|
|
idx: integer;
|
|
begin
|
|
idx := fDelta.IndexOfLoggedField(aName);
|
|
if (idx < 0) then RaiseError(err_CannotFindField, [aName]);
|
|
if idx >= Length(fNewValues) then
|
|
SetLength(fNewValues, idx + 1);
|
|
fNewValues[idx] := Value;
|
|
end;
|
|
|
|
procedure TDADeltaChange.SetOldValue(const aName: string; const Value: Variant);
|
|
var
|
|
idx: integer;
|
|
begin
|
|
idx := fDelta.IndexOfLoggedField(aName);
|
|
if (idx < 0) then RaiseError(err_CannotFindField, [aName]);
|
|
if idx >= Length(fOldValues) then
|
|
SetLength(fOldValues, idx + 1);
|
|
fOldValues[idx] := Value;
|
|
end;
|
|
|
|
{ EDAApplyUpdateFailed }
|
|
|
|
constructor EDAApplyUpdateFailed.Create(aChange: TDADeltaChange; anOriginalException: Exception);
|
|
begin
|
|
if (anOriginalException<>NIL) then begin
|
|
inherited Create(anOriginalException.Message);
|
|
end
|
|
else inherited Create('');
|
|
|
|
fRecID := aChange.RecID;
|
|
fDeltaName := aChange.Delta.LogicalName;
|
|
end;
|
|
|
|
initialization
|
|
RegisterExceptionClass(EDAApplyUpdateFailed);
|
|
finalization
|
|
UnregisterExceptionClass(EDAApplyUpdateFailed);
|
|
end.
|