瀏覽代碼

* fcl-db: sql parser/generator:
- Correct OUTER join: there is no separate OUTER JOIN; the syntax is FULL JOIN or FULL OUTER JOIN
- Support for optional OUTER in LEFT OUTER and RIGHT OUTER JOIN

git-svn-id: trunk@27910 -

reiniero 11 年之前
父節點
當前提交
125845fe52

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

@@ -208,7 +208,7 @@ Resourcestring
   SErrExpectedDBObject = 'Expected database object type. Got: ''%s''';
   SErrDomainNotAllowed = 'Domain name not allowed in type definition.';
   SErrExpectedChar = 'Expected CHAR or CHARACTER, got "%s"';
-  SERRVaryingNotAllowed = 'VARYING not allowed at this point.';
+  SErrVaryingNotAllowed = 'VARYING not allowed at this point.';
   SErrUnknownBooleanOp = 'Unknown boolean operation';
   SErrUnknownComparison = 'unknown Comparison operation';
   SErrIntegerExpected = 'Integer expression expected';
@@ -222,6 +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';
 
 Function StringToSQLExtractElement(Const S : TSQLStringType; Out Res : TSQLExtractElement) : Boolean;
 
@@ -345,6 +346,7 @@ begin
        T.Params:=ParseValueList(AParent,[eoParamValue]);
        GetNextToken;
        end;
+     // Table aliases with and without AS keyword
      if (CurrentToken in [tsqlIdentifier,tsqlAs]) then
        begin
        if CurrentToken=tsqlAs then
@@ -357,7 +359,7 @@ begin
        end;
      end;
    Repeat
-     If CurrentToken in [tsqlInner,tsqlJoin,tsqlOuter,tsqlLeft,tsqlRight] then
+     If CurrentToken in [tsqlInner,tsqlFull,tsqlJoin,tsqlOuter,tsqlLeft,tsqlRight] then
        begin
        J:=TSQLJoinTableReference(CreateElement(TSQLJoinTableReference,AParent));
        J.Left:=Result;
@@ -365,18 +367,26 @@ begin
        Case CurrentToken of
           tsqlInner : J.JoinType:=jtInner;
           tsqlJoin  : J.JoinType:=jtNone;
-          tsqlOuter : J.JoinType:=jtOuter;
+          tsqlFull  : J.JoinType:=jtFullOuter;
           tsqlLeft  : J.JoinType:=jtLeft;
           tsqlRight : J.JoinType:=jtRight;
        end;
        if CurrentToken<>tsqlJoin then
          GetNextToken;
+       // Ignore OUTER in FULL OUTER, LEFT OUTER, RIGHT OUTER...:
+       if CurrentToken=tsqlOuter then
+         begin
+         if PreviousToken in [tsqlFull, tsqlLeft, tSQLRight] then
+           Consume(tsqlOuter)
+         else
+           Error(SErrOuterWithout);
+         end;
        Consume(tsqlJoin);
        J.Right:=ParseTableRef(AParent);
        Consume(tsqlOn);
        J.JoinClause:=ParseExprLevel1(J,[eoJOIN]);
        end;
-  until Not (CurrentToken in [tsqlInner,tsqlJoin,tsqlOuter,tsqlLeft,tsqlRight]);
+  until Not (CurrentToken in [tsqlInner,tsqlFull,tsqlJoin,tsqlOuter,tsqlLeft,tsqlRight]);
 end;
 
 procedure TSQLParser.ParseFromClause(AParent: TSQLSelectStatement;
@@ -1598,7 +1608,7 @@ begin
       GetNextToken
       end
     else
-      Error(SERRVaryingNotAllowed);
+      Error(SErrVaryingNotAllowed);
     end;
   If (CurrentToken=tsqlBraceOpen) then  // (LEN)
     begin

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

@@ -680,7 +680,7 @@ Var
 begin
   Result:=tsqlUnknown;
 
-  // Get "word" finalized by end of string, space/tab.
+  // Get "word" finalized by end of string, space/tab/line ending.
   TokenStart:=TokenStr;
   repeat
     Inc(TokenStr);

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

@@ -1,6 +1,6 @@
 {
     This file is part of the Free Component Library
-    Copyright (c) 2010 by the Free Pascal development team
+    Copyright (c) 2010-2014 by the Free Pascal development team
 
     SQL Abstract syntax tree
 
@@ -614,7 +614,7 @@ Type
   end;
 
   { TSQLJoinTableReference }
-  TSQLJoinType = (jtNone,jtInner,jtLeft,jtRight,jtOuter);
+  TSQLJoinType = (jtNone,jtInner,jtLeft,jtRight,jtFullOuter);
   TSQLJoinTableReference = Class(TSQLTableReference)
   private
     FJoinClause: TSQLExpression;
@@ -3033,7 +3033,7 @@ function TSQLJoinTableReference.GetAsSQL(Options: TSQLFormatOptions;
 
 Const
   Opcodes : Array[TSQLJoinTYpe] of String
-          = ('','INNER ','LEFT ','RIGHT ','OUTER ');
+          = ('','INNER ','LEFT ','RIGHT ','FULL OUTER ');
 
 Var
   L,R,O,Sep,prefix : TSQLStringType;

+ 7 - 8
packages/fcl-db/tests/tcgensql.pas

@@ -962,22 +962,21 @@ begin
   AssertSQL(J,'A LEFT JOIN B ON (C = D)');
   J.JoinType:=jtRight;
   AssertSQL(J,'A RIGHT JOIN B ON (C = D)');
-  J.JoinType:=jtOuter;
-  AssertSQL(J,'A OUTER JOIN B ON (C = D)');
-  AssertSQL(J,'A'+sLinebreak+'OUTER JOIN B ON (C = D)',[sfoOneTablePerLine]);
-  AssertSQL(J,'A'+sLinebreak+'  OUTER JOIN B ON (C = D)',[sfoOneTablePerLine,sfoIndentTables]);
+  J.JoinType:=jtFullOuter;
+  AssertSQL(J,'A FULL OUTER JOIN B ON (C = D)');
+  AssertSQL(J,'A'+sLinebreak+'FULL OUTER JOIN B ON (C = D)',[sfoOneTablePerLine]);
+  AssertSQL(J,'A'+sLinebreak+'  FULL OUTER JOIN B ON (C = D)',[sfoOneTablePerLine,sfoIndentTables]);
   J2:=CreateJoinTableReference(CreateSimpleTableReference('E',''),CreateSimpleTableReference('F',''));
   J2.JoinClause:=CreateBinaryExpression(CreateIdentifierExpression('G'),CreateIdentifierExpression('H'));
   TSQLBinaryExpression(J2.JoinClause).Operation:=boEQ;
   J.Right.Free;
   J.Right:=J2;
   FTofree:=J;
-  AssertSQL(J,'A OUTER JOIN (E JOIN F ON (G = H)) ON (C = D)');
-  AssertSQL(J,'A OUTER JOIN E JOIN F ON (G = H) ON (C = D)',[sfoNoBracketRightJoin]);
+  AssertSQL(J,'A FULL OUTER JOIN (E JOIN F ON (G = H)) ON (C = D)');
+  AssertSQL(J,'A FULL OUTER JOIN E JOIN F ON (G = H) ON (C = D)',[sfoNoBracketRightJoin]);
   J.Right:=J.Left;
   J.Left:=J2;
-  AssertSQL(J,'(E JOIN F ON (G = H)) OUTER JOIN A ON (C = D)',[sfoBracketLeftJoin]);
-
+  AssertSQL(J,'(E JOIN F ON (G = H)) FULL OUTER JOIN A ON (C = D)',[sfoBracketLeftJoin]);
 end;
 
 procedure TTestGenerateSQL.TestPlanNatural;

+ 19 - 4
packages/fcl-db/tests/tcparser.pas

@@ -388,7 +388,8 @@ type
     procedure TestSelectTwoFieldsTwoTablesJoin;
     procedure TestSelectTwoFieldsTwoInnerTablesJoin;
     procedure TestSelectTwoFieldsTwoLeftTablesJoin;
-    procedure TestSelectTwoFieldsTwoOuterTablesJoin;
+    procedure TestSelectTwoFieldsTwoFullOuterTablesJoin;
+    procedure TestSelectTwoFieldsTwoFullTablesJoin;
     procedure TestSelectTwoFieldsTwoRightTablesJoin;
     procedure TestSelectTwoFieldsThreeTablesJoin;
     procedure TestSelectTwoFieldsBracketThreeTablesJoin;
@@ -3851,17 +3852,31 @@ begin
   AssertJoinOn(J.JoinClause,'E','F',boEq);
 end;
 
-procedure TTestSelectParser.TestSelectTwoFieldsTwoOuterTablesJoin;
+procedure TTestSelectParser.TestSelectTwoFieldsTwoFullOuterTablesJoin;
 Var
   J : TSQLJoinTableReference;
 
 begin
-  TestSelect('SELECT B,C FROM A OUTER JOIN D ON E=F');
+  TestSelect('SELECT B,C FROM A FULL OUTER JOIN D ON E=F');
   AssertEquals('Two fields',2,Select.Fields.Count);
   AssertField(Select.Fields[0],'B');
   AssertField(Select.Fields[1],'C');
   AssertEquals('One table',1,Select.Tables.Count);
-  J:=AssertJoin(Select.Tables[0],'A','D',jtOuter);
+  J:=AssertJoin(Select.Tables[0],'A','D',jtFullOuter);
+  AssertJoinOn(J.JoinClause,'E','F',boEq);
+end;
+
+procedure TTestSelectParser.TestSelectTwoFieldsTwoFullTablesJoin;
+Var
+  J : TSQLJoinTableReference;
+
+begin
+  TestSelect('SELECT B,C FROM A FULL JOIN D ON E=F');
+  AssertEquals('Two fields',2,Select.Fields.Count);
+  AssertField(Select.Fields[0],'B');
+  AssertField(Select.Fields[1],'C');
+  AssertEquals('One table',1,Select.Tables.Count);
+  J:=AssertJoin(Select.Tables[0],'A','D',jtFullOuter);
   AssertJoinOn(J.JoinClause,'E','F',boEq);
 end;