123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786 |
- {
- This file is part of the Free Pascal run time library.
- Copyright (c) 2008 by the Free Pascal development team
- Abstract SQL scripting engine.
- See the file COPYING.FPC, included in this distribution,
- for details about the copyright.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- **********************************************************************}
- unit SQLScript;
- {$mode objfpc}{$H+}
- interface
- uses
- Classes, SysUtils;
- Const
- MinSQLSeps = 5; // Default, minimum number of standard SQL separators.
- type
- TSQLScriptStatementEvent = procedure(Sender: TObject; Statement: TStrings; var StopExecution: Boolean) of object;
- TSQLScriptDirectiveEvent = procedure(Sender: TObject; Directive, Argument: AnsiString; var StopExecution: Boolean) of object;
- TSQLScriptExceptionEvent = procedure(Sender: TObject; Statement: TStrings; TheException: Exception; var Continue: boolean) of object;
- TSQLSkipMode = (smNone, smIfBranch, smElseBranch, smAll);
- { TCustomSQLScript }
- TCustomSQLScript = class(TComponent)
- private
- FAutoCommit: Boolean;
- FDollarStrings: Tstrings;
- FLine: Integer;
- FCol: Integer;
- FDefines: TStrings;
- FOnException: TSQLScriptExceptionEvent;
- FSkipMode: TSQLSkipMode;
- FIsSkipping: Boolean;
- FSkipStackIndex: Integer;
- FSkipModeStack: array[0..255] of TSQLSkipMode;
- FIsSkippingStack: array[0..255] of Boolean;
- FAborted: Boolean;
- FUseDollarString: Boolean;
- FUseSetTerm, FUseDefines, FUseCommit,
- FCommentsInSQL: Boolean;
- FTerminator: AnsiString;
- FSQL: TStrings;
- FCurrentStripped,
- FCurrentStatement: TStrings;
- FDirectives: TStrings;
- FComment,
- FEmitLine: Boolean;
- FSeps : Array of string;
- procedure SetDefines(const Value: TStrings);
- function FindNextSeparator(ASeps: Array of string; Out IsExtended : Boolean): AnsiString;
- procedure AddToStatement(value: AnsiString; ForceNewLine : boolean);
- procedure SetDirectives(value: TStrings);
- procedure SetDollarStrings(AValue: TStrings);
- procedure SetSQL(value: TStrings);
- procedure SetTerminator(AValue: AnsiString);
- procedure SetUseDollarString(AValue: Boolean);
- procedure SQLChange(Sender: TObject);
- procedure DollarStringsChange(Sender : TObject);
- Procedure RecalcSeps;
- function GetLine: Integer;
- protected
- procedure ClearStatement; virtual;
- procedure InternalStatement (Statement: TStrings; var StopExecution: Boolean); virtual;
- procedure InternalDirective (Directive, Argument: String; var StopExecution: Boolean); virtual;
- // Runs commit. If ComitRetaining, use CommitRetraining if possible, else stop/starttransaction
- procedure InternalCommit(CommitRetaining: boolean=true); virtual;
- Function ProcessConditional(Directive : String; Param : String) : Boolean; virtual;
- function NextStatement: AnsiString; virtual;
- procedure ProcessStatement; virtual;
- function Available: Boolean; virtual;
- procedure DefaultDirectives; virtual;
- procedure ExecuteStatement (Statement: TStrings; var StopExecution: Boolean); virtual; abstract;
- procedure ExecuteDirective (Directive, Argument: String; var StopExecution: Boolean); virtual; abstract;
- // Executes commit. If possible and CommitRetaining, use CommitRetaining, else
- procedure ExecuteCommit(CommitRetaining: boolean=true); virtual; abstract;
- public
- constructor Create (AnOwner: TComponent); override;
- destructor Destroy; override;
- procedure Execute; virtual;
- protected
- property Aborted: Boolean read FAborted;
- property Line: Integer read GetLine;
- Property AutoCommit : Boolean Read FAutoCommit Write FAutoCommit default false;
- property CommentsInSQL: Boolean read FCommentsInSQL write FCommentsInSQL default true;
- property UseSetTerm: Boolean read FUseSetTerm write FUseSetTerm default true;
- property UseCommit: Boolean read FUseCommit write FUseCommit Default true;
- property UseDefines: Boolean read FUseDefines write FUseDefines default true;
- Property UseDollarString : Boolean Read FUseDollarString Write SetUseDollarString default false;
- Property DollarStrings : TStrings Read FDollarStrings Write SetDollarStrings;
- property Defines : TStrings Read FDefines Write SetDefines;
- property Directives: TStrings read FDirectives write SetDirectives;
- property Script: TStrings read FSQL write SetSQL; // script to execute
- property Terminator: AnsiString read FTerminator write SetTerminator;
- property OnException : TSQLScriptExceptionEvent read FOnException write FOnException;
- end;
- { TEventSQLScript }
- TEventSQLScript = class (TCustomSQLScript)
- private
- FAfterExec: TNotifyEvent;
- FBeforeExec: TNotifyEvent;
- FOnCommit: TNotifyEvent;
- FOnSQLStatement: TSQLScriptStatementEvent;
- FOnDirective: TSQLScriptDirectiveEvent;
- protected
- procedure ExecuteStatement (SQLStatement: TStrings; var StopExecution: Boolean); override;
- procedure ExecuteDirective (Directive, Argument: String; var StopExecution: Boolean); override;
- procedure ExecuteCommit(CommitRetaining: boolean=true); override;
- public
- procedure Execute; override;
- property Aborted;
- property Line;
- published
- Property UseDollarString;
- Property DollarStrings;
- property Directives;
- property Defines;
- property Script;
- property Terminator;
- property CommentsinSQL;
- property UseSetTerm;
- property UseCommit;
- property UseDefines;
- property OnException;
- property OnSQLStatement: TSQLScriptStatementEvent read FOnSQLStatement write FOnSQLStatement;
- property OnDirective: TSQLScriptDirectiveEvent read FOnDirective write FOnDirective;
- property OnCommit: TNotifyEvent read FOnCommit write FOnCommit;
- property BeforeExecute : TNotifyEvent read FBeforeExec write FBeforeExec;
- property AfterExecute : TNotifyEvent read FAfterExec write FAfterExec;
- end;
- ESQLScript = Class(Exception);
- implementation
- Resourcestring
- SErrIfXXXNestingLimitReached = '#IFDEF nesting limit reached';
- SErrInvalidEndif = '#ENDIF without #IFDEF';
- SErrInvalidElse = '#ELSE without #IFDEF';
- { ---------------------------------------------------------------------
- Auxiliary Functions
- ---------------------------------------------------------------------}
-
- function StartsWith(S1, S2: AnsiString): Boolean;
- var
- L1,L2 : Integer;
- begin
- Result:=False;
- L1:=Length(S1);
- L2:=Length(S2);
- if (L2=0) or (L1<L2) then
- Exit;
- Result:=(AnsiCompareStr(Copy(s1,1,L2),S2)=0);
- Result := Result and ((L2 = L1) or (s1[L2+1] = ' '));
- end;
- function GetFirstSeparator(S: AnsiString; Sep: array of string): integer;
- var
- i, C, M: Integer;
- begin
- M:=length(S) + 1;
- Result:=-1;
- for i:=0 to high(Sep) do
- begin
- C:=Pos(Sep[i],S);
- if (C<>0) and (C<M) then
- begin
- M:=C;
- Result:=i;
- end;
- end;
- end;
- Function ConvertWhiteSpace(S : String) : String;
- begin
- Result:=StringReplace(S,#13,' ',[rfReplaceAll]);
- Result:=StringReplace(Result,#10,' ',[rfReplaceAll]);
- Result:=Trim(Result);
- end;
- { ---------------------------------------------------------------------
- TSQLScript
- ---------------------------------------------------------------------}
- procedure TCustomSQLScript.SQLChange(Sender: TObject);
- begin
- FLine:=1;
- FCol:=1;
- end;
- procedure TCustomSQLScript.DollarStringsChange(Sender: TObject);
- begin
- RecalcSeps;
- end;
- procedure TCustomSQLScript.RecalcSeps;
- Var
- L : Integer;
- begin
- L:=MinSQLSeps;
- If UseDollarString then
- L:=L+1+DollarStrings.Count;
- SetLength(FSeps,L);
- FSeps[0]:=FTerminator;
- FSeps[1]:='/*';
- FSeps[2]:='"';
- FSeps[3]:='''';
- FSeps[4]:='--';
- If UseDollarString then
- begin
- FSeps[MinSQLSeps]:='$$';
- For L:=0 to FDollarStrings.Count-1 do
- FSeps[MinSQLSeps+1+L]:='$'+FDollarStrings[L]+'$';
- end;
- end;
- procedure TCustomSQLScript.SetDirectives(value: TStrings);
- var
- i : Integer;
- S : AnsiString;
-
- begin
- FDirectives.Clear();
- if (Value<>Nil) then
- begin
- for i:=0 to value.Count - 1 do
- begin
- S:=UpperCase(ConvertWhiteSpace(value[i]));
- if Length(S)>0 then
- FDirectives.Add(S);
- end;
- end;
- DefaultDirectives;
- end;
- procedure TCustomSQLScript.SetDollarStrings(AValue: TStrings);
- begin
- if FDollarStrings=AValue then Exit;
- FDollarStrings.Assign(AValue);
- If FUseDollarString then
- RecalcSeps;
- end;
- procedure TCustomSQLScript.SetSQL(value: TStrings);
- begin
- FSQL.Assign(value);
- FLine:=1;
- FCol:=1;
- end;
- procedure TCustomSQLScript.SetTerminator(AValue: AnsiString);
- begin
- if FTerminator=AValue then Exit;
- FTerminator:=AValue;
- if Length(FSeps)>0 then
- FSeps[0]:=FTerminator;
- end;
- procedure TCustomSQLScript.SetUseDollarString(AValue: Boolean);
- begin
- if FUseDollarString=AValue then Exit;
- FUseDollarString:=AValue;
- RecalcSeps;
- end;
- function TCustomSQLScript.GetLine: Integer;
- begin
- Result:=FLine - 1;
- end;
- procedure TCustomSQLScript.AddToStatement(value: AnsiString;
- ForceNewLine: boolean);
- Procedure DA(L : TStrings);
- begin
- With L do
- if ForceNewLine or (Count=0) then
- Add(value)
- else
- Strings[Count-1]:=Strings[Count-1] + value;
- end;
- begin
- DA(FCurrentStatement);
- if Not FComment then
- DA(FCurrentStripped);
- end;
- function TCustomSQLScript.FindNextSeparator(ASeps: array of string; out
- IsExtended: Boolean): AnsiString;
- var
- S: AnsiString;
- I : Integer;
- begin
- Result:='';
- while (FLine<=FSQL.Count) do
- begin
- S:=FSQL.Strings[FLine-1];
- if (FCol>1) then
- begin
- S:=Copy(S,FCol,length(S));
- end;
- I:=GetFirstSeparator(S,ASeps);
- if (I=-1) then
- begin
- if FEmitLine then
- AddToStatement(S,(FCol<=1));
- FCol:=1;
- FLine:=FLine+1;
- end
- else
- begin
- Result:=ASeps[i];
- IsExtended:=I>=MinSQLSeps;
- if FEmitLine then
- AddToStatement(Copy(S,1,Pos(Result,S)-1),(FCol=1));
- FCol:=(FCol-1)+Pos(Result,S);
- break;
- end;
- end;
- end;
- function TCustomSQLScript.Available: Boolean;
- begin
- With FSQL do
- Result:=(FLine<Count) or
- (
- ( FLine = Count ) and
- ( FCol < Length(Strings[Count-1] ) )
- );
- end;
- procedure TCustomSQLScript.InternalStatement(Statement: TStrings; var StopExecution: Boolean);
- var
- cont : boolean;
-
- begin
- try
- ExecuteStatement(Statement, StopExecution);
- except
- on E : Exception do
- begin
- cont := false;
- if assigned (FOnException) then
- FOnException (self, Statement, E, cont);
- if not cont then
- Raise;
- end;
- end;
- end;
- procedure TCustomSQLScript.InternalDirective(Directive, Argument: String; var StopExecution: Boolean);
- var
- cont : boolean;
- l : TStrings;
-
- begin
- try
- ExecuteDirective(Directive, Argument, StopExecution);
- except
- on E : Exception do
- begin
- cont := false;
- if assigned (FOnException) then
- begin
- l := TStringlist.Create;
- try
- L.Add(Directive);
- if Argument <> '' then
- L.Add(Argument);
- FOnException (self, l, E, cont);
- finally
- L.Free;
- end;
- end;
- if not cont then
- Raise;
- end;
- end;
- end;
- procedure TCustomSQLScript.InternalCommit(CommitRetaining: boolean=true);
- var
- cont : boolean;
- l : TStrings;
-
- begin
- try
- ExecuteCommit(CommitRetaining);
- except
- on E : Exception do
- begin
- cont := false;
- if assigned (FOnException) then
- begin
- l := TStringlist.Create;
- try
- L.Add('COMMIT');
- FOnException (self, l, E, cont);
- finally
- L.Free;
- end;
- end;
- if not cont then
- Raise;
- end;
- end;
- end;
- procedure TCustomSQLScript.ClearStatement;
- begin
- FCurrentStatement.Clear;
- FCurrentStripped.Clear;
- end;
- procedure TCustomSQLScript.ProcessStatement;
- Var
- S,
- Directive : String;
- I : longint;
- begin
- if (FCurrentStatement.Count=0) then
- Exit;
- S:=Trim(FCurrentStripped.Text);
- I:=0;
- Directive:='';
- While (i<FDirectives.Count) and (Directive='') do
- begin
- If StartsWith(AnsiUpperCase(S), FDirectives[i]) Then
- Directive:=FDirectives[i];
- Inc(I);
- end;
- If (Directive<>'') then
- begin
- S:=Trim(Copy(S,Length(Directive)+1,length(S)));
- If (Directive[1]='#') then
- begin
- if not FUseDefines or not ProcessConditional(Directive,S) then
- if Not FIsSkipping then
- InternalDirective (Directive, S, FAborted);
- end
- else If Not FIsSkipping then
- begin
- // If AutoCommit, skip any explicit commits.
- if FUseCommit
- and ((Directive = 'COMMIT') or (Directive = 'COMMIT WORK' {SQL standard}))
- and not FAutoCommit then
- InternalCommit(false) //explicit commit, no commit retaining
- else if FUseCommit
- and (Directive = 'COMMIT RETAIN') {at least Firebird syntax}
- and not FAutoCommit then
- InternalCommit(true)
- else if FUseSetTerm
- and (Directive = 'SET TERM' {Firebird/Interbase only}) then
- begin
- FTerminator:=S;
- RecalcSeps;
- end
- else
- InternalDirective (Directive,S,FAborted)
- end
- end
- else
- if (not FIsSkipping) then
- begin
- InternalStatement(FCurrentStatement,FAborted);
- If FAutoCommit and not FAborted then
- InternalCommit;
- end;
- end;
- procedure TCustomSQLScript.Execute;
- begin
- FSkipMode:=smNone;
- FIsSkipping:=False;
- FSkipStackIndex:=0;
- Faborted:=False;
- FLine:=1;
- DefaultDirectives;
- Repeat
- NextStatement();
- if Length(Trim(FCurrentStripped.Text))>0 then
- ProcessStatement;
- Until FAborted or Not Available;
- end;
- function TCustomSQLScript.NextStatement: AnsiString;
- var
- pnt: AnsiString;
- b,isExtra: Boolean;
- begin
- ClearStatement;
- while FLine <= FSQL.Count do
- begin
- pnt:=FindNextSeparator(FSeps,isExtra);
- if (pnt=FTerminator) then
- begin
- FCol:=FCol + length(pnt);
- break;
- end
- else if pnt = '/*' then
- begin
- FComment:=True;
- if FCommentsInSQL then
- AddToStatement(pnt,false)
- else
- FEmitLine:=False;
- FCol:=FCol + length(pnt);
- pnt:=FindNextSeparator(['*/'],b);
- if FCommentsInSQL then
- AddToStatement(pnt,false)
- else
- FEmitLine:=True;
- FCol:=FCol + length(pnt);
- FComment:=False;
- end
- else if pnt = '--' then
- begin
- FComment:=True;
- if FCommentsInSQL then
- AddToStatement(Copy(FSQL[FLine-1],FCol,Length(FSQL[FLine-1])-FCol+1),False);
- Inc(Fline);
- FCol:=1;
- FComment:=False;
- end
- else if pnt = '"' then
- begin
- AddToStatement(pnt,false);
- FCol:=FCol + length(pnt);
- pnt:=FindNextSeparator(['"'],b);
- AddToStatement(pnt,false);
- FCol:=FCol + length(pnt);
- end
- else if pnt = '''' then
- begin
- AddToStatement(pnt,False);
- FCol:=FCol + length(pnt);
- pnt:=FindNextSeparator([''''],b);
- AddToStatement(pnt,false);
- FCol:=FCol + length(pnt);
- end
- else if IsExtra then
- begin
- AddToStatement(pnt,false);
- FCol:=FCol + length(pnt);
- pnt:=FindNextSeparator([pnt],b);
- AddToStatement(pnt,false);
- FCol:=FCol + length(pnt);
- end;
- end;
- while (FCurrentStatement.Count > 0) and (trim(FCurrentStatement.Strings[0]) = '') do
- FCurrentStatement.Delete(0);
- while (FCurrentStripped.Count > 0) and (trim(FCurrentStripped.Strings[0]) = '') do
- FCurrentStripped.Delete(0);
- Result:=FCurrentStatement.Text;
- end;
- constructor TCustomSQLScript.Create(AnOwner: TComponent);
- Var
- L : TStringList;
- begin
- inherited;
- L:=TStringList.Create;
- With L do
- begin
- Sorted:=True;
- Duplicates:=dupIgnore;
- end;
- FDefines:=L;
- FCommentsInSQL:=True;
- FTerminator:=';';
- L:=TStringList.Create();
- L.OnChange:=@SQLChange;
- FSQL:=L;
- L:=TStringList.Create();
- L.OnChange:=@DollarStringsChange;
- FDollarStrings:=L;
- ReCalcSeps;
- FDirectives:=TStringList.Create();
- FCurrentStripped:=TStringList.Create();
- FCurrentStatement:=TStringList.Create();
- FLine:=1;
- FCol:=1;
- FEmitLine:=True;
- FUseCommit := true;
- FUseDefines := True;
- FUseSetTerm := True;
- DefaultDirectives;
- end;
- destructor TCustomSQLScript.Destroy;
- begin
- FreeAndNil(FCurrentStripped);
- FreeAndNil(FCurrentStatement);
- FreeAndNil(FSQL);
- FreeAndNil(FDirectives);
- FreeAndNil(FDefines);
- FreeAndNil(FDollarStrings);
- inherited Destroy;
- end;
- procedure TCustomSQLScript.SetDefines(const Value: TStrings);
- begin
- FDefines.Assign(Value);
- end;
- procedure TCustomSQLScript.DefaultDirectives;
- Procedure Add(S : String);
-
- begin
- if FDirectives.IndexOf(S)=-1 then
- FDirectives.Add(S);
- end;
- begin
- // Insertion order matters as testing for directives will be done with StartsWith
- if FUseSetTerm then
- Add('SET TERM');
- if FUseCommit then
- begin
- Add('COMMIT WORK'); {SQL Standard, equivalent to commit}
- Add('COMMIT RETAIN'); {Firebird/Interbase; probably won't hurt on other dbs}
- Add('COMMIT'); {Shorthand used in many dbs, e.g. Firebird}
- end;
- if FUseDefines then
- begin
- Add('#IFDEF');
- Add('#IFNDEF');
- Add('#ELSE');
- Add('#ENDIF');
- Add('#DEFINE');
- Add('#UNDEF');
- Add('#UNDEFINE');
- end;
- end;
- function TCustomSQLScript.ProcessConditional(Directive: String; Param: String
- ): Boolean;
- Procedure PushSkipMode;
- begin
- if FSkipStackIndex=High(FSkipModeStack) then
- Raise ESQLScript.Create(SErrIfXXXNestingLimitReached);
- FSkipModeStack[FSkipStackIndex]:=FSkipMode;
- FIsSkippingStack[FSkipStackIndex]:=FIsSkipping;
- Inc(FSkipStackIndex);
- end;
- Procedure PopSkipMode;
- begin
- if FSkipStackIndex = 0 then
- Raise ESQLScript.Create(SErrInvalidEndif);
- Dec(FSkipStackIndex);
- FSkipMode := FSkipModeStack[FSkipStackIndex];
- FIsSkipping := FIsSkippingStack[FSkipStackIndex];
- end;
- Var
- Index : Integer;
- begin
- Result:=True;
- if (Directive='#DEFINE') then
- begin
- if not FIsSkipping then
- FDefines.Add(Param);
- end
- else if (Directive='#UNDEF') or (Directive='#UNDEFINE') then
- begin
- if not FIsSkipping then
- begin
- Index:=FDefines.IndexOf(Param);
- if (Index>=0) then
- FDefines.Delete(Index);
- end;
- end
- else if (Directive='#IFDEF') or (Directive='#IFNDEF') then
- begin
- PushSkipMode;
- if FIsSkipping then
- begin
- FSkipMode:=smAll;
- FIsSkipping:=true;
- end
- else
- begin
- Index:=FDefines.IndexOf(Param);
- if ((Directive='#IFDEF') and (Index<0)) or
- ((Directive='#IFNDEF') and (Index>=0)) then
- begin
- FSkipMode:=smIfBranch;
- FIsSkipping:=true;
- end
- else
- FSkipMode := smElseBranch;
- end;
- end
- else if (Directive='#ELSE') then
- begin
- if (FSkipStackIndex=0) then
- Raise ESQLScript.Create(SErrInvalidElse);
- if (FSkipMode=smIfBranch) then
- FIsSkipping:=false
- else if (FSkipMode=smElseBranch) then
- FIsSkipping:=true;
- end
- else if (Directive='#ENDIF') then
- PopSkipMode
- else
- Result:=False;
- end;
- { TEventSQLScript }
- procedure TEventSQLScript.ExecuteStatement(SQLStatement: TStrings;
- var StopExecution: Boolean);
- begin
- if assigned (FOnSQLStatement) then
- FOnSQLStatement (self, SQLStatement, StopExecution);
- end;
- procedure TEventSQLScript.ExecuteDirective(Directive, Argument: String;
- var StopExecution: Boolean);
- begin
- if assigned (FOnDirective) then
- FOnDirective (Self, Directive, Argument, StopExecution);
- end;
- procedure TEventSQLScript.ExecuteCommit(CommitRetaining: boolean=true);
- begin
- if assigned (FOnCommit) then
- FOnCommit (Self);
- end;
- procedure TEventSQLScript.Execute;
- begin
- if assigned (FBeforeExec) then
- FBeforeExec (Self);
- inherited Execute;
- if assigned (FAfterExec) then
- FAfterExec (Self);
- end;
- end.
|