static ParsedCopyStatement parse()

in src/main/java/com/google/cloud/spanner/pgadapter/statements/CopyStatement.java [603:731]


  static ParsedCopyStatement parse(String sql) {
    Preconditions.checkNotNull(sql);

    SimpleParser parser = new SimpleParser(sql);
    if (!parser.eatKeyword("copy")) {
      throw PGExceptionFactory.newPGException(
          "not a valid COPY statement: " + sql, SQLState.SyntaxError);
    }
    ParsedCopyStatement.Builder builder = new ParsedCopyStatement.Builder();
    if (parser.eatToken("(")) {
      builder.query = parser.parseExpressionUntilKeyword(ImmutableList.of(), true, true, false);
      if (!parser.eatToken(")")) {
        throw PGExceptionFactory.newPGException(
            "missing closing parentheses after query", SQLState.SyntaxError);
      }
    } else {
      builder.table = parser.readTableOrIndexName();
      if (builder.table == null) {
        throw PGExceptionFactory.newPGException(
            "invalid or missing table name", SQLState.SyntaxError);
      }
      if (parser.peekToken("(")) {
        builder.columns = parser.readColumnListInParentheses("columns");
      }
    }

    if (!(parser.peekKeyword("from") || parser.peekKeyword("to"))) {
      throw PGExceptionFactory.newPGException(
          "missing 'FROM' or 'TO' keyword: " + sql, SQLState.SyntaxError);
    }
    builder.direction = Direction.valueOf(parser.readKeyword().toString().toUpperCase());
    if (builder.direction == Direction.FROM) {
      // Silently ignore typo 'copy from stdout'.
      // See
      // https://github.com/postgres/postgres/blob/03ec203164119f11f0eab4c83c97a8527e2b108d/src/backend/parser/gram.y#L3463
      if (!parser.eatKeyword("stdin") && !parser.eatKeyword("stdout")) {
        throw PGExceptionFactory.newPGException(
            "missing 'STDIN' keyword. PGAdapter only supports COPY ... FROM STDIN: " + sql,
            SQLState.SyntaxError);
      }
    } else {
      // Silently ignore typo 'copy to stdin'.
      // See
      // https://github.com/postgres/postgres/blob/03ec203164119f11f0eab4c83c97a8527e2b108d/src/backend/parser/gram.y#L3463
      if (!parser.eatKeyword("stdout") && !parser.eatKeyword("stdin")) {
        throw PGExceptionFactory.newPGException(
            "missing 'STDOUT' keyword. PGAdapter only supports COPY ... TO STDOUT: " + sql,
            SQLState.SyntaxError);
      }
    }
    parser.eatKeyword("with");
    if (parser.eatToken("(")) {
      List<String> optionExpressions = parser.parseExpressionListUntilKeyword(null, true);
      if (!parser.eatToken(")")) {
        throw PGExceptionFactory.newPGException(
            "missing closing parentheses for options list", SQLState.SyntaxError);
      }
      if (optionExpressions == null || optionExpressions.isEmpty()) {
        throw PGExceptionFactory.newPGException("empty options list: " + sql, SQLState.SyntaxError);
      }
      for (String optionExpression : optionExpressions) {
        SimpleParser optionParser = new SimpleParser(optionExpression);
        if (optionParser.eatKeyword("format")) {
          if (optionParser.peekKeyword("text")
              || optionParser.peekKeyword("csv")
              || optionParser.peekKeyword("binary")) {
            builder.format = Format.valueOf(optionParser.readKeyword().toString().toUpperCase());
          } else {
            throw PGExceptionFactory.newPGException("Invalid format option: " + optionExpression);
          }
        } else if (optionParser.eatKeyword("freeze")) {
          if (optionParser.hasMoreTokens()) {
            CharSequence value = optionParser.readKeyword();
            builder.freeze = BooleanParser.toBoolean(value.toString());
          } else {
            builder.freeze = true;
          }
        } else if (optionParser.eatKeyword("delimiter")) {
          eatDelimiter(optionParser, builder);
        } else if (optionParser.eatKeyword("null")) {
          builder.nullString = optionParser.readSingleQuotedString().getValue();
        } else if (optionParser.eatKeyword("header")) {
          if (optionParser.eatKeyword("match")) {
            builder.headerMatch = true;
            builder.header = true;
          } else if (optionParser.hasMoreTokens()) {
            CharSequence value = optionParser.readKeyword();
            builder.header = BooleanParser.toBoolean(value.toString());
          } else {
            builder.header = true;
          }
        } else if (optionParser.eatKeyword("quote")) {
          eatQuote(optionParser, builder);
        } else if (optionParser.eatKeyword("escape")) {
          eatEscape(optionParser, builder);
        } else if (optionParser.eatKeyword("force_quote")) {
          if (optionParser.eatToken("*")) {
            builder.forceQuote = ImmutableList.of();
          } else {
            builder.forceQuote = optionParser.readColumnListInParentheses("force_quote");
          }
        } else if (optionParser.eatKeyword("force_not_null")) {
          builder.forceNotNull = optionParser.readColumnListInParentheses("force_not_null");
        } else if (optionParser.eatKeyword("force_null")) {
          builder.forceNull = optionParser.readColumnListInParentheses("force_null");
        } else if (optionParser.eatKeyword("encoding")) {
          builder.encoding = optionParser.readSingleQuotedString().getValue();
        } else {
          throw PGExceptionFactory.newPGException(
              "Invalid or unknown option: " + optionExpression, SQLState.SyntaxError);
        }
        optionParser.throwIfHasMoreTokens();
      }
    } else if (parser.peekKeyword("binary")
        || parser.peekKeyword("delimiter")
        || parser.peekKeyword("null")
        || parser.peekKeyword("csv")) {
      parseLegacyOptions(parser, builder);
    }
    if (parser.eatKeyword("where")) {
      throw PGExceptionFactory.newPGException(
          "PGAdapter does not support conditions in COPY ... FROM STDIN: " + sql,
          SQLState.SyntaxError);
    }
    parser.eatToken(";");
    parser.throwIfHasMoreTokens();

    return builder.build();
  }