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

# Content
1 (*
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 {$codepage UTF8}
31
32 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 FOwner: TSelectSQLParser;
121 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 constructor Create(aOwner: TSelectSQLParser; SQLText: TStrings; StartLine, StartIndex: integer); overload;
141 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 if not (FState in [stInSingleQuotes,stInDoubleQuotes]) then
295 SetState(stInParam);
296 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 Begin
455 FLiteral := FLiteral + #$0A;
456 Exit;
457 End;
458 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 FUnion := TSelectSQLParser.Create(self,Lines,I+1,1)
587 else
588 raise Exception.Create(sIncomplete)
589 else
590 FUnion := TSelectSQLParser.Create(self,Lines,I,FIndex);
591 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 Create(nil,SQLText,0,1)
673 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 constructor TSelectSQLParser.Create(aOwner: TSelectSQLParser;
688 SQLText: TStrings; StartLine, StartIndex: integer);
689 begin
690 inherited Create;
691 FOwner := aOwner;
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 FOwner <> nil then
705 FOwner.Changed
706 else
707 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 if (Result = sqString) and not (FState in [stInComment,stInCommentLine, stInSingleQuotes,stInDoubleQuotes])then
797 Result := Check4ReservedWord(FString);
798
799 if (index > Length(Line)) then
800 begin
801 if (Result = sqNone) then
802 Result := sqEOL
803 else
804 if (FLastSymbol = sqNone) and (Result <> sqEOL) then
805 FLastSymbol := sqEOL;
806 end;
807
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 if FSelectClause <> Value then
878 begin
879 FSelectClause := Value;
880 Changed
881 end;
882 end;
883
884 procedure TSelectSQLParser.SetFromClause(const Value: string);
885 begin
886 if FFromClause <> Value then
887 begin
888 FFromClause := Value;
889 Changed
890 end;
891 end;
892
893 procedure TSelectSQLParser.SetGroupClause(const Value: string);
894 begin
895 if FGroupClause <> Value then
896 begin
897 FGroupClause := Value;
898 Changed
899 end;
900 end;
901
902 procedure TSelectSQLParser.SetOrderByClause(const Value: string);
903 begin
904 if Union <> nil then
905 Union.OrderByClause := Value
906 else
907 if FOrderByClause <> Value then
908 begin
909 FOrderByClause := Value;
910 Changed
911 end;
912 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