ViewVC Help
View File | Revision Log | Show Annotations | Download File | View Changeset | Root Listing
root/public/ibx/trunk/runtime/IBSQLParser.pas
Revision: 39
Committed: Tue May 17 08:14:52 2016 UTC (8 years, 6 months ago) by tony
Content type: text/x-pascal
File size: 25688 byte(s)
Log Message:
Committing updates for Release R1-4-1

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