docs: @Override
      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 privilege-type ON [TABLE] { table-Name | view-Name } TO grantees
          //
          // Syntax for routines
          //
          // GRANT EXECUTE ON { FUNCTION | PROCEDURE } {function-name | procedure-name} TO grantees
          //
          // 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("EXECUTE", "ON")) {
              AstNode node = nodeFactory().node("privilege");
              nodeFactory().setType(node, GRANT_PRIVILEGE);
              node.setProperty(TYPE, "EXECUTE");
              privileges = new ArrayList<AstNode>();
              privileges.add(node);
              if (tokens.canConsume("FUNCTION")) {
                  String name = parseName(tokens);
                  grantNode = nodeFactory().node(name, parentNode, TYPE_GRANT_ON_FUNCTION_STATEMENT);
              } else {
                  tokens.consume("PROCEDURE");
                  String name = parseName(tokens);
                  grantNode = nodeFactory().node(name, parentNode, TYPE_GRANT_ON_PROCEDURE_STATEMENT);
              }
          } else {
  
              if (tokens.canConsume("ALL", "PRIVILEGES")) {
                  allPrivileges = true;
              } else {
                  parseGrantPrivileges(tokens, privileges);
  
                  if (privileges.isEmpty()) {
                      // ASSUME: GRANT roleName [ {, roleName }* ] TO grantees
                      grantNode = nodeFactory().node("grantRoles", parentNode, TYPE_GRANT_ROLES_STATEMENT);
                      do {
                          String roleName = parseName(tokens);
                          nodeFactory().node(roleName, grantNode, ROLE_NAME);
                      } while (tokens.canConsume(COMMA));
                  }
              }
              if (grantNode == null) {
                  tokens.consume("ON");
                  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));
  
          markEndOfStatement(tokens, grantNode);
  
          return grantNode;
      }