ViewVC Help
View File | Revision Log | Show Annotations | Download File | View Changeset | Root Listing
root/public/ibx/trunk/runtime/IBSQLParser.pas
Revision: 49
Committed: Thu Feb 2 16:20:12 2017 UTC (7 years, 2 months ago) by tony
Content type: text/x-pascal
File size: 25844 byte(s)
Log Message:
Committing updates for Trunk

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