浏览代码

* fcl-db: sql parser:
- fix parsing scripts containing SET AUTODDL and SET TERM statements
- SET TERM/SET AUTODDL will be output as SQL comments when regenerating SQL so thethe commands work with e.g. sqldb

git-svn-id: trunk@27923 -

reiniero 11 年之前
父节点
当前提交
81548f0e9c
共有 3 个文件被更改,包括 110 次插入29 次删除
  1. 49 22
      packages/fcl-db/src/sql/fpsqlparser.pas
  2. 38 2
      packages/fcl-db/src/sql/fpsqltree.pp
  3. 23 5
      packages/fcl-db/tests/tcparser.pas

+ 49 - 22
packages/fcl-db/src/sql/fpsqlparser.pas

@@ -101,6 +101,7 @@ Type
     function ParseCreateViewStatement(AParent: TSQLElement; IsAlter: Boolean): TSQLCreateOrAlterStatement;
     function ParseCreateTriggerStatement(AParent: TSQLElement; IsAlter: Boolean): TSQLCreateOrAlterStatement;
     function ParseSetGeneratorStatement(AParent: TSQLElement) : TSQLSetGeneratorStatement;
+    function ParseSetISQLStatement(AParent: TSQLElement) : TSQLSetISQLStatement;
     function ParseSetTermStatement(AParent: TSQLElement) : TSQLSetTermStatement;
     function ParseCreateDatabaseStatement(AParent: TSQLElement; IsAlter: Boolean ): TSQLCreateDatabaseStatement;
     function ParseCreateShadowStatement(AParent: TSQLElement; IsAlter: Boolean ): TSQLCreateShadowStatement;
@@ -158,7 +159,9 @@ Type
     Function ParseConnectStatement(AParent : TSQLElement) : TSQLConnectStatement;
     Function ParseGrantStatement(AParent: TSQLElement): TSQLGrantStatement;
     Function ParseRevokeStatement(AParent: TSQLElement): TSQLGrantStatement;
+    // Parse single element
     Function Parse : TSQLElement;
+    // Parse script containing 1 or more elements
     Function ParseScript(AllowPartial : Boolean = False) : TSQLElementList;
     // Gets statement terminator (as e.g. used in SET TERM) so statements like
     // EXECUTE BLOCK or CREATE PROCEDURE that contain semicolons can be parsed
@@ -2965,11 +2968,11 @@ begin
   Consume(tsqlGenerator) ;
   try
     Result:=TSQLSetGeneratorStatement(CreateElement(TSQLSetGeneratorStatement,AParent));
-    expect(tsqlidentifier);
+    Expect(tsqlidentifier);
     Result.ObjectName:=CreateIdentifier(Result,CurrentTokenString);
     GetNextToken;
-    consume(tsqlto);
-    expect(tsqlIntegerNumber);
+    Consume(tsqlto);
+    Expect(tsqlIntegerNumber);
     Result.NewValue:=StrToInt(CurrentTokenString);
     GetNextToken;
   except
@@ -2978,6 +2981,37 @@ begin
   end;
 end;
 
+function TSQLParser.ParseSetISQLStatement(AParent: TSQLElement
+  ): TSQLSetISQLStatement;
+begin
+  // On entry, we're on the first argument e.g. AUTODDL in SET AUTODDL
+  // for now, only support AutoDDL
+  //SET AUTODDL: ignore these isql commands for now
+  case CurrentToken of
+    tsqlAutoDDL:
+      begin
+      Result:=TSQLSetISQLStatement(CreateElement(TSQLSetISQLStatement,AParent));
+      // SET AUTODDL ON, SET AUTODDL OFF; optional arguments
+      if (PeekNextToken in [tsqlOn, tsqlOff]) then
+        begin
+        GetNextToken;
+        if CurrentToken=tsqlOFF then
+          Result.Arguments:='AUTODDL OFF'
+        else
+          Result.Arguments:='AUTODDL ON';
+        Consume([tsqlOn,tsqlOff]);
+        end
+      else
+        begin
+        Result.Arguments:='AUTODDL ON';
+        Consume(tsqlAutoDDL);
+        end;
+      end
+    else
+      UnexpectedToken;
+  end;
+end;
+
 function TSQLParser.ParseSetTermStatement(AParent: TSQLElement
   ): TSQLSetTermStatement;
 var
@@ -2987,23 +3021,29 @@ begin
   Consume(tsqlTerm) ;
   try
     Result:=TSQLSetTermStatement(CreateElement(TSQLSetTermStatement,AParent));
-    expect([tsqlSemiColon,tsqlStatementTerminator,tsqlSymbolString,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];
-      tsqlSymbolString, tsqlString: Result.NewValue:=CurrentTokenString;
+      tsqlSemiColon, tsqlStatementTerminator: Result.NewTerminator:=TokenInfos[CurrentToken];
+      tsqlSymbolString, tsqlString: Result.NewTerminator:=CurrentTokenString;
     end;
     // Expect the old terminator...
     GetNextToken;
     // Parser will give tsqlSemicolon rather than tsqlStatementTerminator:
     if TokenInfos[tsqlStatementTerminator]=TokenInfos[tsqlSEMICOLON] then
-      Expect(tsqlSEMICOLON)
+    begin
+      Expect(tsqlSEMICOLON);
+      Result.OldTerminator:=TokenInfos[tsqlSEMICOLON];
+    end
     else
+    begin
       Expect(tsqlStatementTerminator);
+      Result.OldTerminator:=TokenInfos[tsqlStatementTerminator];
+    end;
     //... and now set the new terminator:
-    TokenInfos[tsqlStatementTerminator]:=Result.NewValue; //process new terminator value
+    TokenInfos[tsqlStatementTerminator]:=Result.NewTerminator; //process new terminator value
   except
     FreeAndNil(Result);
     Raise;
@@ -3414,20 +3454,7 @@ begin
   Case CurrentToken of
     tsqlGenerator : Result:=ParseSetGeneratorStatement(AParent); //SET GENERATOR
     tsqlTerm : Result:=ParseSetTermStatement(AParent); //SET TERM
-    tsqlAutoDDL : //SET AUTODDL: ignore these isql commands for now
-      begin
-      // SET AUTODDL ON, SET AUTODDL OFF; optional arguments
-      if (PeekNextToken in [tsqlOn, tsqlOff]) then
-        begin
-        GetNextToken;
-        Consume([tsqlOn,tsqlOff]);
-        end
-      else
-        begin
-        Consume(tsqlAutoDDL);
-        end;
-        Result:=nil; //ignore
-      end;
+    tsqlAutoDDL : Result:=ParseSetISQLStatement(AParent); //SET AUTODDL
   else
     // For the time being
     UnexpectedToken;

+ 38 - 2
packages/fcl-db/src/sql/fpsqltree.pp

@@ -26,7 +26,7 @@ Type
   TSQLFormatOption = (sfoDoubleQuotes,           // Use double quote character for string literals
                       sfoBackslashEscape,        // Backslash escapes in string literals
                       sfoSingleQuoteIdentifier,  // quote Identifiers using '
-                      sfoDoubleQuoteIdentifier,  // quote Identifiers using "
+                      sfoDoubleQuoteIdentifier,  // quote Identifiers using " (e.g. as in Firebird)
                       sfoBackQuoteIdentifier,    // quote Identifiers using `
                       sfoLowercaseKeyword,       // Lowercase SQL keywords
                       sfoOneFieldPerLine,        // One field per line in SELECT, Update, Insert
@@ -901,13 +901,29 @@ Type
     Property NewValue : Integer Read FNewValue Write FNewValue;
   end;
 
+  { TSQLSetISQLStatement }
+  // SET statements as used by the isql Firebird command line utility
+  TSQLSetISQLStatement = Class(TSQLStatement)
+  private
+    FArgument: string;
+  Public
+    Function GetAsSQL(Options : TSQLFormatOptions; AIndent : Integer = 0): TSQLStringType; override;
+    // The test of the SET statement excluding the SET command
+    Property Arguments : string Read FArgument Write FArgument;
+  end;
+
   { TSQLSetTermStatement }
 
   TSQLSetTermStatement = Class(TSQLStatement)
   private
     FNewValue: string;
+    FOldValue: string;
   Public
-    Property NewValue : string Read FNewValue Write FNewValue;
+    Function GetAsSQL(Options : TSQLFormatOptions; AIndent : Integer = 0): TSQLStringType; override;
+    // The first, new terminator in the SET TERM statement
+    Property NewTerminator : string Read FNewValue Write FNewValue;
+    // The second, old terminator in the SET TERM statement
+    Property OldTerminator : string Read FOldValue Write FOldValue;
   end;
 
   { TSQLCreateRoleStatement }
@@ -1864,6 +1880,26 @@ begin
     Sep:=', ';
 end;
 
+{ TSQLSetISQLStatement }
+
+function TSQLSetISQLStatement.GetAsSQL(Options: TSQLFormatOptions;
+  AIndent: Integer): TSQLStringType;
+begin
+  // Note: we generate this as a comment as this is ISQL-specific and will generate
+  // errors when passed as SQL to servers
+  Result:='-- SET '+Arguments;
+end;
+
+{ TSQLSetTermStatement }
+
+function TSQLSetTermStatement.GetAsSQL(Options: TSQLFormatOptions;
+  AIndent: Integer): TSQLStringType;
+begin
+  // Note: we generate this as a comment as this is ISQL-specific and will generate
+  // errors when passed as SQL to servers
+  Result:='-- SET TERM '+NewTerminator+' '+OldTerminator;
+end;
+
 function TSQLElementList.GetE(AIndex : Integer): TSQLElement;
 begin
   Result:=TSQLElement(Items[AIndex]);

+ 23 - 5
packages/fcl-db/tests/tcparser.pas

@@ -872,21 +872,39 @@ uses typinfo;
 { TTestSetParser }
 
 procedure TTestSetParser.TestSetAutoDDL;
+Const
+  Desired='-- SET AUTODDL ON';
+Var
+  I: TSQLSetISQLStatement;
 begin
   CreateParser('SET AUTODDL;');
-  AssertNull('SET AUTODDL should be ignored and give nil result',Parser.Parse);
+  FToFree:=Parser.Parse;
+  I:=TSQLSetISQLStatement(CheckClass(FToFree,TSQLSetISQLStatement));
+  AssertEquals('GetAsSQL',I.GetAsSQL([]),Desired);
 end;
 
 procedure TTestSetParser.TestSetAutoDDLOn;
+Const
+  Desired='-- SET AUTODDL ON';
+Var
+  I: TSQLSetISQLStatement;
 begin
   CreateParser('SET AUTODDL ON;');
-  AssertNull('SET AUTODDL should be ignored and give nil result',Parser.Parse);
+  FToFree:=Parser.Parse;
+  I:=TSQLSetISQLStatement(CheckClass(FToFree,TSQLSetISQLStatement));
+  AssertEquals('GetAsSQL',I.GetAsSQL([]),Desired);
 end;
 
 procedure TTestSetParser.TestSetAutoDDLOff;
+Const
+  Desired='-- SET AUTODDL OFF';
+Var
+  I: TSQLSetISQLStatement;
 begin
   CreateParser('SET AUTODDL OFF;');
-  AssertNull('SET AUTODDL should be ignored and give nil result',Parser.Parse);
+  FToFree:=Parser.Parse;
+  I:=TSQLSetISQLStatement(CheckClass(FToFree,TSQLSetISQLStatement));
+  AssertEquals('GetAsSQL',I.GetAsSQL([]),Desired);
 end;
 
 procedure TTestSetParser.TestSetAutoDDLCreateProcedure;
@@ -913,7 +931,7 @@ begin
   CreateParser('SET TERM ^ ;');
   FToFree:=Parser.Parse;
   S:=TSQLSetTermStatement(CheckClass(FToFree,TSQLSetTermStatement));
-  AssertEquals('New value','^',S.NewValue);
+  AssertEquals('New terminator','^',S.NewTerminator);
   AssertEquals('Closing semicolon',tsqlSEMICOLON,Parser.CurrentToken);
   Parser.GetNextToken;
   AssertEquals('End of stream reached',tsqlEOF,Parser.CurrentToken);
@@ -929,7 +947,7 @@ begin
   AssertEquals('Closing statement terminator should match ^','^',Parser.GetStatementTerminator);
   FToFree:=Parser.Parse;
   S:=TSQLSetTermStatement(CheckClass(FToFree,TSQLSetTermStatement));
-  AssertEquals('New value',';',S.NewValue);
+  AssertEquals('New terminator',';',S.NewTerminator);
   AssertEquals('Closing terminator',tsqlStatementTerminator,Parser.CurrentToken);
   AssertEquals('Closing ^','^',Parser.CurrentTokenString);
   Parser.GetNextToken;