docs: protected AstNode parseGrantStatement( DdlTokenStream tokens,
                                             AstNode parentNode ) throws ParsingException {
          assert tokens != null;
          assert parentNode != null;
          assert tokens.matches(GRANT);
  
          markStartOfStatement(tokens);
  
          // Syntax for tables
          //
          // GRANT <privileges> ON <object name>
          // TO <grantee> [ { <comma> <grantee> }... ]
          // [ WITH GRANT OPTION ]
          //
          // <object name> ::=
          // [ TABLE ] <table name>
          // | DOMAIN <domain name>
          // | COLLATION <collation name>
          // | CHARACTER SET <character set name>
          // | TRANSLATION <translation name>
          //
          // Syntax for roles
          //
          // GRANT roleName [ {, roleName }* ] TO grantees
  
          // privilege-types
          //
          // ALL PRIVILEGES | privilege-list
          //
          AstNode grantNode = null;
          boolean allPrivileges = false;
  
          List<AstNode> privileges = new ArrayList<AstNode>();
  
          tokens.consume("GRANT");
  
          if (tokens.canConsume("ALL", "PRIVILEGES")) {
              allPrivileges = true;
          } else {
              parseGrantPrivileges(tokens, privileges);
          }
          tokens.consume("ON");
  
          if (tokens.canConsume("DOMAIN")) {
              String name = parseName(tokens);
              grantNode = nodeFactory().node(name, parentNode, TYPE_GRANT_ON_DOMAIN_STATEMENT);
          } else if (tokens.canConsume("COLLATION")) {
              String name = parseName(tokens);
              grantNode = nodeFactory().node(name, parentNode, TYPE_GRANT_ON_COLLATION_STATEMENT);
          } else if (tokens.canConsume("CHARACTER", "SET")) {
              String name = parseName(tokens);
              grantNode = nodeFactory().node(name, parentNode, TYPE_GRANT_ON_CHARACTER_SET_STATEMENT);
          } else if (tokens.canConsume("TRANSLATION")) {
              String name = parseName(tokens);
              grantNode = nodeFactory().node(name, parentNode, TYPE_GRANT_ON_TRANSLATION_STATEMENT);
          } else {
              tokens.canConsume(TABLE); // OPTIONAL
              String name = parseName(tokens);
              grantNode = nodeFactory().node(name, parentNode, TYPE_GRANT_ON_TABLE_STATEMENT);
          }
  
          // Attach privileges to grant node
          for (AstNode node : privileges) {
              node.setParent(grantNode);
          }
          if (allPrivileges) {
              grantNode.setProperty(ALL_PRIVILEGES, allPrivileges);
          }
  
          tokens.consume("TO");
  
          do {
              String grantee = parseName(tokens);
              nodeFactory().node(grantee, grantNode, GRANTEE);
          } while (tokens.canConsume(COMMA));
  
          if (tokens.canConsume("WITH", "GRANT", "OPTION")) {
              grantNode.setProperty(WITH_GRANT_OPTION, "WITH GRANT OPTION");
          }
  
          markEndOfStatement(tokens, grantNode);
  
          return grantNode;
      }