Ver código fonte

sql parser: support functions with identifier path (MSSQL)

git-svn-id: trunk@46467 -
ondrej 5 anos atrás
pai
commit
b814c41ea9

+ 45 - 45
packages/fcl-db/src/sql/fpsqlparser.pas

@@ -2885,65 +2885,65 @@ begin
       //   To-Do: remove some of them if necessary
       if CurrentToken in [tsqlIdentifier, FirstKeyword..LastKeyWord] then
         begin
+        C:=TSQLIdentifierExpression;
         N:=CurrentTokenString;
-        If (GetNextToken<>tsqlBraceOpen) then
+        If (eoCheckConstraint in EO) and not (eoTableConstraint in EO) then
+          Error(SErrUnexpectedToken,[CurrentTokenString]);
+        // Plain identifier
+        IdentifierPath:=TSQLIdentifierPath.Create;
+        IdentifierPath.Add(CreateIdentifier(nil,N));
+        GetNextToken;
+        while (CurrentToken=tsqlDot) do
           begin
-          If (eoCheckConstraint in EO) and not (eoTableConstraint in EO) then
-            Error(SErrUnexpectedToken,[CurrentTokenString]);
-          // Plain identifier
-          IdentifierPath:=TSQLIdentifierPath.Create;
-          IdentifierPath.Add(CreateIdentifier(Result,N));
-          while (CurrentToken=tsqlDot) do
+          GetNextToken;
+          if CurrentToken=tsqlMUL then
             begin
+            C:=TSQLAsteriskExpression;
             GetNextToken;
-            if CurrentToken=tsqlMUL then
-              begin
-              Result:=TSQLAsteriskExpression(CreateElement(TSQLAsteriskExpression,APArent));
-              GetNextToken;
-              break;
-              end
-            else
-              begin
-              Expect(tsqlIdentifier);
-              N:=CurrentTokenString;
-              IdentifierPath.Add(CreateIdentifier(Result,N));
-              GetNextToken;
-              end;
-            end;
-          if not Assigned(Result) then
-            Result:=TSQLIdentifierExpression(CreateElement(TSQLIdentifierExpression,APArent));
-          TSQLIdentifierPathExpression(Result).IdentifierPath:=IdentifierPath;
-          // Array access ?
-          If (CurrentToken=tsqlSquareBraceOpen) and (Result is TSQLIdentifierExpression) then
-            // Either something like array[5] or,
-            // in procedures etc array[i:] where i is a variable
+            break;
+            end
+          else
             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;
+            Expect(tsqlIdentifier);
+            N:=CurrentTokenString;
+            IdentifierPath.Add(CreateIdentifier(nil,N));
             GetNextToken;
-            Consume(tsqlSquareBraceClose);
             end;
-          end
-        else
+          end;
+        If (CurrentToken=tsqlBraceOpen) and (C=TSQLIdentifierExpression) then
           begin
           L:=ParseValueList(AParent,EO);
           GetNextToken; // Consume );
           // Function call
           Result:=TSQLFunctionCallExpression(CreateElement(TSQLFunctionCallExpression,AParent));
-          TSQLFunctionCallExpression(Result).IDentifier:=N;
           TSQLFunctionCallExpression(Result).Arguments:=L;
+          end
+        Else
+        // Array access ?
+        If (CurrentToken=tsqlSquareBraceOpen) and (C=TSQLIdentifierExpression) then
+          // Either something like array[5] or,
+          // in procedures etc array[i:] where i is a variable
+          begin
+          Result:=TSQLIdentifierExpression(CreateElement(TSQLIdentifierExpression,APArent));
+          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;
+        if not Assigned(Result) then
+          Result:=TSQLExpression(CreateElement(C,AParent));
+        TSQLIdentifierPathExpression(Result).IdentifierPath:=IdentifierPath;
         end
       else
         UnexpectedToken;

+ 41 - 5
packages/fcl-db/src/sql/fpsqltree.pp

@@ -382,14 +382,15 @@ Type
 
   { TSQLFunctionCallExpression }
 
-  TSQLFunctionCallExpression = Class(TSQLExpression)
+  TSQLFunctionCallExpression = Class(TSQLIdentifierPathExpression)
   private
     FArguments:TSQLElementList;
-    FIdentifier: TSQLStringType;
+    function GetIdentifier: TSQLStringType;
+    procedure SetIdentifier(const AIdentifier: TSQLStringType);
   Public
     Destructor Destroy; override;
     Function GetAsSQL(Options : TSQLFormatOptions; AIndent : Integer = 0): TSQLStringType; override;
-    Property Identifier : TSQLStringType Read FIdentifier Write FIdentifier;
+    Property Identifier : TSQLStringType Read GetIdentifier Write SetIdentifier;
     Property Arguments : TSQLElementList Read FArguments Write Farguments;
   end;
 
@@ -2685,10 +2686,12 @@ function TSQLFunctionCallExpression.GetAsSQL(Options: TSQLFormatOptions;
 
 Var
   I : Integer;
-  Sep : String;
+  Sep,Name: String;
+  N : TSQLIdentifierName;
 
 begin
   Result:='';
+
   Sep:=SQLListSeparator(Options);
   If Assigned(FArguments) and (FArguments.Count>0) then
     For I:=0 to FArguments.Count-1 do
@@ -2697,7 +2700,40 @@ begin
         Result:=Result+Sep;
       Result:=Result+Farguments[i].GetAsSQL(Options,AIndent);
       end;
-  Result:=SQLKeyWord(Identifier,Options)+'('+Result+')';
+
+  Name:='';
+  for Pointer(N) in FIdentifierPath do
+    begin
+    if Name<>'' then
+      Name:=Name+'.';
+    Name:=Name+SQLKeyWord(N.Name,Options);
+    end;
+
+  Result:=Name+'('+Result+')';
+end;
+
+function TSQLFunctionCallExpression.GetIdentifier: TSQLStringType;
+var
+  Name: TSQLIdentifierName;
+begin
+  Name := TSQLIdentifierName(FIdentifierPath.Last);
+  if Assigned(Name) then
+    Result:=Name.Name
+  else
+    Result:='';
+end;
+
+procedure TSQLFunctionCallExpression.SetIdentifier(const AIdentifier: TSQLStringType);
+var
+  NewName: TSQLIdentifierName;
+begin
+  if Assigned(FIdentifierPath) then
+    FIdentifierPath.Clear
+  else
+    FIdentifierPath:=TSQLIdentifierPath.Create;
+  NewName:=TSQLIdentifierName.Create(Parent);
+  NewName.Name:=AIdentifier;
+  FIdentifierPath.Add(NewName);
 end;
 
 { TSQLTernaryExpression }

+ 33 - 3
packages/fcl-db/tests/tcparser.pas

@@ -388,7 +388,7 @@ type
   TTestSelectParser = Class(TTestSQLParser)
   Private
     FSelect : TSQLSelectStatement;
-    function TestSelect(Const ASource : String) : TSQLSelectStatement;
+    function TestSelect(Const ASource : String; AOptions : TParserOptions = []; AScannerOptions : TSQLScannerOptions = []) : TSQLSelectStatement;
     procedure TestSelectError(Const ASource : String);
     procedure DoExtractSimple(Expected : TSQLExtractElement);
     property Select : TSQLSelectStatement Read FSelect;
@@ -451,6 +451,7 @@ type
     procedure TestUpperConst;
     procedure TestUpperError;
     procedure TestLeft;
+    procedure TestFunctionWithPath;
     procedure TestGenID;
     procedure TestGenIDError1;
     procedure TestGenIDError2;
@@ -3821,9 +3822,11 @@ end;
 
 { TTestSelectParser }
 
-function TTestSelectParser.TestSelect(const ASource : String): TSQLSelectStatement;
+function TTestSelectParser.TestSelect(const ASource: String; AOptions: TParserOptions;
+  AScannerOptions: TSQLScannerOptions): TSQLSelectStatement;
 begin
-  CreateParser(ASource);
+  CreateParser(ASource,AOptions);
+  Parser.Scanner.Options:=AScannerOptions;
   FToFree:=Parser.Parse;
   Result:=TSQLSelectStatement(CheckClass(FToFree,TSQLSelectStatement));
   FSelect:=Result;
@@ -4551,6 +4554,33 @@ begin
     DoExtractSimple(E);
 end;
 
+procedure TTestSelectParser.TestFunctionWithPath;
+
+Var
+  E : TSQLFunctionCallExpression;
+  L : TSQLLiteralExpression;
+  S : TSQLStringLiteral;
+  I : TSQLIntegerLiteral;
+
+begin
+  TestSelect('SELECT [dbo].[myFunc] (''abc'', 1) FROM A', [], [soSquareBracketsIdentifier]);
+  AssertEquals('One field',1,Select.Fields.Count);
+  AssertEquals('One table',1,Select.Tables.Count);
+  AssertTable(Select.Tables[0],'A');
+  CheckClass(Select.Fields[0],TSQLSelectField);
+  E:=TSQLFunctionCallExpression(CheckClass(TSQLSelectField(Select.Fields[0]).Expression,TSQLFunctionCallExpression));
+  AssertEquals('Function two identifiers',2,E.IdentifierPath.Count);
+  AssertEquals('dbo function first identifier','dbo',E.IdentifierPath[0].Name);
+  AssertEquals('myFunc function second identifier','myFunc',E.IdentifierPath[1].Name);
+  AssertEquals('Two function elements',2,E.Arguments.Count);
+  L:=TSQLLiteralExpression(CheckClass(E.Arguments[0],TSQLLiteralExpression));
+  S:=TSQLStringLiteral(CheckClass(L.Literal,TSQLStringLiteral));
+  AssertEquals('Correct string constant','abc',S.Value);
+  L:=TSQLLiteralExpression(CheckClass(E.Arguments[1],TSQLLiteralExpression));
+  I:=TSQLIntegerLiteral(CheckClass(L.Literal,TSQLIntegerLiteral));
+  AssertEquals('Correct integer constant',1,I.Value);
+end;
+
 procedure TTestSelectParser.TestOrderByOneField;
 
 begin