Browse Source

* Added tests for Grant/Revoke, fixed some bugs on the way

git-svn-id: trunk@15942 -
michael 15 years ago
parent
commit
6df8ec1ce8

+ 169 - 14
packages/fcl-db/src/sql/fpsqlparser.pas

@@ -48,8 +48,6 @@ Type
     FPeekToken: TSQLToken;
     FPeekToken: TSQLToken;
     FPeekTokenString: String;
     FPeekTokenString: String;
     Procedure CheckEOF;
     Procedure CheckEOF;
-    procedure ParseGranteeList(AParent: TSQLElement; List: TSQLElementList;
-      AllowObject, AllowGroup: Boolean);
   protected
   protected
     Procedure UnexpectedToken; overload;
     Procedure UnexpectedToken; overload;
     Procedure UnexpectedToken(AExpected : TSQLTokens); overload;
     Procedure UnexpectedToken(AExpected : TSQLTokens); overload;
@@ -105,10 +103,14 @@ Type
     function ParseDeclareFunctionStatement(AParent: TSQLElement): TSQLDeclareExternalFunctionStatement;
     function ParseDeclareFunctionStatement(AParent: TSQLElement): TSQLDeclareExternalFunctionStatement;
     function ParseDeclareStatement(AParent: TSQLElement): TSQLStatement;
     function ParseDeclareStatement(AParent: TSQLElement): TSQLStatement;
     // GRANT parsing
     // GRANT parsing
-    function ParseGrantStatement(AParent: TSQLElement): TSQLGrantStatement;
+    procedure ParseGranteeList(AParent: TSQLElement; List: TSQLElementList; AllowObject, AllowGroup,AllowPublic : Boolean; IsRevoke: Boolean = False);
     function ParseGrantExecuteStatement(AParent: TSQLElement): TSQLProcedureGrantStatement;
     function ParseGrantExecuteStatement(AParent: TSQLElement): TSQLProcedureGrantStatement;
     function ParseGrantRoleStatement(AParent: TSQLElement): TSQLRoleGrantStatement;
     function ParseGrantRoleStatement(AParent: TSQLElement): TSQLRoleGrantStatement;
     function ParseGrantTableStatement(AParent: TSQLElement): TSQLTableGrantStatement;
     function ParseGrantTableStatement(AParent: TSQLElement): TSQLTableGrantStatement;
+    // REVOKE parsing
+    function ParseRevokeExecuteStatement(AParent: TSQLElement): TSQLProcedureRevokeStatement;
+    function ParseRevokeRoleStatement(AParent: TSQLElement): TSQLRoleRevokeStatement;
+    function ParseRevokeTableStatement(AParent: TSQLElement): TSQLTableRevokeStatement;
     // SELECT parsing
     // SELECT parsing
     function ParseExprAggregate(AParent: TSQLElement; EO: TExpressionOptions): TSQLAggregateFunctionExpression;
     function ParseExprAggregate(AParent: TSQLElement; EO: TExpressionOptions): TSQLAggregateFunctionExpression;
     procedure ParseFromClause(AParent: TSQLSelectStatement; AList: TSQLElementList);
     procedure ParseFromClause(AParent: TSQLSelectStatement; AList: TSQLElementList);
@@ -147,6 +149,8 @@ Type
     Function ParseCommitStatement(AParent : TSQLElement) : TSQLCommitStatement;
     Function ParseCommitStatement(AParent : TSQLElement) : TSQLCommitStatement;
     Function ParseSetStatement(AParent : TSQLElement) : TSQLStatement;
     Function ParseSetStatement(AParent : TSQLElement) : TSQLStatement;
     Function ParseConnectStatement(AParent : TSQLElement) : TSQLConnectStatement;
     Function ParseConnectStatement(AParent : TSQLElement) : TSQLConnectStatement;
+    Function ParseGrantStatement(AParent: TSQLElement): TSQLGrantStatement;
+    Function ParseRevokeStatement(AParent: TSQLElement): TSQLGrantStatement;
     Function Parse : TSQLElement;
     Function Parse : TSQLElement;
     Function ParseScript(AllowPartial : Boolean = False) : TSQLElementList;
     Function ParseScript(AllowPartial : Boolean = False) : TSQLElementList;
     // Auxiliary stuff
     // Auxiliary stuff
@@ -3386,7 +3390,8 @@ begin
 
 
 end;
 end;
 
 
-Procedure TSQLParser.ParseGranteeList(AParent : TSQLElement; List : TSQLElementList; AllowObject,AllowGroup : Boolean);
+procedure TSQLParser.ParseGranteeList(AParent: TSQLElement;
+  List: TSQLElementList; AllowObject, AllowGroup, AllowPublic: Boolean; IsRevoke: Boolean = False);
 
 
 Type
 Type
   TSQLGranteeClass = Class of TSQLGrantee;
   TSQLGranteeClass = Class of TSQLGrantee;
@@ -3407,10 +3412,15 @@ Var
   E : TSQLTokens;
   E : TSQLTokens;
 
 
 begin
 begin
-  Consume(tsqlTo);
-  E:=[tsqlIdentifier];
+  if IsRevoke then
+    Consume(tsqlFrom)
+  else
+    Consume(tsqlTo);
+  E:=[tsqlIdentifier,tsqlUser];
   If AllowObject then
   If AllowObject then
-    E:=E+[tsqlProcedure,tsqlView,tsqlTrigger,tsqlPublic];
+    E:=E+[tsqlProcedure,tsqlView,tsqlTrigger,tsqlPublic]
+  else If AllowPublic then
+    E:=E+[tsqlPublic];
   If AllowGroup then
   If AllowGroup then
     E:=E+[tsqlGROUP];
     E:=E+[tsqlGROUP];
   Expect(E);
   Expect(E);
@@ -3429,9 +3439,9 @@ begin
         end;
         end;
       TsqlPublic :
       TsqlPublic :
         begin
         begin
-        If Not AllowGroup then
+        If Not (AllowPublic or AllowObject)  then
           UnexpectedToken;
           UnexpectedToken;
-        CreateGrantee(true,TSQLPublicGrantee);
+        CreateGrantee(False,TSQLPublicGrantee);
         end;
         end;
       TsqlTrigger:
       TsqlTrigger:
         begin
         begin
@@ -3493,6 +3503,7 @@ begin
             GetNextToken;
             GetNextToken;
             If (CurrentToken=tsqlBraceOpen) then
             If (CurrentToken=tsqlBraceOpen) then
               begin
               begin
+              GetNextToken;
               C.Columns:=TSQLElementList.Create(True);
               C.Columns:=TSQLElementList.Create(True);
               ParseIdentifierList(C,C.Columns);
               ParseIdentifierList(C,C.Columns);
               end;
               end;
@@ -3508,7 +3519,7 @@ begin
     Expect(tsqlidentifier);
     Expect(tsqlidentifier);
     Result.TableName:=CreateIdentifier(Result,CurrentTokenString);
     Result.TableName:=CreateIdentifier(Result,CurrentTokenString);
     GetNextToken;
     GetNextToken;
-    ParseGranteeList(Result,Result.Grantees,True,True);
+    ParseGranteeList(Result,Result.Grantees,True,True,True);
     If (CurrentToken=tsqlWith) then
     If (CurrentToken=tsqlWith) then
       begin
       begin
       Consume(tsqlWith);
       Consume(tsqlWith);
@@ -3522,6 +3533,121 @@ begin
   end;
   end;
 end;
 end;
 
 
+function TSQLParser.ParseRevokeExecuteStatement(AParent: TSQLElement
+  ): TSQLProcedureRevokeStatement;
+BEGIN
+  // On entry, we're on the EXECUTE token
+  Consume(tsqlExecute);
+  Consume(tsqlOn);
+  Consume(tsqlProcedure);
+  Expect(tsqlIdentifier);
+  Result:=TSQLProcedureRevokeStatement(CreateElement(TSQLProcedureRevokeStatement,AParent));
+  try
+    Result.ProcedureName:=CreateIdentifier(Result,CurrentTokenString);
+    GetNextToken;
+    ParseGranteeList(Result,Result.Grantees,True,False,True,True);
+    If (CurrentToken=tsqlWith) then
+      begin
+      Consume(tsqlWith);
+      Consume(tsqlGrant);
+      Consume(tsqlOption);
+      Result.GrantOption:=True;
+      end;
+  except
+    FreeAndNil(Result);
+    Raise;
+  end;
+end;
+
+function TSQLParser.ParseRevokeRoleStatement(AParent: TSQLElement
+  ): TSQLRoleRevokeStatement;
+begin
+  Result:=Nil;
+  // On entry, we're on the identifier token
+  expect(tsqlIdentifier);
+  Result:=TSQLRoleRevokeStatement(CreateElement(TSQLRoleRevokeStatement,AParent));
+  try
+    Repeat
+      if CurrentToken=tsqlComma then
+        GetNextToken;
+      expect(tsqlIdentifier);
+      Result.Roles.Add(CreateIDentifier(Aparent,CurrentTokenString));
+    Until (GetNextToken<>tsqlComma);
+    Expect(tsqlFrom);
+    ParseGranteeList(Result,Result.Grantees,False,False,True,True);
+  except
+    FreeAndNil(Result);
+    Raise;
+  end;
+end;
+
+function TSQLParser.ParseRevokeTableStatement(AParent: TSQLElement
+  ): TSQLTableRevokeStatement;
+Var
+  C : TSQLColumnPrivilege;
+  P : TSQLPrivilege;
+
+begin
+  Result:=TSQLTableRevokeStatement(CreateElement(TSQLTableRevokeStatement,APArent));
+  try
+    // On entry, we're on the first GRANT,ALL/SELECT/UPDATE/INSERT/DELETE/REFERENCE etc. token.
+    If (CurrentToken=tsqlGrant) then
+      begin
+      Consume(tsqlGrant);
+      Consume(tsqlOption);
+      Consume(tsqlFor);
+      Result.GrantOption:=True;
+      end;
+    if CurrentToken=tsqlAll then
+      begin
+      Result.Privileges.Add(CreateElement(TSQLAllPrivilege,Result));
+      If GetNextToken=tsqlPrivileges then
+        GetNextToken;
+      end
+    else
+      Repeat
+        P:=Nil;
+        C:=Nil;
+        if CurrentToken=tsqlComma then
+          GetNextToken;
+        Case CurrentToken of
+          tsqlSelect : P:=TSQLSelectPrivilege(CreateElement(TSQLSelectPrivilege,Result));
+          tsqlInsert : P:=TSQLInsertPrivilege(CreateElement(TSQLInsertPrivilege,Result));
+          tsqlDelete : P:=TSQLDeletePrivilege(CreateElement(TSQLDeletePrivilege,Result));
+          tsqlUpdate,
+          tsqlReferences :
+            begin
+            if CurrentToken=tsqlUpdate then
+              C:=TSQLUpdatePrivilege(CreateElement(TSQLUpdatePrivilege,AParent))
+            else
+              C:=TSQLReferencePrivilege(CreateElement(TSQLReferencePrivilege,AParent));
+            P:=C;
+            GetNextToken;
+            If (CurrentToken=tsqlBraceOpen) then
+              begin
+              GetNextToken;
+              C.Columns:=TSQLElementList.Create(True);
+              ParseIdentifierList(C,C.Columns);
+              end;
+            end;
+        else
+          UnexpectedToken([tsqlselect,tsqlInsert,tsqlDelete,tsqlUpdate,tsqlReferences]);
+        end;
+        Result.Privileges.Add(P);
+        If C=Nil then
+          GetNextToken;
+      Until (CurrentToken<>tsqlComma);
+    Consume(tsqlOn);
+    Expect(tsqlidentifier);
+    Result.TableName:=CreateIdentifier(Result,CurrentTokenString);
+    GetNextToken;
+    ParseGranteeList(Result,Result.Grantees,True,True,True,True);
+  except
+    FreeAndNil(Result);
+    Raise;
+  end;
+end;
+
 function TSQLParser.ParseGrantExecuteStatement(AParent: TSQLElement): TSQLProcedureGrantStatement;
 function TSQLParser.ParseGrantExecuteStatement(AParent: TSQLElement): TSQLProcedureGrantStatement;
 
 
 begin
 begin
@@ -3534,7 +3660,7 @@ begin
   try
   try
     Result.ProcedureName:=CreateIdentifier(Result,CurrentTokenString);
     Result.ProcedureName:=CreateIdentifier(Result,CurrentTokenString);
     GetNextToken;
     GetNextToken;
-    ParseGranteeList(Result,Result.Grantees,True,False);
+    ParseGranteeList(Result,Result.Grantees,True,False,True);
     If (CurrentToken=tsqlWith) then
     If (CurrentToken=tsqlWith) then
       begin
       begin
       Consume(tsqlWith);
       Consume(tsqlWith);
@@ -3562,8 +3688,8 @@ begin
       expect(tsqlIdentifier);
       expect(tsqlIdentifier);
       Result.Roles.Add(CreateIDentifier(Aparent,CurrentTokenString));
       Result.Roles.Add(CreateIDentifier(Aparent,CurrentTokenString));
     Until (GetNextToken<>tsqlComma);
     Until (GetNextToken<>tsqlComma);
-    Consume(tsqlTo);
-    ParseGranteeList(Result,Result.Grantees,False,False);
+    Expect(tsqlTo);
+    ParseGranteeList(Result,Result.Grantees,False,False,True);
     If (CurrentToken=tsqlWith) then
     If (CurrentToken=tsqlWith) then
       begin
       begin
       Consume(tsqlWith);
       Consume(tsqlWith);
@@ -3586,6 +3712,7 @@ begin
     Consume(tsqlGrant);
     Consume(tsqlGrant);
     Case CurrentToken of
     Case CurrentToken of
       tsqlExecute: Result:=ParseGrantExecutestatement(AParent);
       tsqlExecute: Result:=ParseGrantExecutestatement(AParent);
+      tsqlAll,
       tsqlUpdate,
       tsqlUpdate,
       tsqlReferences,
       tsqlReferences,
       tsqlInsert,
       tsqlInsert,
@@ -3593,7 +3720,34 @@ begin
       tsqlSelect : Result:=ParseGrantTablestatement(AParent);
       tsqlSelect : Result:=ParseGrantTablestatement(AParent);
       tsqlIdentifier : Result:=ParseGrantRolestatement(AParent);
       tsqlIdentifier : Result:=ParseGrantRolestatement(AParent);
     else
     else
-      UnExpectedToken([tsqlIdentifier, tsqlExecute,
+      UnExpectedToken([tsqlIdentifier, tsqlExecute, tsqlall,
+                       tsqlUpdate, tsqldelete, tsqlReferences, tsqlInsert, tsqlSelect]);
+    end;
+  except
+    FreeAndNil(Result);
+    Raise;
+  end;
+end;
+
+function TSQLParser.ParseRevokeStatement(AParent: TSQLElement
+  ): TSQLGrantStatement;
+begin
+  // On entry, we're on the GRANT token
+  Result:=Nil;
+  try
+    Consume(tsqlRevoke);
+    Case CurrentToken of
+      tsqlExecute: Result:=ParseRevokeExecutestatement(AParent);
+      tsqlGrant,
+      tsqlAll,
+      tsqlUpdate,
+      tsqlReferences,
+      tsqlInsert,
+      tsqldelete,
+      tsqlSelect : Result:=ParseRevokeTablestatement(AParent);
+      tsqlIdentifier : Result:=ParseRevokeRolestatement(AParent);
+    else
+      UnExpectedToken([tsqlIdentifier, tsqlExecute,tsqlgrant,tsqlall,
                        tsqlUpdate, tsqldelete, tsqlReferences, tsqlInsert, tsqlSelect]);
                        tsqlUpdate, tsqldelete, tsqlReferences, tsqlInsert, tsqlSelect]);
     end;
     end;
   except
   except
@@ -3620,6 +3774,7 @@ begin
     tsqlConnect : Result:=ParseConnectStatement(Nil);
     tsqlConnect : Result:=ParseConnectStatement(Nil);
     tsqlDeclare : Result:=ParseDeclareStatement(Nil);
     tsqlDeclare : Result:=ParseDeclareStatement(Nil);
     tsqlGrant : Result:=ParseGrantStatement(Nil);
     tsqlGrant : Result:=ParseGrantStatement(Nil);
+    tsqlRevoke : Result:=ParseRevokeStatement(Nil);
   else
   else
     UnexpectedToken;
     UnexpectedToken;
   end;
   end;

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

@@ -59,7 +59,7 @@ type
    tsqlNOT, tsqlNULL, tsqlNUMERIC , tsqlNChar, tsqlNATIONAL,tsqlNO, tsqlNatural,
    tsqlNOT, tsqlNULL, tsqlNUMERIC , tsqlNChar, tsqlNATIONAL,tsqlNO, tsqlNatural,
    tsqlON, tsqlOR, tsqlORDER, tsqlOUTER, tsqlOption,
    tsqlON, tsqlOR, tsqlORDER, tsqlOUTER, tsqlOption,
    tsqlPRIMARY,  tsqlProcedure, tsqlPosition, tsqlPlan, tsqlPassword, tsqlPage,tsqlPages,tsqlPageSize,tsqlPostEvent,tsqlPrivileges,tsqlPublic,
    tsqlPRIMARY,  tsqlProcedure, tsqlPosition, tsqlPlan, tsqlPassword, tsqlPage,tsqlPages,tsqlPageSize,tsqlPostEvent,tsqlPrivileges,tsqlPublic,
-   tsqlRIGHT, tsqlROLE, tsqlReferences, tsqlRollBack, tsqlRelease,  tsqlretain,  tsqlReturningValues,tsqlReturns,
+   tsqlRIGHT, tsqlROLE, tsqlReferences, tsqlRollBack, tsqlRelease,  tsqlretain,  tsqlReturningValues,tsqlReturns, tsqlrevoke,
    tsqlSELECT, tsqlSET, tsqlSINGULAR, tsqlSOME, tsqlSTARTING, tsqlSUM, tsqlSKIP,tsqlSUBTYPE,tsqlSize,tsqlSegment, tsqlSORT, tsqlSnapShot,tsqlSchema,tsqlShadow,tsqlSuspend,tsqlSQLCode,tsqlSmallint,
    tsqlSELECT, tsqlSET, tsqlSINGULAR, tsqlSOME, tsqlSTARTING, tsqlSUM, tsqlSKIP,tsqlSUBTYPE,tsqlSize,tsqlSegment, tsqlSORT, tsqlSnapShot,tsqlSchema,tsqlShadow,tsqlSuspend,tsqlSQLCode,tsqlSmallint,
    tSQLTABLE, tsqlTrigger,tsqlTime,tsqlTimeStamp,tsqlType, tsqlTo, tsqlTransaction,tsqlThen,
    tSQLTABLE, tsqlTrigger,tsqlTime,tsqlTimeStamp,tsqlType, tsqlTo, tsqlTransaction,tsqlThen,
    tsqlUNION, tsqlUPDATE, tsqlUPPER,  tsqlUNIQUE, tsqlUSER,
    tsqlUNION, tsqlUPDATE, tsqlUPPER,  tsqlUNIQUE, tsqlUSER,
@@ -99,7 +99,8 @@ const
        'NOT', 'NULL', 'NUMERIC','NCHAR','NATIONAL', 'NO', 'NATURAL',
        'NOT', 'NULL', 'NUMERIC','NCHAR','NATIONAL', 'NO', 'NATURAL',
        'ON', 'OR', 'ORDER', 'OUTER', 'OPTION',
        'ON', 'OR', 'ORDER', 'OUTER', 'OPTION',
        'PRIMARY', 'PROCEDURE','POSITION','PLAN', 'PASSWORD','PAGE','PAGES','PAGE_SIZE','POST_EVENT','PRIVILEGES','PUBLIC',
        'PRIMARY', 'PROCEDURE','POSITION','PLAN', 'PASSWORD','PAGE','PAGES','PAGE_SIZE','POST_EVENT','PRIVILEGES','PUBLIC',
-       'RIGHT', 'ROLE', 'REFERENCES', 'ROLLBACK','RELEASE', 'RETAIN', 'RETURNING_VALUES', 'RETURNS', 'SELECT', 'SET', 'SINGULAR', 'SOME', 'STARTING', 'SUM', 'SKIP','SUB_TYPE', 'SIZE', 'SEGMENT', 'SORT', 'SNAPSHOT','SCHEMA','SHADOW','SUSPEND','SQLCODE','SMALLINT',
+       'RIGHT', 'ROLE', 'REFERENCES', 'ROLLBACK','RELEASE', 'RETAIN', 'RETURNING_VALUES', 'RETURNS','REVOKE',
+       'SELECT', 'SET', 'SINGULAR', 'SOME', 'STARTING', 'SUM', 'SKIP','SUB_TYPE', 'SIZE', 'SEGMENT', 'SORT', 'SNAPSHOT','SCHEMA','SHADOW','SUSPEND','SQLCODE','SMALLINT',
        'TABLE','TRIGGER',  'TIME','TIMESTAMP',  'TYPE', 'TO', 'TRANSACTION','THEN',
        'TABLE','TRIGGER',  'TIME','TIMESTAMP',  'TYPE', 'TO', 'TRANSACTION','THEN',
        'UNION', 'UPDATE', 'UPPER', 'UNIQUE', 'USER',
        'UNION', 'UPDATE', 'UPPER', 'UNIQUE', 'USER',
        'VALUE','VALUES','VARIABLE', 'VIEW','VARCHAR','VARYING',
        'VALUE','VALUES','VARIABLE', 'VIEW','VARCHAR','VARYING',

+ 113 - 4
packages/fcl-db/src/sql/fpsqltree.pp

@@ -1723,12 +1723,12 @@ Type
   private
   private
     FGrantees: TSQLElementList;
     FGrantees: TSQLElementList;
   Public
   Public
-    Function GranteesAsSQL(Options : TSQLFormatOptions; AIndent : Integer) : TSQLStringType;
+    Function GranteesAsSQL(Options : TSQLFormatOptions; AIndent : Integer; IsRevoke : Boolean = False) : TSQLStringType;
     Constructor Create(AParent : TSQLElement); override;
     Constructor Create(AParent : TSQLElement); override;
     Destructor Destroy; override;
     Destructor Destroy; override;
     Property Grantees : TSQLElementList Read FGrantees;
     Property Grantees : TSQLElementList Read FGrantees;
   end;
   end;
-
+  TSQLRevokeStatement = TSQLGrantStatement;
   { TSQLTableGrantStatement }
   { TSQLTableGrantStatement }
 
 
   TSQLTableGrantStatement = Class(TSQLGrantStatement)
   TSQLTableGrantStatement = Class(TSQLGrantStatement)
@@ -1745,6 +1745,11 @@ Type
     Property GrantOption : Boolean Read FGrantOption Write FGrantOption;
     Property GrantOption : Boolean Read FGrantOption Write FGrantOption;
   end;
   end;
 
 
+  { TSQLTableRevokeStatement }
+
+  TSQLTableRevokeStatement = Class(TSQLTableGrantStatement)
+    Function GetAsSQL(Options : TSQLFormatOptions; AIndent : Integer = 0): TSQLStringType; override;
+  end;
   { TSQLProcedureGrantStatement }
   { TSQLProcedureGrantStatement }
 
 
   TSQLProcedureGrantStatement = Class(TSQLGrantStatement)
   TSQLProcedureGrantStatement = Class(TSQLGrantStatement)
@@ -1753,10 +1758,17 @@ Type
     FProcedureName: TSQLIdentifierName;
     FProcedureName: TSQLIdentifierName;
   Public
   Public
     Destructor Destroy; override;
     Destructor Destroy; override;
+    Function GetAsSQL(Options : TSQLFormatOptions; AIndent : Integer = 0): TSQLStringType; override;
     Property ProcedureName : TSQLIdentifierName Read FProcedureName Write FProcedureName;
     Property ProcedureName : TSQLIdentifierName Read FProcedureName Write FProcedureName;
     Property GrantOption : Boolean Read FGrantOption Write FGrantOption;
     Property GrantOption : Boolean Read FGrantOption Write FGrantOption;
   end;
   end;
 
 
+  { TSQLProcedureRevokeStatement }
+
+  TSQLProcedureRevokeStatement = Class(TSQLProcedureGrantStatement)
+    Function GetAsSQL(Options : TSQLFormatOptions; AIndent : Integer = 0): TSQLStringType; override;
+  end;
+
   { TSQLRoleGrantStatement }
   { TSQLRoleGrantStatement }
 
 
   TSQLRoleGrantStatement = Class(TSQLGrantStatement)
   TSQLRoleGrantStatement = Class(TSQLGrantStatement)
@@ -1766,10 +1778,17 @@ Type
   Public
   Public
     Constructor Create(AParent : TSQLElement); override;
     Constructor Create(AParent : TSQLElement); override;
     Destructor Destroy; override;
     Destructor Destroy; override;
+    Function GetAsSQL(Options : TSQLFormatOptions; AIndent : Integer = 0): TSQLStringType; override;
     Property Roles : TSQLElementList Read FRoles;
     Property Roles : TSQLElementList Read FRoles;
     Property AdminOption : Boolean Read FAdminOption Write FAdminOption;
     Property AdminOption : Boolean Read FAdminOption Write FAdminOption;
   end;
   end;
 
 
+  { TSQLRoleRevokeStatement }
+
+  TSQLRoleRevokeStatement = Class(TSQLRoleGrantStatement)
+    Function GetAsSQL(Options : TSQLFormatOptions; AIndent : Integer = 0): TSQLStringType; override;
+  end;
+
 Const
 Const
   CharTypes = [sdtChar,sdtVarChar,sdtNChar,sdtNVarChar,sdtCString];
   CharTypes = [sdtChar,sdtVarChar,sdtNChar,sdtNVarChar,sdtCString];
   ExtractElementNames : Array[TSQLExtractElement] of String
   ExtractElementNames : Array[TSQLExtractElement] of String
@@ -4510,7 +4529,7 @@ end;
 
 
 { TSQLGrantStatement }
 { TSQLGrantStatement }
 
 
-function TSQLGrantStatement.GranteesAsSQL(Options: TSQLFormatOptions; AIndent : Integer): TSQLStringType;
+function TSQLGrantStatement.GranteesAsSQL(Options: TSQLFormatOptions; AIndent : Integer; IsRevoke : Boolean = False): TSQLStringType;
 
 
 Var
 Var
   Sep : TSQLStringType;
   Sep : TSQLStringType;
@@ -4524,7 +4543,10 @@ begin
       Result:=Result+Sep;
       Result:=Result+Sep;
     Result:=Result+Grantees[i].GetAsSQl(Options,AIndent);
     Result:=Result+Grantees[i].GetAsSQl(Options,AIndent);
     end;
     end;
-  Result:=SQLKeyWord(' TO ',Options)+Result;
+  If IsRevoke then
+    Result:=SQLKeyWord(' FROM ',Options)+Result
+  else
+    Result:=SQLKeyWord(' TO ',Options)+Result;
 end;
 end;
 
 
 constructor TSQLGrantStatement.Create(AParent: TSQLElement);
 constructor TSQLGrantStatement.Create(AParent: TSQLElement);
@@ -4585,6 +4607,15 @@ begin
   inherited Destroy;
   inherited Destroy;
 end;
 end;
 
 
+function TSQLProcedureGrantStatement.GetAsSQL(Options: TSQLFormatOptions;
+  AIndent: Integer): TSQLStringType;
+begin
+  Result:=SQLKeyWord('GRANT EXECUTE ON PROCEDURE ',OPtions);
+  If Assigned(FProcedureName) then
+    Result:=Result+FProcedureName.GetAsSQl(Options,AIndent);
+  Result:=Result+GranteesAsSQL(Options,AIndent);
+end;
+
 { TSQLRoleGrantStatement }
 { TSQLRoleGrantStatement }
 
 
 constructor TSQLRoleGrantStatement.Create(AParent: TSQLElement);
 constructor TSQLRoleGrantStatement.Create(AParent: TSQLElement);
@@ -4599,6 +4630,26 @@ begin
   inherited Destroy;
   inherited Destroy;
 end;
 end;
 
 
+function TSQLRoleGrantStatement.GetAsSQL(Options: TSQLFormatOptions;
+  AIndent: Integer): TSQLStringType;
+
+Var
+  Sep : TSQLStringType;
+  I : Integer;
+begin
+   Sep:=SQLListSeparator(Options);
+   For I:=0 to Roles.Count-1 do
+      begin
+      If (Result<>'') then
+        Result:=Result+Sep;
+      Result:=Result+Roles[i].GetAsSQl(Options,AIndent);
+      end;
+  Result:=SQLKeyWord('GRANT ',Options)+Result;
+  Result:=Result+GranteesAsSQL(Options,AIndent);
+  If AdminOption then
+    Result:=Result+SQLKeyWord(' WITH ADMIN OPTION',OPtions);
+end;
+
 { TSQLInsertPrivilege }
 { TSQLInsertPrivilege }
 
 
 function TSQLInsertPrivilege.GetAsSQL(Options: TSQLFormatOptions;
 function TSQLInsertPrivilege.GetAsSQL(Options: TSQLFormatOptions;
@@ -4693,5 +4744,63 @@ begin
   Result:=SQLKeyWord('PUBLIC',Options);
   Result:=SQLKeyWord('PUBLIC',Options);
 end;
 end;
 
 
+{ TSQLTableRevokeStatement }
+
+function TSQLTableRevokeStatement.GetAsSQL(Options: TSQLFormatOptions;
+  AIndent: Integer): TSQLStringType;
+
+Var
+  S,Sep : TSQLStringType;
+  I : Integer;
+
+begin
+  Sep:=SQLListSeparator(Options);
+  S:='';
+  For I:=0 to Privileges.Count-1 do
+    begin
+    If (S<>'') then
+      S:=S+Sep;
+    S:=S+Privileges[i].GetAsSQL(Options,AIndent);
+    end;
+  Result:=SQLKeyWord('REVOKE ',Options);
+  If GrantOption then
+    Result:=Result+SQLKeyWord('GRANT OPTION FOR ',Options);
+  Result:=Result+S+SQLKeyWord(' ON ',Options);
+  If Assigned(FTableName) then
+    Result:=Result+FTableName.GetAsSQl(Options,AIndent);
+  Result:=Result+Self.GranteesAsSQL(Options,AIndent,True);
+end;
+
+{ TSQLProcedureRevokeStatement }
+
+function TSQLProcedureRevokeStatement.GetAsSQL(Options: TSQLFormatOptions;
+  AIndent: Integer): TSQLStringType;
+begin
+  Result:=SQLKeyWord('REVOKE EXECUTE ON PROCEDURE ',OPtions);
+  If Assigned(FProcedureName) then
+    Result:=Result+FProcedureName.GetAsSQl(Options,AIndent);
+  Result:=Result+GranteesAsSQL(Options,AIndent,True);
+end;
+
+{ TSQLRoleRevokeStatement }
+
+function TSQLRoleRevokeStatement.GetAsSQL(Options: TSQLFormatOptions;
+  AIndent: Integer): TSQLStringType;
+
+Var
+  Sep : TSQLStringType;
+  I : Integer;
+begin
+  Sep:=SQLListSeparator(Options);
+  For I:=0 to Roles.Count-1 do
+    begin
+    If (Result<>'') then
+      Result:=Result+Sep;
+    Result:=Result+Roles[i].GetAsSQl(Options,AIndent);
+    end;
+  Result:=SQLKeyWord('REVOKE ',Options)+Result;
+  Result:=Result+GranteesAsSQL(Options,AIndent,True);
+end;
+
 end.
 end.
 
 

+ 289 - 0
packages/fcl-db/tests/tcgensql.pas

@@ -23,6 +23,7 @@ uses
 
 
 type
 type
   TSQLDropStatementClass = Class of TSQLDropStatement;
   TSQLDropStatementClass = Class of TSQLDropStatement;
+  TSQLGranteeClass = Class of TSQLGrantee;
 
 
   { TTestGenerateSQL }
   { TTestGenerateSQL }
 
 
@@ -36,6 +37,7 @@ type
     procedure DoTestAlterCreateProcedure(S: TSQLAlterCreateProcedureStatement; PHEAD: String);
     procedure DoTestAlterCreateProcedure(S: TSQLAlterCreateProcedureStatement; PHEAD: String);
     procedure DoTestAlterCreateTrigger(S: TSQLAlterCreateTriggerStatement; PHEAD: String);
     procedure DoTestAlterCreateTrigger(S: TSQLAlterCreateTriggerStatement; PHEAD: String);
     Function CreateIdentifier(Const AName : TSQLStringType) : TSQLIdentifierName;
     Function CreateIdentifier(Const AName : TSQLStringType) : TSQLIdentifierName;
+    Function CreateGrantee(Const AName : TSQLStringType; AClass : TSQLGranteeClass = Nil) : TSQLGrantee;
     Function CreateLiteral(Const AValue : Integer) : TSQLIntegerLiteral;
     Function CreateLiteral(Const AValue : Integer) : TSQLIntegerLiteral;
     Function CreateLiteral(Const AValue : TSQLStringType) : TSQLStringLiteral;
     Function CreateLiteral(Const AValue : TSQLStringType) : TSQLStringLiteral;
     Function CreateLiteral(Const AValue : Double) : TSQLFloatLiteral;
     Function CreateLiteral(Const AValue : Double) : TSQLFloatLiteral;
@@ -152,6 +154,12 @@ type
     procedure TestConnect;
     procedure TestConnect;
     procedure TestExtract;
     procedure TestExtract;
     procedure TestParamExpression;
     procedure TestParamExpression;
+    Procedure TestGrantTable;
+    Procedure TestGrantProcedure;
+    Procedure TestGrantRole;
+    Procedure TestRevokeTable;
+    Procedure TestRevokeProcedure;
+    Procedure TestRevokeRole;
   end;
   end;
 
 
 implementation
 implementation
@@ -174,6 +182,16 @@ begin
   FToFree:=Result;
   FToFree:=Result;
 end;
 end;
 
 
+function TTestGenerateSQL.CreateGrantee(const AName: TSQLStringType;
+  AClass: TSQLGranteeClass = Nil): TSQLGrantee;
+begin
+  If AClass=Nil then
+    AClass:=TSQLGrantee;
+  Result:=AClass.Create(Nil);
+  Result.Name:=AName;
+  FToFree:=Result;
+end;
+
 procedure TTestGenerateSQL.AssertSQL(const AElement: TSQLElement;
 procedure TTestGenerateSQL.AssertSQL(const AElement: TSQLElement;
   const ASQL: TSQLStringType; AOptions: TSQLFormatOptions = []);
   const ASQL: TSQLStringType; AOptions: TSQLFormatOptions = []);
 
 
@@ -2254,6 +2272,277 @@ begin
   AssertSQL(P,':P');
   AssertSQL(P,':P');
 end;
 end;
 
 
+procedure TTestGenerateSQL.TestGrantTable;
+
+Var
+  G : TSQLTableGrantStatement;
+  U : TSQLUserGrantee;
+  PU : TSQLColumnPrivilege;
+  PG : TSQLProcedureGrantee;
+
+begin
+  G:=TSQLTableGrantStatement.Create(Nil);
+  G.TableName:=CreateIdentifier('A');
+  FtoFree:=G;
+  G.Privileges.Add(TSQLSelectPrivilege.Create(Nil));
+  G.Grantees.add(CreateGrantee('B'));
+  FtoFree:=G;
+  AssertSQl(G,'GRANT SELECT ON A TO B');
+  G.Grantees.add(CreateGrantee('C'));
+  FtoFree:=G;
+  AssertSQl(G,'GRANT SELECT ON A TO B , C');
+  G.Privileges.Clear;
+  G.Privileges.Add(TSQLUPdatePrivilege.Create(Nil));
+  FtoFree:=G;
+  AssertSQl(G,'GRANT UPDATE ON A TO B , C');
+  G.Privileges.Clear;
+  G.Privileges.Add(TSQLDeletePrivilege.Create(Nil));
+  FtoFree:=G;
+  AssertSQl(G,'GRANT DELETE ON A TO B , C');
+  G.Privileges.Clear;
+  G.Privileges.Add(TSQLINSERTPrivilege.Create(Nil));
+  FtoFree:=G;
+  AssertSQl(G,'GRANT INSERT ON A TO B , C');
+  G.Privileges.Clear;
+  G.Privileges.Add(TSQLReferencePrivilege.Create(Nil));
+  FtoFree:=G;
+  AssertSQl(G,'GRANT REFERENCES ON A TO B , C');
+  G.Privileges.Clear;
+  G.Privileges.Add(TSQLAllPrivilege.Create(Nil));
+  FtoFree:=G;
+  AssertSQl(G,'GRANT ALL PRIVILEGES ON A TO B , C');
+  G.GrantOption:=True;
+  AssertSQl(G,'GRANT ALL PRIVILEGES ON A TO B , C WITH GRANT OPTION');
+  G.GrantOption:=False;
+  G.Privileges.Clear;
+  G.Privileges.Add(TSQLSelectPrivilege.Create(Nil));
+  G.Grantees.Clear;
+  G.Grantees.Add(TSQLPublicGrantee.Create(Nil));
+  FtoFree:=G;
+  AssertSQl(G,'GRANT SELECT ON A TO PUBLIC');
+  G.Grantees.Clear;
+  G.Grantees.Add(CreateGrantee('B',TSQLProcedureGrantee));
+  AssertSQl(G,'GRANT SELECT ON A TO PROCEDURE B');
+  G.Grantees.Clear;
+  G.Grantees.Add(CreateGrantee('B',TSQLViewGrantee));
+  AssertSQl(G,'GRANT SELECT ON A TO VIEW B');
+  G.Grantees.Clear;
+  G.Grantees.Add(CreateGrantee('B',TSQLTriggerGrantee));
+  AssertSQl(G,'GRANT SELECT ON A TO TRIGGER B');
+  FtoFree:=G;
+  G.Privileges.Clear;
+  Pu:=TSQLUPdatePrivilege.Create(Nil);
+  PU.Columns:=TSQLElementList.Create(True);
+  PU.Columns.Add(CreateIdentifier('C'));
+  G.Privileges.Add(PU);
+  FtoFree:=G;
+  AssertSQl(G,'GRANT UPDATE (C) ON A TO TRIGGER B');
+  PU.Columns.Add(CreateIdentifier('D'));
+  FtoFree:=G;
+  AssertSQl(G,'GRANT UPDATE (C , D) ON A TO TRIGGER B');
+  G.Privileges.Clear;
+  Pu:=TSQLReferencePrivilege.Create(Nil);
+  PU.Columns:=TSQLElementList.Create(True);
+  PU.Columns.Add(CreateIdentifier('C'));
+  G.Privileges.Add(PU);
+  FtoFree:=G;
+  AssertSQl(G,'GRANT REFERENCES (C) ON A TO TRIGGER B');
+  PU.Columns.Add(CreateIdentifier('D'));
+  FtoFree:=G;
+  AssertSQl(G,'GRANT REFERENCES (C , D) ON A TO TRIGGER B');
+end;
+
+procedure TTestGenerateSQL.TestGrantProcedure;
+
+Var
+  G : TSQLProcedureGrantStatement;
+  U : TSQLUserGrantee;
+  PU : TSQLColumnPrivilege;
+  PG : TSQLProcedureGrantee;
+
+begin
+  G:=TSQLProcedureGrantStatement.Create(Nil);
+  G.ProcedureName:=CreateIdentifier('A');
+  FtoFree:=G;
+  G.Grantees.add(CreateGrantee('B'));
+  FtoFree:=G;
+  AssertSQL(G,'GRANT EXECUTE ON PROCEDURE A TO B');
+  G.Grantees.add(CreateGrantee('C'));
+  FtoFree:=G;
+  AssertSQL(G,'GRANT EXECUTE ON PROCEDURE A TO B , C');
+  G.Grantees.Clear;
+  G.Grantees.Add(CreateGrantee('B',TSQLTriggerGrantee));
+  FtoFree:=G;
+  AssertSQL(G,'GRANT EXECUTE ON PROCEDURE A TO TRIGGER B');
+  G.Grantees.Clear;
+  G.Grantees.Add(CreateGrantee('B',TSQLProcedureGrantee));
+  FtoFree:=G;
+  AssertSQL(G,'GRANT EXECUTE ON PROCEDURE A TO PROCEDURE B');
+  G.Grantees.Clear;
+  G.Grantees.Add(CreateGrantee('B',TSQLViewGrantee));
+  FtoFree:=G;
+  AssertSQL(G,'GRANT EXECUTE ON PROCEDURE A TO VIEW B');
+  G.Grantees.Clear;
+  G.Grantees.Add(CreateGrantee('B',TSQLPublicGrantee));
+  FtoFree:=G;
+  AssertSQL(G,'GRANT EXECUTE ON PROCEDURE A TO PUBLIC');
+end;
+
+procedure TTestGenerateSQL.TestGrantRole;
+Var
+  G : TSQLRoleGrantStatement;
+  U : TSQLUserGrantee;
+
+begin
+  G:=TSQLRoleGrantStatement.Create(Nil);
+  G.Roles.Add(CreateIdentifier('A'));
+  FtoFree:=G;
+  G.Grantees.add(CreateGrantee('B'));
+  FtoFree:=G;
+  AssertSQL(G,'GRANT A TO B');
+  G.Roles.Add(CreateIdentifier('C'));
+  FtoFree:=G;
+  AssertSQL(G,'GRANT A , C TO B');
+  G.Grantees.add(CreateGrantee('D'));
+  FtoFree:=G;
+  AssertSQL(G,'GRANT A , C TO B , D');
+  G.AdminOption:=True;
+  AssertSQL(G,'GRANT A , C TO B , D WITH ADMIN OPTION');
+end;
+
+procedure TTestGenerateSQL.TestRevokeTable;
+Var
+  G : TSQLTableRevokeStatement;
+  U : TSQLUserGrantee;
+  PU : TSQLColumnPrivilege;
+  PG : TSQLProcedureGrantee;
+
+begin
+  G:=TSQLTableRevokeStatement.Create(Nil);
+  G.TableName:=CreateIdentifier('A');
+  FtoFree:=G;
+  G.Privileges.Add(TSQLSelectPrivilege.Create(Nil));
+  G.Grantees.add(CreateGrantee('B'));
+  FtoFree:=G;
+  AssertSQl(G,'REVOKE SELECT ON A FROM B');
+  G.Grantees.add(CreateGrantee('C'));
+  FtoFree:=G;
+  AssertSQl(G,'REVOKE SELECT ON A FROM B , C');
+  G.Privileges.Clear;
+  G.Privileges.Add(TSQLUPdatePrivilege.Create(Nil));
+  FtoFree:=G;
+  AssertSQl(G,'REVOKE UPDATE ON A FROM B , C');
+  G.Privileges.Clear;
+  G.Privileges.Add(TSQLDeletePrivilege.Create(Nil));
+  FtoFree:=G;
+  AssertSQl(G,'REVOKE DELETE ON A FROM B , C');
+  G.Privileges.Clear;
+  G.Privileges.Add(TSQLINSERTPrivilege.Create(Nil));
+  FtoFree:=G;
+  AssertSQl(G,'REVOKE INSERT ON A FROM B , C');
+  G.Privileges.Clear;
+  G.Privileges.Add(TSQLReferencePrivilege.Create(Nil));
+  FtoFree:=G;
+  AssertSQl(G,'REVOKE REFERENCES ON A FROM B , C');
+  G.Privileges.Clear;
+  G.Privileges.Add(TSQLAllPrivilege.Create(Nil));
+  FtoFree:=G;
+  AssertSQl(G,'REVOKE ALL PRIVILEGES ON A FROM B , C');
+  G.GrantOption:=True;
+  AssertSQl(G,'REVOKE GRANT OPTION FOR ALL PRIVILEGES ON A FROM B , C');
+  G.GrantOption:=False;
+  G.Privileges.Clear;
+  G.Privileges.Add(TSQLSelectPrivilege.Create(Nil));
+  G.Grantees.Clear;
+  G.Grantees.Add(TSQLPublicGrantee.Create(Nil));
+  FtoFree:=G;
+  AssertSQl(G,'REVOKE SELECT ON A FROM PUBLIC');
+  G.Grantees.Clear;
+  G.Grantees.Add(CreateGrantee('B',TSQLProcedureGrantee));
+  AssertSQl(G,'REVOKE SELECT ON A FROM PROCEDURE B');
+  G.Grantees.Clear;
+  G.Grantees.Add(CreateGrantee('B',TSQLViewGrantee));
+  AssertSQl(G,'REVOKE SELECT ON A FROM VIEW B');
+  G.Grantees.Clear;
+  G.Grantees.Add(CreateGrantee('B',TSQLTriggerGrantee));
+  AssertSQl(G,'REVOKE SELECT ON A FROM TRIGGER B');
+  FtoFree:=G;
+  G.Privileges.Clear;
+  Pu:=TSQLUPdatePrivilege.Create(Nil);
+  PU.Columns:=TSQLElementList.Create(True);
+  PU.Columns.Add(CreateIdentifier('C'));
+  G.Privileges.Add(PU);
+  FtoFree:=G;
+  AssertSQl(G,'REVOKE UPDATE (C) ON A FROM TRIGGER B');
+  PU.Columns.Add(CreateIdentifier('D'));
+  FtoFree:=G;
+  AssertSQl(G,'REVOKE UPDATE (C , D) ON A FROM TRIGGER B');
+  G.Privileges.Clear;
+  Pu:=TSQLReferencePrivilege.Create(Nil);
+  PU.Columns:=TSQLElementList.Create(True);
+  PU.Columns.Add(CreateIdentifier('C'));
+  G.Privileges.Add(PU);
+  FtoFree:=G;
+  AssertSQl(G,'REVOKE REFERENCES (C) ON A FROM TRIGGER B');
+  PU.Columns.Add(CreateIdentifier('D'));
+  FtoFree:=G;
+  AssertSQl(G,'REVOKE REFERENCES (C , D) ON A FROM TRIGGER B');
+end;
+
+procedure TTestGenerateSQL.TestRevokeProcedure;
+Var
+  G : TSQLProcedureRevokeStatement;
+  PG : TSQLProcedureGrantee;
+
+begin
+  G:=TSQLProcedureRevokeStatement.Create(Nil);
+  G.ProcedureName:=CreateIdentifier('A');
+  FtoFree:=G;
+  G.Grantees.add(CreateGrantee('B'));
+  FtoFree:=G;
+  AssertSQL(G,'REVOKE EXECUTE ON PROCEDURE A FROM B');
+  G.Grantees.add(CreateGrantee('C'));
+  FtoFree:=G;
+  AssertSQL(G,'REVOKE EXECUTE ON PROCEDURE A FROM B , C');
+  G.Grantees.Clear;
+  G.Grantees.Add(CreateGrantee('B',TSQLTriggerGrantee));
+  FtoFree:=G;
+  AssertSQL(G,'REVOKE EXECUTE ON PROCEDURE A FROM TRIGGER B');
+  G.Grantees.Clear;
+  G.Grantees.Add(CreateGrantee('B',TSQLProcedureGrantee));
+  FtoFree:=G;
+  AssertSQL(G,'REVOKE EXECUTE ON PROCEDURE A FROM PROCEDURE B');
+  G.Grantees.Clear;
+  G.Grantees.Add(CreateGrantee('B',TSQLViewGrantee));
+  FtoFree:=G;
+  AssertSQL(G,'REVOKE EXECUTE ON PROCEDURE A FROM VIEW B');
+  G.Grantees.Clear;
+  G.Grantees.Add(CreateGrantee('B',TSQLPublicGrantee));
+  FtoFree:=G;
+  AssertSQL(G,'REVOKE EXECUTE ON PROCEDURE A FROM PUBLIC');
+end;
+
+procedure TTestGenerateSQL.TestRevokeRole;
+Var
+  G : TSQLRoleRevokeStatement;
+
+begin
+  G:=TSQLRoleRevokeStatement.Create(Nil);
+  G.Roles.Add(CreateIdentifier('A'));
+  FtoFree:=G;
+  G.Grantees.add(CreateGrantee('B'));
+  FtoFree:=G;
+  AssertSQL(G,'REVOKE A FROM B');
+  G.Roles.Add(CreateIdentifier('C'));
+  FtoFree:=G;
+  AssertSQL(G,'REVOKE A , C FROM B');
+  G.Grantees.add(CreateGrantee('D'));
+  FtoFree:=G;
+  AssertSQL(G,'REVOKE A , C FROM B , D');
+  G.AdminOption:=True;
+  AssertSQL(G,'REVOKE A , C FROM B , D');
+end;
+
 
 
 initialization
 initialization
   RegisterTest(TTestGenerateSQL);
   RegisterTest(TTestGenerateSQL);

+ 972 - 1
packages/fcl-db/tests/tcparser.pas

@@ -761,6 +761,76 @@ type
     procedure TestTwoArgumentsFunction;
     procedure TestTwoArgumentsFunction;
   end;
   end;
 
 
+  { TTestGrantParser }
+
+  TTestGrantParser = Class(TTestSQLParser)
+  Private
+    FStatement : TSQLGrantStatement;
+    Function TestGrant(Const ASource : String) : TSQLGrantStatement;
+    Procedure TestGrantError(Const ASource : String);
+    Property Statement : TSQLGrantStatement Read FStatement;
+  Published
+    Procedure TestSimple;
+    Procedure Test2Operations;
+    Procedure TestDeletePrivilege;
+    Procedure TestUpdatePrivilege;
+    Procedure TestInsertPrivilege;
+    Procedure TestReferencePrivilege;
+    Procedure TestAllPrivileges;
+    Procedure TestAllPrivileges2;
+    Procedure TestUpdateColPrivilege;
+    Procedure TestUpdate2ColsPrivilege;
+    Procedure TestReferenceColPrivilege;
+    Procedure TestReference2ColsPrivilege;
+    Procedure TestUserPrivilege;
+    Procedure TestUserPrivilegeWithGrant;
+    procedure TestGroupPrivilege;
+    procedure TestProcedurePrivilege;
+    procedure TestViewPrivilege;
+    procedure TestTriggerPrivilege;
+    procedure TestPublicPrivilege;
+    Procedure TestExecuteToUser;
+    procedure TestExecuteToProcedure;
+    procedure TestRoleToUser;
+    procedure TestRoleToUserWithAdmin;
+    procedure TestRoleToPublic;
+    procedure Test2RolesToUser;
+  end;
+  { TTestGrantParser }
+
+  TTestRevokeParser = Class(TTestSQLParser)
+  Private
+    FStatement : TSQLRevokeStatement;
+    Function TestRevoke(Const ASource : String) : TSQLRevokeStatement;
+    Procedure TestRevokeError(Const ASource : String);
+    Property Statement : TSQLRevokeStatement Read FStatement;
+  Published
+    Procedure TestSimple;
+    Procedure Test2Operations;
+    Procedure TestDeletePrivilege;
+    Procedure TestUpdatePrivilege;
+    Procedure TestInsertPrivilege;
+    Procedure TestReferencePrivilege;
+    Procedure TestAllPrivileges;
+    Procedure TestAllPrivileges2;
+    Procedure TestUpdateColPrivilege;
+    Procedure TestUpdate2ColsPrivilege;
+    Procedure TestReferenceColPrivilege;
+    Procedure TestReference2ColsPrivilege;
+    Procedure TestUserPrivilege;
+    Procedure TestUserPrivilegeWithRevoke;
+    procedure TestGroupPrivilege;
+    procedure TestProcedurePrivilege;
+    procedure TestViewPrivilege;
+    procedure TestTriggerPrivilege;
+    procedure TestPublicPrivilege;
+    Procedure TestExecuteToUser;
+    procedure TestExecuteToProcedure;
+    procedure TestRoleToUser;
+    procedure TestRoleToPublic;
+    procedure Test2RolesToUser;
+  end;
+
 implementation
 implementation
 
 
 uses typinfo;
 uses typinfo;
@@ -6995,6 +7065,905 @@ begin
   AssertEquals('Correct return type',sdtInteger,Statement.ReturnType.DataType);
   AssertEquals('Correct return type',sdtInteger,Statement.ReturnType.DataType);
 end;
 end;
 
 
+{ TTestGrantParser }
+
+function TTestGrantParser.TestGrant(const ASource: String): TSQLGrantStatement;
+begin
+  CreateParser(ASource);
+  FToFree:=Parser.Parse;
+  If not (FToFree is  TSQLGrantStatement) then
+    Fail(Format('Wrong parse result class. Expected TSQLGrantStatement, got %s',[FTofree.ClassName]));
+  Result:=TSQLGrantStatement(Ftofree);
+  FSTatement:=Result;
+  AssertEquals('End of stream reached',tsqlEOF,Parser.CurrentToken);
+end;
+
+procedure TTestGrantParser.TestGrantError(const ASource: String);
+begin
+  FErrSource:=ASource;
+  AssertException(ESQLParser,@TestParseError);
+end;
+
+procedure TTestGrantParser.TestSimple;
+
+Var
+  t : TSQLTableGrantStatement;
+  G : TSQLUSerGrantee;
+
+begin
+  TestGrant('GRANT SELECT ON A TO B');
+  T:=TSQLTableGrantStatement(CheckClass(Statement,TSQLTableGrantStatement));
+  AssertIdentifierName('Table name','A',T.TableName);
+  AssertEquals('One grantee', 1,T.Grantees.Count);
+  G:=TSQLUSerGrantee(CheckClass(T.Grantees[0],TSQLUSerGrantee));
+  AssertEquals('Grantee B','B',G.Name);
+  AssertEquals('One permission',1,T.Privileges.Count);
+  CheckClass(T.Privileges[0],TSQLSelectPrivilege);
+  AssertEquals('No grant option',False,T.GrantOption);
+end;
+
+procedure TTestGrantParser.Test2Operations;
+
+Var
+  t : TSQLTableGrantStatement;
+  G : TSQLUSerGrantee;
+
+begin
+  TestGrant('GRANT SELECT,INSERT ON A TO B');
+  T:=TSQLTableGrantStatement(CheckClass(Statement,TSQLTableGrantStatement));
+  AssertIdentifierName('Table name','A',T.TableName);
+  AssertEquals('One grantee', 1,T.Grantees.Count);
+  G:=TSQLUSerGrantee(CheckClass(T.Grantees[0],TSQLUSerGrantee));
+  AssertEquals('Grantee B','B',G.Name);
+  AssertEquals('Two permissions',2,T.Privileges.Count);
+  CheckClass(T.Privileges[0],TSQLSelectPrivilege);
+  CheckClass(T.Privileges[1],TSQLINSERTPrivilege);
+  AssertEquals('No grant option',False,T.GrantOption);
+end;
+
+procedure TTestGrantParser.TestDeletePrivilege;
+
+Var
+  t : TSQLTableGrantStatement;
+  G : TSQLUSerGrantee;
+
+begin
+  TestGrant('GRANT DELETE ON A TO B');
+  T:=TSQLTableGrantStatement(CheckClass(Statement,TSQLTableGrantStatement));
+  AssertIdentifierName('Table name','A',T.TableName);
+  AssertEquals('One grantee', 1,T.Grantees.Count);
+  G:=TSQLUSerGrantee(CheckClass(T.Grantees[0],TSQLUSerGrantee));
+  AssertEquals('Grantee B','B',G.Name);
+  AssertEquals('One permission',1,T.Privileges.Count);
+  CheckClass(T.Privileges[0],TSQLDeletePrivilege);
+  AssertEquals('No grant option',False,T.GrantOption);
+end;
+
+procedure TTestGrantParser.TestUpdatePrivilege;
+Var
+  t : TSQLTableGrantStatement;
+  G : TSQLUSerGrantee;
+
+begin
+  TestGrant('GRANT UPDATE ON A TO B');
+  T:=TSQLTableGrantStatement(CheckClass(Statement,TSQLTableGrantStatement));
+  AssertIdentifierName('Table name','A',T.TableName);
+  AssertEquals('One grantee', 1,T.Grantees.Count);
+  G:=TSQLUSerGrantee(CheckClass(T.Grantees[0],TSQLUSerGrantee));
+  AssertEquals('Grantee B','B',G.Name);
+  AssertEquals('One permission',1,T.Privileges.Count);
+  CheckClass(T.Privileges[0],TSQLUPDATEPrivilege);
+  AssertEquals('No grant option',False,T.GrantOption);
+end;
+
+procedure TTestGrantParser.TestInsertPrivilege;
+
+Var
+  t : TSQLTableGrantStatement;
+  G : TSQLUSerGrantee;
+
+begin
+  TestGrant('GRANT INSERT ON A TO B');
+  T:=TSQLTableGrantStatement(CheckClass(Statement,TSQLTableGrantStatement));
+  AssertIdentifierName('Table name','A',T.TableName);
+  AssertEquals('One grantee', 1,T.Grantees.Count);
+  G:=TSQLUSerGrantee(CheckClass(T.Grantees[0],TSQLUSerGrantee));
+  AssertEquals('Grantee B','B',G.Name);
+  AssertEquals('One permission',1,T.Privileges.Count);
+  CheckClass(T.Privileges[0],TSQLInsertPrivilege);
+  AssertEquals('No grant option',False,T.GrantOption);
+end;
+
+procedure TTestGrantParser.TestReferencePrivilege;
+Var
+  t : TSQLTableGrantStatement;
+  G : TSQLUSerGrantee;
+
+begin
+  TestGrant('GRANT REFERENCES ON A TO B');
+  T:=TSQLTableGrantStatement(CheckClass(Statement,TSQLTableGrantStatement));
+  AssertIdentifierName('Table name','A',T.TableName);
+  AssertEquals('One grantee', 1,T.Grantees.Count);
+  G:=TSQLUSerGrantee(CheckClass(T.Grantees[0],TSQLUSerGrantee));
+  AssertEquals('Grantee B','B',G.Name);
+  AssertEquals('One permission',1,T.Privileges.Count);
+  CheckClass(T.Privileges[0],TSQLReferencePrivilege);
+  AssertEquals('No grant option',False,T.GrantOption);
+end;
+
+procedure TTestGrantParser.TestAllPrivileges;
+Var
+  t : TSQLTableGrantStatement;
+  G : TSQLUSerGrantee;
+
+begin
+  TestGrant('GRANT ALL ON A TO B');
+  T:=TSQLTableGrantStatement(CheckClass(Statement,TSQLTableGrantStatement));
+  AssertIdentifierName('Table name','A',T.TableName);
+  AssertEquals('One grantee', 1,T.Grantees.Count);
+  G:=TSQLUSerGrantee(CheckClass(T.Grantees[0],TSQLUSerGrantee));
+  AssertEquals('Grantee B','B',G.Name);
+  AssertEquals('One permission',1,T.Privileges.Count);
+  CheckClass(T.Privileges[0],TSQLAllPrivilege);
+  AssertEquals('No grant option',False,T.GrantOption);
+end;
+
+procedure TTestGrantParser.TestAllPrivileges2;
+Var
+  t : TSQLTableGrantStatement;
+  G : TSQLUSerGrantee;
+
+begin
+  TestGrant('GRANT ALL PRIVILEGES ON A TO B');
+  T:=TSQLTableGrantStatement(CheckClass(Statement,TSQLTableGrantStatement));
+  AssertIdentifierName('Table name','A',T.TableName);
+  AssertEquals('One grantee', 1,T.Grantees.Count);
+  G:=TSQLUSerGrantee(CheckClass(T.Grantees[0],TSQLUSerGrantee));
+  AssertEquals('Grantee B','B',G.Name);
+  AssertEquals('One permission',1,T.Privileges.Count);
+  CheckClass(T.Privileges[0],TSQLAllPrivilege);
+  AssertEquals('No grant option',False,T.GrantOption);
+end;
+
+procedure TTestGrantParser.TestUpdateColPrivilege;
+Var
+  t : TSQLTableGrantStatement;
+  G : TSQLUSerGrantee;
+  U : TSQLUPDATEPrivilege;
+
+begin
+  TestGrant('GRANT UPDATE (C) ON A TO B');
+  T:=TSQLTableGrantStatement(CheckClass(Statement,TSQLTableGrantStatement));
+  AssertIdentifierName('Table name','A',T.TableName);
+  AssertEquals('One grantee', 1,T.Grantees.Count);
+  G:=TSQLUSerGrantee(CheckClass(T.Grantees[0],TSQLUSerGrantee));
+  AssertEquals('Grantee B','B',G.Name);
+  AssertEquals('One permission',1,T.Privileges.Count);
+  U:=TSQLUPDATEPrivilege(CheckClass(T.Privileges[0],TSQLUPDATEPrivilege));
+  AssertEquals('1 column',1,U.Columns.Count);
+  AssertIdentifierName('Column C','C',U.Columns[0]);
+  AssertEquals('No grant option',False,T.GrantOption);
+end;
+
+procedure TTestGrantParser.TestUpdate2ColsPrivilege;
+Var
+  t : TSQLTableGrantStatement;
+  G : TSQLUSerGrantee;
+  U : TSQLUPDATEPrivilege;
+
+begin
+  TestGrant('GRANT UPDATE (C,D) ON A TO B');
+  T:=TSQLTableGrantStatement(CheckClass(Statement,TSQLTableGrantStatement));
+  AssertIdentifierName('Table name','A',T.TableName);
+  AssertEquals('One grantee', 1,T.Grantees.Count);
+  G:=TSQLUSerGrantee(CheckClass(T.Grantees[0],TSQLUSerGrantee));
+  AssertEquals('Grantee B','B',G.Name);
+  AssertEquals('One permission',1,T.Privileges.Count);
+  U:=TSQLUPDATEPrivilege(CheckClass(T.Privileges[0],TSQLUPDATEPrivilege));
+  AssertEquals('2 column',2,U.Columns.Count);
+  AssertIdentifierName('Column C','C',U.Columns[0]);
+  AssertIdentifierName('Column D','D',U.Columns[1]);
+  AssertEquals('No grant option',False,T.GrantOption);
+end;
+
+procedure TTestGrantParser.TestReferenceColPrivilege;
+Var
+  t : TSQLTableGrantStatement;
+  G : TSQLUSerGrantee;
+  U : TSQLReferencePrivilege;
+
+begin
+  TestGrant('GRANT REFERENCES (C) ON A TO B');
+  T:=TSQLTableGrantStatement(CheckClass(Statement,TSQLTableGrantStatement));
+  AssertIdentifierName('Table name','A',T.TableName);
+  AssertEquals('One grantee', 1,T.Grantees.Count);
+  G:=TSQLUSerGrantee(CheckClass(T.Grantees[0],TSQLUSerGrantee));
+  AssertEquals('Grantee B','B',G.Name);
+  AssertEquals('One permission',1,T.Privileges.Count);
+  U:=TSQLReferencePrivilege(CheckClass(T.Privileges[0],TSQLReferencePrivilege));
+  AssertEquals('1 column',1,U.Columns.Count);
+  AssertIdentifierName('Column C','C',U.Columns[0]);
+  AssertEquals('No grant option',False,T.GrantOption);
+end;
+
+procedure TTestGrantParser.TestReference2ColsPrivilege;
+Var
+  t : TSQLTableGrantStatement;
+  G : TSQLUSerGrantee;
+  U : TSQLReferencePrivilege;
+
+begin
+  TestGrant('GRANT REFERENCES (C,D) ON A TO B');
+  T:=TSQLTableGrantStatement(CheckClass(Statement,TSQLTableGrantStatement));
+  AssertIdentifierName('Table name','A',T.TableName);
+  AssertEquals('One grantee', 1,T.Grantees.Count);
+  G:=TSQLUSerGrantee(CheckClass(T.Grantees[0],TSQLUSerGrantee));
+  AssertEquals('Grantee B','B',G.Name);
+  AssertEquals('One permission',1,T.Privileges.Count);
+  U:=TSQLReferencePrivilege(CheckClass(T.Privileges[0],TSQLReferencePrivilege));
+  AssertEquals('2 column',2,U.Columns.Count);
+  AssertIdentifierName('Column C','C',U.Columns[0]);
+  AssertIdentifierName('Column D','D',U.Columns[1]);
+  AssertEquals('No grant option',False,T.GrantOption);
+end;
+
+procedure TTestGrantParser.TestUserPrivilege;
+
+Var
+  t : TSQLTableGrantStatement;
+  G : TSQLUSerGrantee;
+
+begin
+    TestGrant('GRANT SELECT ON A TO USER B');
+    T:=TSQLTableGrantStatement(CheckClass(Statement,TSQLTableGrantStatement));
+    AssertIdentifierName('Table name','A',T.TableName);
+    AssertEquals('One grantee', 1,T.Grantees.Count);
+    G:=TSQLUSerGrantee(CheckClass(T.Grantees[0],TSQLUSerGrantee));
+    AssertEquals('Grantee B','B',G.Name);
+    AssertEquals('One permission',1,T.Privileges.Count);
+    CheckClass(T.Privileges[0],TSQLSelectPrivilege);
+    AssertEquals('No grant option',False,T.GrantOption);
+end;
+
+procedure TTestGrantParser.TestUserPrivilegeWithGrant;
+Var
+  t : TSQLTableGrantStatement;
+  G : TSQLUSerGrantee;
+
+begin
+    TestGrant('GRANT SELECT ON A TO USER B WITH GRANT OPTION');
+    T:=TSQLTableGrantStatement(CheckClass(Statement,TSQLTableGrantStatement));
+    AssertIdentifierName('Table name','A',T.TableName);
+    AssertEquals('One grantee', 1,T.Grantees.Count);
+    G:=TSQLUSerGrantee(CheckClass(T.Grantees[0],TSQLUSerGrantee));
+    AssertEquals('Grantee B','B',G.Name);
+    AssertEquals('One permission',1,T.Privileges.Count);
+    CheckClass(T.Privileges[0],TSQLSelectPrivilege);
+    AssertEquals('With grant option',True,T.GrantOption);
+end;
+
+procedure TTestGrantParser.TestGroupPrivilege;
+
+Var
+  t : TSQLTableGrantStatement;
+  G : TSQLGroupGrantee;
+
+begin
+    TestGrant('GRANT SELECT ON A TO GROUP B');
+    T:=TSQLTableGrantStatement(CheckClass(Statement,TSQLTableGrantStatement));
+    AssertIdentifierName('Table name','A',T.TableName);
+    AssertEquals('One grantee', 1,T.Grantees.Count);
+    G:=TSQLGroupGrantee(CheckClass(T.Grantees[0],TSQLGroupGrantee));
+    AssertEquals('Grantee B','B',G.Name);
+    AssertEquals('One permission',1,T.Privileges.Count);
+    CheckClass(T.Privileges[0],TSQLSelectPrivilege);
+    AssertEquals('No grant option',False,T.GrantOption);
+end;
+
+procedure TTestGrantParser.TestProcedurePrivilege;
+Var
+  t : TSQLTableGrantStatement;
+  G : TSQLProcedureGrantee;
+
+begin
+  TestGrant('GRANT SELECT ON A TO PROCEDURE B');
+  T:=TSQLTableGrantStatement(CheckClass(Statement,TSQLTableGrantStatement));
+  AssertIdentifierName('Table name','A',T.TableName);
+  AssertEquals('One grantee', 1,T.Grantees.Count);
+  G:=TSQLProcedureGrantee(CheckClass(T.Grantees[0],TSQLProcedureGrantee));
+  AssertEquals('Grantee B','B',G.Name);
+  AssertEquals('One permission',1,T.Privileges.Count);
+  CheckClass(T.Privileges[0],TSQLSelectPrivilege);
+  AssertEquals('No grant option',False,T.GrantOption);
+end;
+
+procedure TTestGrantParser.TestViewPrivilege;
+Var
+  t : TSQLTableGrantStatement;
+  G : TSQLViewGrantee;
+
+begin
+  TestGrant('GRANT SELECT ON A TO VIEW B');
+  T:=TSQLTableGrantStatement(CheckClass(Statement,TSQLTableGrantStatement));
+  AssertIdentifierName('Table name','A',T.TableName);
+  AssertEquals('One grantee', 1,T.Grantees.Count);
+  G:=TSQLViewGrantee(CheckClass(T.Grantees[0],TSQLViewGrantee));
+  AssertEquals('Grantee B','B',G.Name);
+  AssertEquals('One permission',1,T.Privileges.Count);
+  CheckClass(T.Privileges[0],TSQLSelectPrivilege);
+  AssertEquals('No grant option',False,T.GrantOption);
+end;
+
+procedure TTestGrantParser.TestTriggerPrivilege;
+
+Var
+  t : TSQLTableGrantStatement;
+  G : TSQLTriggerGrantee;
+
+begin
+  TestGrant('GRANT SELECT ON A TO TRIGGER B');
+  T:=TSQLTableGrantStatement(CheckClass(Statement,TSQLTableGrantStatement));
+  AssertIdentifierName('Table name','A',T.TableName);
+  AssertEquals('One grantee', 1,T.Grantees.Count);
+  G:=TSQLTriggerGrantee(CheckClass(T.Grantees[0],TSQLTriggerGrantee));
+  AssertEquals('Grantee B','B',G.Name);
+  AssertEquals('One permission',1,T.Privileges.Count);
+  CheckClass(T.Privileges[0],TSQLSelectPrivilege);
+  AssertEquals('No grant option',False,T.GrantOption);
+end;
+
+procedure TTestGrantParser.TestPublicPrivilege;
+Var
+  t : TSQLTableGrantStatement;
+  P : TSQLPublicGrantee;
+
+begin
+  TestGrant('GRANT SELECT ON A TO PUBLIC');
+  T:=TSQLTableGrantStatement(CheckClass(Statement,TSQLTableGrantStatement));
+  AssertIdentifierName('Table name','A',T.TableName);
+  AssertEquals('One grantee', 1,T.Grantees.Count);
+  (CheckClass(T.Grantees[0],TSQLPublicGrantee));
+  AssertEquals('One permission',1,T.Privileges.Count);
+  CheckClass(T.Privileges[0],TSQLSelectPrivilege);
+  AssertEquals('No grant option',False,T.GrantOption);
+end;
+
+procedure TTestGrantParser.TestExecuteToUser;
+Var
+  P : TSQLProcedureGrantStatement;
+  U : TSQLUserGrantee;
+
+begin
+  TestGrant('GRANT EXECUTE ON PROCEDURE A TO B');
+  P:=TSQLProcedureGrantStatement(CheckClass(Statement,TSQLProcedureGrantStatement));
+  AssertIdentifierName('Procedure name','A',P.ProcedureName);
+  AssertEquals('One grantee', 1,P.Grantees.Count);
+  U:=TSQLUserGrantee(CheckClass(P.Grantees[0],TSQLUserGrantee));
+  AssertEquals('User name','B',U.Name);
+  AssertEquals('No grant option',False,P.GrantOption);
+end;
+
+procedure TTestGrantParser.TestExecuteToProcedure;
+Var
+  P : TSQLProcedureGrantStatement;
+  U : TSQLProcedureGrantee;
+
+begin
+  TestGrant('GRANT EXECUTE ON PROCEDURE A TO PROCEDURE B');
+  P:=TSQLProcedureGrantStatement(CheckClass(Statement,TSQLProcedureGrantStatement));
+  AssertIdentifierName('Procedure name','A',P.ProcedureName);
+  AssertEquals('One grantee', 1,P.Grantees.Count);
+  U:=TSQLProcedureGrantee(CheckClass(P.Grantees[0],TSQLProcedureGrantee));
+  AssertEquals('Procedure grantee name','B',U.Name);
+  AssertEquals('No grant option',False,P.GrantOption);
+end;
+
+procedure TTestGrantParser.TestRoleToUser;
+Var
+  R : TSQLRoleGrantStatement;
+  U : TSQLUserGrantee;
+
+begin
+  TestGrant('GRANT A TO B');
+  R:=TSQLRoleGrantStatement(CheckClass(Statement,TSQLRoleGrantStatement));
+  AssertEquals('One role', 1,R.Roles.Count);
+  AssertIdentifierName('Role name','A',R.Roles[0]);
+  AssertEquals('One grantee', 1,R.Grantees.Count);
+  U:=TSQLUserGrantee(CheckClass(R.Grantees[0],TSQLUserGrantee));
+  AssertEquals('Procedure grantee name','B',U.Name);
+  AssertEquals('No admin option',False,R.AdminOption);
+end;
+
+procedure TTestGrantParser.TestRoleToUserWithAdmin;
+Var
+  R : TSQLRoleGrantStatement;
+  U : TSQLUserGrantee;
+
+begin
+  TestGrant('GRANT A TO B WITH ADMIN OPTION');
+  R:=TSQLRoleGrantStatement(CheckClass(Statement,TSQLRoleGrantStatement));
+  AssertEquals('One role', 1,R.Roles.Count);
+  AssertIdentifierName('Role name','A',R.Roles[0]);
+  AssertEquals('One grantee', 1,R.Grantees.Count);
+  U:=TSQLUserGrantee(CheckClass(R.Grantees[0],TSQLUserGrantee));
+  AssertEquals('Procedure grantee name','B',U.Name);
+  AssertEquals('Admin option',True,R.AdminOption);
+end;
+
+procedure TTestGrantParser.TestRoleToPublic;
+Var
+  R : TSQLRoleGrantStatement;
+
+begin
+  TestGrant('GRANT A TO PUBLIC');
+  R:=TSQLRoleGrantStatement(CheckClass(Statement,TSQLRoleGrantStatement));
+  AssertEquals('One role', 1,R.Roles.Count);
+  AssertIdentifierName('Role name','A',R.Roles[0]);
+  AssertEquals('One grantee', 1,R.Grantees.Count);
+  CheckClass(R.Grantees[0],TSQLPublicGrantee);
+  AssertEquals('No admin option',False,R.AdminOption);
+end;
+
+procedure TTestGrantParser.Test2RolesToUser;
+
+Var
+  R : TSQLRoleGrantStatement;
+  U : TSQLUserGrantee;
+
+begin
+  TestGrant('GRANT A,C TO B');
+  R:=TSQLRoleGrantStatement(CheckClass(Statement,TSQLRoleGrantStatement));
+  AssertEquals('2 roles', 2,R.Roles.Count);
+  AssertIdentifierName('Role name','A',R.Roles[0]);
+  AssertIdentifierName('Role name','C',R.Roles[1]);
+  AssertEquals('One grantee', 1,R.Grantees.Count);
+  U:=TSQLUserGrantee(CheckClass(R.Grantees[0],TSQLUserGrantee));
+  AssertEquals('Procedure grantee name','B',U.Name);
+  AssertEquals('No admin option',False,R.AdminOption);
+end;
+
+{ TTestRevokeParser }
+
+function TTestRevokeParser.TestRevoke(const ASource: String): TSQLRevokeStatement;
+begin
+  CreateParser(ASource);
+  FToFree:=Parser.Parse;
+  If not (FToFree is  TSQLRevokeStatement) then
+    Fail(Format('Wrong parse result class. Expected TSQLRevokeStatement, got %s',[FTofree.ClassName]));
+  Result:=TSQLRevokeStatement(Ftofree);
+  FSTatement:=Result;
+  AssertEquals('End of stream reached',tsqlEOF,Parser.CurrentToken);
+end;
+
+procedure TTestRevokeParser.TestRevokeError(const ASource: String);
+begin
+  FErrSource:=ASource;
+  AssertException(ESQLParser,@TestParseError);
+end;
+
+procedure TTestRevokeParser.TestSimple;
+
+Var
+  t : TSQLTableRevokeStatement;
+  G : TSQLUSerGrantee;
+
+begin
+  TestRevoke('Revoke SELECT ON A FROM B');
+  T:=TSQLTableRevokeStatement(CheckClass(Statement,TSQLTableRevokeStatement));
+  AssertIdentifierName('Table name','A',T.TableName);
+  AssertEquals('One Grantee', 1,T.Grantees.Count);
+  G:=TSQLUSerGrantee(CheckClass(T.Grantees[0],TSQLUSerGrantee));
+  AssertEquals('Grantee B','B',G.Name);
+  AssertEquals('One permission',1,T.Privileges.Count);
+  CheckClass(T.Privileges[0],TSQLSelectPrivilege);
+  AssertEquals('No Revoke option',False,T.GrantOption);
+end;
+
+procedure TTestRevokeParser.Test2Operations;
+
+Var
+  t : TSQLTableRevokeStatement;
+  G : TSQLUSerGrantee;
+
+begin
+  TestRevoke('Revoke SELECT,INSERT ON A FROM B');
+  T:=TSQLTableRevokeStatement(CheckClass(Statement,TSQLTableRevokeStatement));
+  AssertIdentifierName('Table name','A',T.TableName);
+  AssertEquals('One Grantee', 1,T.Grantees.Count);
+  G:=TSQLUSerGrantee(CheckClass(T.Grantees[0],TSQLUSerGrantee));
+  AssertEquals('Grantee B','B',G.Name);
+  AssertEquals('Two permissions',2,T.Privileges.Count);
+  CheckClass(T.Privileges[0],TSQLSelectPrivilege);
+  CheckClass(T.Privileges[1],TSQLINSERTPrivilege);
+  AssertEquals('No Revoke option',False,T.GrantOption);
+end;
+
+procedure TTestRevokeParser.TestDeletePrivilege;
+
+Var
+  t : TSQLTableRevokeStatement;
+  G : TSQLUSerGrantee;
+
+begin
+  TestRevoke('Revoke DELETE ON A FROM B');
+  T:=TSQLTableRevokeStatement(CheckClass(Statement,TSQLTableRevokeStatement));
+  AssertIdentifierName('Table name','A',T.TableName);
+  AssertEquals('One Grantee', 1,T.Grantees.Count);
+  G:=TSQLUSerGrantee(CheckClass(T.Grantees[0],TSQLUSerGrantee));
+  AssertEquals('Grantee B','B',G.Name);
+  AssertEquals('One permission',1,T.Privileges.Count);
+  CheckClass(T.Privileges[0],TSQLDeletePrivilege);
+  AssertEquals('No Revoke option',False,T.GrantOption);
+end;
+
+procedure TTestRevokeParser.TestUpdatePrivilege;
+Var
+  t : TSQLTableRevokeStatement;
+  G : TSQLUSerGrantee;
+
+begin
+  TestRevoke('Revoke UPDATE ON A FROM B');
+  T:=TSQLTableRevokeStatement(CheckClass(Statement,TSQLTableRevokeStatement));
+  AssertIdentifierName('Table name','A',T.TableName);
+  AssertEquals('One Grantee', 1,T.Grantees.Count);
+  G:=TSQLUSerGrantee(CheckClass(T.Grantees[0],TSQLUSerGrantee));
+  AssertEquals('Grantee B','B',G.Name);
+  AssertEquals('One permission',1,T.Privileges.Count);
+  CheckClass(T.Privileges[0],TSQLUPDATEPrivilege);
+  AssertEquals('No Revoke option',False,T.GrantOption);
+end;
+
+procedure TTestRevokeParser.TestInsertPrivilege;
+
+Var
+  t : TSQLTableRevokeStatement;
+  G : TSQLUSerGrantee;
+
+begin
+  TestRevoke('Revoke INSERT ON A FROM B');
+  T:=TSQLTableRevokeStatement(CheckClass(Statement,TSQLTableRevokeStatement));
+  AssertIdentifierName('Table name','A',T.TableName);
+  AssertEquals('One Grantee', 1,T.Grantees.Count);
+  G:=TSQLUSerGrantee(CheckClass(T.Grantees[0],TSQLUSerGrantee));
+  AssertEquals('Grantee B','B',G.Name);
+  AssertEquals('One permission',1,T.Privileges.Count);
+  CheckClass(T.Privileges[0],TSQLInsertPrivilege);
+  AssertEquals('No Revoke option',False,T.GrantOption);
+end;
+
+procedure TTestRevokeParser.TestReferencePrivilege;
+Var
+  t : TSQLTableRevokeStatement;
+  G : TSQLUSerGrantee;
+
+begin
+  TestRevoke('Revoke REFERENCES ON A FROM B');
+  T:=TSQLTableRevokeStatement(CheckClass(Statement,TSQLTableRevokeStatement));
+  AssertIdentifierName('Table name','A',T.TableName);
+  AssertEquals('One Grantee', 1,T.Grantees.Count);
+  G:=TSQLUSerGrantee(CheckClass(T.Grantees[0],TSQLUSerGrantee));
+  AssertEquals('Grantee B','B',G.Name);
+  AssertEquals('One permission',1,T.Privileges.Count);
+  CheckClass(T.Privileges[0],TSQLReferencePrivilege);
+  AssertEquals('No Revoke option',False,T.GrantOption);
+end;
+
+procedure TTestRevokeParser.TestAllPrivileges;
+Var
+  t : TSQLTableRevokeStatement;
+  G : TSQLUSerGrantee;
+
+begin
+  TestRevoke('Revoke ALL ON A FROM B');
+  T:=TSQLTableRevokeStatement(CheckClass(Statement,TSQLTableRevokeStatement));
+  AssertIdentifierName('Table name','A',T.TableName);
+  AssertEquals('One Grantee', 1,T.Grantees.Count);
+  G:=TSQLUSerGrantee(CheckClass(T.Grantees[0],TSQLUSerGrantee));
+  AssertEquals('Grantee B','B',G.Name);
+  AssertEquals('One permission',1,T.Privileges.Count);
+  CheckClass(T.Privileges[0],TSQLAllPrivilege);
+  AssertEquals('No Revoke option',False,T.GrantOption);
+end;
+
+procedure TTestRevokeParser.TestAllPrivileges2;
+Var
+  t : TSQLTableRevokeStatement;
+  G : TSQLUSerGrantee;
+
+begin
+  TestRevoke('Revoke ALL PRIVILEGES ON A FROM B');
+  T:=TSQLTableRevokeStatement(CheckClass(Statement,TSQLTableRevokeStatement));
+  AssertIdentifierName('Table name','A',T.TableName);
+  AssertEquals('One Grantee', 1,T.Grantees.Count);
+  G:=TSQLUSerGrantee(CheckClass(T.Grantees[0],TSQLUSerGrantee));
+  AssertEquals('Grantee B','B',G.Name);
+  AssertEquals('One permission',1,T.Privileges.Count);
+  CheckClass(T.Privileges[0],TSQLAllPrivilege);
+  AssertEquals('No Revoke option',False,T.GrantOption);
+end;
+
+procedure TTestRevokeParser.TestUpdateColPrivilege;
+Var
+  t : TSQLTableRevokeStatement;
+  G : TSQLUSerGrantee;
+  U : TSQLUPDATEPrivilege;
+
+begin
+  TestRevoke('Revoke UPDATE (C) ON A FROM B');
+  T:=TSQLTableRevokeStatement(CheckClass(Statement,TSQLTableRevokeStatement));
+  AssertIdentifierName('Table name','A',T.TableName);
+  AssertEquals('One Grantee', 1,T.Grantees.Count);
+  G:=TSQLUSerGrantee(CheckClass(T.Grantees[0],TSQLUSerGrantee));
+  AssertEquals('Grantee B','B',G.Name);
+  AssertEquals('One permission',1,T.Privileges.Count);
+  U:=TSQLUPDATEPrivilege(CheckClass(T.Privileges[0],TSQLUPDATEPrivilege));
+  AssertEquals('1 column',1,U.Columns.Count);
+  AssertIdentifierName('Column C','C',U.Columns[0]);
+  AssertEquals('No Revoke option',False,T.GrantOption);
+end;
+
+procedure TTestRevokeParser.TestUpdate2ColsPrivilege;
+Var
+  t : TSQLTableRevokeStatement;
+  G : TSQLUSerGrantee;
+  U : TSQLUPDATEPrivilege;
+
+begin
+  TestRevoke('Revoke UPDATE (C,D) ON A FROM B');
+  T:=TSQLTableRevokeStatement(CheckClass(Statement,TSQLTableRevokeStatement));
+  AssertIdentifierName('Table name','A',T.TableName);
+  AssertEquals('One Grantee', 1,T.Grantees.Count);
+  G:=TSQLUSerGrantee(CheckClass(T.Grantees[0],TSQLUSerGrantee));
+  AssertEquals('Grantee B','B',G.Name);
+  AssertEquals('One permission',1,T.Privileges.Count);
+  U:=TSQLUPDATEPrivilege(CheckClass(T.Privileges[0],TSQLUPDATEPrivilege));
+  AssertEquals('2 column',2,U.Columns.Count);
+  AssertIdentifierName('Column C','C',U.Columns[0]);
+  AssertIdentifierName('Column D','D',U.Columns[1]);
+  AssertEquals('No Revoke option',False,T.GrantOption);
+end;
+
+procedure TTestRevokeParser.TestReferenceColPrivilege;
+Var
+  t : TSQLTableRevokeStatement;
+  G : TSQLUSerGrantee;
+  U : TSQLReferencePrivilege;
+
+begin
+  TestRevoke('Revoke REFERENCES (C) ON A FROM B');
+  T:=TSQLTableRevokeStatement(CheckClass(Statement,TSQLTableRevokeStatement));
+  AssertIdentifierName('Table name','A',T.TableName);
+  AssertEquals('One Grantee', 1,T.Grantees.Count);
+  G:=TSQLUSerGrantee(CheckClass(T.Grantees[0],TSQLUSerGrantee));
+  AssertEquals('Grantee B','B',G.Name);
+  AssertEquals('One permission',1,T.Privileges.Count);
+  U:=TSQLReferencePrivilege(CheckClass(T.Privileges[0],TSQLReferencePrivilege));
+  AssertEquals('1 column',1,U.Columns.Count);
+  AssertIdentifierName('Column C','C',U.Columns[0]);
+  AssertEquals('No Revoke option',False,T.GrantOption);
+end;
+
+procedure TTestRevokeParser.TestReference2ColsPrivilege;
+Var
+  t : TSQLTableRevokeStatement;
+  G : TSQLUSerGrantee;
+  U : TSQLReferencePrivilege;
+
+begin
+  TestRevoke('Revoke REFERENCES (C,D) ON A FROM B');
+  T:=TSQLTableRevokeStatement(CheckClass(Statement,TSQLTableRevokeStatement));
+  AssertIdentifierName('Table name','A',T.TableName);
+  AssertEquals('One Grantee', 1,T.Grantees.Count);
+  G:=TSQLUSerGrantee(CheckClass(T.Grantees[0],TSQLUSerGrantee));
+  AssertEquals('Grantee B','B',G.Name);
+  AssertEquals('One permission',1,T.Privileges.Count);
+  U:=TSQLReferencePrivilege(CheckClass(T.Privileges[0],TSQLReferencePrivilege));
+  AssertEquals('2 column',2,U.Columns.Count);
+  AssertIdentifierName('Column C','C',U.Columns[0]);
+  AssertIdentifierName('Column D','D',U.Columns[1]);
+  AssertEquals('No Revoke option',False,T.GrantOption);
+end;
+
+procedure TTestRevokeParser.TestUserPrivilege;
+
+Var
+  t : TSQLTableRevokeStatement;
+  G : TSQLUSerGrantee;
+
+begin
+    TestRevoke('Revoke SELECT ON A FROM USER B');
+    T:=TSQLTableRevokeStatement(CheckClass(Statement,TSQLTableRevokeStatement));
+    AssertIdentifierName('Table name','A',T.TableName);
+    AssertEquals('One Grantee', 1,T.Grantees.Count);
+    G:=TSQLUSerGrantee(CheckClass(T.Grantees[0],TSQLUSerGrantee));
+    AssertEquals('Grantee B','B',G.Name);
+    AssertEquals('One permission',1,T.Privileges.Count);
+    CheckClass(T.Privileges[0],TSQLSelectPrivilege);
+    AssertEquals('No Revoke option',False,T.GrantOption);
+end;
+
+procedure TTestRevokeParser.TestUserPrivilegeWithRevoke;
+Var
+  t : TSQLTableRevokeStatement;
+  G : TSQLUSerGrantee;
+
+begin
+    TestRevoke('Revoke GRANT OPTION FOR SELECT ON A FROM USER B');
+    T:=TSQLTableRevokeStatement(CheckClass(Statement,TSQLTableRevokeStatement));
+    AssertIdentifierName('Table name','A',T.TableName);
+    AssertEquals('One Grantee', 1,T.Grantees.Count);
+    G:=TSQLUSerGrantee(CheckClass(T.Grantees[0],TSQLUSerGrantee));
+    AssertEquals('Grantee B','B',G.Name);
+    AssertEquals('One permission',1,T.Privileges.Count);
+    CheckClass(T.Privileges[0],TSQLSelectPrivilege);
+    AssertEquals('With Revoke option',True,T.GrantOption);
+end;
+
+procedure TTestRevokeParser.TestGroupPrivilege;
+
+Var
+  t : TSQLTableRevokeStatement;
+  G : TSQLGroupGrantee;
+
+begin
+    TestRevoke('Revoke SELECT ON A FROM GROUP B');
+    T:=TSQLTableRevokeStatement(CheckClass(Statement,TSQLTableRevokeStatement));
+    AssertIdentifierName('Table name','A',T.TableName);
+    AssertEquals('One Grantee', 1,T.Grantees.Count);
+    G:=TSQLGroupGrantee(CheckClass(T.Grantees[0],TSQLGroupGrantee));
+    AssertEquals('Grantee B','B',G.Name);
+    AssertEquals('One permission',1,T.Privileges.Count);
+    CheckClass(T.Privileges[0],TSQLSelectPrivilege);
+    AssertEquals('No Revoke option',False,T.GrantOption);
+end;
+
+procedure TTestRevokeParser.TestProcedurePrivilege;
+Var
+  t : TSQLTableRevokeStatement;
+  G : TSQLProcedureGrantee;
+
+begin
+  TestRevoke('Revoke SELECT ON A FROM PROCEDURE B');
+  T:=TSQLTableRevokeStatement(CheckClass(Statement,TSQLTableRevokeStatement));
+  AssertIdentifierName('Table name','A',T.TableName);
+  AssertEquals('One Grantee', 1,T.Grantees.Count);
+  G:=TSQLProcedureGrantee(CheckClass(T.Grantees[0],TSQLProcedureGrantee));
+  AssertEquals('Grantee B','B',G.Name);
+  AssertEquals('One permission',1,T.Privileges.Count);
+  CheckClass(T.Privileges[0],TSQLSelectPrivilege);
+  AssertEquals('No Revoke option',False,T.GrantOption);
+end;
+
+procedure TTestRevokeParser.TestViewPrivilege;
+Var
+  t : TSQLTableRevokeStatement;
+  G : TSQLViewGrantee;
+
+begin
+  TestRevoke('Revoke SELECT ON A FROM VIEW B');
+  T:=TSQLTableRevokeStatement(CheckClass(Statement,TSQLTableRevokeStatement));
+  AssertIdentifierName('Table name','A',T.TableName);
+  AssertEquals('One Grantee', 1,T.Grantees.Count);
+  G:=TSQLViewGrantee(CheckClass(T.Grantees[0],TSQLViewGrantee));
+  AssertEquals('Grantee B','B',G.Name);
+  AssertEquals('One permission',1,T.Privileges.Count);
+  CheckClass(T.Privileges[0],TSQLSelectPrivilege);
+  AssertEquals('No Revoke option',False,T.GrantOption);
+end;
+
+procedure TTestRevokeParser.TestTriggerPrivilege;
+
+Var
+  t : TSQLTableRevokeStatement;
+  G : TSQLTriggerGrantee;
+
+begin
+  TestRevoke('Revoke SELECT ON A FROM TRIGGER B');
+  T:=TSQLTableRevokeStatement(CheckClass(Statement,TSQLTableRevokeStatement));
+  AssertIdentifierName('Table name','A',T.TableName);
+  AssertEquals('One Grantee', 1,T.Grantees.Count);
+  G:=TSQLTriggerGrantee(CheckClass(T.Grantees[0],TSQLTriggerGrantee));
+  AssertEquals('Grantee B','B',G.Name);
+  AssertEquals('One permission',1,T.Privileges.Count);
+  CheckClass(T.Privileges[0],TSQLSelectPrivilege);
+  AssertEquals('No Revoke option',False,T.GrantOption);
+end;
+
+procedure TTestRevokeParser.TestPublicPrivilege;
+Var
+  t : TSQLTableRevokeStatement;
+  P : TSQLPublicGrantee;
+
+begin
+  TestRevoke('Revoke SELECT ON A FROM PUBLIC');
+  T:=TSQLTableRevokeStatement(CheckClass(Statement,TSQLTableRevokeStatement));
+  AssertIdentifierName('Table name','A',T.TableName);
+  AssertEquals('One Grantee', 1,T.Grantees.Count);
+  (CheckClass(T.Grantees[0],TSQLPublicGrantee));
+  AssertEquals('One permission',1,T.Privileges.Count);
+  CheckClass(T.Privileges[0],TSQLSelectPrivilege);
+  AssertEquals('No Revoke option',False,T.GrantOption);
+end;
+
+procedure TTestRevokeParser.TestExecuteToUser;
+Var
+  P : TSQLProcedureRevokeStatement;
+  U : TSQLUserGrantee;
+
+begin
+  TestRevoke('Revoke EXECUTE ON PROCEDURE A FROM B');
+  P:=TSQLProcedureRevokeStatement(CheckClass(Statement,TSQLProcedureRevokeStatement));
+  AssertIdentifierName('Procedure name','A',P.ProcedureName);
+  AssertEquals('One Grantee', 1,P.Grantees.Count);
+  U:=TSQLUserGrantee(CheckClass(P.Grantees[0],TSQLUserGrantee));
+  AssertEquals('User name','B',U.Name);
+  AssertEquals('No Revoke option',False,P.GrantOption);
+end;
+
+procedure TTestRevokeParser.TestExecuteToProcedure;
+Var
+  P : TSQLProcedureRevokeStatement;
+  U : TSQLProcedureGrantee;
+
+begin
+  TestRevoke('Revoke EXECUTE ON PROCEDURE A FROM PROCEDURE B');
+  P:=TSQLProcedureRevokeStatement(CheckClass(Statement,TSQLProcedureRevokeStatement));
+  AssertIdentifierName('Procedure name','A',P.ProcedureName);
+  AssertEquals('One Grantee', 1,P.Grantees.Count);
+  U:=TSQLProcedureGrantee(CheckClass(P.Grantees[0],TSQLProcedureGrantee));
+  AssertEquals('Procedure Grantee name','B',U.Name);
+  AssertEquals('No Revoke option',False,P.GrantOption);
+end;
+
+procedure TTestRevokeParser.TestRoleToUser;
+Var
+  R : TSQLRoleRevokeStatement;
+  U : TSQLUserGrantee;
+
+begin
+  TestRevoke('Revoke A FROM B');
+  R:=TSQLRoleRevokeStatement(CheckClass(Statement,TSQLRoleRevokeStatement));
+  AssertEquals('One role', 1,R.Roles.Count);
+  AssertIdentifierName('Role name','A',R.Roles[0]);
+  AssertEquals('One Grantee', 1,R.Grantees.Count);
+  U:=TSQLUserGrantee(CheckClass(R.Grantees[0],TSQLUserGrantee));
+  AssertEquals('Procedure Grantee name','B',U.Name);
+  AssertEquals('No admin option',False,R.AdminOption);
+end;
+
+procedure TTestRevokeParser.TestRoleToPublic;
+Var
+  R : TSQLRoleRevokeStatement;
+
+begin
+  TestRevoke('Revoke A FROM PUBLIC');
+  R:=TSQLRoleRevokeStatement(CheckClass(Statement,TSQLRoleRevokeStatement));
+  AssertEquals('One role', 1,R.Roles.Count);
+  AssertIdentifierName('Role name','A',R.Roles[0]);
+  AssertEquals('One Grantee', 1,R.Grantees.Count);
+  CheckClass(R.Grantees[0],TSQLPublicGrantee);
+  AssertEquals('No admin option',False,R.AdminOption);
+end;
+
+procedure TTestRevokeParser.Test2RolesToUser;
+
+Var
+  R : TSQLRoleRevokeStatement;
+  U : TSQLUserGrantee;
+
+begin
+  TestRevoke('Revoke A,C FROM B');
+  R:=TSQLRoleRevokeStatement(CheckClass(Statement,TSQLRoleRevokeStatement));
+  AssertEquals('2 roles', 2,R.Roles.Count);
+  AssertIdentifierName('Role name','A',R.Roles[0]);
+  AssertIdentifierName('Role name','C',R.Roles[1]);
+  AssertEquals('One Grantee', 1,R.Grantees.Count);
+  U:=TSQLUserGrantee(CheckClass(R.Grantees[0],TSQLUserGrantee));
+  AssertEquals('Procedure Grantee name','B',U.Name);
+  AssertEquals('No admin option',False,R.AdminOption);
+end;
+
+
 initialization
 initialization
   RegisterTests([TTestDropParser,
   RegisterTests([TTestDropParser,
                  TTestGeneratorParser,
                  TTestGeneratorParser,
@@ -7020,6 +7989,8 @@ initialization
                  TTestProcedureStatement,
                  TTestProcedureStatement,
                  TTestCreateProcedureParser,
                  TTestCreateProcedureParser,
                  TTestCreateTriggerParser,
                  TTestCreateTriggerParser,
-                 TTestDeclareExternalFunctionParser]);
+                 TTestDeclareExternalFunctionParser,
+                 TTestGrantParser,
+                 TTestRevokeParser]);
 end.
 end.
 
 

+ 6 - 0
packages/fcl-db/tests/tcsqlscanner.pas

@@ -175,6 +175,7 @@ type
     Procedure TestReturningValues;
     Procedure TestReturningValues;
     Procedure TestReturns;
     Procedure TestReturns;
     Procedure TestRetain;
     Procedure TestRetain;
+    Procedure TestRevoke;
     Procedure TestRight;
     Procedure TestRight;
     Procedure TestRole;
     Procedure TestRole;
     Procedure TestRollback;
     Procedure TestRollback;
@@ -869,6 +870,11 @@ begin
   Checktoken(tsqlRetain,'retain');
   Checktoken(tsqlRetain,'retain');
 end;
 end;
 
 
+procedure TTestSQLScanner.TestRevoke;
+begin
+  Checktoken(tsqlRevoke,'revoke');
+end;
+
 procedure TTestSQLScanner.TestRight;
 procedure TTestSQLScanner.TestRight;
 begin
 begin
   Checktoken(tsqlright,'right');
   Checktoken(tsqlright,'right');

+ 76 - 75
packages/fcl-db/tests/testsqlscanner.lpi

@@ -7,7 +7,6 @@
       <TargetFileExt Value=""/>
       <TargetFileExt Value=""/>
       <ResourceType Value="res"/>
       <ResourceType Value="res"/>
       <UseXPManifest Value="True"/>
       <UseXPManifest Value="True"/>
-      <Icon Value="0"/>
       <ActiveWindowIndexAtStart Value="0"/>
       <ActiveWindowIndexAtStart Value="0"/>
     </General>
     </General>
     <VersionInfo>
     <VersionInfo>
@@ -40,7 +39,6 @@
         <Filename Value="testsqlscanner.lpr"/>
         <Filename Value="testsqlscanner.lpr"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
         <UnitName Value="testsqlscanner"/>
         <UnitName Value="testsqlscanner"/>
-        <IsVisibleTab Value="True"/>
         <EditorIndex Value="1"/>
         <EditorIndex Value="1"/>
         <WindowIndex Value="0"/>
         <WindowIndex Value="0"/>
         <TopLine Value="1"/>
         <TopLine Value="1"/>
@@ -54,8 +52,8 @@
         <UnitName Value="tcsqlscanner"/>
         <UnitName Value="tcsqlscanner"/>
         <EditorIndex Value="0"/>
         <EditorIndex Value="0"/>
         <WindowIndex Value="0"/>
         <WindowIndex Value="0"/>
-        <TopLine Value="1"/>
-        <CursorPos X="1" Y="15"/>
+        <TopLine Value="873"/>
+        <CursorPos X="3" Y="875"/>
         <UsageCount Value="208"/>
         <UsageCount Value="208"/>
         <Loaded Value="True"/>
         <Loaded Value="True"/>
       </Unit1>
       </Unit1>
@@ -63,10 +61,13 @@
         <Filename Value="../src/sql/fpsqltree.pp"/>
         <Filename Value="../src/sql/fpsqltree.pp"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
         <UnitName Value="fpsqltree"/>
         <UnitName Value="fpsqltree"/>
+        <IsVisibleTab Value="True"/>
+        <EditorIndex Value="6"/>
         <WindowIndex Value="0"/>
         <WindowIndex Value="0"/>
-        <TopLine Value="263"/>
-        <CursorPos X="41" Y="278"/>
+        <TopLine Value="4751"/>
+        <CursorPos X="49" Y="4767"/>
         <UsageCount Value="208"/>
         <UsageCount Value="208"/>
+        <Loaded Value="True"/>
       </Unit2>
       </Unit2>
       <Unit3>
       <Unit3>
         <Filename Value="../src/sql/fpsqlscanner.pp"/>
         <Filename Value="../src/sql/fpsqlscanner.pp"/>
@@ -74,8 +75,8 @@
         <UnitName Value="fpsqlscanner"/>
         <UnitName Value="fpsqlscanner"/>
         <EditorIndex Value="4"/>
         <EditorIndex Value="4"/>
         <WindowIndex Value="0"/>
         <WindowIndex Value="0"/>
-        <TopLine Value="466"/>
-        <CursorPos X="1" Y="480"/>
+        <TopLine Value="43"/>
+        <CursorPos X="126" Y="62"/>
         <UsageCount Value="208"/>
         <UsageCount Value="208"/>
         <Loaded Value="True"/>
         <Loaded Value="True"/>
       </Unit3>
       </Unit3>
@@ -83,10 +84,10 @@
         <Filename Value="../src/sql/fpsqlparser.pas"/>
         <Filename Value="../src/sql/fpsqlparser.pas"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
         <UnitName Value="fpsqlparser"/>
         <UnitName Value="fpsqlparser"/>
-        <EditorIndex Value="6"/>
+        <EditorIndex Value="7"/>
         <WindowIndex Value="0"/>
         <WindowIndex Value="0"/>
-        <TopLine Value="737"/>
-        <CursorPos X="3" Y="740"/>
+        <TopLine Value="3562"/>
+        <CursorPos X="12" Y="3588"/>
         <UsageCount Value="208"/>
         <UsageCount Value="208"/>
         <Loaded Value="True"/>
         <Loaded Value="True"/>
       </Unit4>
       </Unit4>
@@ -96,8 +97,8 @@
         <UnitName Value="tcparser"/>
         <UnitName Value="tcparser"/>
         <EditorIndex Value="5"/>
         <EditorIndex Value="5"/>
         <WindowIndex Value="0"/>
         <WindowIndex Value="0"/>
-        <TopLine Value="1"/>
-        <CursorPos X="1" Y="15"/>
+        <TopLine Value="7911"/>
+        <CursorPos X="30" Y="7924"/>
         <UsageCount Value="220"/>
         <UsageCount Value="220"/>
         <Loaded Value="True"/>
         <Loaded Value="True"/>
       </Unit5>
       </Unit5>
@@ -107,8 +108,8 @@
         <UnitName Value="tcgensql"/>
         <UnitName Value="tcgensql"/>
         <EditorIndex Value="3"/>
         <EditorIndex Value="3"/>
         <WindowIndex Value="0"/>
         <WindowIndex Value="0"/>
-        <TopLine Value="1"/>
-        <CursorPos X="41" Y="5"/>
+        <TopLine Value="2449"/>
+        <CursorPos X="13" Y="2452"/>
         <UsageCount Value="371"/>
         <UsageCount Value="371"/>
         <Loaded Value="True"/>
         <Loaded Value="True"/>
       </Unit6>
       </Unit6>
@@ -127,129 +128,129 @@
         <WindowIndex Value="0"/>
         <WindowIndex Value="0"/>
         <TopLine Value="438"/>
         <TopLine Value="438"/>
         <CursorPos X="3" Y="453"/>
         <CursorPos X="3" Y="453"/>
-        <UsageCount Value="9"/>
+        <UsageCount Value="0"/>
       </Unit8>
       </Unit8>
     </Units>
     </Units>
     <JumpHistory Count="30" HistoryIndex="29">
     <JumpHistory Count="30" HistoryIndex="29">
       <Position1>
       <Position1>
-        <Filename Value="tcsqlscanner.pas"/>
-        <Caret Line="108" Column="24" TopLine="84"/>
+        <Filename Value="tcgensql.pas"/>
+        <Caret Line="2286" Column="1" TopLine="2258"/>
       </Position1>
       </Position1>
       <Position2>
       <Position2>
-        <Filename Value="tcsqlscanner.pas"/>
-        <Caret Line="156" Column="26" TopLine="138"/>
+        <Filename Value="../src/sql/fpsqltree.pp"/>
+        <Caret Line="4607" Column="32" TopLine="4586"/>
       </Position2>
       </Position2>
       <Position3>
       <Position3>
-        <Filename Value="tcsqlscanner.pas"/>
-        <Caret Line="37" Column="24" TopLine="23"/>
+        <Filename Value="tcgensql.pas"/>
+        <Caret Line="2327" Column="1" TopLine="2315"/>
       </Position3>
       </Position3>
       <Position4>
       <Position4>
-        <Filename Value="tcsqlscanner.pas"/>
-        <Caret Line="156" Column="29" TopLine="142"/>
+        <Filename Value="tcgensql.pas"/>
+        <Caret Line="159" Column="28" TopLine="143"/>
       </Position4>
       </Position4>
       <Position5>
       <Position5>
-        <Filename Value="tcparser.pas"/>
-        <Caret Line="376" Column="15" TopLine="361"/>
+        <Filename Value="tcgensql.pas"/>
+        <Caret Line="2394" Column="14" TopLine="2372"/>
       </Position5>
       </Position5>
       <Position6>
       <Position6>
-        <Filename Value="tcparser.pas"/>
-        <Caret Line="163" Column="24" TopLine="136"/>
+        <Filename Value="tcgensql.pas"/>
+        <Caret Line="2395" Column="9" TopLine="2376"/>
       </Position6>
       </Position6>
       <Position7>
       <Position7>
-        <Filename Value="tcparser.pas"/>
-        <Caret Line="19" Column="15" TopLine="4"/>
+        <Filename Value="tcgensql.pas"/>
+        <Caret Line="2390" Column="22" TopLine="2376"/>
       </Position7>
       </Position7>
       <Position8>
       <Position8>
-        <Filename Value="tcparser.pas"/>
-        <Caret Line="78" Column="29" TopLine="63"/>
+        <Filename Value="../src/sql/fpsqltree.pp"/>
+        <Caret Line="1781" Column="1" TopLine="1759"/>
       </Position8>
       </Position8>
       <Position9>
       <Position9>
-        <Filename Value="tcparser.pas"/>
-        <Caret Line="208" Column="33" TopLine="199"/>
+        <Filename Value="../src/sql/fpsqltree.pp"/>
+        <Caret Line="4632" Column="4" TopLine="4625"/>
       </Position9>
       </Position9>
       <Position10>
       <Position10>
-        <Filename Value="tcparser.pas"/>
-        <Caret Line="648" Column="34" TopLine="636"/>
+        <Filename Value="../src/sql/fpsqltree.pp"/>
+        <Caret Line="4625" Column="16" TopLine="4625"/>
       </Position10>
       </Position10>
       <Position11>
       <Position11>
-        <Filename Value="../src/sql/fpsqlparser.pas"/>
-        <Caret Line="1208" Column="27" TopLine="1187"/>
+        <Filename Value="../src/sql/fpsqltree.pp"/>
+        <Caret Line="1774" Column="42" TopLine="1759"/>
       </Position11>
       </Position11>
       <Position12>
       <Position12>
-        <Filename Value="tcparser.pas"/>
-        <Caret Line="655" Column="31" TopLine="634"/>
+        <Filename Value="../src/sql/fpsqltree.pp"/>
+        <Caret Line="1726" Column="3" TopLine="1707"/>
       </Position12>
       </Position12>
       <Position13>
       <Position13>
-        <Filename Value="tcparser.pas"/>
-        <Caret Line="682" Column="15" TopLine="667"/>
+        <Filename Value="../src/sql/fpsqltree.pp"/>
+        <Caret Line="4640" Column="52" TopLine="4625"/>
       </Position13>
       </Position13>
       <Position14>
       <Position14>
-        <Filename Value="tcparser.pas"/>
-        <Caret Line="61" Column="1" TopLine="61"/>
+        <Filename Value="../src/sql/fpsqltree.pp"/>
+        <Caret Line="4534" Column="3" TopLine="4527"/>
       </Position14>
       </Position14>
       <Position15>
       <Position15>
-        <Filename Value="tcparser.pas"/>
-        <Caret Line="317" Column="36" TopLine="295"/>
+        <Filename Value="../src/sql/fpsqltree.pp"/>
+        <Caret Line="4640" Column="46" TopLine="4625"/>
       </Position15>
       </Position15>
       <Position16>
       <Position16>
-        <Filename Value="tcparser.pas"/>
-        <Caret Line="328" Column="15" TopLine="313"/>
+        <Filename Value="../src/sql/fpsqltree.pp"/>
+        <Caret Line="1781" Column="20" TopLine="1760"/>
       </Position16>
       </Position16>
       <Position17>
       <Position17>
-        <Filename Value="tcparser.pas"/>
-        <Caret Line="2990" Column="31" TopLine="2974"/>
+        <Filename Value="../src/sql/fpsqltree.pp"/>
+        <Caret Line="82" Column="14" TopLine="67"/>
       </Position17>
       </Position17>
       <Position18>
       <Position18>
-        <Filename Value="tcparser.pas"/>
-        <Caret Line="2994" Column="90" TopLine="2974"/>
+        <Filename Value="../src/sql/fpsqltree.pp"/>
+        <Caret Line="1789" Column="3" TopLine="1772"/>
       </Position18>
       </Position18>
       <Position19>
       <Position19>
-        <Filename Value="tcparser.pas"/>
-        <Caret Line="3001" Column="31" TopLine="2974"/>
+        <Filename Value="../src/sql/fpsqltree.pp"/>
+        <Caret Line="4594" Column="31" TopLine="4576"/>
       </Position19>
       </Position19>
       <Position20>
       <Position20>
-        <Filename Value="tcparser.pas"/>
-        <Caret Line="3006" Column="90" TopLine="2991"/>
+        <Filename Value="../src/sql/fpsqltree.pp"/>
+        <Caret Line="1726" Column="74" TopLine="1711"/>
       </Position20>
       </Position20>
       <Position21>
       <Position21>
-        <Filename Value="tcparser.pas"/>
-        <Caret Line="3008" Column="90" TopLine="2991"/>
+        <Filename Value="../src/sql/fpsqltree.pp"/>
+        <Caret Line="4549" Column="3" TopLine="4531"/>
       </Position21>
       </Position21>
       <Position22>
       <Position22>
-        <Filename Value="tcparser.pas"/>
-        <Caret Line="3110" Column="35" TopLine="3095"/>
+        <Filename Value="../src/sql/fpsqltree.pp"/>
+        <Caret Line="4765" Column="32" TopLine="4742"/>
       </Position22>
       </Position22>
       <Position23>
       <Position23>
-        <Filename Value="tcparser.pas"/>
-        <Caret Line="3251" Column="34" TopLine="3230"/>
+        <Filename Value="../src/sql/fpsqltree.pp"/>
+        <Caret Line="4771" Column="57" TopLine="4749"/>
       </Position23>
       </Position23>
       <Position24>
       <Position24>
-        <Filename Value="tcparser.pas"/>
-        <Caret Line="3393" Column="1" TopLine="3371"/>
+        <Filename Value="tcgensql.pas"/>
+        <Caret Line="160" Column="25" TopLine="144"/>
       </Position24>
       </Position24>
       <Position25>
       <Position25>
-        <Filename Value="tcparser.pas"/>
-        <Caret Line="3405" Column="29" TopLine="3390"/>
+        <Filename Value="tcgensql.pas"/>
+        <Caret Line="2443" Column="1" TopLine="2413"/>
       </Position25>
       </Position25>
       <Position26>
       <Position26>
-        <Filename Value="tcparser.pas"/>
-        <Caret Line="3427" Column="29" TopLine="3412"/>
+        <Filename Value="tcgensql.pas"/>
+        <Caret Line="2426" Column="1" TopLine="2413"/>
       </Position26>
       </Position26>
       <Position27>
       <Position27>
-        <Filename Value="tcparser.pas"/>
-        <Caret Line="3443" Column="29" TopLine="3428"/>
+        <Filename Value="tcgensql.pas"/>
+        <Caret Line="2451" Column="1" TopLine="2421"/>
       </Position27>
       </Position27>
       <Position28>
       <Position28>
-        <Filename Value="tcparser.pas"/>
-        <Caret Line="664" Column="6" TopLine="664"/>
+        <Filename Value="tcgensql.pas"/>
+        <Caret Line="2419" Column="1" TopLine="2406"/>
       </Position28>
       </Position28>
       <Position29>
       <Position29>
-        <Filename Value="tcsqlscanner.pas"/>
-        <Caret Line="168" Column="15" TopLine="168"/>
+        <Filename Value="tcgensql.pas"/>
+        <Caret Line="2490" Column="1" TopLine="2475"/>
       </Position29>
       </Position29>
       <Position30>
       <Position30>
         <Filename Value="tcgensql.pas"/>
         <Filename Value="tcgensql.pas"/>
-        <Caret Line="513" Column="27" TopLine="513"/>
+        <Caret Line="2414" Column="1" TopLine="2400"/>
       </Position30>
       </Position30>
     </JumpHistory>
     </JumpHistory>
   </ProjectOptions>
   </ProjectOptions>