ViewVC Help
View File | Revision Log | Show Annotations | Download File | View Changeset | Root Listing
root/public/ibx/trunk/runtime/IBSQLParser.pas
Revision: 21
Committed: Thu Feb 26 10:33:34 2015 UTC (9 years, 9 months ago) by tony
Content type: text/x-pascal
File size: 25464 byte(s)
Log Message:
Committing updates for Release R1-2-0

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     SetState(stInParam);
292     end;
293    
294     sqSemiColon:
295     if not (FState in [stInComment,stInCommentLine]) then
296     case FState of
297     stInWhere,stInGroupBy,
298     stInHaving,stInPlan,stInFrom:
299     begin
300     FState := stDone;
301     Exit
302     end;
303    
304     stInSingleQuotes, stInDoubleQuotes:
305     AddToSQL(';');
306    
307     else
308     raise Exception.Create('Unexpected ";"')
309     end;
310    
311     sqAsterisk:
312     if not (FState in [stInComment,stInCommentLine]) then
313     AddToSQL('*');
314    
315     sqForwardSlash:
316     if not (FState in [stInComment,stInCommentLine]) then
317     AddToSQL('/');
318    
319     sqOpenBracket:
320     if not (FState in [stInComment,stInCommentLine]) then
321     begin
322     if FNested = 0 then
323     case FState of
324     stInSelect,
325     stNestedSelect:
326     SetState(stNestedSelect);
327    
328     stInFrom,
329     stNestedFrom:
330     SetState(stNestedFrom);
331    
332     stInWhere,
333     stNestedWhere:
334     SetState(stNestedWhere);
335    
336     stInGroupBy,
337     stNestedGroupBy:
338     SetState(stNestedGroupBy);
339    
340     stCTE3:
341     begin
342     FState := stCTEClosed;
343     SetState(stInCTE);
344     end;
345     end;
346     if (FNested > 0 ) or (FState <> stInCTE) then
347     AddToSQL('(');
348     Inc(FNested);
349     end;
350    
351     sqCloseBracket:
352     if not (FState in [stInComment,stInCommentLine]) then
353     begin
354     Dec(FNested);
355     if (FNested > 0) or (FState <> stInCTE) then
356     AddToSQL(')');
357     if FNested = 0 then
358     begin
359     if FState = stInCTE then
360     FState := PopState
361     else
362     if FState in [stNestedSelect,stNestedFrom,stNestedWhere,stNestedGroupBy] then
363     FState := PopState;
364     end;
365     if FState = stCTEClosed then
366     AddCTE;
367     end;
368    
369     sqComma:
370     if FState = stCTEClosed then
371     FState := stCTE
372     else
373     AddToSQL(',');
374    
375     sqCommentStart:
376     if not (FState in [stInComment,stInCommentLine]) then
377     SetState(stInComment);
378    
379     sqCommentEnd:
380     if FState = stInComment then
381     FState := PopState
382     else
383     FState := stError;
384    
385     sqCommentLine:
386     if not (FState in [stInComment,stInCommentLine]) then
387     SetState(stInCommentLine);
388    
389     sqSingleQuotes:
390     if not (FState in [stInComment,stInCommentLine]) then
391     begin
392     case FState of
393     stInSingleQuotes:
394     begin
395     FState := PopState;
396     AddToSQL(FLiteral)
397     end;
398     stInDoubleQuotes:
399     {Ignore};
400     else
401     begin
402     FLiteral := '';
403     SetState(stInSingleQuotes)
404     end
405     end;
406     AddToSQL('''')
407     end;
408    
409     sqDoubleQuotes:
410     if not (FState in [stInComment,stInCommentLine]) then
411     begin
412     case FState of
413     stInSingleQuotes:
414     {Ignore};
415     stInDoubleQuotes:
416     begin
417     FState := PopState;
418     AddToSQL(FLiteral)
419     end;
420     else
421     begin
422     FLiteral := '';
423     SetState(stInDoubleQuotes)
424     end
425     end;
426     AddToSQL('"')
427     end;
428    
429     sqString:
430     if not (FState in [stInComment,stInCommentLine]) then
431     begin
432     if FState = stInParam then
433     begin
434     FState := PopState;
435     ParamList.Add(FString)
436     end
437     else
438     if FState in [stCTE, stCTE1] then
439     FState := stCTE2;
440     AddToSQL(FString)
441     end;
442    
443     sqEOL:
444     begin
445     case FState of
446     stInCommentLine:
447     FState := PopState;
448     stInDoubleQuotes,
449     stInSingleQuotes:
450     raise Exception.Create(sNoEndToThis);
451     end;
452     AddToSQL(' ');
453     Exit;
454     end;
455    
456     sqSelect:
457     if FState in [stInSingleQuotes,stInDoubleQuotes,stNestedFrom,stNestedWhere,stNestedSelect,stInCTE] then
458     AddToSql(FString)
459     else
460     FState := stInSelect;
461    
462     sqFrom:
463     if FState = stInSelect then
464     FState := stInFrom
465     else
466     AddToSql(FString);
467     { if FState in [stInSingleQuotes,stInDoubleQuotes,stNestedFrom,stNestedWhere,
468     stNestedGroupBy,stNestedSelect] then
469     AddToSql(FString)
470     else
471     FState := stInFrom;}
472    
473     sqGroup:
474     if FState in [stInSingleQuotes,stInDoubleQuotes,stNestedFrom,stNestedWhere,stInCTE] then
475     AddToSql(FString)
476     else
477     FState := stInGroup;
478    
479     sqWhere:
480     if FState in [stInSingleQuotes,stInDoubleQuotes,stNestedFrom,stNestedWhere,stNestedSelect,stInCTE] then
481     AddToSql(FString)
482     else
483     FState := stInWhere;
484    
485     sqHaving:
486     if FState in [stInSingleQuotes,stInDoubleQuotes,stNestedFrom,stNestedWhere,stInCTE] then
487     AddToSql(FString)
488     else
489     FState := stInHaving;
490    
491     sqPlan:
492     if FState in [stInSingleQuotes,stInDoubleQuotes,stNestedFrom,stNestedWhere,stInCTE] then
493     AddToSql(FString)
494     else
495     FState := stInPlan;
496    
497     sqOrder:
498     if FState in [stInSingleQuotes,stInDoubleQuotes,stNestedFrom,stNestedWhere] then
499     AddToSql(FString)
500     else
501     FState := stInOrder;
502    
503     sqUnion:
504     if FState in [stInSingleQuotes,stInDoubleQuotes,stNestedFrom,stNestedWhere,stInCTE] then
505     AddToSql(FString)
506     else
507     begin
508     FState := stUnion;
509     Exit
510     end;
511    
512     sqAll:
513     if FState in [stInSingleQuotes,stInDoubleQuotes,stNestedFrom,stNestedWhere,stInCTE] then
514     AddToSql(FString)
515     else
516     if (FState = stInit) and FAllowUnionAll and not FUnionAll then
517     FUnionAll := true
518     else
519     raise Exception.Create('Unexpected symbol "all"');
520    
521     sqBy:
522     case FState of
523     stInGroup:
524     FState := stInGroupBy;
525     stInOrder:
526     FState := stInOrderBy;
527     stNestedFrom,stNestedWhere,stInCTE,
528     stInSingleQuotes,
529     stInDoubleQuotes:
530     AddToSql(FString);
531     else
532     raise Exception.CreateFmt(sBadBy,[Line])
533     end;
534    
535     sqWith:
536     if FState = stInit then
537     begin
538     FState := stCTE;
539     InitCTE;
540     end
541     else
542     raise Exception.Create('Unexpected symbol "with"');
543    
544     sqRecursive:
545     if FState = stCTE then
546     begin
547     FCTE.Recursive := true;
548     FState := stCTE1
549     end
550     else
551     raise Exception.Create('Unexpected symbol "recursive"');
552    
553     sqAs:
554     if FState = stCTE2 then
555     FState := stCTE3
556     else
557     AddToSQL('as');
558    
559     else
560     raise Exception.Create(sBadSymbol);
561     end
562     end
563     end;
564    
565     procedure TSelectSQLParser.AnalyseSQL(Lines: TStrings);
566     var I: integer;
567     begin
568     try
569     for I := FStartLine to Lines.Count - 1 do
570     try
571     AnalyseLine(Lines[I]);
572     case FState of
573     stDone:
574     break;
575     stUnion:
576     begin
577     if FIndex > length(Lines[I]) then
578     if I+1 < Lines.Count then
579     FUnion := TSelectSQLParser.Create(Lines,I+1,1)
580     else
581     raise Exception.Create(sIncomplete)
582     else
583     FUnion := TSelectSQLParser.Create(Lines,I,FIndex);
584     Exit
585     end;
586     end;
587     FIndex := 1;
588     except on E: Exception do
589     raise Exception.CreateFmt(sBadSQL,[Lines[I],E.Message])
590     end;
591     finally
592     FOriginalWhereClause := WhereClause;
593     FOriginalHavingClause := HavingClause;
594     FOriginalOrderByClause := OrderByClause
595     end;
596     end;
597    
598     procedure TSelectSQLParser.InitCTE;
599     begin
600     with FCTE do
601     begin
602     Recursive := false;
603     Name := '';
604     Text := '';
605     end;
606     end;
607    
608     procedure TSelectSQLParser.AddCTE;
609     var cte: PCTEDef;
610     begin
611     new(cte);
612     cte^.Name := FCTE.Name;
613     cte^.Recursive := FCTE.Recursive;
614     cte^.Text := FCTE.Text;
615     FCTEs.add(cte);
616     InitCTE;
617     end;
618    
619     function TSelectSQLParser.Check4ReservedWord(const Text: string): TSQLSymbol;
620     begin
621     Result := sqString;
622     if CompareText(Text,'select') = 0 then
623     Result := sqSelect
624     else
625     if CompareText(Text,'from') = 0 then
626     Result := sqFrom
627     else
628     if CompareText(Text,'where') = 0 then
629     Result := sqWhere
630     else
631     if CompareText(Text,'group') = 0 then
632     Result := sqGroup
633     else
634     if CompareText(Text,'by') = 0 then
635     Result := sqBy
636     else
637     if CompareText(Text,'having') = 0 then
638     Result := sqHaving
639     else
640     if CompareText(Text,'plan') = 0 then
641     Result := sqPlan
642     else
643     if CompareText(Text,'union') = 0 then
644     Result := sqUnion
645     else
646     if CompareText(Text,'all') = 0 then
647     Result := sqAll
648     else
649     if CompareText(Text,'order') = 0 then
650     Result := sqOrder
651     else
652     if CompareText(Text,'with') = 0 then
653     Result := sqWith
654     else
655     if CompareText(Text,'recursive') = 0 then
656     Result := sqRecursive
657     else
658     if CompareText(Text,'as') = 0 then
659     Result := sqAs
660     end;
661    
662     constructor TSelectSQLParser.Create(aDataSet: TDataSet; SQLText: TStrings);
663     begin
664     FDataSet := aDataSet;
665     Create(SQLText,0,1)
666     end;
667    
668     constructor TSelectSQLParser.Create(aDataSet: TDataSet; const SQLText: string);
669     var Lines: TStringList;
670     begin
671     Lines := TStringList.Create;
672     try
673     Lines.Text := SQLText;
674     Create(aDataSet,Lines)
675     finally
676     Lines.Free
677     end
678     end;
679    
680     constructor TSelectSQLParser.Create(SQLText: TStrings; StartLine,
681     StartIndex: integer);
682     begin
683     inherited Create;
684     FParamList := TStringList.Create;
685     FCTEs := TList.Create;
686     FLastSymbol := sqNone;
687     FState := stInit;
688     FStartLine := StartLine;
689     FIndex := StartIndex;
690     FAllowUnionAll := true;
691     AnalyseSQL(SQLText);
692     end;
693    
694     procedure TSelectSQLParser.Changed;
695     begin
696     if assigned(FOnSQLChanging) and not FDestroying then
697     OnSQLChanging(self)
698     end;
699    
700     function TSelectSQLParser.GetNextSymbol(C: char): TSQLSymbol;
701     begin
702     case C of
703     ' ',#9:
704     Result := sqSpace;
705     ';':
706     Result := sqSemiColon;
707     '"':
708     Result := sqDoubleQuotes;
709     '''':
710     Result := sqSingleQuotes;
711     '/':
712     Result := sqForwardSlash;
713     '*':
714     Result := sqAsterisk;
715     '(':
716     Result := sqOpenBracket;
717     ')':
718     Result := sqCloseBracket;
719     ':':
720     Result := sqColon;
721     ',':
722     Result := sqComma;
723     else
724     begin
725     Result := sqString;
726     FLastChar := C
727     end
728     end
729     end;
730    
731     function TSelectSQLParser.GetSymbol(const Line: string; var index: integer): TSQLSymbol;
732     begin
733     Result := FLastSymbol;
734     if Result = sqString then
735     FString := FLastChar;
736     FLastSymbol := sqNone;
737    
738     while (index <= Length(Line)) and (FLastSymbol = sqNone) do
739     begin
740     FLastSymbol := GetNextSymbol(Line[index]);
741     {combine if possible}
742     case Result of
743     sqNone:
744     begin
745     Result := FLastSymbol;
746     if FLastSymbol = sqString then
747     FString := FLastChar;
748     FLastSymbol := sqNone
749     end;
750    
751     sqSpace:
752     if FLastSymbol = sqSpace then
753     FLastSymbol := sqNone;
754    
755     sqForwardSlash:
756     if FLastSymbol = sqAsterisk then
757     begin
758     Result := sqCommentStart;
759     FLastSymbol := sqNone
760     end
761     else
762     if FLastSymbol = sqForwardSlash then
763     begin
764     Result := sqCommentLine;
765     FLastSymbol := sqNone
766     end;
767    
768     sqAsterisk:
769     if FLastSymbol = sqForwardSlash then
770     begin
771     Result := sqCommentEnd;
772     FLastSymbol := sqNone
773     end;
774    
775     sqString:
776     if FLastSymbol = sqString then
777     begin
778     FString := FString + FLastChar;
779     FLastSymbol := sqNone
780     end;
781     end;
782     Inc(index)
783     end;
784    
785     if (Result = sqString) and not (FState in [stInComment,stInCommentLine])then
786     Result := Check4ReservedWord(FString);
787    
788     if (index > Length(Line)) then
789     if Result = sqNone then
790     Result := sqEOL
791     else
792     if (FLastSymbol = sqNone) and (Result <> sqEOL) then
793     FLastSymbol := sqEOL;
794    
795     end;
796    
797     function TSelectSQLParser.GetSQlText: string;
798     var SQL: TStringList;
799     I: integer;
800     begin
801     SQL := TStringList.Create;
802     try
803     for I := 0 to CTECount - 1 do
804     begin
805     if I = 0 then
806     begin
807     if CTEs[I]^.Recursive then
808     SQL.Add('WITH RECURSIVE ' + CTEs[I]^.Name + ' AS (' + CTES[I]^.Text + ')')
809     else
810     SQL.Add('WITH ' + CTEs[I]^.Name + ' AS (' + CTES[I]^.Text +')')
811     end
812     else
813     begin
814     SQL.Add(',');
815     SQL.Add(CTEs[I]^.Name + ' AS (' + CTES[I]^.Text +')')
816     end
817     end;
818     if CTECount > 0 then
819     SQL.Add('');
820     SQL.Add('SELECT ' + SelectClause + #13#10' FROM ' + FromClause);
821     if WhereClause <> '' then
822     SQL.Add('Where ' + WhereClause);
823     if GroupClause <> '' then
824     SQL.Add('GROUP BY ' + GroupClause);
825     if HavingClause <> '' then
826     SQL.Add('HAVING ' + HavingClause);
827     if PlanClause <> '' then
828     SQL.Add('PLAN ' + PlanClause);
829     if OrderByClause <> '' then
830     SQL.Add('ORDER BY ' + OrderByClause);
831     if Union <> nil then
832     begin
833     if Union.UnionAll then
834     SQL.Add('UNION ALL')
835     else
836     SQL.Add('UNION');
837     SQL.Add(Union.SQLText)
838     end;
839     Result := SQL.Text
840     finally
841     SQL.Free
842     end
843     end;
844    
845     function TSelectSQLParser.PopState: TSQLStates;
846     begin
847     if FStackIndex = 0 then
848     raise Exception.Create(sStackUnderFlow);
849     Dec(FStackIndex);
850     Result := FStack[FStackIndex]
851     end;
852    
853     procedure TSelectSQLParser.SetState(AState: TSQLStates);
854     begin
855     if FStackIndex > 16 then
856     raise Exception.Create(sStackOverFlow);
857     FStack[FStackIndex] := FState;
858     Inc(FStackIndex);
859     FState := AState
860     end;
861    
862     procedure TSelectSQLParser.SetSelectClause(const Value: string);
863     begin
864     if Union <> nil then Union.SelectClause := Value;
865     FSelectClause := Value;
866     Changed
867     end;
868    
869     procedure TSelectSQLParser.SetFromClause(const Value: string);
870     begin
871     if Union <> nil then
872     Union.FromClause := Value
873     else
874     FFromClause := Value;
875     Changed
876     end;
877    
878     procedure TSelectSQLParser.SetGroupClause(const Value: string);
879     begin
880     if Union <> nil then
881     Union.GroupClause := Value
882     else
883     FGroupClause := Value;
884     Changed
885     end;
886    
887     procedure TSelectSQLParser.SetOrderByClause(const Value: string);
888     begin
889     if Union <> nil then
890     Union.OrderByClause := Value
891     else
892     FOrderByClause := Value;
893     Changed
894     end;
895    
896     procedure TSelectSQLParser.DropUnion;
897     begin
898     if FUnion <> nil then
899     begin
900     FUnion.Free;
901     FUnion := nil;
902     Changed
903     end
904     end;
905    
906     function TSelectSQLParser.GetFieldPosition(AliasName: string): integer;
907     begin
908     if assigned(FDataSet) and (FDataSet is TIBCustomDataset) then
909     Result := TIBCustomDataset(FDataSet).GetFieldPosition(AliasName)
910     else
911     Result := 0;
912     end;
913    
914     procedure TSelectSQLParser.ResetWhereClause;
915     begin
916     FWhereClause := FOriginalWhereClause;
917     if Union <> nil then
918     Union.ResetWhereClause;
919     Changed
920     end;
921    
922     procedure TSelectSQLParser.ResetHavingClause;
923     begin
924     FHavingClause := FOriginalHavingClause;
925     if Union <> nil then
926     Union.ResetHavingClause;
927     Changed
928     end;
929    
930     procedure TSelectSQLParser.ResetOrderByClause;
931     begin
932     FOrderbyClause := FOriginalOrderByClause;
933     if Union <> nil then
934     Union.ResetOrderByClause;
935     Changed
936     end;
937    
938     procedure TSelectSQLParser.Reset;
939     begin
940     ResetWhereClause;
941     ResetHavingClause;
942     ResetOrderByClause
943     end;
944    
945     destructor TSelectSQLParser.Destroy;
946     begin
947     FDestroying := true;
948     DropUnion;
949     if FParamList <> nil then FParamList.Free;
950     if FCTEs <> nil then
951     begin
952     CTEClear;
953     FCTEs.Free;
954     end;
955     inherited;
956     end;
957    
958     end.
959    
960