mirror of
https://gitlab.com/freepascal.org/fpc/source.git
synced 2025-08-12 01:06:02 +02:00
* Allow for async apply updates
git-svn-id: trunk@47641 -
This commit is contained in:
parent
05065e1d86
commit
a9e7d1ef0d
@ -45,6 +45,12 @@ type
|
|||||||
BlobBuffer : PBlobBuffer;
|
BlobBuffer : PBlobBuffer;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
TApplyRecUpdateResult = Record
|
||||||
|
HadError : Boolean;
|
||||||
|
Response : TResolverResponse;
|
||||||
|
Async : Boolean;
|
||||||
|
end;
|
||||||
|
|
||||||
{ TBufBlobStream }
|
{ TBufBlobStream }
|
||||||
|
|
||||||
TBufBlobStream = class(TStream)
|
TBufBlobStream = class(TStream)
|
||||||
@ -78,6 +84,7 @@ type
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
TRecUpdateBuffer = record
|
TRecUpdateBuffer = record
|
||||||
|
Processing : Boolean;
|
||||||
UpdateKind : TUpdateKind;
|
UpdateKind : TUpdateKind;
|
||||||
{ BookMarkData:
|
{ BookMarkData:
|
||||||
- Is -1 if the update has canceled out. For example: an appended record has been deleted again
|
- Is -1 if the update has canceled out. For example: an appended record has been deleted again
|
||||||
@ -558,6 +565,14 @@ type
|
|||||||
procedure BuildIndexes;
|
procedure BuildIndexes;
|
||||||
procedure RemoveRecordFromIndexes(const ABookmark : TBufBookmark);
|
procedure RemoveRecordFromIndexes(const ABookmark : TBufBookmark);
|
||||||
procedure InternalCreateIndex(F: TBufDataSetIndex); virtual;
|
procedure InternalCreateIndex(F: TBufDataSetIndex); virtual;
|
||||||
|
// Position record for update. Note that no check on state is done.
|
||||||
|
procedure PrepareForUpdate(aUpdate: TRecUpdateBuffer);
|
||||||
|
// Apply update for current record. Called in sequence by ApplyUpdates. The active buffer is positioned on the record to be updated.
|
||||||
|
function DoApplyUpdate(var aUpdate : TRecUpdateBuffer; AbortOnError : Boolean): TApplyRecUpdateResult;
|
||||||
|
// Call this when an update failed. This will return true if update must be retried.
|
||||||
|
function HandleUpdateError(aUpdate: TRecUpdateBuffer; var aResult: TApplyRecUpdateResult; E: Exception): Boolean;
|
||||||
|
// Call this when a record has been resolved. It will free temp buffers.
|
||||||
|
procedure ResolvedRecord(var aUpdate: TRecUpdateBuffer);
|
||||||
Property CurrentIndexBuf : TBufIndex Read GetCurrentIndexBuf;
|
Property CurrentIndexBuf : TBufIndex Read GetCurrentIndexBuf;
|
||||||
Property CurrentIndexDef : TBufDatasetIndex Read FCurrentIndexDef;
|
Property CurrentIndexDef : TBufDatasetIndex Read FCurrentIndexDef;
|
||||||
Property BufIndexDefs[Aindex : Integer] : TBufDatasetIndex Read GetBufIndexDef;
|
Property BufIndexDefs[Aindex : Integer] : TBufDatasetIndex Read GetBufIndexDef;
|
||||||
@ -596,7 +611,8 @@ type
|
|||||||
function GetBookmarkFlag(Buffer: TRecordBuffer): TBookmarkFlag; override;
|
function GetBookmarkFlag(Buffer: TRecordBuffer): TBookmarkFlag; override;
|
||||||
function IsCursorOpen: Boolean; override;
|
function IsCursorOpen: Boolean; override;
|
||||||
function GetRecordCount: Longint; override;
|
function GetRecordCount: Longint; override;
|
||||||
procedure ApplyRecUpdate(UpdateKind : TUpdateKind); virtual;
|
procedure ApplyRecUpdate(UpdateKind : TUpdateKind); virtual; deprecated;
|
||||||
|
Function ApplyRecUpdateEx(UpdateKind : TUpdateKind) : TApplyRecUpdateResult; virtual;
|
||||||
procedure SetOnUpdateError(const AValue: TResolverErrorEvent);
|
procedure SetOnUpdateError(const AValue: TResolverErrorEvent);
|
||||||
procedure SetFilterText(const Value: String); override; {virtual;}
|
procedure SetFilterText(const Value: String); override; {virtual;}
|
||||||
procedure SetFiltered(Value: Boolean); override; {virtual;}
|
procedure SetFiltered(Value: Boolean); override; {virtual;}
|
||||||
@ -1898,6 +1914,7 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TDoubleLinkedBufIndex.BeginUpdate;
|
procedure TDoubleLinkedBufIndex.BeginUpdate;
|
||||||
|
|
||||||
begin
|
begin
|
||||||
if FCurrentRecBuf = FLastRecBuf then
|
if FCurrentRecBuf = FLastRecBuf then
|
||||||
FCursOnFirstRec := True
|
FCursOnFirstRec := True
|
||||||
@ -2837,95 +2854,162 @@ begin
|
|||||||
FOnUpdateError := AValue;
|
FOnUpdateError := AValue;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TCustomBufDataset.ApplyUpdates; // For backward compatibility
|
|
||||||
|
function TCustomBufDataset.ApplyRecUpdateEx(UpdateKind: TUpdateKind): TApplyRecUpdateResult;
|
||||||
|
|
||||||
begin
|
begin
|
||||||
ApplyUpdates(0);
|
Result:=Default(TApplyRecUpdateResult);
|
||||||
|
Result.Async:=False;
|
||||||
|
Result.Response:=rrApply;
|
||||||
|
ApplyRecUpdate(UpdateKind);
|
||||||
|
end;
|
||||||
|
|
||||||
|
Function TCustomBufDataset.HandleUpdateError(aUpdate : TRecUpdateBuffer; var aResult : TApplyRecUpdateResult; E : Exception) : Boolean;
|
||||||
|
|
||||||
|
Var
|
||||||
|
AUpdateError : EUpdateError;
|
||||||
|
|
||||||
|
begin
|
||||||
|
Result:=False;
|
||||||
|
AUpdateError := PSGetUpdateException(Exception(AcquireExceptionObject), nil);
|
||||||
|
if assigned(FOnUpdateError) then
|
||||||
|
begin
|
||||||
|
FOnUpdateError(Self, Self, AUpdateError, aUpdate.UpdateKind, aResult.Response);
|
||||||
|
AUpdateError.Free;
|
||||||
|
Result:=aResult.Response=rrApply;
|
||||||
|
end
|
||||||
|
else if (aResult.Response = rrAbort) then
|
||||||
|
begin
|
||||||
|
raise AUpdateError;
|
||||||
|
end
|
||||||
|
else
|
||||||
|
aUpdateError.Free;
|
||||||
|
end;
|
||||||
|
|
||||||
|
Procedure TCustomBufDataset.PrepareForUpdate(aUpdate : TRecUpdateBuffer);
|
||||||
|
|
||||||
|
begin
|
||||||
|
// For async, this could be a different buffer than the buffer
|
||||||
|
CurrentIndexBuf.GotoBookmark(@aUpdate.BookmarkData);
|
||||||
|
// Synchronise the CurrentBuffer to the ActiveBuffer
|
||||||
|
CurrentRecordToBuffer(ActiveBuffer);
|
||||||
|
end;
|
||||||
|
|
||||||
|
function TCustomBufDataset.DoApplyUpdate(var aUpdate : TRecUpdateBuffer; AbortOnError : Boolean): TApplyRecUpdateResult;
|
||||||
|
|
||||||
|
Const
|
||||||
|
ErrorResponse : Array[Boolean] of TResolverResponse = (rrSkip,rrAbort);
|
||||||
|
|
||||||
|
begin
|
||||||
|
Result.Async:=False;
|
||||||
|
Result.Response:=rrApply;
|
||||||
|
// If the record is first inserted and afterwards deleted, do nothing
|
||||||
|
if ((aUpdate.UpdateKind=ukDelete) and not (assigned(aUpdate.OldValuesBuffer))) then
|
||||||
|
exit;
|
||||||
|
try
|
||||||
|
PrepareForUpdate(aUpdate);
|
||||||
|
Result:=ApplyRecUpdateEx(aUpdate.UpdateKind);
|
||||||
|
except
|
||||||
|
on E: EDatabaseError do
|
||||||
|
begin
|
||||||
|
Result.Response:=ErrorResponse[AbortOnError];
|
||||||
|
if HandleUpdateError(aUpdate,Result,E) then
|
||||||
|
DoApplyUpdate(aUpdate,AbortOnError);
|
||||||
|
Result.HadError:=True;
|
||||||
|
end
|
||||||
|
else
|
||||||
|
raise;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TCustomBufDataset.ResolvedRecord(Var aUpdate : TRecUpdateBuffer);
|
||||||
|
|
||||||
|
begin
|
||||||
|
FreeRecordBuffer(aUpdate.OldValuesBuffer);
|
||||||
|
if aUpdate.UpdateKind = ukDelete then
|
||||||
|
FreeRecordBuffer(TRecordBuffer(AUpdate.BookmarkData.BookmarkData));
|
||||||
|
AUpdate.BookmarkData.BookmarkData := nil;
|
||||||
|
aUpdate.Processing:=False;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TCustomBufDataset.ApplyUpdates(MaxErrors: Integer);
|
procedure TCustomBufDataset.ApplyUpdates(MaxErrors: Integer);
|
||||||
|
|
||||||
var r : Integer;
|
var r : Integer;
|
||||||
FailedCount : integer;
|
FailedCount : integer;
|
||||||
Response : TResolverResponse;
|
Res : TApplyRecUpdateResult;
|
||||||
StoreCurrRec : TBufBookmark;
|
StoreCurrRec : TBufBookmark;
|
||||||
AUpdateError : EUpdateError;
|
aSyncDetected : Boolean;
|
||||||
|
aOldState : TDataSetState;
|
||||||
|
UpdOK : Boolean;
|
||||||
|
|
||||||
begin
|
begin
|
||||||
|
Res:=Default(TApplyRecUpdateResult);
|
||||||
|
Res.Response:=rrApply;
|
||||||
CheckBrowseMode;
|
CheckBrowseMode;
|
||||||
|
|
||||||
CurrentIndexBuf.StoreCurrentRecIntoBookmark(@StoreCurrRec);
|
CurrentIndexBuf.StoreCurrentRecIntoBookmark(@StoreCurrRec);
|
||||||
|
aSyncDetected:=False;
|
||||||
r := 0;
|
aOldState:=SetTempState(dsBlockRead);
|
||||||
FailedCount := 0;
|
|
||||||
Response := rrApply;
|
|
||||||
DisableControls;
|
|
||||||
try
|
try
|
||||||
while (r < Length(FUpdateBuffer)) and (Response <> rrAbort) do
|
DisableControls;
|
||||||
begin
|
r := 0;
|
||||||
// If the record is first inserted and afterwards deleted, do nothing
|
FailedCount := 0;
|
||||||
if not ((FUpdateBuffer[r].UpdateKind=ukDelete) and not (assigned(FUpdateBuffer[r].OldValuesBuffer))) then
|
while (r < Length(FUpdateBuffer)) and (Res.Response <> rrAbort) do
|
||||||
|
// S
|
||||||
|
if Not FUpdateBuffer[r].Processing then
|
||||||
begin
|
begin
|
||||||
CurrentIndexBuf.GotoBookmark(@FUpdateBuffer[r].BookmarkData);
|
UpdOK:=False;
|
||||||
// Synchronise the CurrentBuffer to the ActiveBuffer
|
FUpdateBuffer[r].Processing:=True;
|
||||||
CurrentRecordToBuffer(ActiveBuffer);
|
|
||||||
Response := rrApply;
|
|
||||||
try
|
try
|
||||||
ApplyRecUpdate(FUpdateBuffer[r].UpdateKind);
|
Res:=DoApplyUpdate(FUpdateBuffer[r],FailedCount>=MaxErrors);
|
||||||
except
|
UpdOK:=True;
|
||||||
on E: EDatabaseError do
|
finally
|
||||||
begin
|
if Res.Async then
|
||||||
Inc(FailedCount);
|
aSyncDetected:=True
|
||||||
if FailedCount > word(MaxErrors) then
|
|
||||||
Response := rrAbort
|
|
||||||
else
|
|
||||||
Response := rrSkip;
|
|
||||||
if assigned(FOnUpdateError) then
|
|
||||||
begin
|
|
||||||
AUpdateError := PSGetUpdateException(Exception(AcquireExceptionObject), nil);
|
|
||||||
FOnUpdateError(Self, Self, AUpdateError, FUpdateBuffer[r].UpdateKind, Response);
|
|
||||||
AUpdateError.Free;
|
|
||||||
if Response in [rrApply, rrIgnore] then dec(FailedCount);
|
|
||||||
if Response = rrApply then dec(r);
|
|
||||||
end
|
|
||||||
else if Response = rrAbort then
|
|
||||||
begin
|
|
||||||
AUpdateError := PSGetUpdateException(Exception(AcquireExceptionObject), nil);
|
|
||||||
raise AUpdateError;
|
|
||||||
end;
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
raise;
|
begin
|
||||||
|
FUpdateBuffer[r].Processing:=False;
|
||||||
|
if not UpdOK then // We have an exception, force HadError
|
||||||
|
Res.HadError:=True;
|
||||||
|
if Res.HadError then
|
||||||
|
Inc(FailedCount);
|
||||||
|
if Res.Response in [rrApply, rrIgnore] then
|
||||||
|
ResolvedRecord(FUpdateBuffer[r]);
|
||||||
|
end;
|
||||||
end;
|
end;
|
||||||
if Response in [rrApply, rrIgnore] then
|
inc(r);
|
||||||
begin
|
|
||||||
FreeRecordBuffer(FUpdateBuffer[r].OldValuesBuffer);
|
|
||||||
if FUpdateBuffer[r].UpdateKind = ukDelete then
|
|
||||||
FreeRecordBuffer( TRecordBuffer(FUpdateBuffer[r].BookmarkData.BookmarkData));
|
|
||||||
FUpdateBuffer[r].BookmarkData.BookmarkData := nil;
|
|
||||||
end
|
|
||||||
end;
|
end;
|
||||||
inc(r);
|
|
||||||
end;
|
|
||||||
finally
|
finally
|
||||||
if (FailedCount=0) and Not ManualMergeChangeLog then
|
if (FailedCount=0) and Not (AsyncDetected or ManualMergeChangeLog) then
|
||||||
MergeChangeLog;
|
MergeChangeLog;
|
||||||
InternalGotoBookmark(@StoreCurrRec);
|
InternalGotoBookmark(@StoreCurrRec);
|
||||||
Resync([]);
|
Resync([]);
|
||||||
|
RestoreState(aOldState);
|
||||||
EnableControls;
|
EnableControls;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TCustomBufDataset.MergeChangeLog;
|
procedure TCustomBufDataset.ApplyUpdates; // For backward compatibility
|
||||||
|
|
||||||
var r : Integer;
|
|
||||||
|
|
||||||
begin
|
begin
|
||||||
|
ApplyUpdates(0);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TCustomBufDataset.MergeChangeLog;
|
||||||
|
|
||||||
|
var
|
||||||
|
r,aCount : Integer;
|
||||||
|
|
||||||
|
begin
|
||||||
|
aCount:=0;
|
||||||
|
for r:=0 to length(FUpdateBuffer)-1 do
|
||||||
|
if FUpdateBuffer[r].Processing then
|
||||||
|
Inc(aCount);
|
||||||
|
If aCount>0 then
|
||||||
|
Raise EDatabaseError.CreateFmt(SErrUpdatesInProgess,[ACount]);
|
||||||
for r:=0 to length(FUpdateBuffer)-1 do
|
for r:=0 to length(FUpdateBuffer)-1 do
|
||||||
if assigned(FUpdateBuffer[r].OldValuesBuffer) then
|
if assigned(FUpdateBuffer[r].OldValuesBuffer) then
|
||||||
FreeMem(FUpdateBuffer[r].OldValuesBuffer);
|
FreeMem(FUpdateBuffer[r].OldValuesBuffer);
|
||||||
SetLength(FUpdateBuffer,0);
|
SetLength(FUpdateBuffer,0);
|
||||||
|
|
||||||
if assigned(FUpdateBlobBuffers) then for r:=0 to length(FUpdateBlobBuffers)-1 do
|
if assigned(FUpdateBlobBuffers) then for r:=0 to length(FUpdateBlobBuffers)-1 do
|
||||||
if assigned(FUpdateBlobBuffers[r]) then
|
if assigned(FUpdateBlobBuffers[r]) then
|
||||||
begin
|
begin
|
||||||
|
@ -91,6 +91,7 @@ Resourcestring
|
|||||||
SFieldIsNull = 'The field is null';
|
SFieldIsNull = 'The field is null';
|
||||||
SOnUpdateError = 'An error occurred while applying the updates in a record: %s';
|
SOnUpdateError = 'An error occurred while applying the updates in a record: %s';
|
||||||
SApplyRecNotSupported = 'Applying updates is not supported by this TDataset descendent';
|
SApplyRecNotSupported = 'Applying updates is not supported by this TDataset descendent';
|
||||||
|
SErrUpdatesInProgess = 'Apply updates in progress: %d records being processed.';
|
||||||
SNoWhereFields = 'No %s query specified and failed to generate one. (No fields for inclusion in where statement found)';
|
SNoWhereFields = 'No %s query specified and failed to generate one. (No fields for inclusion in where statement found)';
|
||||||
SNoUpdateFields = 'No %s query specified and failed to generate one. (No fields for insert- or update-statement found)';
|
SNoUpdateFields = 'No %s query specified and failed to generate one. (No fields for insert- or update-statement found)';
|
||||||
SNotSupported = 'Operation is not supported by this type of database';
|
SNotSupported = 'Operation is not supported by this type of database';
|
||||||
|
Loading…
Reference in New Issue
Block a user