ViewVC Help
View File | Revision Log | Show Annotations | Download File | View Changeset | Root Listing
root/public/ibx/trunk/runtime/IBSQLParser.pas
Revision: 31
Committed: Tue Jul 14 15:31:25 2015 UTC (9 years, 4 months ago) by tony
Content type: text/x-pascal
File size: 25629 byte(s)
Log Message:
Committing updates for Release R1-3-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 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 tony 31 Begin
452     FLiteral := FLiteral + #$0A;
453     Exit;
454     End;
455 tony 21 end;
456     AddToSQL(' ');
457     Exit;
458     end;
459    
460     sqSelect:
461     if FState in [stInSingleQuotes,stInDoubleQuotes,stNestedFrom,stNestedWhere,stNestedSelect,stInCTE] then
462     AddToSql(FString)
463     else
464     FState := stInSelect;
465    
466     sqFrom:
467     if FState = stInSelect then
468     FState := stInFrom
469     else
470     AddToSql(FString);
471     { if FState in [stInSingleQuotes,stInDoubleQuotes,stNestedFrom,stNestedWhere,
472     stNestedGroupBy,stNestedSelect] then
473     AddToSql(FString)
474     else
475     FState := stInFrom;}
476    
477     sqGroup:
478     if FState in [stInSingleQuotes,stInDoubleQuotes,stNestedFrom,stNestedWhere,stInCTE] then
479     AddToSql(FString)
480     else
481     FState := stInGroup;
482    
483     sqWhere:
484     if FState in [stInSingleQuotes,stInDoubleQuotes,stNestedFrom,stNestedWhere,stNestedSelect,stInCTE] then
485     AddToSql(FString)
486     else
487     FState := stInWhere;
488    
489     sqHaving:
490     if FState in [stInSingleQuotes,stInDoubleQuotes,stNestedFrom,stNestedWhere,stInCTE] then
491     AddToSql(FString)
492     else
493     FState := stInHaving;
494    
495     sqPlan:
496     if FState in [stInSingleQuotes,stInDoubleQuotes,stNestedFrom,stNestedWhere,stInCTE] then
497     AddToSql(FString)
498     else
499     FState := stInPlan;
500    
501     sqOrder:
502     if FState in [stInSingleQuotes,stInDoubleQuotes,stNestedFrom,stNestedWhere] then
503     AddToSql(FString)
504     else
505     FState := stInOrder;
506    
507     sqUnion:
508     if FState in [stInSingleQuotes,stInDoubleQuotes,stNestedFrom,stNestedWhere,stInCTE] then
509     AddToSql(FString)
510     else
511     begin
512     FState := stUnion;
513     Exit
514     end;
515    
516     sqAll:
517     if FState in [stInSingleQuotes,stInDoubleQuotes,stNestedFrom,stNestedWhere,stInCTE] then
518     AddToSql(FString)
519     else
520     if (FState = stInit) and FAllowUnionAll and not FUnionAll then
521     FUnionAll := true
522     else
523     raise Exception.Create('Unexpected symbol "all"');
524    
525     sqBy:
526     case FState of
527     stInGroup:
528     FState := stInGroupBy;
529     stInOrder:
530     FState := stInOrderBy;
531     stNestedFrom,stNestedWhere,stInCTE,
532     stInSingleQuotes,
533     stInDoubleQuotes:
534     AddToSql(FString);
535     else
536     raise Exception.CreateFmt(sBadBy,[Line])
537     end;
538    
539     sqWith:
540     if FState = stInit then
541     begin
542     FState := stCTE;
543     InitCTE;
544     end
545     else
546     raise Exception.Create('Unexpected symbol "with"');
547    
548     sqRecursive:
549     if FState = stCTE then
550     begin
551     FCTE.Recursive := true;
552     FState := stCTE1
553     end
554     else
555     raise Exception.Create('Unexpected symbol "recursive"');
556    
557     sqAs:
558     if FState = stCTE2 then
559     FState := stCTE3
560     else
561     AddToSQL('as');
562    
563     else
564     raise Exception.Create(sBadSymbol);
565     end
566     end
567     end;
568    
569     procedure TSelectSQLParser.AnalyseSQL(Lines: TStrings);
570     var I: integer;
571     begin
572     try
573     for I := FStartLine to Lines.Count - 1 do
574     try
575     AnalyseLine(Lines[I]);
576     case FState of
577     stDone:
578     break;
579     stUnion:
580     begin
581     if FIndex > length(Lines[I]) then
582     if I+1 < Lines.Count then
583     FUnion := TSelectSQLParser.Create(Lines,I+1,1)
584     else
585     raise Exception.Create(sIncomplete)
586     else
587     FUnion := TSelectSQLParser.Create(Lines,I,FIndex);
588     Exit
589     end;
590     end;
591     FIndex := 1;
592     except on E: Exception do
593     raise Exception.CreateFmt(sBadSQL,[Lines[I],E.Message])
594     end;
595     finally
596     FOriginalWhereClause := WhereClause;
597     FOriginalHavingClause := HavingClause;
598     FOriginalOrderByClause := OrderByClause
599     end;
600     end;
601    
602     procedure TSelectSQLParser.InitCTE;
603     begin
604     with FCTE do
605     begin
606     Recursive := false;
607     Name := '';
608     Text := '';
609     end;
610     end;
611    
612     procedure TSelectSQLParser.AddCTE;
613     var cte: PCTEDef;
614     begin
615     new(cte);
616     cte^.Name := FCTE.Name;
617     cte^.Recursive := FCTE.Recursive;
618     cte^.Text := FCTE.Text;
619     FCTEs.add(cte);
620     InitCTE;
621     end;
622    
623     function TSelectSQLParser.Check4ReservedWord(const Text: string): TSQLSymbol;
624     begin
625     Result := sqString;
626     if CompareText(Text,'select') = 0 then
627     Result := sqSelect
628     else
629     if CompareText(Text,'from') = 0 then
630     Result := sqFrom
631     else
632     if CompareText(Text,'where') = 0 then
633     Result := sqWhere
634     else
635     if CompareText(Text,'group') = 0 then
636     Result := sqGroup
637     else
638     if CompareText(Text,'by') = 0 then
639     Result := sqBy
640     else
641     if CompareText(Text,'having') = 0 then
642     Result := sqHaving
643     else
644     if CompareText(Text,'plan') = 0 then
645     Result := sqPlan
646     else
647     if CompareText(Text,'union') = 0 then
648     Result := sqUnion
649     else
650     if CompareText(Text,'all') = 0 then
651     Result := sqAll
652     else
653     if CompareText(Text,'order') = 0 then
654     Result := sqOrder
655     else
656     if CompareText(Text,'with') = 0 then
657     Result := sqWith
658     else
659     if CompareText(Text,'recursive') = 0 then
660     Result := sqRecursive
661     else
662     if CompareText(Text,'as') = 0 then
663     Result := sqAs
664     end;
665    
666     constructor TSelectSQLParser.Create(aDataSet: TDataSet; SQLText: TStrings);
667     begin
668     FDataSet := aDataSet;
669     Create(SQLText,0,1)
670     end;
671    
672     constructor TSelectSQLParser.Create(aDataSet: TDataSet; const SQLText: string);
673     var Lines: TStringList;
674     begin
675     Lines := TStringList.Create;
676     try
677     Lines.Text := SQLText;
678     Create(aDataSet,Lines)
679     finally
680     Lines.Free
681     end
682     end;
683    
684     constructor TSelectSQLParser.Create(SQLText: TStrings; StartLine,
685     StartIndex: integer);
686     begin
687     inherited Create;
688     FParamList := TStringList.Create;
689     FCTEs := TList.Create;
690     FLastSymbol := sqNone;
691     FState := stInit;
692     FStartLine := StartLine;
693     FIndex := StartIndex;
694     FAllowUnionAll := true;
695     AnalyseSQL(SQLText);
696     end;
697    
698     procedure TSelectSQLParser.Changed;
699     begin
700     if assigned(FOnSQLChanging) and not FDestroying then
701     OnSQLChanging(self)
702     end;
703    
704     function TSelectSQLParser.GetNextSymbol(C: char): TSQLSymbol;
705     begin
706     case C of
707     ' ',#9:
708     Result := sqSpace;
709     ';':
710     Result := sqSemiColon;
711     '"':
712     Result := sqDoubleQuotes;
713     '''':
714     Result := sqSingleQuotes;
715     '/':
716     Result := sqForwardSlash;
717     '*':
718     Result := sqAsterisk;
719     '(':
720     Result := sqOpenBracket;
721     ')':
722     Result := sqCloseBracket;
723     ':':
724     Result := sqColon;
725     ',':
726     Result := sqComma;
727     else
728     begin
729     Result := sqString;
730     FLastChar := C
731     end
732     end
733     end;
734    
735     function TSelectSQLParser.GetSymbol(const Line: string; var index: integer): TSQLSymbol;
736     begin
737     Result := FLastSymbol;
738     if Result = sqString then
739     FString := FLastChar;
740     FLastSymbol := sqNone;
741    
742     while (index <= Length(Line)) and (FLastSymbol = sqNone) do
743     begin
744     FLastSymbol := GetNextSymbol(Line[index]);
745     {combine if possible}
746     case Result of
747     sqNone:
748     begin
749     Result := FLastSymbol;
750     if FLastSymbol = sqString then
751     FString := FLastChar;
752     FLastSymbol := sqNone
753     end;
754    
755     sqSpace:
756     if FLastSymbol = sqSpace then
757     FLastSymbol := sqNone;
758    
759     sqForwardSlash:
760     if FLastSymbol = sqAsterisk then
761     begin
762     Result := sqCommentStart;
763     FLastSymbol := sqNone
764     end
765     else
766     if FLastSymbol = sqForwardSlash then
767     begin
768     Result := sqCommentLine;
769     FLastSymbol := sqNone
770     end;
771    
772     sqAsterisk:
773     if FLastSymbol = sqForwardSlash then
774     begin
775     Result := sqCommentEnd;
776     FLastSymbol := sqNone
777     end;
778    
779     sqString:
780     if FLastSymbol = sqString then
781     begin
782     FString := FString + FLastChar;
783     FLastSymbol := sqNone
784     end;
785     end;
786     Inc(index)
787     end;
788    
789 tony 31 if (Result = sqString) and not (FState in [stInComment,stInCommentLine, stInSingleQuotes,stInDoubleQuotes])then
790 tony 21 Result := Check4ReservedWord(FString);
791    
792     if (index > Length(Line)) then
793 tony 31 begin
794     if (Result = sqNone) then
795 tony 21 Result := sqEOL
796     else
797     if (FLastSymbol = sqNone) and (Result <> sqEOL) then
798     FLastSymbol := sqEOL;
799 tony 31 end;
800 tony 21
801     end;
802    
803     function TSelectSQLParser.GetSQlText: string;
804     var SQL: TStringList;
805     I: integer;
806     begin
807     SQL := TStringList.Create;
808     try
809     for I := 0 to CTECount - 1 do
810     begin
811     if I = 0 then
812     begin
813     if CTEs[I]^.Recursive then
814     SQL.Add('WITH RECURSIVE ' + CTEs[I]^.Name + ' AS (' + CTES[I]^.Text + ')')
815     else
816     SQL.Add('WITH ' + CTEs[I]^.Name + ' AS (' + CTES[I]^.Text +')')
817     end
818     else
819     begin
820     SQL.Add(',');
821     SQL.Add(CTEs[I]^.Name + ' AS (' + CTES[I]^.Text +')')
822     end
823     end;
824     if CTECount > 0 then
825     SQL.Add('');
826     SQL.Add('SELECT ' + SelectClause + #13#10' FROM ' + FromClause);
827     if WhereClause <> '' then
828     SQL.Add('Where ' + WhereClause);
829     if GroupClause <> '' then
830     SQL.Add('GROUP BY ' + GroupClause);
831     if HavingClause <> '' then
832     SQL.Add('HAVING ' + HavingClause);
833     if PlanClause <> '' then
834     SQL.Add('PLAN ' + PlanClause);
835     if OrderByClause <> '' then
836     SQL.Add('ORDER BY ' + OrderByClause);
837     if Union <> nil then
838     begin
839     if Union.UnionAll then
840     SQL.Add('UNION ALL')
841     else
842     SQL.Add('UNION');
843     SQL.Add(Union.SQLText)
844     end;
845     Result := SQL.Text
846     finally
847     SQL.Free
848     end
849     end;
850    
851     function TSelectSQLParser.PopState: TSQLStates;
852     begin
853     if FStackIndex = 0 then
854     raise Exception.Create(sStackUnderFlow);
855     Dec(FStackIndex);
856     Result := FStack[FStackIndex]
857     end;
858    
859     procedure TSelectSQLParser.SetState(AState: TSQLStates);
860     begin
861     if FStackIndex > 16 then
862     raise Exception.Create(sStackOverFlow);
863     FStack[FStackIndex] := FState;
864     Inc(FStackIndex);
865     FState := AState
866     end;
867    
868     procedure TSelectSQLParser.SetSelectClause(const Value: string);
869     begin
870     if Union <> nil then Union.SelectClause := Value;
871     FSelectClause := Value;
872     Changed
873     end;
874    
875     procedure TSelectSQLParser.SetFromClause(const Value: string);
876     begin
877     if Union <> nil then
878     Union.FromClause := Value
879     else
880     FFromClause := Value;
881     Changed
882     end;
883    
884     procedure TSelectSQLParser.SetGroupClause(const Value: string);
885     begin
886     if Union <> nil then
887     Union.GroupClause := Value
888     else
889     FGroupClause := Value;
890     Changed
891     end;
892    
893     procedure TSelectSQLParser.SetOrderByClause(const Value: string);
894     begin
895     if Union <> nil then
896     Union.OrderByClause := Value
897     else
898     FOrderByClause := Value;
899     Changed
900     end;
901    
902     procedure TSelectSQLParser.DropUnion;
903     begin
904     if FUnion <> nil then
905     begin
906     FUnion.Free;
907     FUnion := nil;
908     Changed
909     end
910     end;
911    
912     function TSelectSQLParser.GetFieldPosition(AliasName: string): integer;
913     begin
914     if assigned(FDataSet) and (FDataSet is TIBCustomDataset) then
915     Result := TIBCustomDataset(FDataSet).GetFieldPosition(AliasName)
916     else
917     Result := 0;
918     end;
919    
920     procedure TSelectSQLParser.ResetWhereClause;
921     begin
922     FWhereClause := FOriginalWhereClause;
923     if Union <> nil then
924     Union.ResetWhereClause;
925     Changed
926     end;
927    
928     procedure TSelectSQLParser.ResetHavingClause;
929     begin
930     FHavingClause := FOriginalHavingClause;
931     if Union <> nil then
932     Union.ResetHavingClause;
933     Changed
934     end;
935    
936     procedure TSelectSQLParser.ResetOrderByClause;
937     begin
938     FOrderbyClause := FOriginalOrderByClause;
939     if Union <> nil then
940     Union.ResetOrderByClause;
941     Changed
942     end;
943    
944     procedure TSelectSQLParser.Reset;
945     begin
946     ResetWhereClause;
947     ResetHavingClause;
948     ResetOrderByClause
949     end;
950    
951     destructor TSelectSQLParser.Destroy;
952     begin
953     FDestroying := true;
954     DropUnion;
955     if FParamList <> nil then FParamList.Free;
956     if FCTEs <> nil then
957     begin
958     CTEClear;
959     FCTEs.Free;
960     end;
961     inherited;
962     end;
963    
964     end.
965    
966