diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Statement.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Statement.java index ea6cdf3f65c..a89c7c048fc 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Statement.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Statement.java @@ -178,6 +178,11 @@ public String getSql() { return sql; } + /** Returns a copy of this statement with the SQL string replaced by the given SQL string. */ + public Statement withReplacedSql(String sql) { + return new Statement(sql, this.parameters, this.queryOptions); + } + /** Returns the {@link QueryOptions} that will be used with this {@link Statement}. */ public QueryOptions getQueryOptions() { return queryOptions; diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/AbstractStatementParser.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/AbstractStatementParser.java index d0c06fa1d9d..b45d444b744 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/AbstractStatementParser.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/AbstractStatementParser.java @@ -16,9 +16,13 @@ package com.google.cloud.spanner.connection; +import static com.google.cloud.spanner.connection.SimpleParser.isValidIdentifierChar; +import static com.google.cloud.spanner.connection.StatementHintParser.convertHintsToOptions; + import com.google.api.core.InternalApi; import com.google.cloud.spanner.Dialect; import com.google.cloud.spanner.ErrorCode; +import com.google.cloud.spanner.Options.ReadQueryUpdateTransactionOption; import com.google.cloud.spanner.SpannerException; import com.google.cloud.spanner.SpannerExceptionFactory; import com.google.cloud.spanner.Statement; @@ -169,6 +173,7 @@ public static class ParsedStatement { private final Statement statement; private final String sqlWithoutComments; private final boolean returningClause; + private final ReadQueryUpdateTransactionOption[] optionsFromHints; private static ParsedStatement clientSideStatement( ClientSideStatementImpl clientSideStatement, @@ -182,15 +187,27 @@ private static ParsedStatement ddl(Statement statement, String sqlWithoutComment } private static ParsedStatement query( - Statement statement, String sqlWithoutComments, QueryOptions defaultQueryOptions) { + Statement statement, + String sqlWithoutComments, + QueryOptions defaultQueryOptions, + ReadQueryUpdateTransactionOption[] optionsFromHints) { return new ParsedStatement( - StatementType.QUERY, null, statement, sqlWithoutComments, defaultQueryOptions, false); + StatementType.QUERY, + null, + statement, + sqlWithoutComments, + defaultQueryOptions, + false, + optionsFromHints); } private static ParsedStatement update( - Statement statement, String sqlWithoutComments, boolean returningClause) { + Statement statement, + String sqlWithoutComments, + boolean returningClause, + ReadQueryUpdateTransactionOption[] optionsFromHints) { return new ParsedStatement( - StatementType.UPDATE, statement, sqlWithoutComments, returningClause); + StatementType.UPDATE, statement, sqlWithoutComments, returningClause, optionsFromHints); } private static ParsedStatement unknown(Statement statement, String sqlWithoutComments) { @@ -208,18 +225,20 @@ private ParsedStatement( this.statement = statement; this.sqlWithoutComments = Preconditions.checkNotNull(sqlWithoutComments); this.returningClause = false; + this.optionsFromHints = EMPTY_OPTIONS; } private ParsedStatement( StatementType type, Statement statement, String sqlWithoutComments, - boolean returningClause) { - this(type, null, statement, sqlWithoutComments, null, returningClause); + boolean returningClause, + ReadQueryUpdateTransactionOption[] optionsFromHints) { + this(type, null, statement, sqlWithoutComments, null, returningClause, optionsFromHints); } private ParsedStatement(StatementType type, Statement statement, String sqlWithoutComments) { - this(type, null, statement, sqlWithoutComments, null, false); + this(type, null, statement, sqlWithoutComments, null, false, EMPTY_OPTIONS); } private ParsedStatement( @@ -228,33 +247,37 @@ private ParsedStatement( Statement statement, String sqlWithoutComments, QueryOptions defaultQueryOptions, - boolean returningClause) { + boolean returningClause, + ReadQueryUpdateTransactionOption[] optionsFromHints) { Preconditions.checkNotNull(type); this.type = type; this.clientSideStatement = clientSideStatement; this.statement = statement == null ? null : mergeQueryOptions(statement, defaultQueryOptions); this.sqlWithoutComments = Preconditions.checkNotNull(sqlWithoutComments); this.returningClause = returningClause; + this.optionsFromHints = optionsFromHints; } private ParsedStatement copy(Statement statement, QueryOptions defaultQueryOptions) { return new ParsedStatement( this.type, this.clientSideStatement, - statement, + statement.withReplacedSql(this.statement.getSql()), this.sqlWithoutComments, defaultQueryOptions, - this.returningClause); + this.returningClause, + this.optionsFromHints); } private ParsedStatement forCache() { return new ParsedStatement( this.type, this.clientSideStatement, - null, + Statement.of(this.statement.getSql()), this.sqlWithoutComments, null, - this.returningClause); + this.returningClause, + this.optionsFromHints); } @Override @@ -287,6 +310,11 @@ public boolean hasReturningClause() { return this.returningClause; } + @InternalApi + public ReadQueryUpdateTransactionOption[] getOptionsFromHints() { + return this.optionsFromHints; + } + /** * @return true if the statement is a query that will return a {@link * com.google.cloud.spanner.ResultSet}. @@ -480,14 +508,23 @@ ParsedStatement parse(Statement statement, QueryOptions defaultQueryOptions) { } private ParsedStatement internalParse(Statement statement, QueryOptions defaultQueryOptions) { + StatementHintParser statementHintParser = + new StatementHintParser(getDialect(), statement.getSql()); + ReadQueryUpdateTransactionOption[] optionsFromHints = EMPTY_OPTIONS; + if (statementHintParser.hasStatementHints() + && !statementHintParser.getClientSideStatementHints().isEmpty()) { + statement = + statement.toBuilder().replace(statementHintParser.getSqlWithoutClientSideHints()).build(); + optionsFromHints = convertHintsToOptions(statementHintParser.getClientSideStatementHints()); + } String sql = removeCommentsAndTrim(statement.getSql()); ClientSideStatementImpl client = parseClientSideStatement(sql); if (client != null) { return ParsedStatement.clientSideStatement(client, statement, sql); } else if (isQuery(sql)) { - return ParsedStatement.query(statement, sql, defaultQueryOptions); + return ParsedStatement.query(statement, sql, defaultQueryOptions, optionsFromHints); } else if (isUpdateStatement(sql)) { - return ParsedStatement.update(statement, sql, checkReturningClause(sql)); + return ParsedStatement.update(statement, sql, checkReturningClause(sql), optionsFromHints); } else if (isDdlStatement(sql)) { return ParsedStatement.ddl(statement, sql); } @@ -621,6 +658,10 @@ public String removeCommentsAndTrim(String sql) { /** Removes any statement hints at the beginning of the statement. */ abstract String removeStatementHint(String sql); + @VisibleForTesting + static final ReadQueryUpdateTransactionOption[] EMPTY_OPTIONS = + new ReadQueryUpdateTransactionOption[0]; + /** Parameter information with positional parameters translated to named parameters. */ @InternalApi public static class ParametersInfo { @@ -697,9 +738,10 @@ public boolean checkReturningClause(String sql) { return checkReturningClauseInternal(sql); } + abstract Dialect getDialect(); + /** - * <<<<<<< HEAD Returns true if this dialect supports nested comments. ======= <<<<<<< HEAD - * Returns true if this dialect supports nested comments. >>>>>>> main + * Returns true if this dialect supports nested comments. * *