1 {*********************************************************}
4 { Script Parsing Classes }
6 { Originally written by Sergey Seroukhov }
8 {*********************************************************}
10 {@********************************************************}
11 { Copyright (c) 1999-2012 Zeos Development Group }
13 { License Agreement: }
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. }
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. }
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) }
46 { http://www.sourceforge.net/projects/zeoslib. }
49 { Zeos Development Group. }
50 {********************************************************@}
58 uses Classes, {$IFDEF MSEgui}mclasses,{$ENDIF} SysUtils,
62 {** Defines a SQL delimiter type. }
63 TZDelimiterType = (dtDefault, dtDelimiter, dtGo, dtSetTerm, dtEmptyLine);
65 {** Implements a SQL script parser. }
66 TZSQLScriptParser = class
69 FDelimiterType: TZDelimiterType;
70 FCleanupStatements: Boolean;
71 FTokenizer: IZTokenizer;
72 FUncompletedStatement: string;
73 FStatements: TStrings;
75 function GetStatementCount: Integer;
76 function GetStatement(Index: Integer): string;
80 constructor CreateWithTokenizer(Tokenizer: IZTokenizer);
81 destructor Destroy; override;
84 procedure ClearCompleted;
85 procedure ClearUncompleted;
87 procedure ParseText(const Text: string);
88 procedure ParseLine(const Line: string);
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;
103 uses ZMessages, ZSysUtils;
105 { TZSQLScriptParser }
108 Constructs this script parser class.
110 constructor TZSQLScriptParser.Create;
112 FStatements := TStringList.Create;
114 FDelimiterType := dtDefault;
115 FCleanupStatements := True;
119 Creates this object and assignes a tokenizer object.
120 @param Tokenizer a tokenizer object.
122 constructor TZSQLScriptParser.CreateWithTokenizer(Tokenizer: IZTokenizer);
125 FTokenizer := Tokenizer;
129 Destroys this class and cleanups the memory.
131 destructor TZSQLScriptParser.Destroy;
133 FreeAndNil(FStatements);
139 Gets SQL statements number.
140 @returns SQL statements number.
142 function TZSQLScriptParser.GetStatementCount: Integer;
144 Result := FStatements.Count;
148 Gets a parsed SQL statement by it's index.
149 @param Index a statement index.
150 @returns a SQL statement string.
152 function TZSQLScriptParser.GetStatement(Index: Integer): string;
154 Result := FStatements[Index];
158 Clears all completed and uncompleted statements and line delimiter.
160 procedure TZSQLScriptParser.Clear;
164 FUncompletedStatement := '';
168 Clears only completed statements.
170 procedure TZSQLScriptParser.ClearCompleted;
176 Clears completed and uncompleted statements.
178 procedure TZSQLScriptParser.ClearUncompleted;
181 FUncompletedStatement := '';
185 Parses incrementaly only one single line.
186 The line appends with EOL character.
187 @param Line a line to be parsed.
189 procedure TZSQLScriptParser.ParseLine(const Line: string);
191 ParseText(#10 + Line + #10);
195 Parses a complete text with several lines.
196 @oaram Text a text of the SQL script to be parsed.
198 procedure TZSQLScriptParser.ParseText(const Text: string);
199 const SetTerm = String('SET TERM ');
202 TokenType: TZTokenType;
204 TokenIndex, LastStmtEndingIndex, iPos: Integer;
206 EndOfStatement: Boolean;
210 function CountChars(const Str: string; Chr: Char): Integer;
215 for I := 1 to Length(Str) do
222 procedure SetNextToken;
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;
233 if Tokenizer = nil then
234 raise Exception.Create(STokenizerIsNotDefined);
236 if CleanupStatements then
237 Tokens := Tokenizer.TokenizeBufferToList(Text, [toSkipComments])
238 else Tokens := Tokenizer.TokenizeBufferToList(Text, []);
240 if ( (DelimiterType = dtDelimiter) or
241 (DelimiterType = dtSetTerm) ) and
242 ( Delimiter = '' ) then
243 Delimiter := ';'; //use default delimiter
245 if (DelimiterType = dtDefault) then
246 Delimiter := ';'; //use default delimiter
249 SQL := FUncompletedStatement;
252 if CleanupStatements then
254 else SQL := SQL + #10;
256 FUncompletedStatement := '';
262 case DelimiterType of
264 EndOfStatement := (UpperCase(TokenValue) = 'GO');
267 EndOfStatement := False;
268 if TokenType = ttWhitespace then
271 while (CountChars(Temp, #10) < 2) and (TokenType = ttWhitespace) do
274 if TokenType = ttWhitespace then
275 Temp := Temp + TokenValue;
277 EndOfStatement := (TokenType = ttWhitespace) or EndsWith(Sql, #10);
278 if not EndOfStatement then
281 SQL := Trim(SQL) + ' ';
289 EndOfStatement := False;
290 if not (TokenType in [ttWhitespace, ttEOF]) then
292 if (DelimiterType = dtDelimiter) and (Uppercase(TokenValue) = 'DELIMITER') then
295 Temp := TokenValue; {process the DELIMITER}
296 Temp := Temp + Tokens[TokenIndex]; {process the first ' ' char}
298 while TokenType <> ttWhitespace do
301 if not (TokenType in [ttWhitespace, ttEOF]) then
302 Delimiter := Delimiter + TokenValue; //get the new delimiter
304 SQL := SQL + Temp + Delimiter;
305 EndOfStatement := True;
311 while (Delimiter[1]=Temp[1]) and
312 (Length(Delimiter) > Length(Temp))
313 and not (TokenType in [ttWhitespace, ttEOF]) do
317 if not (TokenType in [ttWhitespace, ttEOF]) then
319 Temp := Temp + TokenValue;
324 EndOfStatement := (Delimiter = Temp);
325 if not EndOfStatement then
328 Temp := Copy(Temp, 1, Length(Temp) - Length(TokenValue));
335 EndOfStatement := False;
338 if TokenType = ttEOF then Break;
340 { Processes the end of statements. }
341 if EndOfStatement then
343 if CleanupStatements then
347 if not CleanupStatements then
350 if (DelimiterType = dtSetTerm) and StartsWith(UpperCase(Temp), SetTerm) then
351 Delimiter := Copy(Temp, 10, Length(Temp) - 9)
353 if (DelimiterType = dtSetTerm) and ( Pos(SetTerm, UpperCase(Temp)) > 0) then
355 iPos := Pos(SetTerm, UpperCase(Temp))+8;
356 Delimiter := Copy(Temp, iPos+1, Length(Temp) - iPos);
357 LastComment := TrimRight(Copy(Temp, 1, iPos-9));
360 if (DelimiterType = dtDelimiter)
361 and StartsWith(UpperCase(Temp), 'DELIMITER ') then
362 Delimiter := Copy(Temp, 11, Length(Temp) - 10)
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
371 FStatements.Add(SQL);
377 { Adds a whitespace token. }
378 else if CleanupStatements and (TokenType = ttWhitespace) then
381 SQL := Trim(SQL) + ' ';
383 { Adds a default token. }
386 // --> ms, 20/10/2005
387 // TokenValue is not a ttWhitespace (#32)
388 if (TokenType = ttWhitespace) and (TokenValue > '') then begin
390 if (SQL <> '') then begin
392 if (Tokenindex = Tokens.count-1) then
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
406 if ((SQL = '') and (trim(TokenValue) = '')) then
409 SQL := SQL + TokenValue;
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)
416 FStatements[FStatements.Count-1] := FStatements[FStatements.Count-1]+#13#10+LastComment;
421 if CleanupStatements then
424 FUncompletedStatement := SQL;