sqlscript.pp 16 KB

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