docs: @Override
      protected AstNode parseGrantStatement( DdlTokenStream tokens,
                                             AstNode parentNode ) throws ParsingException {
          assert tokens != null;
          assert parentNode != null;
          assert tokens.matches(GRANT);
  
          markStartOfStatement(tokens);
  
          // NOTE: The first wack at this does not take into account the apparent potential repeating name elements after each type
          // declaration. Example:
          // GRANT { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER }
          // [,...] | ALL [ PRIVILEGES ] }
          // ON [ TABLE ] tablename [, ...]
          // TO { [ GROUP ] rolename | PUBLIC } [, ...] [ WITH GRANT OPTION ]
          //
          // the "ON [ TABLE ] tablename [, ...]" seems to indicate that you can grant privileges on multiple tables at once, which
          // is
          // different thatn the SQL 92 standard. So this pass ONLY allows one and an parsing error will probably occur if multiple.
          //
          // Syntax for tables
          //
          // GRANT <privileges> ON <object name>
          // TO <grantee> [ { <comma> <grantee> }... ]
          // [ WITH GRANT OPTION ]
          //
          // <object name> ::=
          // [ TABLE ] <table name>
          // | SEQUENCE <sequence name>
          // | DATABASE <db name>
          // | FOREIGN DATA WRAPPER <fdw name>
          // | FOREIGN SERVER <server name>
          // | FUNCTION <function name>
          // | LANGUAGE <language name>
          // | SCHEMA <schema name>
          // | TABLESPACE <tablespace name>
  
          //
          // Syntax for roles
          //
          // GRANT roleName [ {, roleName }* ] TO grantees
  
          // privilege-types
          //
          // ALL PRIVILEGES | privilege-list
          //
          List<AstNode> grantNodes = new ArrayList<AstNode>();
          boolean allPrivileges = false;
  
          List<AstNode> privileges = new ArrayList<AstNode>();
  
          tokens.consume("GRANT");
  
          if (tokens.canConsume("ALL", "PRIVILEGES")) {
              allPrivileges = true;
          } else {
              parseGrantPrivileges(tokens, privileges);
          }
  
          if (allPrivileges || !privileges.isEmpty()) {
  
              tokens.consume("ON");
  
              if (tokens.canConsume("SCHEMA")) {
                  grantNodes = parseMultipleGrantTargets(tokens, parentNode, TYPE_GRANT_ON_SCHEMA_STATEMENT);
              } else if (tokens.canConsume("SEQUENCE")) {
                  grantNodes = parseMultipleGrantTargets(tokens, parentNode, TYPE_GRANT_ON_SEQUENCE_STATEMENT);
              } else if (tokens.canConsume("TABLESPACE")) {
                  grantNodes = parseMultipleGrantTargets(tokens, parentNode, TYPE_GRANT_ON_TABLESPACE_STATEMENT);
              } else if (tokens.canConsume("DATABASE")) {
                  grantNodes = parseMultipleGrantTargets(tokens, parentNode, TYPE_GRANT_ON_DATABASE_STATEMENT);
              } else if (tokens.canConsume("FUNCTION")) {
                  grantNodes = parseFunctionAndParameters(tokens, parentNode);
              } else if (tokens.canConsume("LANGUAGE")) {
                  grantNodes = parseMultipleGrantTargets(tokens, parentNode, TYPE_GRANT_ON_LANGUAGE_STATEMENT);
              } else if (tokens.canConsume("FOREIGN", "DATA", "WRAPPER")) {
                  grantNodes = parseMultipleGrantTargets(tokens, parentNode, TYPE_GRANT_ON_FOREIGN_DATA_WRAPPER_STATEMENT);
              } else if (tokens.canConsume("FOREIGN", "SERVER")) {
                  grantNodes = parseMultipleGrantTargets(tokens, parentNode, TYPE_GRANT_ON_FOREIGN_SERVER_STATEMENT);
              } else {
                  tokens.canConsume(TABLE); // OPTIONAL
                  String name = parseName(tokens);
                  AstNode grantNode = nodeFactory().node(name, parentNode, TYPE_GRANT_ON_TABLE_STATEMENT);
                  grantNodes.add(grantNode);
                  while (tokens.canConsume(COMMA)) {
                      // Assume more names here
                      name = parseName(tokens);
                      grantNode = nodeFactory().node(name, parentNode, TYPE_GRANT_ON_TABLE_STATEMENT);
                      grantNodes.add(grantNode);
                  }
              }
          } else {
              // Assume ROLES here
              // role [, ...]
              AstNode grantNode = nodeFactory().node("roles", parentNode, TYPE_GRANT_ROLES_STATEMENT);
              grantNodes.add(grantNode);
              do {
                  String role = parseName(tokens);
                  nodeFactory().node(role, grantNode, ROLE);
              } while (tokens.canConsume(COMMA));
          }
  
          tokens.consume("TO");
          List<String> grantees = new ArrayList<String>();
  
          do {
              String grantee = parseName(tokens);
              grantees.add(grantee);
          } while (tokens.canConsume(COMMA));
  
          boolean withGrantOption = false;
          if (tokens.canConsume("WITH", "GRANT", "OPTION")) {
              withGrantOption = true;
          }
  
          // Set all properties and children on Grant Nodes
          for (AstNode grantNode : grantNodes) {
              List<AstNode> copyOfPrivileges = copyOfPrivileges(privileges);
              // Attach privileges to grant node
              for (AstNode node : copyOfPrivileges) {
                  node.setParent(grantNode);
              }
              if (allPrivileges) {
                  grantNode.setProperty(ALL_PRIVILEGES, allPrivileges);
              }
              for (String grantee : grantees) {
                  nodeFactory().node(grantee, grantNode, GRANTEE);
              }
  
              if (withGrantOption) {
                  AstNode optionNode = nodeFactory().node("withGrant", grantNode, TYPE_STATEMENT_OPTION);
                  optionNode.setProperty(VALUE, "WITH GRANT OPTION");
              }
          }
          AstNode firstGrantNode = grantNodes.get(0);
  
          markEndOfStatement(tokens, firstGrantNode);
  
          // Update additional grant nodes with statement info
  
          for (int i = 1; i < grantNodes.size(); i++) {
              AstNode grantNode = grantNodes.get(i);
              grantNode.setProperty(DDL_EXPRESSION, firstGrantNode.getProperty(DDL_EXPRESSION));
              grantNode.setProperty(DDL_LENGTH, firstGrantNode.getProperty(DDL_LENGTH));
              grantNode.setProperty(DDL_START_LINE_NUMBER, firstGrantNode.getProperty(DDL_START_LINE_NUMBER));
              grantNode.setProperty(DDL_START_CHAR_INDEX, firstGrantNode.getProperty(DDL_START_CHAR_INDEX));
              grantNode.setProperty(DDL_START_COLUMN_NUMBER, firstGrantNode.getProperty(DDL_START_COLUMN_NUMBER));
          }
  
          return grantNodes.get(0);
      }