ViewVC Help
View File | Revision Log | Show Annotations | Download File | View Changeset | Root Listing
root/public/ibx/trunk/runtime/IBSQLParser.pas
Revision: 39
Committed: Tue May 17 08:14:52 2016 UTC (8 years, 6 months ago) by tony
Content type: text/x-pascal
File size: 25688 byte(s)
Log Message:
Committing updates for Release R1-4-1

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