sqlscript.pp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697
  1. {
  2. This file is part of the Free Pascal run time library.
  3. Copyright (c) 2008 by the Free Pascal development team
  4. Abstract SQL scripting engine.
  5. See the file COPYING.FPC, included in this distribution,
  6. for details about the copyright.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  10. **********************************************************************}
  11. unit sqlscript;
  12. {$mode objfpc}{$H+}
  13. interface
  14. uses
  15. Classes, SysUtils;
  16. type
  17. TSQLScriptStatementEvent = procedure(Sender: TObject; Statement: TStrings; var StopExecution: Boolean) of object;
  18. TSQLScriptDirectiveEvent = procedure(Sender: TObject; Directive, Argument: AnsiString; var StopExecution: Boolean) of object;
  19. TSQLScriptExceptionEvent = procedure(Sender: TObject; Statement: TStrings; TheException: Exception; var Continue: boolean) of object;
  20. TSQLSkipMode = (smNone, smIfBranch, smElseBranch, smAll);
  21. { TCustomSQLScript }
  22. TCustomSQLScript = class(TComponent)
  23. private
  24. FAutoCommit: Boolean;
  25. FLine: Integer;
  26. FCol: Integer;
  27. FDefines: TStrings;
  28. FOnException: TSQLScriptExceptionEvent;
  29. FSkipMode: TSQLSkipMode;
  30. FIsSkipping: Boolean;
  31. FSkipStackIndex: Integer;
  32. FSkipModeStack: array[0..255] of TSQLSkipMode;
  33. FIsSkippingStack: array[0..255] of Boolean;
  34. FAborted: Boolean;
  35. FUseSetTerm, FUseDefines, FUseCommit,
  36. FCommentsInSQL: Boolean;
  37. FTerminator: AnsiString;
  38. FSQL: TStrings;
  39. FCurrentStripped,
  40. FCurrentStatement: TStrings;
  41. FDirectives: TStrings;
  42. FComment,
  43. FEmitLine: Boolean;
  44. procedure SetDefines(const Value: TStrings);
  45. function FindNextSeparator(sep: array of string): AnsiString;
  46. procedure AddToStatement(value: AnsiString; ForceNewLine : boolean);
  47. procedure SetDirectives(value: TStrings);
  48. procedure SetSQL(value: TStrings);
  49. procedure SQLChange(Sender: TObject);
  50. function GetLine: Integer;
  51. protected
  52. procedure ClearStatement; virtual;
  53. procedure InternalStatement (Statement: TStrings; var StopExecution: Boolean); virtual;
  54. procedure InternalDirective (Directive, Argument: String; var StopExecution: Boolean); virtual;
  55. // Runs commit. If ComitRetaining, use CommitRetraining if possible, else stop/starttransaction
  56. procedure InternalCommit(CommitRetaining: boolean=true); virtual;
  57. Function ProcessConditional(Directive : String; Param : String) : Boolean; virtual;
  58. function NextStatement: AnsiString; virtual;
  59. procedure ProcessStatement; virtual;
  60. function Available: Boolean; virtual;
  61. procedure DefaultDirectives; virtual;
  62. procedure ExecuteStatement (Statement: TStrings; var StopExecution: Boolean); virtual; abstract;
  63. procedure ExecuteDirective (Directive, Argument: String; var StopExecution: Boolean); virtual; abstract;
  64. // Executes commit. If possible and CommitRetaining, use CommitRetaining, else
  65. procedure ExecuteCommit(CommitRetaining: boolean=true); virtual; abstract;
  66. public
  67. constructor Create (AnOwner: TComponent); override;
  68. destructor Destroy; override;
  69. procedure Execute; virtual;
  70. protected
  71. property Aborted: Boolean read FAborted;
  72. property Line: Integer read GetLine;
  73. Property AutoCommit : Boolean Read FAutoCommit Write FAutoCommit;
  74. property CommentsInSQL: Boolean read FCommentsInSQL write FCommentsInSQL;
  75. property UseSetTerm: Boolean read FUseSetTerm write FUseSetTerm;
  76. property UseCommit: Boolean read FUseCommit write FUseCommit;
  77. property UseDefines: Boolean read FUseDefines write FUseDefines;
  78. property Defines : TStrings Read FDefines Write SetDefines;
  79. property Directives: TStrings read FDirectives write SetDirectives;
  80. property Script: TStrings read FSQL write SetSQL; // script to execute
  81. property Terminator: AnsiString read FTerminator write FTerminator;
  82. property OnException : TSQLScriptExceptionEvent read FOnException write FOnException;
  83. end;
  84. { TEventSQLScript }
  85. TEventSQLScript = class (TCustomSQLScript)
  86. private
  87. FAfterExec: TNotifyEvent;
  88. FBeforeExec: TNotifyEvent;
  89. FOnCommit: TNotifyEvent;
  90. FOnSQLStatement: TSQLScriptStatementEvent;
  91. FOnDirective: TSQLScriptDirectiveEvent;
  92. protected
  93. procedure ExecuteStatement (SQLStatement: TStrings; var StopExecution: Boolean); override;
  94. procedure ExecuteDirective (Directive, Argument: String; var StopExecution: Boolean); override;
  95. procedure ExecuteCommit(CommitRetaining: boolean=true); override;
  96. public
  97. procedure Execute; override;
  98. property Aborted;
  99. property Line;
  100. published
  101. property Directives;
  102. property Defines;
  103. property Script;
  104. property Terminator;
  105. property CommentsinSQL;
  106. property UseSetTerm;
  107. property UseCommit;
  108. property UseDefines;
  109. property OnException;
  110. property OnSQLStatement: TSQLScriptStatementEvent read FOnSQLStatement write FOnSQLStatement;
  111. property OnDirective: TSQLScriptDirectiveEvent read FOnDirective write FOnDirective;
  112. property OnCommit: TNotifyEvent read FOnCommit write FOnCommit;
  113. property BeforeExecute : TNotifyEvent read FBeforeExec write FBeforeExec;
  114. property AfterExecute : TNotifyEvent read FAfterExec write FAfterExec;
  115. end;
  116. ESQLScript = Class(Exception);
  117. implementation
  118. Resourcestring
  119. SErrIfXXXNestingLimitReached = '#IFDEF nesting limit reached';
  120. SErrInvalidEndif = '#ENDIF without #IFDEF';
  121. SErrInvalidElse = '#ELSE without #IFDEF';
  122. { ---------------------------------------------------------------------
  123. Auxiliary Functions
  124. ---------------------------------------------------------------------}
  125. function StartsWith(S1, S2: AnsiString): Boolean;
  126. var
  127. L1,L2 : Integer;
  128. begin
  129. Result:=False;
  130. L1:=Length(S1);
  131. L2:=Length(S2);
  132. if (L2=0) or (L1<L2) then
  133. Exit;
  134. Result:=(AnsiCompareStr(Copy(s1,1,L2),S2)=0);
  135. Result := Result and ((L2 = L1) or (s1[L2+1] = ' '));
  136. end;
  137. function GetFirstSeparator(S: AnsiString; Sep: array of string): AnsiString;
  138. var
  139. i, C, M: Integer;
  140. begin
  141. M:=length(S) + 1;
  142. Result:='';
  143. for i:=0 to high(Sep) do
  144. begin
  145. C:=Pos(Sep[i],S);
  146. if (C<>0) and (C<M) then
  147. begin
  148. M:=C;
  149. Result:=Sep[i];
  150. end;
  151. end;
  152. end;
  153. Function ConvertWhiteSpace(S : String) : String;
  154. begin
  155. Result:=StringReplace(S,#13,' ',[rfReplaceAll]);
  156. Result:=StringReplace(Result,#10,' ',[rfReplaceAll]);
  157. Result:=Trim(Result);
  158. end;
  159. { ---------------------------------------------------------------------
  160. TSQLScript
  161. ---------------------------------------------------------------------}
  162. procedure TCustomSQLScript.SQLChange(Sender: TObject);
  163. begin
  164. FLine:=1;
  165. FCol:=1;
  166. end;
  167. procedure TCustomSQLScript.SetDirectives(value: TStrings);
  168. var
  169. i : Integer;
  170. S : AnsiString;
  171. begin
  172. FDirectives.Clear();
  173. if (Value<>Nil) then
  174. begin
  175. for i:=0 to value.Count - 1 do
  176. begin
  177. S:=UpperCase(ConvertWhiteSpace(value[i]));
  178. if Length(S)>0 then
  179. FDirectives.Add(S);
  180. end;
  181. end;
  182. DefaultDirectives;
  183. end;
  184. procedure TCustomSQLScript.SetSQL(value: TStrings);
  185. begin
  186. FSQL.Assign(value);
  187. FLine:=1;
  188. FCol:=1;
  189. end;
  190. function TCustomSQLScript.GetLine: Integer;
  191. begin
  192. Result:=FLine - 1;
  193. end;
  194. procedure TCustomSQLScript.AddToStatement(value: AnsiString; ForceNewLine : Boolean);
  195. Procedure DA(L : TStrings);
  196. begin
  197. With L do
  198. if ForceNewLine or (Count=0) then
  199. Add(value)
  200. else
  201. Strings[Count-1]:=Strings[Count-1] + value;
  202. end;
  203. begin
  204. DA(FCurrentStatement);
  205. if Not FComment then
  206. DA(FCurrentStripped);
  207. end;
  208. function TCustomSQLScript.FindNextSeparator(Sep: array of string): AnsiString;
  209. var
  210. S: AnsiString;
  211. begin
  212. Result:='';
  213. while (FLine<=FSQL.Count) do
  214. begin
  215. S:=FSQL.Strings[FLine-1];
  216. if (FCol>1) then
  217. begin
  218. S:=Copy(S,FCol,length(S));
  219. end;
  220. Result:=GetFirstSeparator(S,Sep);
  221. if (Result='') then
  222. begin
  223. if FEmitLine then
  224. AddToStatement(S,(FCol<=1));
  225. FCol:=1;
  226. FLine:=FLine+1;
  227. end
  228. else
  229. begin
  230. if FEmitLine then
  231. AddToStatement(Copy(S,1,Pos(Result,S)-1),(FCol=1));
  232. FCol:=(FCol-1)+Pos(Result,S);
  233. break;
  234. end;
  235. end;
  236. end;
  237. function TCustomSQLScript.Available: Boolean;
  238. begin
  239. With FSQL do
  240. Result:=(FLine<Count) or (FCol<Length(Strings[Count-1]))
  241. end;
  242. procedure TCustomSQLScript.InternalStatement(Statement: TStrings; var StopExecution: Boolean);
  243. var
  244. cont : boolean;
  245. begin
  246. try
  247. ExecuteStatement(Statement, StopExecution);
  248. except
  249. on E : Exception do
  250. begin
  251. cont := false;
  252. if assigned (FOnException) then
  253. FOnException (self, Statement, E, cont);
  254. if not cont then
  255. Raise;
  256. end;
  257. end;
  258. end;
  259. procedure TCustomSQLScript.InternalDirective(Directive, Argument: String; var StopExecution: Boolean);
  260. var
  261. cont : boolean;
  262. l : TStrings;
  263. begin
  264. try
  265. ExecuteDirective(Directive, Argument, StopExecution);
  266. except
  267. on E : Exception do
  268. begin
  269. cont := false;
  270. if assigned (FOnException) then
  271. begin
  272. l := TStringlist.Create;
  273. try
  274. L.Add(Directive);
  275. if Argument <> '' then
  276. L.Add(Argument);
  277. FOnException (self, l, E, cont);
  278. finally
  279. L.Free;
  280. end;
  281. end;
  282. if not cont then
  283. Raise;
  284. end;
  285. end;
  286. end;
  287. procedure TCustomSQLScript.InternalCommit(CommitRetaining: boolean=true);
  288. var
  289. cont : boolean;
  290. l : TStrings;
  291. begin
  292. try
  293. ExecuteCommit(CommitRetaining);
  294. except
  295. on E : Exception do
  296. begin
  297. cont := false;
  298. if assigned (FOnException) then
  299. begin
  300. l := TStringlist.Create;
  301. try
  302. L.Add('COMMIT');
  303. FOnException (self, l, E, cont);
  304. finally
  305. L.Free;
  306. end;
  307. end;
  308. if not cont then
  309. Raise;
  310. end;
  311. end;
  312. end;
  313. procedure TCustomSQLScript.ClearStatement;
  314. begin
  315. FCurrentStatement.Clear;
  316. FCurrentStripped.Clear;
  317. end;
  318. procedure TCustomSQLScript.ProcessStatement;
  319. Var
  320. S,
  321. Directive : String;
  322. I : longint;
  323. begin
  324. if (FCurrentStatement.Count=0) then
  325. Exit;
  326. S:=Trim(FCurrentStripped.Text);
  327. I:=0;
  328. Directive:='';
  329. While (i<FDirectives.Count) and (Directive='') do
  330. begin
  331. If StartsWith(AnsiUpperCase(S), FDirectives[i]) Then
  332. Directive:=FDirectives[i];
  333. Inc(I);
  334. end;
  335. If (Directive<>'') then
  336. begin
  337. S:=Trim(Copy(S,Length(Directive)+1,length(S)));
  338. If (Directive[1]='#') then
  339. begin
  340. if not FUseDefines or not ProcessConditional(Directive,S) then
  341. if Not FIsSkipping then
  342. InternalDirective (Directive, S, FAborted);
  343. end
  344. else If Not FIsSkipping then
  345. begin
  346. // If AutoCommit, skip any explicit commits.
  347. if FUseCommit
  348. and ((Directive = 'COMMIT') or (Directive = 'COMMIT WORK' {SQL standard}))
  349. and not FAutoCommit then
  350. InternalCommit(false) //explicit commit, no commit retaining
  351. else if FUseCommit
  352. and (Directive = 'COMMIT RETAIN') {at least Firebird syntax}
  353. and not FAutoCommit then
  354. InternalCommit(true)
  355. else if FUseSetTerm
  356. and (Directive = 'SET TERM' {Firebird/Interbase only}) then
  357. FTerminator:=S
  358. else
  359. InternalDirective (Directive,S,FAborted)
  360. end
  361. end
  362. else
  363. if (not FIsSkipping) then
  364. begin
  365. InternalStatement(FCurrentStatement,FAborted);
  366. If FAutoCommit and not FAborted then
  367. InternalCommit;
  368. end;
  369. end;
  370. procedure TCustomSQLScript.Execute;
  371. begin
  372. FSkipMode:=smNone;
  373. FIsSkipping:=False;
  374. FSkipStackIndex:=0;
  375. Faborted:=False;
  376. DefaultDirectives;
  377. Repeat
  378. NextStatement();
  379. if Length(Trim(FCurrentStripped.Text))>0 then
  380. ProcessStatement;
  381. Until FAborted or Not Available;
  382. end;
  383. function TCustomSQLScript.NextStatement: AnsiString;
  384. var
  385. pnt: AnsiString;
  386. addnewline,terminator_found: Boolean;
  387. begin
  388. terminator_found:=False;
  389. ClearStatement;
  390. addnewline:=false;
  391. while FLine <= FSQL.Count do
  392. begin
  393. pnt:=FindNextSeparator([FTerminator, '/*', '"', '''', '--']);
  394. if (pnt=FTerminator) then
  395. begin
  396. FCol:=FCol + length(pnt);
  397. terminator_found:=True;
  398. break;
  399. end
  400. else if pnt = '/*' then
  401. begin
  402. FComment:=True;
  403. if FCommentsInSQL then
  404. AddToStatement(pnt,false)
  405. else
  406. FEmitLine:=False;
  407. FCol:=FCol + length(pnt);
  408. pnt:=FindNextSeparator(['*/']);
  409. if FCommentsInSQL then
  410. AddToStatement(pnt,false)
  411. else
  412. FEmitLine:=True;
  413. FCol:=FCol + length(pnt);
  414. FComment:=False;
  415. end
  416. else if pnt = '--' then
  417. begin
  418. FComment:=True;
  419. if FCommentsInSQL then
  420. begin
  421. AddToStatement(Copy(FSQL[FLine-1],FCol,Length(FSQL[FLine-1])-FCol+1),False);
  422. AddNewLine:=true;
  423. end;
  424. Inc(Fline);
  425. FCol:=0;
  426. FComment:=False;
  427. end
  428. else if pnt = '"' then
  429. begin
  430. AddToStatement(pnt,false);
  431. FCol:=FCol + length(pnt);
  432. pnt:=FindNextSeparator(['"']);
  433. AddToStatement(pnt,false);
  434. FCol:=FCol + length(pnt);
  435. end
  436. else if pnt = '''' then
  437. begin
  438. AddToStatement(pnt,False);
  439. FCol:=FCol + length(pnt);
  440. pnt:=FindNextSeparator(['''']);
  441. AddToStatement(pnt,addnewline);
  442. addnewline:=False;
  443. FCol:=FCol + length(pnt);
  444. end;
  445. end;
  446. if not terminator_found then
  447. ClearStatement;
  448. while (FCurrentStatement.Count > 0) and (trim(FCurrentStatement.Strings[0]) = '') do
  449. FCurrentStatement.Delete(0);
  450. while (FCurrentStripped.Count > 0) and (trim(FCurrentStripped.Strings[0]) = '') do
  451. FCurrentStripped.Delete(0);
  452. Result:=FCurrentStatement.Text;
  453. end;
  454. Constructor TCustomSQLScript.Create (AnOwner: TComponent);
  455. Var
  456. L : TStringList;
  457. begin
  458. inherited;
  459. L:=TStringList.Create;
  460. With L do
  461. begin
  462. Sorted:=True;
  463. Duplicates:=dupIgnore;
  464. end;
  465. FDefines:=L;
  466. FCommentsInSQL:=True;
  467. FTerminator:=';';
  468. L:=TStringList.Create();
  469. L.OnChange:=@SQLChange;
  470. FSQL:=L;
  471. FDirectives:=TStringList.Create();
  472. FCurrentStripped:=TStringList.Create();
  473. FCurrentStatement:=TStringList.Create();
  474. FLine:=1;
  475. FCol:=1;
  476. FEmitLine:=True;
  477. FUseCommit := true;
  478. FUseDefines := True;
  479. FUseSetTerm := True;
  480. DefaultDirectives;
  481. end;
  482. destructor TCustomSQLScript.Destroy;
  483. begin
  484. FreeAndNil(FCurrentStripped);
  485. FreeAndNil(FCurrentStatement);
  486. FreeAndNil(FSQL);
  487. FreeAndNil(FDirectives);
  488. FreeAndNil(FDefines);
  489. inherited Destroy;
  490. end;
  491. procedure TCustomSQLScript.SetDefines(const Value: TStrings);
  492. begin
  493. FDefines.Assign(Value);
  494. end;
  495. procedure TCustomSQLScript.DefaultDirectives;
  496. begin
  497. With FDirectives do
  498. begin
  499. // Insertion order matters as testing for directives will be done with StartsWith
  500. if FUseSetTerm then
  501. Add('SET TERM');
  502. if FUseCommit then
  503. begin
  504. Add('COMMIT WORK'); {SQL Standard, equivalent to commit}
  505. Add('COMMIT RETAIN'); {Firebird/Interbase; probably won't hurt on other dbs}
  506. Add('COMMIT'); {Shorthand used in many dbs, e.g. Firebird}
  507. end;
  508. if FUseDefines then
  509. begin
  510. Add('#IFDEF');
  511. Add('#IFNDEF');
  512. Add('#ELSE');
  513. Add('#ENDIF');
  514. Add('#DEFINE');
  515. Add('#UNDEF');
  516. Add('#UNDEFINE');
  517. end;
  518. end;
  519. end;
  520. Function TCustomSQLScript.ProcessConditional(Directive: String; Param : String) : Boolean;
  521. Procedure PushSkipMode;
  522. begin
  523. if FSkipStackIndex=High(FSkipModeStack) then
  524. Raise ESQLScript.Create(SErrIfXXXNestingLimitReached);
  525. FSkipModeStack[FSkipStackIndex]:=FSkipMode;
  526. FIsSkippingStack[FSkipStackIndex]:=FIsSkipping;
  527. Inc(FSkipStackIndex);
  528. end;
  529. Procedure PopSkipMode;
  530. begin
  531. if FSkipStackIndex = 0 then
  532. Raise ESQLScript.Create(SErrInvalidEndif);
  533. Dec(FSkipStackIndex);
  534. FSkipMode := FSkipModeStack[FSkipStackIndex];
  535. FIsSkipping := FIsSkippingStack[FSkipStackIndex];
  536. end;
  537. Var
  538. Index : Integer;
  539. begin
  540. Result:=True;
  541. if (Directive='#DEFINE') then
  542. begin
  543. if not FIsSkipping then
  544. FDefines.Add(Param);
  545. end
  546. else if (Directive='#UNDEF') or (Directive='#UNDEFINE') then
  547. begin
  548. if not FIsSkipping then
  549. begin
  550. Index:=FDefines.IndexOf(Param);
  551. if (Index>=0) then
  552. FDefines.Delete(Index);
  553. end;
  554. end
  555. else if (Directive='#IFDEF') or (Directive='#IFNDEF') then
  556. begin
  557. PushSkipMode;
  558. if FIsSkipping then
  559. begin
  560. FSkipMode:=smAll;
  561. FIsSkipping:=true;
  562. end
  563. else
  564. begin
  565. Index:=FDefines.IndexOf(Param);
  566. if ((Directive='#IFDEF') and (Index<0)) or
  567. ((Directive='#IFNDEF') and (Index>=0)) then
  568. begin
  569. FSkipMode:=smIfBranch;
  570. FIsSkipping:=true;
  571. end
  572. else
  573. FSkipMode := smElseBranch;
  574. end;
  575. end
  576. else if (Directive='#ELSE') then
  577. begin
  578. if (FSkipStackIndex=0) then
  579. Raise ESQLScript.Create(SErrInvalidElse);
  580. if (FSkipMode=smIfBranch) then
  581. FIsSkipping:=false
  582. else if (FSkipMode=smElseBranch) then
  583. FIsSkipping:=true;
  584. end
  585. else if (Directive='#ENDIF') then
  586. PopSkipMode
  587. else
  588. Result:=False;
  589. end;
  590. { TEventSQLScript }
  591. procedure TEventSQLScript.ExecuteStatement(SQLStatement: TStrings;
  592. var StopExecution: Boolean);
  593. begin
  594. if assigned (FOnSQLStatement) then
  595. FOnSQLStatement (self, SQLStatement, StopExecution);
  596. end;
  597. procedure TEventSQLScript.ExecuteDirective(Directive, Argument: String;
  598. var StopExecution: Boolean);
  599. begin
  600. if assigned (FOnDirective) then
  601. FOnDirective (Self, Directive, Argument, StopExecution);
  602. end;
  603. procedure TEventSQLScript.ExecuteCommit(CommitRetaining: boolean=true);
  604. begin
  605. if assigned (FOnCommit) then
  606. FOnCommit (Self);
  607. end;
  608. procedure TEventSQLScript.Execute;
  609. begin
  610. if assigned (FBeforeExec) then
  611. FBeforeExec (Self);
  612. inherited Execute;
  613. if assigned (FAfterExec) then
  614. FAfterExec (Self);
  615. end;
  616. end.