docs: protected AstNode parseCreateFunction( DdlTokenStream tokens,
                                             AstNode parentNode ) throws ParsingException {
          assert tokens != null;
          assert parentNode != null;
  
          markStartOfStatement(tokens);
          // CREATE FUNCTION function-name ( [ FunctionParameter [, FunctionParameter] ] * )
          // RETURNS ReturnDataType [ FunctionElement ] *
  
          // FunctionElement
          // {
          // | LANGUAGE { JAVA }
          // | {DETERMINISTIC | NOT DETERMINISTIC}
          // | EXTERNAL NAME string
          // | PARAMETER STYLE {JAVA | DERBY_JDBC_RESULT_SET}
          // | { NO SQL | CONTAINS SQL | READS SQL DATA }
          // | { RETURNS NULL ON NULL INPUT | CALLED ON NULL INPUT }
          // }
          tokens.consume(CREATE, "FUNCTION");
  
          String functionName = parseName(tokens);
  
          AstNode functionNode = nodeFactory().node(functionName, parentNode, TYPE_CREATE_FUNCTION_STATEMENT);
  
          parseFunctionParameters(tokens, functionNode);
  
          tokens.consume("RETURNS");
  
          if (tokens.canConsume("TABLE")) {
              AstNode tableNode = nodeFactory().node("TABLE", functionNode, TYPE_CREATE_TABLE_STATEMENT);
              tableNode.setProperty(DDL_START_LINE_NUMBER, getCurrentMarkedPosition().getLine());
              tableNode.setProperty(DDL_START_CHAR_INDEX, getCurrentMarkedPosition().getIndexInContent());
              tableNode.setProperty(DDL_START_COLUMN_NUMBER, getCurrentMarkedPosition().getColumn());
              parseColumnsAndConstraints(tokens, tableNode);
              String expressionSource = "TABLE " + tokens.getMarkedContent();
              tableNode.setProperty(DDL_EXPRESSION, expressionSource);
              tableNode.setProperty(DDL_LENGTH, expressionSource.length());
  
              functionNode.setProperty(IS_TABLE_TYPE, true);
          } else {
              // Assume DataType
              DataType datatype = getDatatypeParser().parse(tokens);
              if (datatype != null) {
                  getDatatypeParser().setPropertiesOnNode(functionNode, datatype);
              } else {
                  String msg = DdlSequencerI18n.missingReturnTypeForFunction.text(functionName);
                  DdlParserProblem problem = new DdlParserProblem(Problems.WARNING, getCurrentMarkedPosition(), msg);
                  addProblem(problem, functionNode);
              }
          }
  
          while (!isTerminator(tokens)) {
              if (tokens.matches("LANGUAGE")) {
                  AstNode optionNode = nodeFactory().node("language", functionNode, TYPE_STATEMENT_OPTION);
                  if (tokens.canConsume("LANGUAGE", "JAVA")) {
                      optionNode.setProperty(VALUE, "LANGUAGE JAVA");
                  } else {
                      tokens.consume("LANGUAGE");
                      optionNode.setProperty(VALUE, "LANGUAGE");
                  }
              } else if (tokens.canConsume("DETERMINISTIC")) {
                  AstNode optionNode = nodeFactory().node("deterministic", functionNode, TYPE_STATEMENT_OPTION);
                  optionNode.setProperty(VALUE, "DETERMINISTIC");
              } else if (tokens.canConsume("NOT", "DETERMINISTIC")) {
                  AstNode optionNode = nodeFactory().node("deterministic", functionNode, TYPE_STATEMENT_OPTION);
                  optionNode.setProperty(VALUE, "NOT DETERMINISTIC");
              } else if (tokens.canConsume("EXTERNAL", "NAME")) {
                  String extName = parseName(tokens);
                  AstNode optionNode = nodeFactory().node("externalName", functionNode, TYPE_STATEMENT_OPTION);
                  optionNode.setProperty(VALUE, "EXTERNAL NAME" + SPACE + extName);
              } else if (tokens.canConsume("PARAMETER", "STYLE")) {
                  if (tokens.canConsume("JAVA")) {
                      functionNode.setProperty(PARAMETER_STYLE, "PARAMETER STYLE" + SPACE + "JAVA");
                  } else {
                      tokens.consume("DERBY_JDBC_RESULT_SET");
                      functionNode.setProperty(PARAMETER_STYLE, "PARAMETER STYLE" + SPACE + "DERBY_JDBC_RESULT_SET");
                  }
              } else if (tokens.canConsume("NO", "SQL")) {
                  AstNode optionNode = nodeFactory().node("sqlStatus", functionNode, TYPE_STATEMENT_OPTION);
                  optionNode.setProperty(VALUE, "NO SQL");
              } else if (tokens.canConsume("CONTAINS", "SQL")) {
                  AstNode optionNode = nodeFactory().node("sqlStatus", functionNode, TYPE_STATEMENT_OPTION);
                  optionNode.setProperty(VALUE, "CONTAINS SQL");
              } else if (tokens.canConsume("READS", "SQL", "DATA")) {
                  AstNode optionNode = nodeFactory().node("sqlStatus", functionNode, TYPE_STATEMENT_OPTION);
                  optionNode.setProperty(VALUE, "READS SQL DATA");
              } else if (tokens.canConsume("RETURNS", "NULL", "ON", "NULL", "INPUT")) {
                  AstNode optionNode = nodeFactory().node("nullInput", functionNode, TYPE_STATEMENT_OPTION);
                  optionNode.setProperty(VALUE, "RETURNS NULL ON NULL INPUT");
              } else if (tokens.canConsume("CALLED", "ON", "NULL", "INPUT")) {
                  AstNode optionNode = nodeFactory().node("nullInput", functionNode, TYPE_STATEMENT_OPTION);
                  optionNode.setProperty(VALUE, "CALLED ON NULL INPUT");
              } else {
                  String msg = DdlSequencerI18n.errorParsingDdlContent.text(functionName);
                  DdlParserProblem problem = new DdlParserProblem(Problems.ERROR, getCurrentMarkedPosition(), msg);
                  addProblem(problem, functionNode);
                  break;
              }
          }
  
          markEndOfStatement(tokens, functionNode);
  
          return functionNode;
      }