Browse Source

sql parser: support field with schema

git-svn-id: trunk@46421 -
(cherry picked from commit 97ced59af04004398a4a4688c082089a7cf05063)
ondrej 5 years ago
parent
commit
24571f17f2

+ 6 - 5
packages/fcl-db/src/sql/fpsqlparser.pas

@@ -2757,16 +2757,17 @@ begin
           begin
           If (eoCheckConstraint in EO) and not (eoTableConstraint in EO) then
             Error(SErrUnexpectedToken,[CurrentTokenString]);
-          If (CurrentToken=tsqlDot) then
+          // Plain identifier
+          Result:=TSQLIdentifierExpression(CreateElement(TSQLIdentifierExpression,APArent));
+          TSQLIdentifierExpression(Result).AddIdentifierToPath(CreateIdentifier(Result,N));
+          while (CurrentToken=tsqlDot) do
             begin
             GetNextToken;
             Expect(tsqlIdentifier);
-            N:=N+'.'+CurrentTokenString;
+            N:=CurrentTokenString;
+            TSQLIdentifierExpression(Result).AddIdentifierToPath(CreateIdentifier(Result,N));
             GetNextToken;
             end;
-          // Plain identifier
-          Result:=TSQLIdentifierExpression(CreateElement(TSQLIdentifierExpression,APArent));
-          TSQLIdentifierExpression(Result).Identifier:=CreateIdentifier(Result,N);
           // Array access ?
           If (CurrentToken=tsqlSquareBraceOpen) then
             // Either something like array[5] or,

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

@@ -188,12 +188,20 @@ Type
   TSQLIdentifierExpression = Class(TSQLExpression)
   private
     FElementIndex: Integer;
-    FIdentifier: TSQLIdentifierName;
+    FIdentifierPath: array of TSQLIdentifierName;
+    function GetIdentifier: TSQLIdentifierName;
+    function GetIdentifierPath(Index: Integer): TSQLIdentifierName;
+    function GetIdentifierPathCount: Integer;
+    procedure SetIdentifier(const AName: TSQLIdentifierName);
   Public
     Constructor Create(AParent : TSQLElement); override;
     Destructor Destroy; override;
     Function GetAsSQL(Options : TSQLFormatOptions; AIndent : Integer = 0): TSQLStringType; override;
-    Property Identifier : TSQLIdentifierName Read FIdentifier Write FIdentifier;
+    Property Identifier : TSQLIdentifierName Read GetIdentifier Write SetIdentifier;
+    Property IdentifierPathCount: Integer Read GetIdentifierPathCount;
+    Procedure AddIdentifierToPath(AName: TSQLIdentifierName);
+    Procedure ClearIdentifierPath;
+    Property IdentifierPath[Index: Integer] : TSQLIdentifierName Read GetIdentifierPath;
     // For array types: index of element in array
     Property ElementIndex : Integer Read FElementIndex Write FElementIndex;
   end;
@@ -4149,20 +4157,66 @@ begin
   FElementIndex:=-1;
 end;
 
+procedure TSQLIdentifierExpression.AddIdentifierToPath(AName: TSQLIdentifierName);
+begin
+  SetLength(FIdentifierPath, Length(FIdentifierPath)+1);
+  FIdentifierPath[High(FIdentifierPath)] := AName;
+end;
+
+procedure TSQLIdentifierExpression.ClearIdentifierPath;
+var
+  N: TSQLIdentifierName;
+begin
+  for N in FIdentifierPath do
+    N.Free;
+  FIdentifierPath:=nil;
+end;
+
 destructor TSQLIdentifierExpression.Destroy;
 begin
-  FreeAndNil(FIdentifier);
+  ClearIdentifierPath;
   inherited Destroy;
 end;
 
 function TSQLIdentifierExpression.GetAsSQL(Options: TSQLFormatOptions; AIndent : Integer = 0): TSQLStringType;
+var
+  N: TSQLIdentifierName;
 begin
-  If Assigned(FIdentifier) then
-    Result:= Identifier.GetAsSQL(Options);
+  Result := '';
+  for N in FIdentifierPath do
+    begin
+    if Result<>'' then
+      Result:=Result+'.';
+    Result:=Result+N.GetAsSQL(Options);
+    end;
   If (ElementIndex<>-1) then
     Result:=Result+Format('[%d]',[Elementindex]);
 end;
 
+function TSQLIdentifierExpression.GetIdentifier: TSQLIdentifierName;
+begin
+  if Length(FIdentifierPath)>0 then
+    Result:=FIdentifierPath[High(FIdentifierPath)]
+  else
+    Result:=nil;
+end;
+
+function TSQLIdentifierExpression.GetIdentifierPath(Index: Integer): TSQLIdentifierName;
+begin
+  Result := FIdentifierPath[Index];
+end;
+
+function TSQLIdentifierExpression.GetIdentifierPathCount: Integer;
+begin
+  Result := Length(FIdentifierPath);
+end;
+
+procedure TSQLIdentifierExpression.SetIdentifier(const AName: TSQLIdentifierName);
+begin
+  ClearIdentifierPath;
+  AddIdentifierToPath(AName);
+end;
+
 { TSQLSelectExpression }
 
 function TSQLSelectExpression.GetAsSQL(Options: TSQLFormatOptions; AIndent : Integer = 0): TSQLStringType;

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

@@ -412,6 +412,7 @@ type
     procedure TestSelectTwoFieldsBracketThreeTablesJoin;
     procedure TestSelectTwoFieldsThreeBracketTablesJoin;
     procedure TestSelectTableWithSchema;
+    procedure TestSelectFieldWithSchema;
     procedure TestAggregateCount;
     procedure TestAggregateCountAsterisk;
     procedure TestAggregateCountAll;
@@ -3737,6 +3738,26 @@ begin
   AssertException(ESQLParser,@TestParseError);
 end;
 
+procedure TTestSelectParser.TestSelectFieldWithSchema;
+
+Var
+  Expr: TSQLIdentifierExpression;
+
+begin
+  TestSelect('SELECT S.A.B,C FROM S.A');
+  AssertEquals('Two fields',2,Select.Fields.Count);
+  AssertField(Select.Fields[0],'B');
+  Expr := ((Select.Fields[0] as TSQLSelectField).Expression as TSQLIdentifierExpression);
+  AssertEquals('Field[0] path has 3 identifiers',3,Expr.IdentifierPathCount);
+  AssertEquals('Field[0] schema is S','S',Expr.IdentifierPath[0].Name);
+  AssertEquals('Field[0] table is A','A',Expr.IdentifierPath[1].Name);
+  AssertField(Select.Fields[1],'C');
+  AssertEquals('One table',1,Select.Tables.Count);
+  AssertTable(Select.Tables[0],'A','');
+  AssertEquals('Table path has 2 objects',2,(Select.Tables[0] as TSQLSimpleTableReference).ObjectNamePathCount);
+  AssertEquals('Schema name = S','S',(Select.Tables[0] as TSQLSimpleTableReference).ObjectNamePath[0].Name);
+end;
+
 procedure TTestSelectParser.TestSelectOneFieldOneTable;
 begin
   TestSelect('SELECT B FROM A');
@@ -3802,12 +3823,17 @@ end;
 
 procedure TTestSelectParser.TestSelectOneTableFieldOneTable;
 
+Var
+  Expr: TSQLIdentifierExpression;
+
 begin
   TestSelect('SELECT A.B FROM A');
   AssertEquals('One field',1,Select.Fields.Count);
-  // Field does not support linking/refering to a table, so the field name is
-  // assigned as A.B (instead of B with a <link to table A>)
-  AssertField(Select.Fields[0],'A.B');
+  // Field supports linking/refering to a table
+  AssertField(Select.Fields[0],'B');
+  Expr := ((Select.Fields[0] as TSQLSelectField).Expression as TSQLIdentifierExpression);
+  AssertEquals('Field has explicit table',2,Expr.IdentifierPathCount);
+  AssertEquals('Field has explicit table named A','A',Expr.IdentifierPath[0].Name);
   AssertEquals('One table',1,Select.Tables.Count);
   AssertTable(Select.Tables[0],'A');
 end;
@@ -3815,6 +3841,7 @@ end;
 procedure TTestSelectParser.TestSelectTableWithSchema;
 begin
   TestSelect('SELECT B,C FROM S.A');
+  AssertEquals('Two fields',2,Select.Fields.Count);
   AssertField(Select.Fields[0],'B');
   AssertField(Select.Fields[1],'C');
   AssertEquals('One table',1,Select.Tables.Count);
@@ -3863,19 +3890,33 @@ begin
 end;
 
 procedure TTestSelectParser.TestSelectOneFieldOneTableAlias;
+
+Var
+  Expr: TSQLIdentifierExpression;
+
 begin
   TestSelect('SELECT C.B FROM A C');
   AssertEquals('One field',1,Select.Fields.Count);
-  AssertField(Select.Fields[0],'C.B');
+  AssertField(Select.Fields[0],'B');
+  Expr := ((Select.Fields[0] as TSQLSelectField).Expression as TSQLIdentifierExpression);
+  AssertEquals('Field has explicit table',2,Expr.IdentifierPathCount);
+  AssertEquals('Field has explicit table named C','C',Expr.IdentifierPath[0].Name);
   AssertEquals('One table',1,Select.Tables.Count);
   AssertTable(Select.Tables[0],'A');
 end;
 
 procedure TTestSelectParser.TestSelectOneFieldOneTableAsAlias;
+
+Var
+  Expr: TSQLIdentifierExpression;
+
 begin
   TestSelect('SELECT C.B FROM A AS C');
   AssertEquals('One field',1,Select.Fields.Count);
-  AssertField(Select.Fields[0],'C.B');
+  AssertField(Select.Fields[0],'B');
+  Expr := ((Select.Fields[0] as TSQLSelectField).Expression as TSQLIdentifierExpression);
+  AssertEquals('Field has explicit table',2,Expr.IdentifierPathCount);
+  AssertEquals('Field has explicit table named C','C',Expr.IdentifierPath[0].Name);
   AssertEquals('One table',1,Select.Tables.Count);
   AssertTable(Select.Tables[0],'A');
 end;