ViewVC Help
View File | Revision Log | Show Annotations | Download File | View Changeset | Root Listing
root/public/ibx/trunk/runtime/IBSQLParser.pas
Revision: 139
Committed: Wed Jan 24 16:16:29 2018 UTC (6 years, 10 months ago) by tony
Content type: text/x-pascal
File size: 25801 byte(s)
Log Message:
Fixes Merged

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 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(self,Lines,I+1,1)
586 else
587 raise Exception.Create(sIncomplete)
588 else
589 FUnion := TSelectSQLParser.Create(self,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(nil,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(aOwner: TSelectSQLParser;
687 SQLText: TStrings; StartLine, StartIndex: integer);
688 begin
689 inherited Create;
690 FOwner := aOwner;
691 FParamList := TStringList.Create;
692 FCTEs := TList.Create;
693 FLastSymbol := sqNone;
694 FState := stInit;
695 FStartLine := StartLine;
696 FIndex := StartIndex;
697 FAllowUnionAll := true;
698 AnalyseSQL(SQLText);
699 end;
700
701 procedure TSelectSQLParser.Changed;
702 begin
703 if FOwner <> nil then
704 FOwner.Changed
705 else
706 if assigned(FOnSQLChanging) and not FDestroying then
707 OnSQLChanging(self)
708 end;
709
710 function TSelectSQLParser.GetNextSymbol(C: char): TSQLSymbol;
711 begin
712 case C of
713 ' ',#9:
714 Result := sqSpace;
715 ';':
716 Result := sqSemiColon;
717 '"':
718 Result := sqDoubleQuotes;
719 '''':
720 Result := sqSingleQuotes;
721 '/':
722 Result := sqForwardSlash;
723 '*':
724 Result := sqAsterisk;
725 '(':
726 Result := sqOpenBracket;
727 ')':
728 Result := sqCloseBracket;
729 ':':
730 Result := sqColon;
731 ',':
732 Result := sqComma;
733 else
734 begin
735 Result := sqString;
736 FLastChar := C
737 end
738 end
739 end;
740
741 function TSelectSQLParser.GetSymbol(const Line: string; var index: integer): TSQLSymbol;
742 begin
743 Result := FLastSymbol;
744 if Result = sqString then
745 FString := FLastChar;
746 FLastSymbol := sqNone;
747
748 while (index <= Length(Line)) and (FLastSymbol = sqNone) do
749 begin
750 FLastSymbol := GetNextSymbol(Line[index]);
751 {combine if possible}
752 case Result of
753 sqNone:
754 begin
755 Result := FLastSymbol;
756 if FLastSymbol = sqString then
757 FString := FLastChar;
758 FLastSymbol := sqNone
759 end;
760
761 sqSpace:
762 if FLastSymbol = sqSpace then
763 FLastSymbol := sqNone;
764
765 sqForwardSlash:
766 if FLastSymbol = sqAsterisk then
767 begin
768 Result := sqCommentStart;
769 FLastSymbol := sqNone
770 end
771 else
772 if FLastSymbol = sqForwardSlash then
773 begin
774 Result := sqCommentLine;
775 FLastSymbol := sqNone
776 end;
777
778 sqAsterisk:
779 if FLastSymbol = sqForwardSlash then
780 begin
781 Result := sqCommentEnd;
782 FLastSymbol := sqNone
783 end;
784
785 sqString:
786 if FLastSymbol = sqString then
787 begin
788 FString := FString + FLastChar;
789 FLastSymbol := sqNone
790 end;
791 end;
792 Inc(index)
793 end;
794
795 if (Result = sqString) and not (FState in [stInComment,stInCommentLine, stInSingleQuotes,stInDoubleQuotes])then
796 Result := Check4ReservedWord(FString);
797
798 if (index > Length(Line)) then
799 begin
800 if (Result = sqNone) then
801 Result := sqEOL
802 else
803 if (FLastSymbol = sqNone) and (Result <> sqEOL) then
804 FLastSymbol := sqEOL;
805 end;
806
807 end;
808
809 function TSelectSQLParser.GetSQlText: string;
810 var SQL: TStringList;
811 I: integer;
812 begin
813 SQL := TStringList.Create;
814 try
815 for I := 0 to CTECount - 1 do
816 begin
817 if I = 0 then
818 begin
819 if CTEs[I]^.Recursive then
820 SQL.Add('WITH RECURSIVE ' + CTEs[I]^.Name + ' AS (' + CTES[I]^.Text + ')')
821 else
822 SQL.Add('WITH ' + CTEs[I]^.Name + ' AS (' + CTES[I]^.Text +')')
823 end
824 else
825 begin
826 SQL.Add(',');
827 SQL.Add(CTEs[I]^.Name + ' AS (' + CTES[I]^.Text +')')
828 end
829 end;
830 if CTECount > 0 then
831 SQL.Add('');
832 SQL.Add('SELECT ' + SelectClause + #13#10' FROM ' + FromClause);
833 if WhereClause <> '' then
834 SQL.Add('Where ' + WhereClause);
835 if GroupClause <> '' then
836 SQL.Add('GROUP BY ' + GroupClause);
837 if HavingClause <> '' then
838 SQL.Add('HAVING ' + HavingClause);
839 if PlanClause <> '' then
840 SQL.Add('PLAN ' + PlanClause);
841 if OrderByClause <> '' then
842 SQL.Add('ORDER BY ' + OrderByClause);
843 if Union <> nil then
844 begin
845 if Union.UnionAll then
846 SQL.Add('UNION ALL')
847 else
848 SQL.Add('UNION');
849 SQL.Add(Union.SQLText)
850 end;
851 Result := SQL.Text
852 finally
853 SQL.Free
854 end
855 end;
856
857 function TSelectSQLParser.PopState: TSQLStates;
858 begin
859 if FStackIndex = 0 then
860 raise Exception.Create(sStackUnderFlow);
861 Dec(FStackIndex);
862 Result := FStack[FStackIndex]
863 end;
864
865 procedure TSelectSQLParser.SetState(AState: TSQLStates);
866 begin
867 if FStackIndex > 16 then
868 raise Exception.Create(sStackOverFlow);
869 FStack[FStackIndex] := FState;
870 Inc(FStackIndex);
871 FState := AState
872 end;
873
874 procedure TSelectSQLParser.SetSelectClause(const Value: string);
875 begin
876 if FSelectClause <> Value then
877 begin
878 FSelectClause := Value;
879 Changed
880 end;
881 end;
882
883 procedure TSelectSQLParser.SetFromClause(const Value: string);
884 begin
885 if FFromClause <> Value then
886 begin
887 FFromClause := Value;
888 Changed
889 end;
890 end;
891
892 procedure TSelectSQLParser.SetGroupClause(const Value: string);
893 begin
894 if FGroupClause <> Value then
895 begin
896 FGroupClause := Value;
897 Changed
898 end;
899 end;
900
901 procedure TSelectSQLParser.SetOrderByClause(const Value: string);
902 begin
903 if Union <> nil then
904 Union.OrderByClause := Value
905 else
906 if FOrderByClause <> Value then
907 begin
908 FOrderByClause := Value;
909 Changed
910 end;
911 end;
912
913 procedure TSelectSQLParser.DropUnion;
914 begin
915 if FUnion <> nil then
916 begin
917 FUnion.Free;
918 FUnion := nil;
919 Changed
920 end
921 end;
922
923 function TSelectSQLParser.GetFieldPosition(AliasName: string): integer;
924 begin
925 if assigned(FDataSet) and (FDataSet is TIBCustomDataset) then
926 Result := TIBCustomDataset(FDataSet).GetFieldPosition(AliasName)
927 else
928 Result := 0;
929 end;
930
931 procedure TSelectSQLParser.ResetWhereClause;
932 begin
933 FWhereClause := FOriginalWhereClause;
934 if Union <> nil then
935 Union.ResetWhereClause;
936 Changed
937 end;
938
939 procedure TSelectSQLParser.ResetHavingClause;
940 begin
941 FHavingClause := FOriginalHavingClause;
942 if Union <> nil then
943 Union.ResetHavingClause;
944 Changed
945 end;
946
947 procedure TSelectSQLParser.ResetOrderByClause;
948 begin
949 FOrderbyClause := FOriginalOrderByClause;
950 if Union <> nil then
951 Union.ResetOrderByClause;
952 Changed
953 end;
954
955 procedure TSelectSQLParser.Reset;
956 begin
957 ResetWhereClause;
958 ResetHavingClause;
959 ResetOrderByClause
960 end;
961
962 destructor TSelectSQLParser.Destroy;
963 begin
964 FDestroying := true;
965 DropUnion;
966 if FParamList <> nil then FParamList.Free;
967 if FCTEs <> nil then
968 begin
969 CTEClear;
970 FCTEs.Free;
971 end;
972 inherited;
973 end;
974
975 end.
976
977