ViewVC Help
View File | Revision Log | Show Annotations | Download File | View Changeset | Root Listing
root/public/ibx/trunk/runtime/IBSQLParser.pas
Revision: 21
Committed: Thu Feb 26 10:33:34 2015 UTC (9 years, 9 months ago) by tony
Content type: text/x-pascal
File size: 25464 byte(s)
Log Message:
Committing updates for Release R1-2-0

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 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 SetState(stInParam);
292 end;
293
294 sqSemiColon:
295 if not (FState in [stInComment,stInCommentLine]) then
296 case FState of
297 stInWhere,stInGroupBy,
298 stInHaving,stInPlan,stInFrom:
299 begin
300 FState := stDone;
301 Exit
302 end;
303
304 stInSingleQuotes, stInDoubleQuotes:
305 AddToSQL(';');
306
307 else
308 raise Exception.Create('Unexpected ";"')
309 end;
310
311 sqAsterisk:
312 if not (FState in [stInComment,stInCommentLine]) then
313 AddToSQL('*');
314
315 sqForwardSlash:
316 if not (FState in [stInComment,stInCommentLine]) then
317 AddToSQL('/');
318
319 sqOpenBracket:
320 if not (FState in [stInComment,stInCommentLine]) then
321 begin
322 if FNested = 0 then
323 case FState of
324 stInSelect,
325 stNestedSelect:
326 SetState(stNestedSelect);
327
328 stInFrom,
329 stNestedFrom:
330 SetState(stNestedFrom);
331
332 stInWhere,
333 stNestedWhere:
334 SetState(stNestedWhere);
335
336 stInGroupBy,
337 stNestedGroupBy:
338 SetState(stNestedGroupBy);
339
340 stCTE3:
341 begin
342 FState := stCTEClosed;
343 SetState(stInCTE);
344 end;
345 end;
346 if (FNested > 0 ) or (FState <> stInCTE) then
347 AddToSQL('(');
348 Inc(FNested);
349 end;
350
351 sqCloseBracket:
352 if not (FState in [stInComment,stInCommentLine]) then
353 begin
354 Dec(FNested);
355 if (FNested > 0) or (FState <> stInCTE) then
356 AddToSQL(')');
357 if FNested = 0 then
358 begin
359 if FState = stInCTE then
360 FState := PopState
361 else
362 if FState in [stNestedSelect,stNestedFrom,stNestedWhere,stNestedGroupBy] then
363 FState := PopState;
364 end;
365 if FState = stCTEClosed then
366 AddCTE;
367 end;
368
369 sqComma:
370 if FState = stCTEClosed then
371 FState := stCTE
372 else
373 AddToSQL(',');
374
375 sqCommentStart:
376 if not (FState in [stInComment,stInCommentLine]) then
377 SetState(stInComment);
378
379 sqCommentEnd:
380 if FState = stInComment then
381 FState := PopState
382 else
383 FState := stError;
384
385 sqCommentLine:
386 if not (FState in [stInComment,stInCommentLine]) then
387 SetState(stInCommentLine);
388
389 sqSingleQuotes:
390 if not (FState in [stInComment,stInCommentLine]) then
391 begin
392 case FState of
393 stInSingleQuotes:
394 begin
395 FState := PopState;
396 AddToSQL(FLiteral)
397 end;
398 stInDoubleQuotes:
399 {Ignore};
400 else
401 begin
402 FLiteral := '';
403 SetState(stInSingleQuotes)
404 end
405 end;
406 AddToSQL('''')
407 end;
408
409 sqDoubleQuotes:
410 if not (FState in [stInComment,stInCommentLine]) then
411 begin
412 case FState of
413 stInSingleQuotes:
414 {Ignore};
415 stInDoubleQuotes:
416 begin
417 FState := PopState;
418 AddToSQL(FLiteral)
419 end;
420 else
421 begin
422 FLiteral := '';
423 SetState(stInDoubleQuotes)
424 end
425 end;
426 AddToSQL('"')
427 end;
428
429 sqString:
430 if not (FState in [stInComment,stInCommentLine]) then
431 begin
432 if FState = stInParam then
433 begin
434 FState := PopState;
435 ParamList.Add(FString)
436 end
437 else
438 if FState in [stCTE, stCTE1] then
439 FState := stCTE2;
440 AddToSQL(FString)
441 end;
442
443 sqEOL:
444 begin
445 case FState of
446 stInCommentLine:
447 FState := PopState;
448 stInDoubleQuotes,
449 stInSingleQuotes:
450 raise Exception.Create(sNoEndToThis);
451 end;
452 AddToSQL(' ');
453 Exit;
454 end;
455
456 sqSelect:
457 if FState in [stInSingleQuotes,stInDoubleQuotes,stNestedFrom,stNestedWhere,stNestedSelect,stInCTE] then
458 AddToSql(FString)
459 else
460 FState := stInSelect;
461
462 sqFrom:
463 if FState = stInSelect then
464 FState := stInFrom
465 else
466 AddToSql(FString);
467 { if FState in [stInSingleQuotes,stInDoubleQuotes,stNestedFrom,stNestedWhere,
468 stNestedGroupBy,stNestedSelect] then
469 AddToSql(FString)
470 else
471 FState := stInFrom;}
472
473 sqGroup:
474 if FState in [stInSingleQuotes,stInDoubleQuotes,stNestedFrom,stNestedWhere,stInCTE] then
475 AddToSql(FString)
476 else
477 FState := stInGroup;
478
479 sqWhere:
480 if FState in [stInSingleQuotes,stInDoubleQuotes,stNestedFrom,stNestedWhere,stNestedSelect,stInCTE] then
481 AddToSql(FString)
482 else
483 FState := stInWhere;
484
485 sqHaving:
486 if FState in [stInSingleQuotes,stInDoubleQuotes,stNestedFrom,stNestedWhere,stInCTE] then
487 AddToSql(FString)
488 else
489 FState := stInHaving;
490
491 sqPlan:
492 if FState in [stInSingleQuotes,stInDoubleQuotes,stNestedFrom,stNestedWhere,stInCTE] then
493 AddToSql(FString)
494 else
495 FState := stInPlan;
496
497 sqOrder:
498 if FState in [stInSingleQuotes,stInDoubleQuotes,stNestedFrom,stNestedWhere] then
499 AddToSql(FString)
500 else
501 FState := stInOrder;
502
503 sqUnion:
504 if FState in [stInSingleQuotes,stInDoubleQuotes,stNestedFrom,stNestedWhere,stInCTE] then
505 AddToSql(FString)
506 else
507 begin
508 FState := stUnion;
509 Exit
510 end;
511
512 sqAll:
513 if FState in [stInSingleQuotes,stInDoubleQuotes,stNestedFrom,stNestedWhere,stInCTE] then
514 AddToSql(FString)
515 else
516 if (FState = stInit) and FAllowUnionAll and not FUnionAll then
517 FUnionAll := true
518 else
519 raise Exception.Create('Unexpected symbol "all"');
520
521 sqBy:
522 case FState of
523 stInGroup:
524 FState := stInGroupBy;
525 stInOrder:
526 FState := stInOrderBy;
527 stNestedFrom,stNestedWhere,stInCTE,
528 stInSingleQuotes,
529 stInDoubleQuotes:
530 AddToSql(FString);
531 else
532 raise Exception.CreateFmt(sBadBy,[Line])
533 end;
534
535 sqWith:
536 if FState = stInit then
537 begin
538 FState := stCTE;
539 InitCTE;
540 end
541 else
542 raise Exception.Create('Unexpected symbol "with"');
543
544 sqRecursive:
545 if FState = stCTE then
546 begin
547 FCTE.Recursive := true;
548 FState := stCTE1
549 end
550 else
551 raise Exception.Create('Unexpected symbol "recursive"');
552
553 sqAs:
554 if FState = stCTE2 then
555 FState := stCTE3
556 else
557 AddToSQL('as');
558
559 else
560 raise Exception.Create(sBadSymbol);
561 end
562 end
563 end;
564
565 procedure TSelectSQLParser.AnalyseSQL(Lines: TStrings);
566 var I: integer;
567 begin
568 try
569 for I := FStartLine to Lines.Count - 1 do
570 try
571 AnalyseLine(Lines[I]);
572 case FState of
573 stDone:
574 break;
575 stUnion:
576 begin
577 if FIndex > length(Lines[I]) then
578 if I+1 < Lines.Count then
579 FUnion := TSelectSQLParser.Create(Lines,I+1,1)
580 else
581 raise Exception.Create(sIncomplete)
582 else
583 FUnion := TSelectSQLParser.Create(Lines,I,FIndex);
584 Exit
585 end;
586 end;
587 FIndex := 1;
588 except on E: Exception do
589 raise Exception.CreateFmt(sBadSQL,[Lines[I],E.Message])
590 end;
591 finally
592 FOriginalWhereClause := WhereClause;
593 FOriginalHavingClause := HavingClause;
594 FOriginalOrderByClause := OrderByClause
595 end;
596 end;
597
598 procedure TSelectSQLParser.InitCTE;
599 begin
600 with FCTE do
601 begin
602 Recursive := false;
603 Name := '';
604 Text := '';
605 end;
606 end;
607
608 procedure TSelectSQLParser.AddCTE;
609 var cte: PCTEDef;
610 begin
611 new(cte);
612 cte^.Name := FCTE.Name;
613 cte^.Recursive := FCTE.Recursive;
614 cte^.Text := FCTE.Text;
615 FCTEs.add(cte);
616 InitCTE;
617 end;
618
619 function TSelectSQLParser.Check4ReservedWord(const Text: string): TSQLSymbol;
620 begin
621 Result := sqString;
622 if CompareText(Text,'select') = 0 then
623 Result := sqSelect
624 else
625 if CompareText(Text,'from') = 0 then
626 Result := sqFrom
627 else
628 if CompareText(Text,'where') = 0 then
629 Result := sqWhere
630 else
631 if CompareText(Text,'group') = 0 then
632 Result := sqGroup
633 else
634 if CompareText(Text,'by') = 0 then
635 Result := sqBy
636 else
637 if CompareText(Text,'having') = 0 then
638 Result := sqHaving
639 else
640 if CompareText(Text,'plan') = 0 then
641 Result := sqPlan
642 else
643 if CompareText(Text,'union') = 0 then
644 Result := sqUnion
645 else
646 if CompareText(Text,'all') = 0 then
647 Result := sqAll
648 else
649 if CompareText(Text,'order') = 0 then
650 Result := sqOrder
651 else
652 if CompareText(Text,'with') = 0 then
653 Result := sqWith
654 else
655 if CompareText(Text,'recursive') = 0 then
656 Result := sqRecursive
657 else
658 if CompareText(Text,'as') = 0 then
659 Result := sqAs
660 end;
661
662 constructor TSelectSQLParser.Create(aDataSet: TDataSet; SQLText: TStrings);
663 begin
664 FDataSet := aDataSet;
665 Create(SQLText,0,1)
666 end;
667
668 constructor TSelectSQLParser.Create(aDataSet: TDataSet; const SQLText: string);
669 var Lines: TStringList;
670 begin
671 Lines := TStringList.Create;
672 try
673 Lines.Text := SQLText;
674 Create(aDataSet,Lines)
675 finally
676 Lines.Free
677 end
678 end;
679
680 constructor TSelectSQLParser.Create(SQLText: TStrings; StartLine,
681 StartIndex: integer);
682 begin
683 inherited Create;
684 FParamList := TStringList.Create;
685 FCTEs := TList.Create;
686 FLastSymbol := sqNone;
687 FState := stInit;
688 FStartLine := StartLine;
689 FIndex := StartIndex;
690 FAllowUnionAll := true;
691 AnalyseSQL(SQLText);
692 end;
693
694 procedure TSelectSQLParser.Changed;
695 begin
696 if assigned(FOnSQLChanging) and not FDestroying then
697 OnSQLChanging(self)
698 end;
699
700 function TSelectSQLParser.GetNextSymbol(C: char): TSQLSymbol;
701 begin
702 case C of
703 ' ',#9:
704 Result := sqSpace;
705 ';':
706 Result := sqSemiColon;
707 '"':
708 Result := sqDoubleQuotes;
709 '''':
710 Result := sqSingleQuotes;
711 '/':
712 Result := sqForwardSlash;
713 '*':
714 Result := sqAsterisk;
715 '(':
716 Result := sqOpenBracket;
717 ')':
718 Result := sqCloseBracket;
719 ':':
720 Result := sqColon;
721 ',':
722 Result := sqComma;
723 else
724 begin
725 Result := sqString;
726 FLastChar := C
727 end
728 end
729 end;
730
731 function TSelectSQLParser.GetSymbol(const Line: string; var index: integer): TSQLSymbol;
732 begin
733 Result := FLastSymbol;
734 if Result = sqString then
735 FString := FLastChar;
736 FLastSymbol := sqNone;
737
738 while (index <= Length(Line)) and (FLastSymbol = sqNone) do
739 begin
740 FLastSymbol := GetNextSymbol(Line[index]);
741 {combine if possible}
742 case Result of
743 sqNone:
744 begin
745 Result := FLastSymbol;
746 if FLastSymbol = sqString then
747 FString := FLastChar;
748 FLastSymbol := sqNone
749 end;
750
751 sqSpace:
752 if FLastSymbol = sqSpace then
753 FLastSymbol := sqNone;
754
755 sqForwardSlash:
756 if FLastSymbol = sqAsterisk then
757 begin
758 Result := sqCommentStart;
759 FLastSymbol := sqNone
760 end
761 else
762 if FLastSymbol = sqForwardSlash then
763 begin
764 Result := sqCommentLine;
765 FLastSymbol := sqNone
766 end;
767
768 sqAsterisk:
769 if FLastSymbol = sqForwardSlash then
770 begin
771 Result := sqCommentEnd;
772 FLastSymbol := sqNone
773 end;
774
775 sqString:
776 if FLastSymbol = sqString then
777 begin
778 FString := FString + FLastChar;
779 FLastSymbol := sqNone
780 end;
781 end;
782 Inc(index)
783 end;
784
785 if (Result = sqString) and not (FState in [stInComment,stInCommentLine])then
786 Result := Check4ReservedWord(FString);
787
788 if (index > Length(Line)) then
789 if Result = sqNone then
790 Result := sqEOL
791 else
792 if (FLastSymbol = sqNone) and (Result <> sqEOL) then
793 FLastSymbol := sqEOL;
794
795 end;
796
797 function TSelectSQLParser.GetSQlText: string;
798 var SQL: TStringList;
799 I: integer;
800 begin
801 SQL := TStringList.Create;
802 try
803 for I := 0 to CTECount - 1 do
804 begin
805 if I = 0 then
806 begin
807 if CTEs[I]^.Recursive then
808 SQL.Add('WITH RECURSIVE ' + CTEs[I]^.Name + ' AS (' + CTES[I]^.Text + ')')
809 else
810 SQL.Add('WITH ' + CTEs[I]^.Name + ' AS (' + CTES[I]^.Text +')')
811 end
812 else
813 begin
814 SQL.Add(',');
815 SQL.Add(CTEs[I]^.Name + ' AS (' + CTES[I]^.Text +')')
816 end
817 end;
818 if CTECount > 0 then
819 SQL.Add('');
820 SQL.Add('SELECT ' + SelectClause + #13#10' FROM ' + FromClause);
821 if WhereClause <> '' then
822 SQL.Add('Where ' + WhereClause);
823 if GroupClause <> '' then
824 SQL.Add('GROUP BY ' + GroupClause);
825 if HavingClause <> '' then
826 SQL.Add('HAVING ' + HavingClause);
827 if PlanClause <> '' then
828 SQL.Add('PLAN ' + PlanClause);
829 if OrderByClause <> '' then
830 SQL.Add('ORDER BY ' + OrderByClause);
831 if Union <> nil then
832 begin
833 if Union.UnionAll then
834 SQL.Add('UNION ALL')
835 else
836 SQL.Add('UNION');
837 SQL.Add(Union.SQLText)
838 end;
839 Result := SQL.Text
840 finally
841 SQL.Free
842 end
843 end;
844
845 function TSelectSQLParser.PopState: TSQLStates;
846 begin
847 if FStackIndex = 0 then
848 raise Exception.Create(sStackUnderFlow);
849 Dec(FStackIndex);
850 Result := FStack[FStackIndex]
851 end;
852
853 procedure TSelectSQLParser.SetState(AState: TSQLStates);
854 begin
855 if FStackIndex > 16 then
856 raise Exception.Create(sStackOverFlow);
857 FStack[FStackIndex] := FState;
858 Inc(FStackIndex);
859 FState := AState
860 end;
861
862 procedure TSelectSQLParser.SetSelectClause(const Value: string);
863 begin
864 if Union <> nil then Union.SelectClause := Value;
865 FSelectClause := Value;
866 Changed
867 end;
868
869 procedure TSelectSQLParser.SetFromClause(const Value: string);
870 begin
871 if Union <> nil then
872 Union.FromClause := Value
873 else
874 FFromClause := Value;
875 Changed
876 end;
877
878 procedure TSelectSQLParser.SetGroupClause(const Value: string);
879 begin
880 if Union <> nil then
881 Union.GroupClause := Value
882 else
883 FGroupClause := Value;
884 Changed
885 end;
886
887 procedure TSelectSQLParser.SetOrderByClause(const Value: string);
888 begin
889 if Union <> nil then
890 Union.OrderByClause := Value
891 else
892 FOrderByClause := Value;
893 Changed
894 end;
895
896 procedure TSelectSQLParser.DropUnion;
897 begin
898 if FUnion <> nil then
899 begin
900 FUnion.Free;
901 FUnion := nil;
902 Changed
903 end
904 end;
905
906 function TSelectSQLParser.GetFieldPosition(AliasName: string): integer;
907 begin
908 if assigned(FDataSet) and (FDataSet is TIBCustomDataset) then
909 Result := TIBCustomDataset(FDataSet).GetFieldPosition(AliasName)
910 else
911 Result := 0;
912 end;
913
914 procedure TSelectSQLParser.ResetWhereClause;
915 begin
916 FWhereClause := FOriginalWhereClause;
917 if Union <> nil then
918 Union.ResetWhereClause;
919 Changed
920 end;
921
922 procedure TSelectSQLParser.ResetHavingClause;
923 begin
924 FHavingClause := FOriginalHavingClause;
925 if Union <> nil then
926 Union.ResetHavingClause;
927 Changed
928 end;
929
930 procedure TSelectSQLParser.ResetOrderByClause;
931 begin
932 FOrderbyClause := FOriginalOrderByClause;
933 if Union <> nil then
934 Union.ResetOrderByClause;
935 Changed
936 end;
937
938 procedure TSelectSQLParser.Reset;
939 begin
940 ResetWhereClause;
941 ResetHavingClause;
942 ResetOrderByClause
943 end;
944
945 destructor TSelectSQLParser.Destroy;
946 begin
947 FDestroying := true;
948 DropUnion;
949 if FParamList <> nil then FParamList.Free;
950 if FCTEs <> nil then
951 begin
952 CTEClear;
953 FCTEs.Free;
954 end;
955 inherited;
956 end;
957
958 end.
959
960