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();
}