zeoslib  UNKNOWN
 All Files
ZGenericSqlAnalyser.pas
Go to the documentation of this file.
1 {*********************************************************}
2 { }
3 { Zeos Database Objects }
4 { SQL Statements Analysing 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 ZGenericSqlAnalyser;
53 
54 interface
55 
56 {$I ZParseSql.inc}
57 
58 uses Classes, {$IFDEF MSEgui}mclasses,{$ENDIF} Contnrs,
59  ZClasses, ZTokenizer, ZSelectSchema, ZCompatibility;
60 
61 type
62 
63  {** Implements a section of the parsed SQL statement. }
64  TZStatementSection = class (TObject)
65  private
66  FName: string;
67  FTokens: TStrings;
68  public
69  constructor Create(const Name: string; Tokens: TStrings);
70  destructor Destroy; override;
71 
72  function Clone: TZStatementSection;
73 
74  property Name: string read FName write FName;
75  property Tokens: TStrings read FTokens;
76  end;
77 
78  {** Implements a publicly available interface to statement analyser. }
79  IZStatementAnalyser = interface(IZInterface)
80  ['{967635B6-411B-4DEF-990C-9C6C01F3DC0A}']
81 
82  function TokenizeQuery(Tokenizer: IZTokenizer; const SQL: string;
83  Cleanup: Boolean): TStrings;
84  function SplitSections(Tokens: TStrings): TObjectList;
85 
86  function ComposeTokens(Tokens: TStrings): string;
87  function ComposeSections(Sections: TObjectList): string;
88 
89  function DefineSelectSchemaFromSections(
90  Sections: TObjectList): IZSelectSchema;
91  function DefineSelectSchemaFromQuery(Tokenizer: IZTokenizer;
92  const SQL: string): IZSelectSchema;
93  end;
94 
95  {** Implements an SQL statements analyser. }
96  TZGenericStatementAnalyser = class (TZAbstractObject, IZStatementAnalyser)
97  private
98  FSectionNames: TStrings;
99  FSelectOptions: TStrings;
100  FFromJoins: TStrings;
101  FFromClauses: TStrings;
102  protected
103  function ArrayToStrings(const Value: array of string): TStrings;
104  function CheckForKeyword(Tokens: TStrings; TokenIndex: Integer;
105  Keywords: TStrings; var Keyword: string; var WordCount: Integer): Boolean;
106  function FindSectionTokens(Sections: TObjectList; const Name: string): TStrings;
107 
108  procedure FillFieldRefs(SelectSchema: IZSelectSchema; SelectTokens: TStrings);
109  procedure FillTableRefs(SelectSchema: IZSelectSchema; FromTokens: TStrings);
110 
111  function SkipOptionTokens(Tokens: TStrings; var TokenIndex: Integer;
112  Options: TStrings): Boolean;
113  function SkipBracketTokens(Tokens: TStrings; var TokenIndex: Integer):
114  Boolean;
115 
116  property SectionNames: TStrings read FSectionNames write FSectionNames;
117  property SelectOptions: TStrings read FSelectOptions write FSelectOptions;
118  property FromJoins: TStrings read FFromJoins write FFromJoins;
119  property FromClauses: TStrings read FFromClauses write FFromClauses;
120  public
121  constructor Create;
122  destructor Destroy; override;
123 
124  function TokenizeQuery(Tokenizer: IZTokenizer; const SQL: string;
125  Cleanup: Boolean): TStrings;
126  function SplitSections(Tokens: TStrings): TObjectList;
127 
128  function ComposeTokens(Tokens: TStrings): string;
129  function ComposeSections(Sections: TObjectList): string;
130 
131  function DefineSelectSchemaFromSections(
132  Sections: TObjectList): IZSelectSchema;
133  function DefineSelectSchemaFromQuery(Tokenizer: IZTokenizer; const SQL: string):
134  IZSelectSchema;
135  end;
136 
137 implementation
138 
139 uses SysUtils, ZSysUtils;
140 
141 { TZStatementSection }
142 
143 {**
144  Create SQL statement section object.
145 }
146 constructor TZStatementSection.Create(const Name: string; Tokens: TStrings);
147 begin
148  FName := Name;
149  FTokens := Tokens;
150 end;
151 
152 {**
153  Destroys this object and cleanups the memory.
154 }
155 destructor TZStatementSection.Destroy;
156 begin
157  FTokens.Free;
158  inherited Destroy;
159 end;
160 
161 {**
162  Clones an object instance.
163  @return a clonned object instance.
164 }
165 function TZStatementSection.Clone: TZStatementSection;
166 var
167  Temp: TStrings;
168 begin
169  Temp := TStringList.Create;
170  Temp.AddStrings(FTokens);
171  Result := TZStatementSection.Create(FName, Temp);
172 end;
173 
174 const
175  {** The generic constants.}
176  GenericSectionNames: array[0..12] of string = (
177  'SELECT', 'UPDATE', 'DELETE', 'INSERT', 'FROM',
178  'WHERE', 'INTO', 'GROUP*BY', 'HAVING', 'ORDER*BY',
179  'FOR*UPDATE', 'LIMIT', 'OFFSET'
180  );
181  GenericSelectOptions: array[0..1] of string = (
182  'DISTINCT', 'ALL'
183  );
184  GenericFromJoins: array[0..5] of string = (
185  'NATURAL', 'RIGHT', 'LEFT', 'INNER', 'OUTER', 'JOIN'
186  );
187  GenericFromClauses: array[0..0] of string = (
188  'ON'
189  );
190 
191 { TZGenericStatementAnalyser }
192 
193 {**
194  Creates the object and assignes the main properties.
195 }
196 constructor TZGenericStatementAnalyser.Create;
197 begin
198  FSectionNames := ArrayToStrings(GenericSectionNames);
199  FSelectOptions := ArrayToStrings(GenericSelectOptions);
200  FFromJoins := ArrayToStrings(GenericFromJoins);
201  FFromClauses := ArrayToStrings(GenericFromClauses);
202 end;
203 
204 {**
205  Destroys this object and cleanups the memory.
206 }
207 destructor TZGenericStatementAnalyser.Destroy;
208 begin
209  FSectionNames.Free;
210  FSelectOptions.Free;
211  FFromJoins.Free;
212  FFromClauses.Free;
213  inherited Destroy;
214 end;
215 
216 {**
217  Converts an array of strings into TStrings object.
218  @param Value an array of strings to be converted.
219  @return a TStrings object with specified strings.
220 }
221 function TZGenericStatementAnalyser.ArrayToStrings(
222  const Value: array of string): TStrings;
223 var
224  I: Integer;
225 begin
226  Result := TStringList.Create;
227  for I := Low(Value) to High(Value) do
228  Result.Add(Value[I]);
229 end;
230 
231 {**
232  Checks for keyword with one, two or three consisted words in the list
233  @param Tokens a list or tokens
234  @param TokenIndex an index of the current token
235  @param Keywords a list of keywords (in uppers case delimited with '*')
236  @param Keyword an out parameter with found keyword.
237  @param WordCount a count of words in the found keyword.
238 }
239 function TZGenericStatementAnalyser.CheckForKeyword(Tokens: TStrings;
240  TokenIndex: Integer; Keywords: TStrings; var Keyword: string;
241  var WordCount: Integer): Boolean;
242 var
243  I: Integer;
244 begin
245  WordCount := 0;
246  Keyword := '';
247  Result := False;
248 
249  for I := 1 to 3 do
250  begin
251  if (Tokens.Count <= TokenIndex) then
252  Break;
253  if TZTokenType({$IFDEF FPC}Pointer({$ENDIF}
254  Tokens.Objects[TokenIndex]{$IFDEF FPC}){$ENDIF}) <> ttWord then
255  Break;
256  if Keyword <> '' then
257  Keyword := Keyword + '*';
258  Keyword := Keyword + AnsiUpperCase(Tokens[TokenIndex]);
259  Inc(WordCount);
260  if Keywords.IndexOf(Keyword) >= 0 then
261  begin
262  Result := True;
263  Break;
264  end;
265  Inc(TokenIndex);
266  { Skips whitespaces. }
267  while Tokens.Count > TokenIndex do
268  begin
269  if not (TZTokenType({$IFDEF FPC}Pointer({$ENDIF}
270  Tokens.Objects[TokenIndex]{$IFDEF FPC}){$ENDIF})
271  in [ttWhitespace, ttComment]) then
272  Break;
273  Inc(TokenIndex);
274  Inc(WordCount);
275  end;
276  end;
277 
278  if not Result then
279  begin
280  WordCount := 0;
281  Keyword := '';
282  end;
283 end;
284 
285 {**
286  Finds a section by it's name.
287  @param Sections a list of sections.
288  @param Name a name of the section to be found.
289  @return a list of section tokens or <code>null</code>
290  if section is was not found.
291 }
292 function TZGenericStatementAnalyser.FindSectionTokens(
293  Sections: TObjectList; const Name: string): TStrings;
294 var
295  I: Integer;
296  Current: TZStatementSection;
297 begin
298  Result := nil;
299  for I := 0 to Sections.Count - 1 do
300  begin
301  Current := TZStatementSection(Sections[I]);
302  if Current.Name = Name then
303  begin
304  Result := Current.Tokens;
305  Break;
306  end;
307  end;
308 end;
309 
310 {**
311  Tokenizes a given SQL query into a list of tokens with tokenizer.
312  @param Tokenizer a tokenizer object.
313  @param SQL a SQL query to be tokenized.
314  @return a list with tokens.
315 }
316 function TZGenericStatementAnalyser.TokenizeQuery(
317  Tokenizer: IZTokenizer; const SQL: string; Cleanup: Boolean): TStrings;
318 begin
319  if Cleanup then
320  begin
321  Result := Tokenizer.TokenizeBufferToList(SQL,
322  [toSkipEOF, toSkipComments, toUnifyWhitespaces])
323  end else
324  Result := Tokenizer.TokenizeBufferToList(SQL, [toSkipEOF]);
325 end;
326 
327 {**
328  Splits a given list of tokens into the list named sections.
329  @param Tokens a list of tokens.
330  @return a list of section names where object property contains
331  a list of tokens in the section. It initial list is not started
332  with a section name the first section is unnamed ('').
333 }
334 function TZGenericStatementAnalyser.SplitSections(Tokens: TStrings): TObjectList;
335 var
336  I: Integer;
337  Keyword: string;
338  WordCount: Integer;
339  TokenIndex: Integer;
340  Elements: TStrings;
341  FoundSection: Boolean;
342  BracketCount: Integer;
343 begin
344  Result := TObjectList.Create;
345  TokenIndex := 0;
346  FoundSection := True;
347  Elements := nil;
348  CheckForKeyword(Tokens, TokenIndex, SectionNames, Keyword, WordCount);
349 
350  while TokenIndex < Tokens.Count do
351  begin
352  if FoundSection then
353  begin
354  Elements := TStringList.Create;
355  for I := 0 to WordCount - 1 do
356  begin
357  Elements.AddObject(Tokens[TokenIndex + I],
358  Tokens.Objects[TokenIndex + I]);
359  end;
360  Inc(TokenIndex, WordCount);
361  Result.Add(TZStatementSection.Create(Keyword, Elements));
362  end;
363  FoundSection := CheckForKeyword(Tokens, TokenIndex, SectionNames,
364  Keyword, WordCount);
365  if not FoundSection and (TokenIndex < Tokens.Count) then
366  begin
367  BracketCount := 0;
368  repeat
369  Elements.AddObject(Tokens[TokenIndex], Tokens.Objects[TokenIndex]);
370  if Tokens[TokenIndex] = '(' then
371  Inc(BracketCount)
372  else if Tokens[TokenIndex] = ')' then
373  Dec(BracketCount);
374  Inc(TokenIndex);
375  until (BracketCount <= 0) or (TokenIndex >= Tokens.Count);
376  end;
377  end;
378 end;
379 
380 {**
381  Composes a string from the list of tokens.
382  @param Tokens a list of tokens.
383  @returns a composes string.
384 }
385 function TZGenericStatementAnalyser.ComposeTokens(Tokens: TStrings): string;
386 begin
387  Result := ComposeString(Tokens, '');
388 end;
389 
390 {**
391  Composes a string from the list of statement sections.
392  @param Tokens a list of statement sections.
393  @returns a composes string.
394 }
395 function TZGenericStatementAnalyser.ComposeSections(Sections: TObjectList): string;
396 var
397  I: Integer;
398 begin
399  Result := '';
400  for I := 0 to Sections.Count - 1 do
401  Result := Result + ComposeTokens(TZStatementSection(Sections[I]).Tokens);
402 end;
403 
404 {**
405  Skips tokens inside brackets.
406  @param Tokens a list of tokens to scan.
407  @param TokenIndex the index of the current token.
408  @return <code>true</code> if some tokens were skipped.
409 }
410 function TZGenericStatementAnalyser.SkipBracketTokens(Tokens: TStrings;
411  var TokenIndex: Integer): Boolean;
412 var
413  BracketCount: Integer;
414  Current: string;
415 begin
416  { Checks for the start bracket. }
417  if (TokenIndex < Tokens.Count) and (Tokens[TokenIndex] <> '(') then
418  begin
419  Result := False;
420  Exit;
421  end;
422 
423  { Skips the expression in brackets. }
424  Result := True;
425  BracketCount := 1;
426  Inc(TokenIndex);
427  while (TokenIndex < Tokens.Count) and (BracketCount > 0) do
428  begin
429  Current := Tokens[TokenIndex];
430  if Current = '(' then
431  Inc(BracketCount)
432  else if Current = ')' then
433  Dec(BracketCount);
434  Inc(TokenIndex);
435  end;
436 end;
437 
438 {**
439  Skips option tokens specified in the string list.
440  @param Tokens a list of tokens to scan.
441  @param TokenIndex the index of the current token.
442  @param Options a list of option keyword strings in the upper case.
443  @return <code>true</code> if some tokens were skipped.
444 }
445 function TZGenericStatementAnalyser.SkipOptionTokens(Tokens: TStrings;
446  var TokenIndex: Integer; Options: TStrings): Boolean;
447 begin
448  Result := False;
449  while TokenIndex < Tokens.Count do
450  begin
451  if not (TZTokenType({$IFDEF FPC}Pointer({$ENDIF}
452  Tokens.Objects[TokenIndex]{$IFDEF FPC}){$ENDIF})
453  in [ttWhitespace, ttComment])
454  and (Options.IndexOf(AnsiUpperCase(Tokens[TokenIndex])) < 0) then
455  begin
456  Break;
457  end;
458  Inc(TokenIndex);
459  Result := True;
460  end;
461 end;
462 
463 {**
464  Fills select schema with field references.
465  @param SelectSchema a select schema object.
466  @param SelectTokens a list of tokens in select section.
467 }
468 procedure TZGenericStatementAnalyser.FillFieldRefs(
469  SelectSchema: IZSelectSchema; SelectTokens: TStrings);
470 var
471  TokenIndex: Integer;
472  Catalog: string;
473  Schema: string;
474  Table: string;
475  Field: string;
476  Alias: string;
477  CurrentValue: string;
478  CurrentType: TZTokenType;
479  CurrentUpper: string;
480  ReadField: Boolean;
481  HadWhitespace : Boolean;
482  LastWasBracketSection: Boolean;
483  CurrentUpperIs_AS: Boolean; //place holder to avoid compare the token twice
484 
485  procedure ClearElements;
486  begin
487  Catalog := '';
488  Schema := '';
489  Table := '';
490  Field := '';
491  Alias := '';
492  ReadField := True;
493  LastWasBracketSection := False;
494  end;
495 
496  { improve fail of fieldname detection if whitespaces and non ttWord or ttQuotedIdentifier previously detected
497  f.e.: select first 100 skip 10 field1, field2}
498  function CheckNextTokenForCommaAndWhiteSpaces: Boolean;
499  var
500  CurrentValue: string;
501  CurrentType: TZTokenType;
502  I: Integer;
503  begin
504  Result := False;
505  I := 1;
506  //Check to right side to avoid wrong alias detection
507  while SelectTokens.Count > TokenIndex +i do
508  begin
509  CurrentValue := SelectTokens[TokenIndex+i];
510  CurrentType := TZTokenType({$IFDEF FPC}Pointer({$ENDIF}
511  SelectTokens.Objects[TokenIndex+i]{$IFDEF FPC}){$ENDIF});
512  if CurrentType in [ttWhiteSpace, ttSymbol] then
513  begin
514  if (CurrentValue = ',') then
515  begin
516  Result := True;
517  Break;
518  end;
519  end
520  else
521  break;
522  Inc(i);
523  end;
524 
525  if Result then
526  begin
527  i := 1;
528  while Tokenindex - i > 0 do
529  if TZTokenType({$IFDEF FPC}Pointer({$ENDIF}
530  SelectTokens.Objects[TokenIndex-i]{$IFDEF FPC}){$ENDIF}) = ttWhiteSpace then
531  Inc(i)
532  else
533  Break;
534  Result := Result and (TokenIndex - I > 0) and
535  not ( TZTokenType({$IFDEF FPC}Pointer({$ENDIF}
536  SelectTokens.Objects[TokenIndex-i]{$IFDEF FPC}){$ENDIF}) = ttWord );
537  end;
538  end;
539 
540 begin
541  TokenIndex := 1;
542  SkipOptionTokens(SelectTokens, TokenIndex, Self.SelectOptions);
543 
544  ClearElements;
545  while TokenIndex < SelectTokens.Count do
546  begin
547  CurrentValue := SelectTokens[TokenIndex];
548  CurrentUpper := AnsiUpperCase(CurrentValue);
549  CurrentType := TZTokenType({$IFDEF FPC}Pointer({$ENDIF}
550  SelectTokens.Objects[TokenIndex]{$IFDEF FPC}){$ENDIF});
551 
552  { Switches to alias part. }
553  CurrentUpperIs_AS := (CurrentUpper = 'AS');
554  if (CurrentType = ttWhitespace) or CurrentUpperIs_AS then
555  ReadField := ReadField and (Field = '') and not CurrentUpperIs_AS
556  { Reads field. }
557  else if ReadField and ((CurrentType = ttWord) or (CurrentType = ttQuotedIdentifier) or
558  (CurrentValue = '*')) then
559  begin
560  Catalog := Schema;
561  Schema := Table;
562  Table := Field;
563  Field := CurrentValue;
564  end
565  { Skips a '.' in field part. }
566  else if ReadField and (CurrentValue = '.') then
567  begin
568  end
569  { Reads alias. }
570  else if not ReadField and (CurrentType in [ttWord, ttQuotedIdentifier]) then
571  Alias := CurrentValue
572  { Ends field reading. }
573  else if CurrentValue = ',' then
574  begin
575  if Field <> '' then
576  SelectSchema.AddField(TZFieldRef.Create(True, Catalog, Schema, Table,
577  Field, Alias, nil));
578  ClearElements;
579  end
580  { Skips till the next field. }
581  else
582  begin
583  ClearElements;
584  HadWhitespace := False;
585  while (TokenIndex < SelectTokens.Count) and (CurrentValue <> ',') do
586  begin
587  CurrentValue := SelectTokens[TokenIndex];
588  if CurrentValue = '(' then
589  begin
590  SkipBracketTokens(SelectTokens, TokenIndex);
591  LastWasBracketSection := True;
592  end
593  else begin
594  CurrentType := TZTokenType({$IFDEF FPC}Pointer({$ENDIF}
595  SelectTokens.Objects[TokenIndex]{$IFDEF FPC}){$ENDIF});
596  if HadWhitespace and (CurrentType in [ttWord, ttQuotedIdentifier]) then
597  if not LastWasBracketSection and CheckNextTokenForCommaAndWhiteSpaces then
598  Break
599  else
600  Alias := CurrentValue
601  else if not (CurrentType in [ttWhitespace, ttComment])
602  and (CurrentValue <> ',') then
603  Alias := ''
604  else if CurrentType = ttWhitespace then
605  HadWhitespace := true;
606  Inc(TokenIndex);
607  end;
608  end;
609  if Alias <> '' then
610  begin
611  SelectSchema.AddField(TZFieldRef.Create(False, '', '', '', '', Alias, nil));
612  ClearElements;
613  end;
614  Dec(TokenIndex); // go back 1 token(Because of Inc in next lines)
615  end;
616  Inc(TokenIndex);
617  end;
618 
619  { Creates a reference to the last processed field. }
620  if Field <> '' then
621  begin
622  SelectSchema.AddField(TZFieldRef.Create(True, Catalog, Schema, Table,
623  Field, Alias, nil));
624  end;
625 end;
626 
627 {**
628  Fills select schema with table references.
629  @param SelectSchema a select schema object.
630  @param FromTokens a list of tokens in from section.
631 }
632 {$HINTS OFF}
633 procedure TZGenericStatementAnalyser.FillTableRefs(
634  SelectSchema: IZSelectSchema; FromTokens: TStrings);
635 var
636  TokenIndex: Integer;
637  Catalog: string;
638  Schema: string;
639  Table: string;
640  Alias: string;
641  CurrentValue: string;
642  CurrentType: TZTokenType;
643  CurrentUpper: string;
644  ReadTable: Boolean;
645 
646  procedure ClearElements;
647  begin
648  Catalog := '';
649  Schema := '';
650  Table := '';
651  Alias := '';
652  ReadTable := True;
653  end;
654 
655 begin
656  TokenIndex := 1;
657 
658  ClearElements;
659  while TokenIndex < FromTokens.Count do
660  begin
661  CurrentValue := FromTokens[TokenIndex];
662  CurrentUpper := AnsiUpperCase(CurrentValue);
663  CurrentType := TZTokenType({$IFDEF FPC}Pointer({$ENDIF}
664  FromTokens.Objects[TokenIndex]{$IFDEF FPC}){$ENDIF});
665 
666  { Processes from join keywords. }
667  if FromJoins.IndexOf(CurrentUpper) >= 0 then
668  begin
669  if Table <> '' then
670  SelectSchema.AddTable(TZTableRef.Create(Catalog, Schema, Table, Alias));
671  ClearElements;
672  SkipOptionTokens(FromTokens, TokenIndex, FromJoins);
673  Continue;
674  end
675  { Skips from clause keywords. }
676  else if FromClauses.IndexOf(CurrentUpper) >= 0 then
677  begin
678  Inc(TokenIndex);
679  CurrentValue := FromTokens[TokenIndex];
680  CurrentUpper := AnsiUpperCase(CurrentValue);
681  while (TokenIndex < FromTokens.Count)
682  and (FromJoins.IndexOf(CurrentUpper) < 0) and (CurrentUpper <> ',') do
683  begin
684  if CurrentUpper = '(' then
685  SkipBracketTokens(FromTokens, TokenIndex)
686  else Inc(TokenIndex);
687  if TokenIndex < FromTokens.Count then
688  begin
689  CurrentValue := FromTokens[TokenIndex];
690  CurrentUpper := AnsiUpperCase(CurrentValue);
691  CurrentType := TZTokenType({$IFDEF FPC}Pointer({$ENDIF}
692  FromTokens.Objects[TokenIndex]{$IFDEF FPC}){$ENDIF});
693  end;
694  end;
695  // We must jump 1 tokens back now when we stopped on a Join clause.
696  // Otherwise the next table is skipped
697  if FromJoins.IndexOf(CurrentUpper) >= 0 then
698  begin
699  Dec(TokenIndex);
700  CurrentValue := FromTokens[TokenIndex];
701  CurrentUpper := AnsiUpperCase(CurrentValue);
702  end;
703  end
704  { Switches to alias part. }
705  else if (CurrentType = ttWhitespace) or (CurrentUpper = 'AS') then
706  begin
707  ReadTable := ReadTable and (Table = '') and (CurrentUpper <> 'AS');
708  end
709  { Reads table. }
710  else if ReadTable and ((CurrentType = ttWord) or (CurrentType = ttQuotedIdentifier)) then
711  begin
712  {Catalog := Schema;
713  Schema := Table;}
714  Table := CurrentValue;
715  end
716  { Skips a '.' in table part. }
717  else if ReadTable and (CurrentValue = '.') then
718  begin
719  Catalog := Schema;
720  Schema := Table;
721  Table := '';
722  end
723  { Reads alias. }
724  else if not ReadTable and (CurrentType = ttWord) then
725  begin
726  Alias := CurrentValue;
727  end;
728  { Ends field reading. }
729  if CurrentValue = ',' then
730  begin
731  if Table <> '' then
732  SelectSchema.AddTable(TZTableRef.Create(Catalog, Schema, Table, Alias));
733  ClearElements;
734  end;
735  { Skips till the next field. }
736  if CurrentValue = '(' then
737  SkipBracketTokens(FromTokens, TokenIndex)
738  else Inc(TokenIndex);
739  end;
740 
741  { Creates a reference to the last processed field. }
742  if Table <> '' then
743  SelectSchema.AddTable(TZTableRef.Create(Catalog, Schema, Table, Alias));
744 end;
745 {$HINTS ON}
746 {**
747  Extracts a select schema from the specified parsed select statement.
748  @param Sections a list of sections.
749  @return a select statement schema.
750 }
751 function TZGenericStatementAnalyser.DefineSelectSchemaFromSections(
752  Sections: TObjectList): IZSelectSchema;
753 var
754  SelectTokens: TStrings;
755  FromTokens: TStrings;
756 begin
757  Result := nil;
758  { Checks for the correct select statement. }
759  if (Sections.Count < 2)
760  or not ((TZStatementSection(Sections[0]).Name = 'SELECT')
761  or ((TZStatementSection(Sections[0]).Name = '')
762  and (TZStatementSection(Sections[1]).Name = 'SELECT'))) then
763  Exit;
764 
765  { Defins sections. }
766  SelectTokens := FindSectionTokens(Sections, 'SELECT');
767  FromTokens := FindSectionTokens(Sections, 'FROM');
768  if (SelectTokens = nil) or (FromTokens = nil) then
769  Exit;
770 
771  { Creates and fills the result object. }
772  Result := TZSelectSchema.Create;
773  FillFieldRefs(Result, SelectTokens);
774  FillTableRefs(Result, FromTokens);
775 end;
776 
777 {**
778  Defines a select schema from the specified SQL query.
779  @param Tokenizer a tokenizer object.
780  @param SQL a SQL query.
781  @return a select statement schema.
782 }
783 function TZGenericStatementAnalyser.DefineSelectSchemaFromQuery(
784  Tokenizer: IZTokenizer; const SQL: string): IZSelectSchema;
785 var
786  Tokens: TStrings;
787  Sections: TObjectList;
788 begin
789  Tokens := TokenizeQuery(Tokenizer, SQL, True);
790  Sections := SplitSections(Tokens);
791  try
792  Result := DefineSelectSchemaFromSections(Sections);
793  finally
794  Tokens.Free;
795  Sections.Free;
796  end;
797 end;
798 
799 end.
800