ViewVC Help
View File | Revision Log | Show Annotations | Download File | View Changeset | Root Listing
root/public/ibx/trunk/runtime/IBSQLParser.pas
Revision: 27
Committed: Tue Apr 14 13:10:23 2015 UTC (9 years, 7 months ago) by tony
Content type: text/x-pascal
File size: 25534 byte(s)
Log Message:
Committing updates for Release R1-2-3

File Contents

# User Rev Content
1 tony 21 (*
2     * IBX For Lazarus (Firebird Express)
3     *
4     * The contents of this file are subject to the Initial Developer's
5     * Public License Version 1.0 (the "License"); you may not use this
6     * file except in compliance with the License. You may obtain a copy
7     * of the License here:
8     *
9     * http://www.firebirdsql.org/index.php?op=doc&id=idpl
10     *
11     * Software distributed under the License is distributed on an "AS
12     * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
13     * implied. See the License for the specific language governing rights
14     * and limitations under the License.
15     *
16     * The Initial Developer of the Original Code is Tony Whyman.
17     *
18     * The Original Code is (C) 2014 Tony Whyman, MWA Software
19     * (http://www.mwasoftware.co.uk).
20     *
21     * All Rights Reserved.
22     *
23     * Contributor(s): ______________________________________.
24     *
25     *)
26     unit IBSQLParser;
27    
28     {$Mode Delphi}
29    
30     interface
31    
32     uses Classes, DB;
33    
34     {
35     The SQL Parser is a partial SQL Parser intended to parser a Firebird DML (Select)
36     statement with the intention of being able to modify the "Where", Having" and
37     "Order By" clauses. It is not an SQL validator as while invalid SQL may be detected,
38     there are many cases of non-compliant SQL that will still be parsed successfully.
39    
40     In use, when a TSelectSQLParser object is created, it is passed a Select SQL Statement
41     that is then parsed into its constituent clauses. CTEs are brought out separately, as is
42     each union. The main clauses are made visible as text properties. Some, such as the
43     order by clause can be replaced fully. The Where and Having Clauses are manipulated by
44     the Add2WhereClause and the Add2HavingClause.
45    
46     Add2WhereClause allows an SQL Condition statement (e.g. A = 1) to be appended to the
47     current WhereClause, ANDing or ORing in the new condition. Normally, Add2WhereClause
48     only manipulates the first Select in a UNION, and conditions must be applied separately
49     to each member of the union. However, Add2WhereClause can also apply the same SQL
50     condition to each member of the UNION.
51    
52     Add2HavingClause behaves identically, except that it applies to the Having Clause.
53    
54     TSelectSQLParser.Reset will return the Where, Having and OrderBy Clauses of all members
55     of the Union to their initial state. ResetWhereClause, ResetHavingClause and
56     ResetOrderByClause allow each clause to be individually reset to their initial
57     state.
58    
59     The property SQLText is used to access the current Select SQL statement including
60     CTEs and UNIONs and modified clauses.
61    
62     }
63    
64     type
65     TSQLSymbol = (sqNone,sqSpace,sqSemiColon,sqSingleQuotes,sqDoubleQuotes, sqComma,
66     sqString,sqCommentStart,sqUnion,sqAll,sqColon,
67     sqCommentEnd,sqCommentLine,sqAsterisk,sqForwardSlash,
68     sqSelect,sqFrom,sqWhere,sqGroup,sqOrder,sqBy,sqOpenBracket,
69     sqCloseBracket,sqHaving,sqPlan,sqEOL,sqWith,sqRecursive,sqAs);
70    
71     TSQLStates = (stInit, stError, stInSelect,stInFrom,stInWhere,stInGroupBy,
72     stInHaving,stInPlan, stNestedSelect,stInSingleQuotes, stInGroup,
73     stInDoubleQuotes, stInComment, stInCommentLine, stInOrder,
74     stNestedWhere,stNestedFrom,stInOrderBy,stDone,stUnion,
75     stInParam,stNestedGroupBy,stCTE,stCTE1,stCTE2,stCTE3, stCTEquoted,
76     stInCTE,stCTEClosed);
77    
78     PCTEDef = ^TCTEDef;
79     TCTEDef = record
80     Recursive: boolean;
81     Name: string;
82     Text: string;
83     end;
84    
85     { TSelectSQLParser }
86    
87     TSelectSQLParser = class
88     private
89     FDataSet: TDataSet;
90     FHavingClause: string;
91     FOriginalHavingClause: string;
92     FOnSQLChanging: TNotifyEvent;
93     FSelectClause: string;
94     FGroupClause: string;
95     FUnionAll: boolean;
96     FWhereClause: string;
97     FOriginalWhereClause: string;
98     FOrderByClause: string;
99     FOriginalOrderByClause: string;
100     FPlanClause: string;
101     FFromClause: string;
102     FState: TSQLStates;
103     FString: string;
104     FLastSymbol: TSQLSymbol;
105     FLastChar: char;
106     FStack: array [0..16] of TSQLStates;
107     FStackindex: integer;
108     FIndex: integer;
109     FStartLine: integer;
110     FUnion: TSelectSQLParser;
111     FAllowUnionAll: boolean;
112     FLiteral: string;
113     FParamList: TStringList;
114     FCTEs: TList;
115     FCTE: TCTEDef;
116     FNested: integer;
117     FDestroying: boolean;
118     procedure AddToSQL(const Word: string);
119     procedure CTEClear;
120     function GetCTE(Index: integer): PCTEDef;
121     function GetCTECount: integer;
122     function GetSQlText: string;
123     function Check4ReservedWord(const Text: string): TSQLSymbol;
124     procedure AnalyseLine(const Line: string);
125     procedure AnalyseSQL(Lines: TStrings);
126     procedure InitCTE;
127     procedure AddCTE;
128     function GetNextSymbol(C: char): TSQLSymbol;
129     function GetSymbol(const Line: string; var index: integer): TSQLSymbol;
130     function PopState: TSQLStates;
131     procedure SetState(AState: TSQLStates);
132     procedure SetSelectClause(const Value: string);
133     procedure SetOrderByClause(const Value: string);
134     procedure SetGroupClause(const Value: string);
135     procedure SetFromClause(const Value: string);
136     protected
137     constructor Create(SQLText: TStrings; StartLine, StartIndex: integer); overload;
138     procedure Changed;
139     public
140     constructor Create(aDataSet: TDataSet; SQLText: TStrings); overload;
141     constructor Create(aDataSet: TDataSet; const SQLText: string); overload;
142     destructor Destroy; override;
143     procedure Add2WhereClause(const Condition: string; OrClause: boolean=false;
144     IncludeUnions: boolean = false);
145     procedure Add2HavingClause(const Condition: string; OrClause: boolean=false;
146     IncludeUnions: boolean = false);
147     procedure DropUnion;
148     function GetFieldPosition(AliasName: string): integer;
149     procedure ResetWhereClause;
150     procedure ResetHavingClause;
151     procedure ResetOrderByClause;
152     procedure Reset;
153     property CTEs[Index: integer]: PCTEDef read GetCTE;
154     property CTECount: integer read GetCTECount;
155     property DataSet: TDataSet read FDataSet;
156     property SelectClause: string read FSelectClause write SetSelectClause;
157     property FromClause: string read FFromClause write SetFromClause;
158     property GroupClause: string read FGroupClause write SetGroupClause;
159     property HavingClause: string read FHavingClause write FHavingClause;
160     property PlanClause: string read FPlanClause;
161     property WhereClause: string read FWhereClause write FWhereClause;
162     property OrderByClause: string read FOrderByClause write SetOrderByClause;
163     property SQLText: string read GetSQLText;
164     property Union: TSelectSQLParser read FUnion;
165     property UnionAll: boolean read FUnionAll write FUnionAll;
166     {When true this is joined by "Union All" to the parent Select}
167     property ParamList: TStringList read FParamList;
168     property OnSQLChanging: TNotifyEvent read FOnSQLChanging write FOnSQLChanging;
169     end;
170    
171     TFilterCallback = procedure(Parser: TSelectSQLParser; Key: integer) of object;
172    
173     implementation
174    
175     uses Sysutils, IBCustomDataSet;
176    
177     resourcestring
178     sNoEndToThis = 'Unterminated string';
179     sBadBy = 'Unexpected symbol "BY" in: %s';
180     sBadSymbol = 'Unknown Symbol';
181     sIncomplete = 'Incomplete Union';
182     sBadSQL = 'Error processing SQL "%s" - %s';
183     sStackUnderFlow = 'Stack Underflow';
184     sStackOverFlow = 'Stack Overflow';
185     sBadParameter = 'Bad SQL Parameter';
186    
187     { TSelectSQLParser }
188    
189     procedure TSelectSQLParser.AddToSQL(const Word: string);
190     begin
191     case FState of
192     stNestedSelect,
193     stInSelect:
194     FSelectClause := FSelectClause + Word;
195     stNestedFrom,
196     stInFrom:
197     FFromClause := FFromClause + Word;
198     stNestedWhere,
199     stInWhere:
200     FWhereClause := FWhereClause + Word;
201     stNestedGroupBy,
202     stInGroupBy:
203     FGroupClause := FGroupClause + Word;
204     stInHaving:
205     FHavingClause := FHavingClause + Word;
206     stInPlan:
207     FPlanClause := FPlanClause + Word;
208     stInOrderBy:
209     FOrderByClause := FOrderByClause + Word;
210     stInDoubleQuotes,
211     stInSingleQuotes:
212     FLiteral := FLiteral + Word;
213     stInCTE:
214     FCTE.Text := FCTE.Text + Word;
215     stCTE2:
216     FCTE.Name := Trim(FCTE.Name + Word);
217     end;
218     end;
219    
220     procedure TSelectSQLParser.CTEClear;
221     var i: integer;
222     begin
223     for i := 0 to FCTEs.Count - 1 do
224     dispose(PCTEDef(FCTEs[i]));
225     FCTEs.Clear;
226     end;
227    
228     function TSelectSQLParser.GetCTE(Index: integer): PCTEDef;
229     begin
230     if (Index < 0) or (index >= FCTEs.Count) then
231     raise Exception.Create('CTE Index out of bounds');
232    
233     Result := FCTEs[Index]
234     end;
235    
236     function TSelectSQLParser.GetCTECount: integer;
237     begin
238     Result := FCTEs.Count;
239     end;
240    
241     procedure TSelectSQLParser.Add2WhereClause(const Condition: string;
242     OrClause: boolean; IncludeUnions: boolean);
243     begin
244     if WhereClause <> '' then
245     if OrClause then
246     FWhereClause := '(' + WhereClause + ') OR (' + Condition + ')'
247     else
248     FWhereClause := '(' + WhereClause + ') AND (' + Condition + ')'
249     else
250     FWhereClause := Condition;
251     if IncludeUnions and (Union <> nil) then
252     Union.Add2WhereClause(Condition,OrClause,IncludeUnions);
253     Changed;
254     end;
255    
256     procedure TSelectSQLParser.Add2HavingClause(const Condition: string;
257     OrClause: boolean; IncludeUnions: boolean);
258     begin
259     if HavingClause <> '' then
260     if OrClause then
261     FHavingClause := '(' + HavingClause + ') OR (' + Condition + ')'
262     else
263     FHavingClause := '(' + HavingClause + ') AND (' + Condition + ')'
264     else
265     FHavingClause := Condition;
266     if IncludeUnions and (Union <> nil) then
267     Union.Add2HavingClause(Condition,OrClause,IncludeUnions);
268     Changed;
269     end;
270    
271     procedure TSelectSQLParser.AnalyseLine(const Line: string);
272     var Symbol: TSQLSymbol;
273     begin
274     while true do
275     begin
276     if FState = stError then
277     raise Exception.Create('Entered Error State');
278     Symbol := GetSymbol(Line,FIndex);
279     if (FState = stInParam) and (Symbol <> sqString) then
280     raise Exception.Create(sBadParameter);
281    
282     case Symbol of
283     sqSpace:
284     if not (FState in [stInComment,stInCommentLine]) then
285     AddToSQL(' ');
286    
287     sqColon:
288     if not (FState in [stInComment,stInCommentLine]) then
289     begin
290     AddToSQL(':');
291 tony 27 if not (FState in [stInSingleQuotes,stInDoubleQuotes]) then
292     SetState(stInParam);
293 tony 21 end;
294    
295     sqSemiColon:
296     if not (FState in [stInComment,stInCommentLine]) then
297     case FState of
298     stInWhere,stInGroupBy,
299     stInHaving,stInPlan,stInFrom:
300     begin
301     FState := stDone;
302     Exit
303     end;
304    
305     stInSingleQuotes, stInDoubleQuotes:
306     AddToSQL(';');
307    
308     else
309     raise Exception.Create('Unexpected ";"')
310     end;
311    
312     sqAsterisk:
313     if not (FState in [stInComment,stInCommentLine]) then
314     AddToSQL('*');
315    
316     sqForwardSlash:
317     if not (FState in [stInComment,stInCommentLine]) then
318     AddToSQL('/');
319    
320     sqOpenBracket:
321     if not (FState in [stInComment,stInCommentLine]) then
322     begin
323     if FNested = 0 then
324     case FState of
325     stInSelect,
326     stNestedSelect:
327     SetState(stNestedSelect);
328    
329     stInFrom,
330     stNestedFrom:
331     SetState(stNestedFrom);
332    
333     stInWhere,
334     stNestedWhere:
335     SetState(stNestedWhere);
336    
337     stInGroupBy,
338     stNestedGroupBy:
339     SetState(stNestedGroupBy);
340    
341     stCTE3:
342     begin
343     FState := stCTEClosed;
344     SetState(stInCTE);
345     end;
346     end;
347     if (FNested > 0 ) or (FState <> stInCTE) then
348     AddToSQL('(');
349     Inc(FNested);
350     end;
351    
352     sqCloseBracket:
353     if not (FState in [stInComment,stInCommentLine]) then
354     begin
355     Dec(FNested);
356     if (FNested > 0) or (FState <> stInCTE) then
357     AddToSQL(')');
358     if FNested = 0 then
359     begin
360     if FState = stInCTE then
361     FState := PopState
362     else
363     if FState in [stNestedSelect,stNestedFrom,stNestedWhere,stNestedGroupBy] then
364     FState := PopState;
365     end;
366     if FState = stCTEClosed then
367     AddCTE;
368     end;
369    
370     sqComma:
371     if FState = stCTEClosed then
372     FState := stCTE
373     else
374     AddToSQL(',');
375    
376     sqCommentStart:
377     if not (FState in [stInComment,stInCommentLine]) then
378     SetState(stInComment);
379    
380     sqCommentEnd:
381     if FState = stInComment then
382     FState := PopState
383     else
384     FState := stError;
385    
386     sqCommentLine:
387     if not (FState in [stInComment,stInCommentLine]) then
388     SetState(stInCommentLine);
389    
390     sqSingleQuotes:
391     if not (FState in [stInComment,stInCommentLine]) then
392     begin
393     case FState of
394     stInSingleQuotes:
395     begin
396     FState := PopState;
397     AddToSQL(FLiteral)
398     end;
399     stInDoubleQuotes:
400     {Ignore};
401     else
402     begin
403     FLiteral := '';
404     SetState(stInSingleQuotes)
405     end
406     end;
407     AddToSQL('''')
408     end;
409    
410     sqDoubleQuotes:
411     if not (FState in [stInComment,stInCommentLine]) then
412     begin
413     case FState of
414     stInSingleQuotes:
415     {Ignore};
416     stInDoubleQuotes:
417     begin
418     FState := PopState;
419     AddToSQL(FLiteral)
420     end;
421     else
422     begin
423     FLiteral := '';
424     SetState(stInDoubleQuotes)
425     end
426     end;
427     AddToSQL('"')
428     end;
429    
430     sqString:
431     if not (FState in [stInComment,stInCommentLine]) then
432     begin
433     if FState = stInParam then
434     begin
435     FState := PopState;
436     ParamList.Add(FString)
437     end
438     else
439     if FState in [stCTE, stCTE1] then
440     FState := stCTE2;
441     AddToSQL(FString)
442     end;
443    
444     sqEOL:
445     begin
446     case FState of
447     stInCommentLine:
448     FState := PopState;
449     stInDoubleQuotes,
450     stInSingleQuotes:
451     raise Exception.Create(sNoEndToThis);
452     end;
453     AddToSQL(' ');
454     Exit;
455     end;
456    
457     sqSelect:
458     if FState in [stInSingleQuotes,stInDoubleQuotes,stNestedFrom,stNestedWhere,stNestedSelect,stInCTE] then
459     AddToSql(FString)
460     else
461     FState := stInSelect;
462    
463     sqFrom:
464     if FState = stInSelect then
465     FState := stInFrom
466     else
467     AddToSql(FString);
468     { if FState in [stInSingleQuotes,stInDoubleQuotes,stNestedFrom,stNestedWhere,
469     stNestedGroupBy,stNestedSelect] then
470     AddToSql(FString)
471     else
472     FState := stInFrom;}
473    
474     sqGroup:
475     if FState in [stInSingleQuotes,stInDoubleQuotes,stNestedFrom,stNestedWhere,stInCTE] then
476     AddToSql(FString)
477     else
478     FState := stInGroup;
479    
480     sqWhere:
481     if FState in [stInSingleQuotes,stInDoubleQuotes,stNestedFrom,stNestedWhere,stNestedSelect,stInCTE] then
482     AddToSql(FString)
483     else
484     FState := stInWhere;
485    
486     sqHaving:
487     if FState in [stInSingleQuotes,stInDoubleQuotes,stNestedFrom,stNestedWhere,stInCTE] then
488     AddToSql(FString)
489     else
490     FState := stInHaving;
491    
492     sqPlan:
493     if FState in [stInSingleQuotes,stInDoubleQuotes,stNestedFrom,stNestedWhere,stInCTE] then
494     AddToSql(FString)
495     else
496     FState := stInPlan;
497    
498     sqOrder:
499     if FState in [stInSingleQuotes,stInDoubleQuotes,stNestedFrom,stNestedWhere] then
500     AddToSql(FString)
501     else
502     FState := stInOrder;
503    
504     sqUnion:
505     if FState in [stInSingleQuotes,stInDoubleQuotes,stNestedFrom,stNestedWhere,stInCTE] then
506     AddToSql(FString)
507     else
508     begin
509     FState := stUnion;
510     Exit
511     end;
512    
513     sqAll:
514     if FState in [stInSingleQuotes,stInDoubleQuotes,stNestedFrom,stNestedWhere,stInCTE] then
515     AddToSql(FString)
516     else
517     if (FState = stInit) and FAllowUnionAll and not FUnionAll then
518     FUnionAll := true
519     else
520     raise Exception.Create('Unexpected symbol "all"');
521    
522     sqBy:
523     case FState of
524     stInGroup:
525     FState := stInGroupBy;
526     stInOrder:
527     FState := stInOrderBy;
528     stNestedFrom,stNestedWhere,stInCTE,
529     stInSingleQuotes,
530     stInDoubleQuotes:
531     AddToSql(FString);
532     else
533     raise Exception.CreateFmt(sBadBy,[Line])
534     end;
535    
536     sqWith:
537     if FState = stInit then
538     begin
539     FState := stCTE;
540     InitCTE;
541     end
542     else
543     raise Exception.Create('Unexpected symbol "with"');
544    
545     sqRecursive:
546     if FState = stCTE then
547     begin
548     FCTE.Recursive := true;
549     FState := stCTE1
550     end
551     else
552     raise Exception.Create('Unexpected symbol "recursive"');
553    
554     sqAs:
555     if FState = stCTE2 then
556     FState := stCTE3
557     else
558     AddToSQL('as');
559    
560     else
561     raise Exception.Create(sBadSymbol);
562     end
563     end
564     end;
565    
566     procedure TSelectSQLParser.AnalyseSQL(Lines: TStrings);
567     var I: integer;
568     begin
569     try
570     for I := FStartLine to Lines.Count - 1 do
571     try
572     AnalyseLine(Lines[I]);
573     case FState of
574     stDone:
575     break;
576     stUnion:
577     begin
578     if FIndex > length(Lines[I]) then
579     if I+1 < Lines.Count then
580     FUnion := TSelectSQLParser.Create(Lines,I+1,1)
581     else
582     raise Exception.Create(sIncomplete)
583     else
584     FUnion := TSelectSQLParser.Create(Lines,I,FIndex);
585     Exit
586     end;
587     end;
588     FIndex := 1;
589     except on E: Exception do
590     raise Exception.CreateFmt(sBadSQL,[Lines[I],E.Message])
591     end;
592     finally
593     FOriginalWhereClause := WhereClause;
594     FOriginalHavingClause := HavingClause;
595     FOriginalOrderByClause := OrderByClause
596     end;
597     end;
598    
599     procedure TSelectSQLParser.InitCTE;
600     begin
601     with FCTE do
602     begin
603     Recursive := false;
604     Name := '';
605     Text := '';
606     end;
607     end;
608    
609     procedure TSelectSQLParser.AddCTE;
610     var cte: PCTEDef;
611     begin
612     new(cte);
613     cte^.Name := FCTE.Name;
614     cte^.Recursive := FCTE.Recursive;
615     cte^.Text := FCTE.Text;
616     FCTEs.add(cte);
617     InitCTE;
618     end;
619    
620     function TSelectSQLParser.Check4ReservedWord(const Text: string): TSQLSymbol;
621     begin
622     Result := sqString;
623     if CompareText(Text,'select') = 0 then
624     Result := sqSelect
625     else
626     if CompareText(Text,'from') = 0 then
627     Result := sqFrom
628     else
629     if CompareText(Text,'where') = 0 then
630     Result := sqWhere
631     else
632     if CompareText(Text,'group') = 0 then
633     Result := sqGroup
634     else
635     if CompareText(Text,'by') = 0 then
636     Result := sqBy
637     else
638     if CompareText(Text,'having') = 0 then
639     Result := sqHaving
640     else
641     if CompareText(Text,'plan') = 0 then
642     Result := sqPlan
643     else
644     if CompareText(Text,'union') = 0 then
645     Result := sqUnion
646     else
647     if CompareText(Text,'all') = 0 then
648     Result := sqAll
649     else
650     if CompareText(Text,'order') = 0 then
651     Result := sqOrder
652     else
653     if CompareText(Text,'with') = 0 then
654     Result := sqWith
655     else
656     if CompareText(Text,'recursive') = 0 then
657     Result := sqRecursive
658     else
659     if CompareText(Text,'as') = 0 then
660     Result := sqAs
661     end;
662    
663     constructor TSelectSQLParser.Create(aDataSet: TDataSet; SQLText: TStrings);
664     begin
665     FDataSet := aDataSet;
666     Create(SQLText,0,1)
667     end;
668    
669     constructor TSelectSQLParser.Create(aDataSet: TDataSet; const SQLText: string);
670     var Lines: TStringList;
671     begin
672     Lines := TStringList.Create;
673     try
674     Lines.Text := SQLText;
675     Create(aDataSet,Lines)
676     finally
677     Lines.Free
678     end
679     end;
680    
681     constructor TSelectSQLParser.Create(SQLText: TStrings; StartLine,
682     StartIndex: integer);
683     begin
684     inherited Create;
685     FParamList := TStringList.Create;
686     FCTEs := TList.Create;
687     FLastSymbol := sqNone;
688     FState := stInit;
689     FStartLine := StartLine;
690     FIndex := StartIndex;
691     FAllowUnionAll := true;
692     AnalyseSQL(SQLText);
693     end;
694    
695     procedure TSelectSQLParser.Changed;
696     begin
697     if assigned(FOnSQLChanging) and not FDestroying then
698     OnSQLChanging(self)
699     end;
700    
701     function TSelectSQLParser.GetNextSymbol(C: char): TSQLSymbol;
702     begin
703     case C of
704     ' ',#9:
705     Result := sqSpace;
706     ';':
707     Result := sqSemiColon;
708     '"':
709     Result := sqDoubleQuotes;
710     '''':
711     Result := sqSingleQuotes;
712     '/':
713     Result := sqForwardSlash;
714     '*':
715     Result := sqAsterisk;
716     '(':
717     Result := sqOpenBracket;
718     ')':
719     Result := sqCloseBracket;
720     ':':
721     Result := sqColon;
722     ',':
723     Result := sqComma;
724     else
725     begin
726     Result := sqString;
727     FLastChar := C
728     end
729     end
730     end;
731    
732     function TSelectSQLParser.GetSymbol(const Line: string; var index: integer): TSQLSymbol;
733     begin
734     Result := FLastSymbol;
735     if Result = sqString then
736     FString := FLastChar;
737     FLastSymbol := sqNone;
738    
739     while (index <= Length(Line)) and (FLastSymbol = sqNone) do
740     begin
741     FLastSymbol := GetNextSymbol(Line[index]);
742     {combine if possible}
743     case Result of
744     sqNone:
745     begin
746     Result := FLastSymbol;
747     if FLastSymbol = sqString then
748     FString := FLastChar;
749     FLastSymbol := sqNone
750     end;
751    
752     sqSpace:
753     if FLastSymbol = sqSpace then
754     FLastSymbol := sqNone;
755    
756     sqForwardSlash:
757     if FLastSymbol = sqAsterisk then
758     begin
759     Result := sqCommentStart;
760     FLastSymbol := sqNone
761     end
762     else
763     if FLastSymbol = sqForwardSlash then
764     begin
765     Result := sqCommentLine;
766     FLastSymbol := sqNone
767     end;
768    
769     sqAsterisk:
770     if FLastSymbol = sqForwardSlash then
771     begin
772     Result := sqCommentEnd;
773     FLastSymbol := sqNone
774     end;
775    
776     sqString:
777     if FLastSymbol = sqString then
778     begin
779     FString := FString + FLastChar;
780     FLastSymbol := sqNone
781     end;
782     end;
783     Inc(index)
784     end;
785    
786     if (Result = sqString) and not (FState in [stInComment,stInCommentLine])then
787     Result := Check4ReservedWord(FString);
788    
789     if (index > Length(Line)) then
790     if Result = sqNone then
791     Result := sqEOL
792     else
793     if (FLastSymbol = sqNone) and (Result <> sqEOL) then
794     FLastSymbol := sqEOL;
795    
796     end;
797    
798     function TSelectSQLParser.GetSQlText: string;
799     var SQL: TStringList;
800     I: integer;
801     begin
802     SQL := TStringList.Create;
803     try
804     for I := 0 to CTECount - 1 do
805     begin
806     if I = 0 then
807     begin
808     if CTEs[I]^.Recursive then
809     SQL.Add('WITH RECURSIVE ' + CTEs[I]^.Name + ' AS (' + CTES[I]^.Text + ')')
810     else
811     SQL.Add('WITH ' + CTEs[I]^.Name + ' AS (' + CTES[I]^.Text +')')
812     end
813     else
814     begin
815     SQL.Add(',');
816     SQL.Add(CTEs[I]^.Name + ' AS (' + CTES[I]^.Text +')')
817     end
818     end;
819     if CTECount > 0 then
820     SQL.Add('');
821     SQL.Add('SELECT ' + SelectClause + #13#10' FROM ' + FromClause);
822     if WhereClause <> '' then
823     SQL.Add('Where ' + WhereClause);
824     if GroupClause <> '' then
825     SQL.Add('GROUP BY ' + GroupClause);
826     if HavingClause <> '' then
827     SQL.Add('HAVING ' + HavingClause);
828     if PlanClause <> '' then
829     SQL.Add('PLAN ' + PlanClause);
830     if OrderByClause <> '' then
831     SQL.Add('ORDER BY ' + OrderByClause);
832     if Union <> nil then
833     begin
834     if Union.UnionAll then
835     SQL.Add('UNION ALL')
836     else
837     SQL.Add('UNION');
838     SQL.Add(Union.SQLText)
839     end;
840     Result := SQL.Text
841     finally
842     SQL.Free
843     end
844     end;
845    
846     function TSelectSQLParser.PopState: TSQLStates;
847     begin
848     if FStackIndex = 0 then
849     raise Exception.Create(sStackUnderFlow);
850     Dec(FStackIndex);
851     Result := FStack[FStackIndex]
852     end;
853    
854     procedure TSelectSQLParser.SetState(AState: TSQLStates);
855     begin
856     if FStackIndex > 16 then
857     raise Exception.Create(sStackOverFlow);
858     FStack[FStackIndex] := FState;
859     Inc(FStackIndex);
860     FState := AState
861     end;
862    
863     procedure TSelectSQLParser.SetSelectClause(const Value: string);
864     begin
865     if Union <> nil then Union.SelectClause := Value;
866     FSelectClause := Value;
867     Changed
868     end;
869    
870     procedure TSelectSQLParser.SetFromClause(const Value: string);
871     begin
872     if Union <> nil then
873     Union.FromClause := Value
874     else
875     FFromClause := Value;
876     Changed
877     end;
878    
879     procedure TSelectSQLParser.SetGroupClause(const Value: string);
880     begin
881     if Union <> nil then
882     Union.GroupClause := Value
883     else
884     FGroupClause := Value;
885     Changed
886     end;
887    
888     procedure TSelectSQLParser.SetOrderByClause(const Value: string);
889     begin
890     if Union <> nil then
891     Union.OrderByClause := Value
892     else
893     FOrderByClause := Value;
894     Changed
895     end;
896    
897     procedure TSelectSQLParser.DropUnion;
898     begin
899     if FUnion <> nil then
900     begin
901     FUnion.Free;
902     FUnion := nil;
903     Changed
904     end
905     end;
906    
907     function TSelectSQLParser.GetFieldPosition(AliasName: string): integer;
908     begin
909     if assigned(FDataSet) and (FDataSet is TIBCustomDataset) then
910     Result := TIBCustomDataset(FDataSet).GetFieldPosition(AliasName)
911     else
912     Result := 0;
913     end;
914    
915     procedure TSelectSQLParser.ResetWhereClause;
916     begin
917     FWhereClause := FOriginalWhereClause;
918     if Union <> nil then
919     Union.ResetWhereClause;
920     Changed
921     end;
922    
923     procedure TSelectSQLParser.ResetHavingClause;
924     begin
925     FHavingClause := FOriginalHavingClause;
926     if Union <> nil then
927     Union.ResetHavingClause;
928     Changed
929     end;
930    
931     procedure TSelectSQLParser.ResetOrderByClause;
932     begin
933     FOrderbyClause := FOriginalOrderByClause;
934     if Union <> nil then
935     Union.ResetOrderByClause;
936     Changed
937     end;
938    
939     procedure TSelectSQLParser.Reset;
940     begin
941     ResetWhereClause;
942     ResetHavingClause;
943     ResetOrderByClause
944     end;
945    
946     destructor TSelectSQLParser.Destroy;
947     begin
948     FDestroying := true;
949     DropUnion;
950     if FParamList <> nil then FParamList.Free;
951     if FCTEs <> nil then
952     begin
953     CTEClear;
954     FCTEs.Free;
955     end;
956     inherited;
957     end;
958    
959     end.
960    
961