ViewVC Help
View File | Revision Log | Show Annotations | Download File | View Changeset | Root Listing
root/public/ibx/trunk/runtime/IBSQLParser.pas
Revision: 45
Committed: Tue Dec 6 10:33:46 2016 UTC (7 years, 4 months ago) by tony
Content type: text/x-pascal
File size: 25647 byte(s)
Log Message:
Committing updates for Release R2-0-0

File Contents

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