zeoslib  UNKNOWN
 All Files
ZScriptParser.pas
Go to the documentation of this file.
1 {*********************************************************}
2 { }
3 { Zeos SQL Shell }
4 { Script Parsing Classes }
5 { }
6 { Originally written by Sergey Seroukhov }
7 { }
8 {*********************************************************}
9 
10 {@********************************************************}
11 { Copyright (c) 1999-2012 Zeos Development Group }
12 { }
13 { License Agreement: }
14 { }
15 { This library is distributed in the hope that it will be }
16 { useful, but WITHOUT ANY WARRANTY; without even the }
17 { implied warranty of MERCHANTABILITY or FITNESS FOR }
18 { A PARTICULAR PURPOSE. See the GNU Lesser General }
19 { Public License for more details. }
20 { }
21 { The source code of the ZEOS Libraries and packages are }
22 { distributed under the Library GNU General Public }
23 { License (see the file COPYING / COPYING.ZEOS) }
24 { with the following modification: }
25 { As a special exception, the copyright holders of this }
26 { library give you permission to link this library with }
27 { independent modules to produce an executable, }
28 { regardless of the license terms of these independent }
29 { modules, and to copy and distribute the resulting }
30 { executable under terms of your choice, provided that }
31 { you also meet, for each linked independent module, }
32 { the terms and conditions of the license of that module. }
33 { An independent module is a module which is not derived }
34 { from or based on this library. If you modify this }
35 { library, you may extend this exception to your version }
36 { of the library, but you are not obligated to do so. }
37 { If you do not wish to do so, delete this exception }
38 { statement from your version. }
39 { }
40 { }
41 { The project web site is located on: }
42 { http://zeos.firmos.at (FORUM) }
43 { http://sourceforge.net/p/zeoslib/tickets/ (BUGTRACKER)}
44 { svn://svn.code.sf.net/p/zeoslib/code-0/trunk (SVN) }
45 { }
46 { http://www.sourceforge.net/projects/zeoslib. }
47 { }
48 { }
49 { Zeos Development Group. }
50 {********************************************************@}
51 
52 unit ZScriptParser;
53 
54 interface
55 
56 {$I ZParseSql.inc}
57 
58 uses Classes, {$IFDEF MSEgui}mclasses,{$ENDIF} SysUtils,
59  ZTokenizer;
60 
61 type
62  {** Defines a SQL delimiter type. }
63  TZDelimiterType = (dtDefault, dtDelimiter, dtGo, dtSetTerm, dtEmptyLine);
64 
65  {** Implements a SQL script parser. }
66  TZSQLScriptParser = class
67  private
68  FDelimiter: string;
69  FDelimiterType: TZDelimiterType;
70  FCleanupStatements: Boolean;
71  FTokenizer: IZTokenizer;
72  FUncompletedStatement: string;
73  FStatements: TStrings;
74 
75  function GetStatementCount: Integer;
76  function GetStatement(Index: Integer): string;
77 
78  public
79  constructor Create;
80  constructor CreateWithTokenizer(Tokenizer: IZTokenizer);
81  destructor Destroy; override;
82 
83  procedure Clear;
84  procedure ClearCompleted;
85  procedure ClearUncompleted;
86 
87  procedure ParseText(const Text: string);
88  procedure ParseLine(const Line: string);
89 
90  property Delimiter: string read FDelimiter write FDelimiter;
91  property DelimiterType: TZDelimiterType read FDelimiterType
92  write FDelimiterType default dtDefault;
93  property CleanupStatements: Boolean read FCleanupStatements
94  write FCleanupStatements default True;
95  property Tokenizer: IZTokenizer read FTokenizer write FTokenizer;
96  property UncompletedStatement: string read FUncompletedStatement;
97  property StatementCount: Integer read GetStatementCount;
98  property Statements[Index: Integer]: string read GetStatement;
99  end;
100 
101 implementation
102 
103 uses ZMessages, ZSysUtils;
104 
105 { TZSQLScriptParser }
106 
107 {**
108  Constructs this script parser class.
109 }
110 constructor TZSQLScriptParser.Create;
111 begin
112  FStatements := TStringList.Create;
113  FDelimiter := ';';
114  FDelimiterType := dtDefault;
115  FCleanupStatements := True;
116 end;
117 
118 {**
119  Creates this object and assignes a tokenizer object.
120  @param Tokenizer a tokenizer object.
121 }
122 constructor TZSQLScriptParser.CreateWithTokenizer(Tokenizer: IZTokenizer);
123 begin
124  Create;
125  FTokenizer := Tokenizer;
126 end;
127 
128 {**
129  Destroys this class and cleanups the memory.
130 }
131 destructor TZSQLScriptParser.Destroy;
132 begin
133  FreeAndNil(FStatements);
134  FTokenizer := nil;
135  inherited Destroy;
136 end;
137 
138 {**
139  Gets SQL statements number.
140  @returns SQL statements number.
141 }
142 function TZSQLScriptParser.GetStatementCount: Integer;
143 begin
144  Result := FStatements.Count;
145 end;
146 
147 {**
148  Gets a parsed SQL statement by it's index.
149  @param Index a statement index.
150  @returns a SQL statement string.
151 }
152 function TZSQLScriptParser.GetStatement(Index: Integer): string;
153 begin
154  Result := FStatements[Index];
155 end;
156 
157 {**
158  Clears all completed and uncompleted statements and line delimiter.
159 }
160 procedure TZSQLScriptParser.Clear;
161 begin
162  FStatements.Clear;
163  FDelimiter := ';';
164  FUncompletedStatement := '';
165 end;
166 
167 {**
168  Clears only completed statements.
169 }
170 procedure TZSQLScriptParser.ClearCompleted;
171 begin
172  FStatements.Clear;
173 end;
174 
175 {**
176  Clears completed and uncompleted statements.
177 }
178 procedure TZSQLScriptParser.ClearUncompleted;
179 begin
180  FStatements.Clear;
181  FUncompletedStatement := '';
182 end;
183 
184 {**
185  Parses incrementaly only one single line.
186  The line appends with EOL character.
187  @param Line a line to be parsed.
188 }
189 procedure TZSQLScriptParser.ParseLine(const Line: string);
190 begin
191  ParseText(#10 + Line + #10);
192 end;
193 
194 {**
195  Parses a complete text with several lines.
196  @oaram Text a text of the SQL script to be parsed.
197 }
198 procedure TZSQLScriptParser.ParseText(const Text: string);
199 const SetTerm = String('SET TERM ');
200 var
201  Tokens: TStrings;
202  TokenType: TZTokenType;
203  TokenValue: string;
204  TokenIndex, LastStmtEndingIndex, iPos: Integer;
205  SQL, Temp: string;
206  EndOfStatement: Boolean;
207  Extract: Boolean;
208  LastComment: String;
209 
210  function CountChars(const Str: string; Chr: Char): Integer;
211  var
212  I: Integer;
213  begin
214  Result := 0;
215  for I := 1 to Length(Str) do
216  begin
217  if Str[I] = Chr then
218  Inc(Result);
219  end;
220  end;
221 
222  procedure SetNextToken;
223  begin
224  TokenValue := Tokens[TokenIndex];
225  TokenType := TZTokenType({$IFDEF FPC}Pointer({$ENDIF}
226  Tokens.Objects[TokenIndex]{$IFDEF FPC}){$ENDIF});
227  if TokenValue = Delimiter then
228  LastStmtEndingIndex := TokenIndex;
229  Inc(TokenIndex);
230  end;
231 
232 begin
233  if Tokenizer = nil then
234  raise Exception.Create(STokenizerIsNotDefined);
235 
236  if CleanupStatements then
237  Tokens := Tokenizer.TokenizeBufferToList(Text, [toSkipComments])
238  else Tokens := Tokenizer.TokenizeBufferToList(Text, []);
239 
240  if ( (DelimiterType = dtDelimiter) or
241  (DelimiterType = dtSetTerm) ) and
242  ( Delimiter = '' ) then
243  Delimiter := ';'; //use default delimiter
244 
245  if (DelimiterType = dtDefault) then
246  Delimiter := ';'; //use default delimiter
247 
248  TokenIndex := 0;
249  SQL := FUncompletedStatement;
250  if SQL <> '' then
251  begin
252  if CleanupStatements then
253  SQL := SQL + ' '
254  else SQL := SQL + #10;
255  end;
256  FUncompletedStatement := '';
257  FStatements.Clear;
258  try
259  repeat
260  SetNextToken;
261 
262  case DelimiterType of
263  dtGo:
264  EndOfStatement := (UpperCase(TokenValue) = 'GO');
265  dtEmptyLine:
266  begin
267  EndOfStatement := False;
268  if TokenType = ttWhitespace then
269  begin
270  Temp := TokenValue;
271  while (CountChars(Temp, #10) < 2) and (TokenType = ttWhitespace) do
272  begin
273  SetNextToken;
274  if TokenType = ttWhitespace then
275  Temp := Temp + TokenValue;
276  end;
277  EndOfStatement := (TokenType = ttWhitespace) or EndsWith(Sql, #10);
278  if not EndOfStatement then
279  begin
280  if SQL <> '' then
281  SQL := Trim(SQL) + ' ';
282  end;
283  end;
284  end;
285  dtDelimiter,
286  dtDefault,
287  dtSetTerm:
288  begin
289  EndOfStatement := False;
290  if not (TokenType in [ttWhitespace, ttEOF]) then
291  begin
292  if (DelimiterType = dtDelimiter) and (Uppercase(TokenValue) = 'DELIMITER') then
293  begin
294  Delimiter := '';
295  Temp := TokenValue; {process the DELIMITER}
296  Temp := Temp + Tokens[TokenIndex]; {process the first ' ' char}
297  Inc(TokenIndex);
298  while TokenType <> ttWhitespace do
299  begin
300  SetNextToken;
301  if not (TokenType in [ttWhitespace, ttEOF]) then
302  Delimiter := Delimiter + TokenValue; //get the new delimiter
303  end;
304  SQL := SQL + Temp + Delimiter;
305  EndOfStatement := True;
306  end
307  else
308  begin
309  Temp := TokenValue;
310  Extract := True;
311  while (Delimiter[1]=Temp[1]) and
312  (Length(Delimiter) > Length(Temp))
313  and not (TokenType in [ttWhitespace, ttEOF]) do
314  begin
315  SetNextToken;
316 
317  if not (TokenType in [ttWhitespace, ttEOF]) then
318  begin
319  Temp := Temp + TokenValue;
320  Extract := True;
321  end else
322  Extract := False;
323  end;
324  EndOfStatement := (Delimiter = Temp);
325  if not EndOfStatement then
326  begin
327  if Extract then
328  Temp := Copy(Temp, 1, Length(Temp) - Length(TokenValue));
329  SQL := SQL + Temp;
330  end;
331  end;
332  end;
333  end;
334  else
335  EndOfStatement := False;
336  end;
337 
338  if TokenType = ttEOF then Break;
339 
340  { Processes the end of statements. }
341  if EndOfStatement then
342  begin
343  if CleanupStatements then
344  SQL := Trim(SQL);
345  if SQL <> '' then
346  begin
347  if not CleanupStatements then
348  Temp := Trim(SQL)
349  else Temp := SQL;
350  if (DelimiterType = dtSetTerm) and StartsWith(UpperCase(Temp), SetTerm) then
351  Delimiter := Copy(Temp, 10, Length(Temp) - 9)
352  else
353  if (DelimiterType = dtSetTerm) and ( Pos(SetTerm, UpperCase(Temp)) > 0) then
354  begin
355  iPos := Pos(SetTerm, UpperCase(Temp))+8;
356  Delimiter := Copy(Temp, iPos+1, Length(Temp) - iPos);
357  LastComment := TrimRight(Copy(Temp, 1, iPos-9));
358  end
359  else
360  if (DelimiterType = dtDelimiter)
361  and StartsWith(UpperCase(Temp), 'DELIMITER ') then
362  Delimiter := Copy(Temp, 11, Length(Temp) - 10)
363  else
364  begin
365  if (DelimiterType = dtEmptyLine) and EndsWith(SQL, ';') then
366  SQL := Copy(SQL, 1, Length(SQL) - 1);
367  if LastComment <> '' then
368  SQL := LastComment+#13#10+SQL;
369  if CleanupStatements then
370  SQL := Trim(SQL);
371  FStatements.Add(SQL);
372  LastComment := '';
373  end;
374  end;
375  SQL := '';
376  end
377  { Adds a whitespace token. }
378  else if CleanupStatements and (TokenType = ttWhitespace) then
379  begin
380  if SQL <> '' then
381  SQL := Trim(SQL) + ' ';
382  end
383  { Adds a default token. }
384  else
385  begin
386  // --> ms, 20/10/2005
387  // TokenValue is not a ttWhitespace (#32)
388  if (TokenType = ttWhitespace) and (TokenValue > '') then begin
389  // SQL is not emtyp
390  if (SQL <> '') then begin
391  // is last token:
392  if (Tokenindex = Tokens.count-1) then
393  TokenValue := '';
394  // next(!) token is also ttWhitespace or delimiter
395  // (TokenIndex was already incremented!)
396  if (Tokenindex < Tokens.count-1) then
397  if ((TZTokenType({$IFDEF FPC}Pointer({$ENDIF}
398  Tokens.Objects[TokenIndex]{$IFDEF FPC}){$ENDIF}) = ttWhitespace) or
399  (Tokens[TokenIndex] = Delimiter)) then
400  TokenValue := '';
401  end
402  // SQL is empty
403  else
404  TokenValue := '';
405  end;
406  if ((SQL = '') and (trim(TokenValue) = '')) then
407  TokenValue := '';
408  // <-- ms
409  SQL := SQL + TokenValue;
410  end;
411  until TokenType = ttEOF;
412  if ( LastComment <> '' ) and ( FStatements.Count > 0) then
413  if CleanupStatements then
414  FStatements[FStatements.Count-1] := FStatements[FStatements.Count-1]+' '+Trim(LastComment)
415  else
416  FStatements[FStatements.Count-1] := FStatements[FStatements.Count-1]+#13#10+LastComment;
417  finally
418  Tokens.Free;
419  end;
420 
421  if CleanupStatements then
422  SQL := Trim(SQL);
423  if SQL <> '' then
424  FUncompletedStatement := SQL;
425 end;
426 
427 end.