Browse Source

* fcl-db: sql parser:
- support for variables in array access (e.g. myarray[:localvar] as happens in stored procs)
- rename TSQLSymbolLiteral to TSQLSymbolString to avoid confusion between enum and the TSQL*Literal classes
- Noted to do: array access via variables results in ElementIndex not being set. Need to verify what impact (if any) this has

git-svn-id: trunk@27918 -

reiniero 11 years ago
parent
commit
ff28acbaad

+ 1 - 1
packages/fcl-db/src/sql/README.txt

@@ -2,7 +2,7 @@ SQL scanner/parser/Abstract Syntax Tree units
 
 This can parse the complete Firebird dialect 3 SQL syntax (which should come pretty close to SQL-92) and builds a syntax tree from it. The Abstract Syntax Tree can re-create the SQL with limited formatting support.
 
-It comes with extensive test suite (over 750 testcases; see the fcl-db\tests directory). It has been tested on almost 400,000 SQL statements. Nevertheless bugs may remain, so any test results you may produce are welcome. Especially the GRANT/REVOKE statements are tested only theoretically.
+It comes with extensive test suite (over 830 test cases; see the fcl-db\tests directory). It has been tested on almost 400,000 SQL statements. Nevertheless bugs may remain, so any test results you may produce are welcome. Especially the GRANT/REVOKE statements are tested only theoretically.
 
 The scanner/parser have been designed so they should be able to cope with other SQL dialects (using a set of flags) such as MySQL, but this support is currently not implemented.
 

+ 24 - 12
packages/fcl-db/src/sql/fpsqlparser.pas

@@ -222,7 +222,7 @@ Resourcestring
   SErrNoAsteriskInSingleTon = '* not allowed in singleton select';
   SErrUnionFieldCountMatch =  'Field count mismatch in select union : %d <> %d';
   SErrInvalidExtract = 'Invalid element for extract: %s';
-  SErrOuterWithout = 'OUTER without previous LEFT, RIGHT or FULL';
+  SErrOuterWithout = 'OUTER without preceding LEFT, RIGHT or FULL';
 
 Function StringToSQLExtractElement(Const S : TSQLStringType; Out Res : TSQLExtractElement) : Boolean;
 
@@ -2709,18 +2709,30 @@ begin
             N:=N+'.'+CurrentTokenString;
             GetNextToken;
             end;
-          // plain identifier
+          // Plain identifier
           Result:=TSQLIdentifierExpression(CreateElement(TSQLIdentifierExpression,APArent));
-          TSQLIdentifierExpression(Result).IDentifier:=CreateIdentifier(Result,N);
+          TSQLIdentifierExpression(Result).Identifier:=CreateIdentifier(Result,N);
           // Array access ?
           If (CurrentToken=tsqlSquareBraceOpen) then
-             begin
-             If (GetNextToken<>tsqlIntegerNumber) then
-                Error(SErrIntegerExpected);
-             TSQLIdentifierExpression(Result).ElementIndex:=StrToInt(CurrentTokenString);
-             GetNextToken;
-             Consume(tsqlSquareBraceClose);
-             end;
+            // Either something like array[5] or,
+            // in procedures etc array[i:] where i is a variable
+            begin
+            case GetNextToken of
+              tsqlIntegerNumber: TSQLIdentifierExpression(Result).ElementIndex:=StrToInt(CurrentTokenString);
+              tsqlColon:
+                begin
+                GetNextToken;
+                Expect(tsqlIdentifier);
+                // We can't set element index here, but it IS an array...
+                //todo: verify if there are repercussions/what these would be
+                TSQLIdentifierExpression(Result).ElementIndex:=maxint;
+                end;
+            else
+               Error(SErrIntegerExpected);
+            end;
+            GetNextToken;
+            Consume(tsqlSquareBraceClose);
+            end;
           end
         else
           begin
@@ -2975,13 +2987,13 @@ begin
   Consume(tsqlTerm) ;
   try
     Result:=TSQLSetTermStatement(CreateElement(TSQLSetTermStatement,AParent));
-    expect([tsqlSemiColon,tsqlStatementTerminator,tsqlSymbolLiteral,tsqlString]);
+    expect([tsqlSemiColon,tsqlStatementTerminator,tsqlSymbolString,tsqlString]);
     // Already set the expression's new value to the new terminator, but do not
     // change tSQLStatementTerminator as GetNextToken etc need the old one to
     // detect the closing terminator
     case CurrentToken of
       tsqlSemiColon, tsqlStatementTerminator: Result.NewValue:=TokenInfos[CurrentToken];
-      tsqlSymbolLiteral, tsqlString: Result.NewValue:=CurrentTokenString;
+      tsqlSymbolString, tsqlString: Result.NewValue:=CurrentTokenString;
     end;
     // Expect the old terminator...
     GetNextToken;

+ 3 - 3
packages/fcl-db/src/sql/fpsqlscanner.pp

@@ -40,7 +40,7 @@ type
    tsqlEOF,tsqlWhiteSpace,
    tsqlString {string literal},
    tsqlIdentifier {a table etc name},
-   tsqlSymbolLiteral {a literal containing symbols/punctuation marks; only rarely used - e.g. in SET TERM ^ ;},
+   tsqlSymbolString {a string containing symbols/punctuation marks; only rarely used - e.g. in SET TERM ^ ;},
    tsqlIntegerNumber,tsqlFloatNumber,tsqlComment,
    tsqlStatementTerminator {statement separator, usually semicolon but may be changed by code. For now, limited to semicolon and symbol literals not already defined like tsqlCOMMA},
    tsqlBraceOpen,tsqlBraceClose,tsqlSquareBraceOpen,tsqlSquareBraceClose,
@@ -80,7 +80,7 @@ const
   LastKeyWord = tsqlWhen;
   sqlComparisons = [tsqleq,tsqlGE,tsqlLE,tsqlNE,tsqlGT,tsqlLT,tsqlIn,tsqlIS,
                     tsqlbetween,tsqlLike,tsqlContaining,tsqlStarting,tsqlNOT];
-  sqlInvertableComparisons = [tsqlLike,tsqlContaining,tsqlStarting,tsqlin,tsqlIS, tsqlbetween];
+  sqlInvertableComparisons = [tsqlLike,tsqlContaining,tsqlStarting,tsqlIN,tsqlIS, tsqlBetween];
 
   // Strings that represent tokens in TSQLToken
   TokenInfos: array[TSQLToken] of string = ('unknown',
@@ -688,7 +688,7 @@ begin
   Len:=(TokenStr-TokenStart);
   if Len > 0 then
     begin
-    result:=tsqlSymbolLiteral;
+    result:=tsqlSymbolString;
     SetLength(FCurTokenString,Len);
     Move(TokenStart^,FCurTokenString[1],Len);
 

+ 1 - 0
packages/fcl-db/src/sql/fpsqltree.pp

@@ -194,6 +194,7 @@ Type
     Destructor Destroy; override;
     Function GetAsSQL(Options : TSQLFormatOptions; AIndent : Integer = 0): TSQLStringType; override;
     Property Identifier : TSQLIdentifierName Read FIdentifier Write FIdentifier;
+    // For array types: index of element in array
     Property ElementIndex : Integer Read FElementIndex Write FElementIndex;
   end;
 

+ 3 - 3
packages/fcl-db/tests/tcsqlscanner.pas

@@ -648,12 +648,12 @@ end;
 
 procedure TTestSQLScanner.TestSymbolLiteral1;
 begin
-  CheckToken(tsqlSymbolLiteral,'%');
+  CheckToken(tsqlSymbolString,'%');
 end;
 
 procedure TTestSQLScanner.TestSymbolLiteral2;
 begin
-  CheckToken(tsqlSymbolLiteral,'%^');
+  CheckToken(tsqlSymbolString,'%^');
 end;
 
 procedure TTestSQLScanner.TestStarting;
@@ -1405,7 +1405,7 @@ end;
 procedure TTestSQLScanner.TestIdentifier5;
 begin
   // $0 should not be parsed as an identifier but as a symbol literal
-  CheckToken(tsqlSymbolLiteral,'$0');
+  CheckToken(tsqlSymbolString,'$0');
 end;
 
 procedure TTestSQLScanner.TestIdentifierDotIdentifier;