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