() {
- @Override
- public ResultSet call() throws Exception {
- return DirectExecuteResultSet.ofResultSet(
- internalExecuteQuery(statement, analyzeMode, options));
- }
- });
- }
-
- ResultSet internalExecuteQuery(
- final ParsedStatement statement, AnalyzeMode analyzeMode, QueryOption... options) {
- if (analyzeMode == AnalyzeMode.NONE) {
- return getReadContext().executeQuery(statement.getStatement(), options);
- }
- return getReadContext()
- .analyzeQuery(statement.getStatement(), analyzeMode.getQueryAnalyzeMode());
- }
-
- @Override
- public long[] runBatch() {
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.FAILED_PRECONDITION, "Run batch is not supported for transactions");
- }
-
- @Override
- public void abortBatch() {
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.FAILED_PRECONDITION, "Run batch is not supported for transactions");
- }
-}
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/AnalyzeMode.java b/src/main/java/com/google/cloud/spanner/jdbc/AnalyzeMode.java
deleted file mode 100644
index 457ceec2..00000000
--- a/src/main/java/com/google/cloud/spanner/jdbc/AnalyzeMode.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright 2019 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.cloud.spanner.jdbc;
-
-import com.google.cloud.spanner.ReadContext.QueryAnalyzeMode;
-
-/**
- * {@link AnalyzeMode} indicates whether a query should be executed as a normal query (NONE),
- * whether only a query plan should be returned, or whether the query should be profiled while
- * executed.
- */
-enum AnalyzeMode {
- NONE(null),
- PLAN(QueryAnalyzeMode.PLAN),
- PROFILE(QueryAnalyzeMode.PROFILE);
-
- private final QueryAnalyzeMode mode;
-
- private AnalyzeMode(QueryAnalyzeMode mode) {
- this.mode = mode;
- }
-
- QueryAnalyzeMode getQueryAnalyzeMode() {
- return mode;
- }
-
- /** Translates from the Spanner client library QueryAnalyzeMode to {@link AnalyzeMode}. */
- static AnalyzeMode of(QueryAnalyzeMode mode) {
- switch (mode) {
- case PLAN:
- return AnalyzeMode.PLAN;
- case PROFILE:
- return AnalyzeMode.PROFILE;
- default:
- throw new IllegalArgumentException(mode + " is unknown");
- }
- }
-}
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/AutocommitDmlMode.java b/src/main/java/com/google/cloud/spanner/jdbc/AutocommitDmlMode.java
deleted file mode 100644
index c390edd1..00000000
--- a/src/main/java/com/google/cloud/spanner/jdbc/AutocommitDmlMode.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright 2019 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.cloud.spanner.jdbc;
-
-/** Enum used to define the behavior of DML statements in autocommit mode */
-enum AutocommitDmlMode {
- TRANSACTIONAL,
- PARTITIONED_NON_ATOMIC;
-
- private final String statementString;
-
- private AutocommitDmlMode() {
- this.statementString = name();
- }
-
- /**
- * Use this method to get the correct format for use in a SQL statement. Autocommit dml mode must
- * be wrapped between single quotes in SQL statements:
- * SET AUTOCOMMIT_DML_MODE='TRANSACTIONAL'
This method returns the value
- * without the single quotes.
- *
- * @return a string representation of this {@link AutocommitDmlMode} that can be used in a SQL
- * statement.
- */
- public String getStatementString() {
- return statementString;
- }
-}
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/ChecksumResultSet.java b/src/main/java/com/google/cloud/spanner/jdbc/ChecksumResultSet.java
deleted file mode 100644
index 0e0d7b43..00000000
--- a/src/main/java/com/google/cloud/spanner/jdbc/ChecksumResultSet.java
+++ /dev/null
@@ -1,355 +0,0 @@
-/*
- * Copyright 2019 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.cloud.spanner.jdbc;
-
-import com.google.cloud.ByteArray;
-import com.google.cloud.Date;
-import com.google.cloud.Timestamp;
-import com.google.cloud.spanner.AbortedException;
-import com.google.cloud.spanner.Options.QueryOption;
-import com.google.cloud.spanner.ResultSet;
-import com.google.cloud.spanner.SpannerException;
-import com.google.cloud.spanner.SpannerExceptionFactory;
-import com.google.cloud.spanner.Struct;
-import com.google.cloud.spanner.Type.Code;
-import com.google.cloud.spanner.jdbc.ReadWriteTransaction.RetriableStatement;
-import com.google.cloud.spanner.jdbc.StatementParser.ParsedStatement;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Preconditions;
-import com.google.common.hash.Funnel;
-import com.google.common.hash.HashCode;
-import com.google.common.hash.HashFunction;
-import com.google.common.hash.Hasher;
-import com.google.common.hash.Hashing;
-import com.google.common.hash.PrimitiveSink;
-import java.util.Objects;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
-
-/**
- * {@link ResultSet} implementation that keeps a running checksum that can be used to determine
- * whether a transaction retry is possible or not. The checksum is based on all the rows that have
- * actually been consumed by the user. If the user has not yet consumed any part of the result set
- * (i.e. never called next()), the checksum will be null
and retry will always be
- * allowed.
- *
- * If all the rows in the result set have been consumed, the checksum will be based on the values
- * of all those rows, and a retry will only be possible if the query returns the exact same results
- * during the retry as during the original transaction.
- *
- *
If some of the rows in the result set have been consumed, the checksum will be based on the
- * values of the rows that have been consumed. A retry will succeed if the query returns the same
- * results for the already consumed rows.
- *
- *
The checksum of a {@link ResultSet} is the SHA256 checksum of the current row together with
- * the previous checksum value of the result set. The calculation of the checksum is executed in a
- * separate {@link Thread} to allow the checksum calculation to lag behind the actual consumption of
- * rows, and catch up again if the client slows down the consumption of rows, for example while
- * waiting for more data from Cloud Spanner. If the checksum calculation queue contains more than
- * {@link ChecksumExecutor#MAX_IN_CHECKSUM_QUEUE} items that have not yet been calculated, calls to
- * {@link ResultSet#next()} will slow down in order to allow the calculation to catch up.
- */
-@VisibleForTesting
-class ChecksumResultSet extends ReplaceableForwardingResultSet implements RetriableStatement {
- private final ReadWriteTransaction transaction;
- private long numberOfNextCalls;
- private final ParsedStatement statement;
- private final AnalyzeMode analyzeMode;
- private final QueryOption[] options;
- private final ChecksumResultSet.ChecksumCalculator checksumCalculator = new ChecksumCalculator();
-
- ChecksumResultSet(
- ReadWriteTransaction transaction,
- ResultSet delegate,
- ParsedStatement statement,
- AnalyzeMode analyzeMode,
- QueryOption... options) {
- super(delegate);
- Preconditions.checkNotNull(transaction);
- Preconditions.checkNotNull(delegate);
- Preconditions.checkNotNull(statement);
- Preconditions.checkNotNull(statement.getStatement());
- Preconditions.checkNotNull(statement.getStatement().getSql());
- this.transaction = transaction;
- this.statement = statement;
- this.analyzeMode = analyzeMode;
- this.options = options;
- }
-
- /** Simple {@link Callable} for calling {@link ResultSet#next()} */
- private final class NextCallable implements Callable {
- @Override
- public Boolean call() throws Exception {
- transaction
- .getStatementExecutor()
- .invokeInterceptors(
- statement, StatementExecutionStep.CALL_NEXT_ON_RESULT_SET, transaction);
- return ChecksumResultSet.super.next();
- }
- }
-
- private final NextCallable nextCallable = new NextCallable();
-
- @Override
- public boolean next() {
- // Call next() with retry.
- boolean res = transaction.runWithRetry(nextCallable);
- // Only update the checksum if there was another row to be consumed.
- if (res) {
- checksumCalculator.calculateNextChecksum(getCurrentRowAsStruct());
- }
- numberOfNextCalls++;
- return res;
- }
-
- @VisibleForTesting
- HashCode getChecksum() throws InterruptedException, ExecutionException {
- // HashCode is immutable and can be safely returned.
- return checksumCalculator.getChecksum();
- }
-
- /**
- * Execute the same query as in the original transaction and consume the {@link ResultSet} to the
- * same point as the original {@link ResultSet}. The {@link HashCode} of the new {@link ResultSet}
- * is compared with the {@link HashCode} of the original {@link ResultSet} at the point where the
- * consumption of the {@link ResultSet} stopped.
- */
- @Override
- public void retry(AbortedException aborted) throws AbortedException {
- // Execute the same query and consume the result set to the same point as the original.
- ChecksumResultSet.ChecksumCalculator newChecksumCalculator = new ChecksumCalculator();
- ResultSet resultSet = null;
- long counter = 0L;
- try {
- transaction
- .getStatementExecutor()
- .invokeInterceptors(statement, StatementExecutionStep.RETRY_STATEMENT, transaction);
- resultSet =
- DirectExecuteResultSet.ofResultSet(
- transaction.internalExecuteQuery(statement, analyzeMode, options));
- boolean next = true;
- while (counter < numberOfNextCalls && next) {
- transaction
- .getStatementExecutor()
- .invokeInterceptors(
- statement, StatementExecutionStep.RETRY_NEXT_ON_RESULT_SET, transaction);
- next = resultSet.next();
- if (next) {
- newChecksumCalculator.calculateNextChecksum(resultSet.getCurrentRowAsStruct());
- }
- counter++;
- }
- } catch (Throwable e) {
- if (resultSet != null) {
- resultSet.close();
- }
- // If it was a SpannerException other than an AbortedException, the retry should fail
- // because of different results from the database.
- if (e instanceof SpannerException && !(e instanceof AbortedException)) {
- throw SpannerExceptionFactory.newAbortedDueToConcurrentModificationException(
- aborted, (SpannerException) e);
- }
- // For other types of exceptions we should just re-throw the exception.
- throw e;
- }
- // Check that we have the same number of rows and the same checksum.
- HashCode newChecksum = newChecksumCalculator.getChecksum();
- HashCode currentChecksum = checksumCalculator.getChecksum();
- if (counter == numberOfNextCalls && Objects.equals(newChecksum, currentChecksum)) {
- // Checksum is ok, we only need to replace the delegate result set if it's still open.
- if (isClosed()) {
- resultSet.close();
- } else {
- replaceDelegate(resultSet);
- }
- } else {
- // The results are not equal, there is an actual concurrent modification, so we cannot
- // continue the transaction.
- throw SpannerExceptionFactory.newAbortedDueToConcurrentModificationException(aborted);
- }
- }
-
- /** Calculates and keeps the current checksum of a {@link ChecksumResultSet} */
- private static final class ChecksumCalculator {
- private static final HashFunction SHA256_FUNCTION = Hashing.sha256();
- private HashCode currentChecksum;
-
- private void calculateNextChecksum(Struct row) {
- Hasher hasher = SHA256_FUNCTION.newHasher();
- if (currentChecksum != null) {
- hasher.putBytes(currentChecksum.asBytes());
- }
- hasher.putObject(row, StructFunnel.INSTANCE);
- currentChecksum = hasher.hash();
- }
-
- private HashCode getChecksum() {
- return currentChecksum;
- }
- }
-
- /**
- * A {@link Funnel} implementation for calculating a {@link HashCode} for each row in a {@link
- * ResultSet}.
- */
- private enum StructFunnel implements Funnel {
- INSTANCE;
- private static final String NULL = "null";
-
- @Override
- public void funnel(Struct row, PrimitiveSink into) {
- for (int i = 0; i < row.getColumnCount(); i++) {
- if (row.isNull(i)) {
- funnelValue(Code.STRING, null, into);
- } else {
- Code type = row.getColumnType(i).getCode();
- switch (type) {
- case ARRAY:
- funnelArray(row.getColumnType(i).getArrayElementType().getCode(), row, i, into);
- break;
- case BOOL:
- funnelValue(type, row.getBoolean(i), into);
- break;
- case BYTES:
- funnelValue(type, row.getBytes(i), into);
- break;
- case DATE:
- funnelValue(type, row.getDate(i), into);
- break;
- case FLOAT64:
- funnelValue(type, row.getDouble(i), into);
- break;
- case INT64:
- funnelValue(type, row.getLong(i), into);
- break;
- case STRING:
- funnelValue(type, row.getString(i), into);
- break;
- case TIMESTAMP:
- funnelValue(type, row.getTimestamp(i), into);
- break;
-
- case STRUCT:
- default:
- throw new IllegalArgumentException("unsupported row type");
- }
- }
- }
- }
-
- private void funnelArray(
- Code arrayElementType, Struct row, int columnIndex, PrimitiveSink into) {
- funnelValue(Code.STRING, "BeginArray", into);
- switch (arrayElementType) {
- case BOOL:
- into.putInt(row.getBooleanList(columnIndex).size());
- for (Boolean value : row.getBooleanList(columnIndex)) {
- funnelValue(Code.BOOL, value, into);
- }
- break;
- case BYTES:
- into.putInt(row.getBytesList(columnIndex).size());
- for (ByteArray value : row.getBytesList(columnIndex)) {
- funnelValue(Code.BYTES, value, into);
- }
- break;
- case DATE:
- into.putInt(row.getDateList(columnIndex).size());
- for (Date value : row.getDateList(columnIndex)) {
- funnelValue(Code.DATE, value, into);
- }
- break;
- case FLOAT64:
- into.putInt(row.getDoubleList(columnIndex).size());
- for (Double value : row.getDoubleList(columnIndex)) {
- funnelValue(Code.FLOAT64, value, into);
- }
- break;
- case INT64:
- into.putInt(row.getLongList(columnIndex).size());
- for (Long value : row.getLongList(columnIndex)) {
- funnelValue(Code.INT64, value, into);
- }
- break;
- case STRING:
- into.putInt(row.getStringList(columnIndex).size());
- for (String value : row.getStringList(columnIndex)) {
- funnelValue(Code.STRING, value, into);
- }
- break;
- case TIMESTAMP:
- into.putInt(row.getTimestampList(columnIndex).size());
- for (Timestamp value : row.getTimestampList(columnIndex)) {
- funnelValue(Code.TIMESTAMP, value, into);
- }
- break;
-
- case ARRAY:
- case STRUCT:
- default:
- throw new IllegalArgumentException("unsupported array element type");
- }
- funnelValue(Code.STRING, "EndArray", into);
- }
-
- private void funnelValue(Code type, T value, PrimitiveSink into) {
- // Include the type name in case the type of a column has changed.
- into.putUnencodedChars(type.name());
- if (value == null) {
- if (type == Code.BYTES || type == Code.STRING) {
- // Put length -1 to distinguish from the string value 'null'.
- into.putInt(-1);
- }
- into.putUnencodedChars(NULL);
- } else {
- switch (type) {
- case BOOL:
- into.putBoolean((Boolean) value);
- break;
- case BYTES:
- ByteArray byteArray = (ByteArray) value;
- into.putInt(byteArray.length());
- into.putBytes(byteArray.toByteArray());
- break;
- case DATE:
- Date date = (Date) value;
- into.putInt(date.getYear()).putInt(date.getMonth()).putInt(date.getDayOfMonth());
- break;
- case FLOAT64:
- into.putDouble((Double) value);
- break;
- case INT64:
- into.putLong((Long) value);
- break;
- case STRING:
- String stringValue = (String) value;
- into.putInt(stringValue.length());
- into.putUnencodedChars(stringValue);
- break;
- case TIMESTAMP:
- Timestamp timestamp = (Timestamp) value;
- into.putLong(timestamp.getSeconds()).putInt(timestamp.getNanos());
- break;
- case ARRAY:
- case STRUCT:
- default:
- throw new IllegalArgumentException("invalid type for single value");
- }
- }
- }
- }
-}
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/ClientSideStatement.java b/src/main/java/com/google/cloud/spanner/jdbc/ClientSideStatement.java
deleted file mode 100644
index 7bd31407..00000000
--- a/src/main/java/com/google/cloud/spanner/jdbc/ClientSideStatement.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright 2019 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.cloud.spanner.jdbc;
-
-import com.google.cloud.spanner.ResultSet;
-import java.util.List;
-
-/**
- * A {@link ClientSideStatement} is a statement that is not sent to Google Cloud Spanner, but that
- * is executed locally to for example set a certain state of a {@link Connection} or get a property
- * of a {@link Connection}.
- */
-interface ClientSideStatement {
-
- /**
- * @return a list of example statements for this {@link ClientSideStatement}. If these statements
- * are parsed, they will all result this in this {@link ClientSideStatement}.
- */
- List getExampleStatements();
-
- /**
- * @return a list of statements that need to be executed on a new connection before the example
- * statements may be executed on a connection. For GET READ_TIMESTAMP this would for example
- * be a couple of statements that generate a read-only transaction.
- */
- List getExamplePrerequisiteStatements();
-
- /**
- * @return true
if this {@link ClientSideStatement} will return a {@link ResultSet}.
- */
- boolean isQuery();
-
- /** @return true
if this {@link ClientSideStatement} will return an update count. */
- boolean isUpdate();
-
- /**
- * Execute this {@link ClientSideStatement} on the given {@link ConnectionStatementExecutor}. The
- * executor calls the appropriate method(s) on the {@link Connection}. The statement argument is
- * used to parse any additional properties that might be needed for the execution.
- *
- * @param executor The {@link ConnectionStatementExecutor} that will be used to call a method on
- * the {@link Connection}.
- * @param statement The original sql statement that has been parsed to this {@link
- * ClientSideStatement}. This statement is used to get any additional arguments that are
- * needed for the execution of the {@link ClientSideStatement}.
- * @return the result of the execution of the statement.
- */
- StatementResult execute(ConnectionStatementExecutor executor, String statement);
-}
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/ClientSideStatementExecutor.java b/src/main/java/com/google/cloud/spanner/jdbc/ClientSideStatementExecutor.java
deleted file mode 100644
index d758286d..00000000
--- a/src/main/java/com/google/cloud/spanner/jdbc/ClientSideStatementExecutor.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright 2019 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.cloud.spanner.jdbc;
-
-import com.google.cloud.spanner.jdbc.ClientSideStatementImpl.CompileException;
-
-/**
- * A {@link ClientSideStatementExecutor} is used to compile {@link ClientSideStatement}s from the
- * json source file, and to execute these against a {@link Connection} (through a {@link
- * ConnectionStatementExecutor}).
- */
-interface ClientSideStatementExecutor {
-
- /**
- * Compiles the given {@link ClientSideStatementImpl} and registers this statement with this
- * executor. A statement must be compiled before it can be executed. The parser automatically
- * compiles all available statements during initialization.
- *
- * @param statement The statement to compile.
- * @throws CompileException If the statement could not be compiled. This should never happen, as
- * it would indicate that an invalid statement has been defined in the source file.
- */
- void compile(ClientSideStatementImpl statement) throws CompileException;
-
- /**
- * Executes the {@link ClientSideStatementImpl} that has been compiled and registered with this
- * executor on the specified connection.
- *
- * @param connectionExecutor The {@link ConnectionStatementExecutor} to use to execute the
- * statement on a {@link Connection}.
- * @param sql The sql statement that is executed. This can be used to parse any additional
- * arguments that might be needed for the execution of the {@link ClientSideStatementImpl}.
- * @return the result of the execution.
- * @throws Exception If an error occurs while executing the statement, for example if an invalid
- * argument has been specified in the sql statement, or if the statement is invalid for the
- * current state of the {@link Connection}.
- */
- StatementResult execute(ConnectionStatementExecutor connectionExecutor, String sql)
- throws Exception;
-}
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/ClientSideStatementImpl.java b/src/main/java/com/google/cloud/spanner/jdbc/ClientSideStatementImpl.java
deleted file mode 100644
index 551ab5aa..00000000
--- a/src/main/java/com/google/cloud/spanner/jdbc/ClientSideStatementImpl.java
+++ /dev/null
@@ -1,217 +0,0 @@
-/*
- * Copyright 2019 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.cloud.spanner.jdbc;
-
-import com.google.cloud.spanner.SpannerException;
-import com.google.cloud.spanner.jdbc.StatementResult.ResultType;
-import com.google.common.base.Preconditions;
-import java.lang.reflect.InvocationTargetException;
-import java.util.Collections;
-import java.util.List;
-import java.util.regex.Pattern;
-
-/**
- * Implementation of the {@link ClientSideStatement} interface. The instances of this class are
- * imported from the file 'ClientSideStatements.json' in the resources folder.
- */
-class ClientSideStatementImpl implements ClientSideStatement {
-
- /**
- * Statements that set a value, such as SET AUTOCOMMIT ON|OFF, must specify a {@link
- * ClientSideSetStatementImpl} that defines how the value is set.
- */
- static class ClientSideSetStatementImpl {
- /** The property name that is to be set, e.g. AUTOCOMMIT. */
- private String propertyName;
- /** The separator between the property and the value (i.e. '=' or '\s+'). */
- private String separator;
- /** Regex specifying the range of allowed values for the property. */
- private String allowedValues;
- /** The class name of the {@link ClientSideStatementValueConverter} to use. */
- private String converterName;
-
- String getPropertyName() {
- return propertyName;
- }
-
- String getSeparator() {
- return separator;
- }
-
- String getAllowedValues() {
- return allowedValues;
- }
-
- String getConverterName() {
- return converterName;
- }
- }
-
- static class CompileException extends Exception {
- private static final long serialVersionUID = 1L;
- private final ClientSideStatementImpl statement;
-
- CompileException(Throwable cause, ClientSideStatementImpl statement) {
- super(cause);
- this.statement = statement;
- }
-
- @Override
- public String getMessage() {
- return "Could not compile statement " + this.statement.name;
- }
- }
-
- static class ExecuteException extends RuntimeException {
- private static final long serialVersionUID = 1L;
- private final ClientSideStatementImpl statement;
- private final String sql;
-
- private ExecuteException(Throwable cause, ClientSideStatementImpl statement, String sql) {
- super(cause);
- this.statement = statement;
- this.sql = sql;
- }
-
- @Override
- public String getMessage() {
- return "Could not execute statement " + this.statement.name + " (" + sql + ")";
- }
- }
-
- /** The name of this statement. Used in error and info messages. */
- private String name;
-
- /**
- * The class name of the {@link ClientSideStatementExecutor} that should be used for this
- * statement.
- */
- private String executorName;
-
- /** The result type of this statement. */
- private ResultType resultType;
-
- /** The regular expression that should be used to recognize this class of statements. */
- private String regex;
-
- /**
- * The method name of the {@link ConnectionStatementExecutor} that should be called when this
- * statement is executed, for example 'statementSetAutocommit'.
- */
- private String method;
-
- /** A list of example statements that is used for testing. */
- private List exampleStatements;
-
- /**
- * A list of statements that need to be executed before the example statements may be executed.
- */
- private List examplePrerequisiteStatements;
-
- /**
- * If this statement sets a value, the statement definition should also contain a {@link
- * ClientSideSetStatementImpl} definition that defines how the value that is to be set should be
- * parsed.
- */
- private ClientSideSetStatementImpl setStatement;
-
- /** The compiled regex pattern for recognizing this statement. */
- private Pattern pattern;
-
- /** A reference to the executor that should be used. */
- private ClientSideStatementExecutor executor;
-
- /**
- * Compiles this {@link ClientSideStatementImpl}. Throws a {@link CompileException} if the
- * compilation fails. This should never happen, and if it does, it is a sign of a invalid
- * statement definition in the ClientSideStatements.json file.
- */
- ClientSideStatementImpl compile() throws CompileException {
- try {
- this.pattern = Pattern.compile(regex);
- this.executor =
- (ClientSideStatementExecutor)
- Class.forName(getClass().getPackage().getName() + "." + executorName).newInstance();
- this.executor.compile(this);
- return this;
- } catch (Exception e) {
- throw new CompileException(e, this);
- }
- }
-
- @Override
- public StatementResult execute(ConnectionStatementExecutor connection, String statement) {
- Preconditions.checkState(executor != null, "This statement has not been compiled");
- try {
- return executor.execute(connection, statement);
- } catch (SpannerException e) {
- throw e;
- } catch (InvocationTargetException e) {
- if (e.getCause() instanceof SpannerException) {
- throw (SpannerException) e.getCause();
- }
- throw new ExecuteException(e.getCause(), this, statement);
- } catch (Exception e) {
- throw new ExecuteException(e, this, statement);
- }
- }
-
- @Override
- public boolean isQuery() {
- return resultType == ResultType.RESULT_SET;
- }
-
- @Override
- public boolean isUpdate() {
- return resultType == ResultType.UPDATE_COUNT;
- }
-
- boolean matches(String statement) {
- Preconditions.checkState(pattern != null, "This statement has not been compiled");
- return pattern.matcher(statement).matches();
- }
-
- @Override
- public String toString() {
- return name;
- }
-
- Pattern getPattern() {
- return pattern;
- }
-
- String getMethodName() {
- return method;
- }
-
- @Override
- public List getExampleStatements() {
- return Collections.unmodifiableList(exampleStatements);
- }
-
- @Override
- public List getExamplePrerequisiteStatements() {
- if (examplePrerequisiteStatements == null) {
- return Collections.emptyList();
- }
- return Collections.unmodifiableList(examplePrerequisiteStatements);
- }
-
- ClientSideSetStatementImpl getSetStatement() {
- return setStatement;
- }
-}
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/ClientSideStatementNoParamExecutor.java b/src/main/java/com/google/cloud/spanner/jdbc/ClientSideStatementNoParamExecutor.java
deleted file mode 100644
index d2ca7fde..00000000
--- a/src/main/java/com/google/cloud/spanner/jdbc/ClientSideStatementNoParamExecutor.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright 2019 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.cloud.spanner.jdbc;
-
-import com.google.cloud.spanner.jdbc.ClientSideStatementImpl.CompileException;
-import java.lang.reflect.Method;
-
-/**
- * Executor to use for statements that do not set a value and do not have any parameters, such as
- * SHOW AUTOCOMMIT. The executor just calls a method with no parameters.
- */
-class ClientSideStatementNoParamExecutor implements ClientSideStatementExecutor {
- private Method method;
-
- ClientSideStatementNoParamExecutor() {}
-
- @Override
- public void compile(ClientSideStatementImpl statement) throws CompileException {
- try {
- this.method = ConnectionStatementExecutor.class.getDeclaredMethod(statement.getMethodName());
- } catch (NoSuchMethodException | SecurityException e) {
- throw new CompileException(e, statement);
- }
- }
-
- @Override
- public StatementResult execute(ConnectionStatementExecutor connection, String statement)
- throws Exception {
- return (StatementResult) method.invoke(connection);
- }
-}
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/ClientSideStatementSetExecutor.java b/src/main/java/com/google/cloud/spanner/jdbc/ClientSideStatementSetExecutor.java
deleted file mode 100644
index aaf01eaa..00000000
--- a/src/main/java/com/google/cloud/spanner/jdbc/ClientSideStatementSetExecutor.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright 2019 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.cloud.spanner.jdbc;
-
-import com.google.cloud.spanner.ErrorCode;
-import com.google.cloud.spanner.SpannerExceptionFactory;
-import com.google.cloud.spanner.jdbc.ClientSideStatementImpl.CompileException;
-import com.google.common.base.Preconditions;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.Method;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Executor for {@link ClientSideStatement}s that sets a value for a property, such as SET
- * AUTOCOMMIT=TRUE.
- */
-class ClientSideStatementSetExecutor implements ClientSideStatementExecutor {
- private ClientSideStatementImpl statement;
- private Method method;
- private ClientSideStatementValueConverter converter;
- private Pattern allowedValuesPattern;
-
- @SuppressWarnings("unchecked")
- @Override
- public void compile(ClientSideStatementImpl statement) throws CompileException {
- Preconditions.checkNotNull(statement.getSetStatement());
- try {
- this.statement = statement;
- this.allowedValuesPattern =
- Pattern.compile(
- String.format(
- "(?is)\\A\\s*set\\s+%s\\s*%s\\s*%s\\s*\\z",
- statement.getSetStatement().getPropertyName(),
- statement.getSetStatement().getSeparator(),
- statement.getSetStatement().getAllowedValues()));
- Class> converterClass =
- (Class>)
- Class.forName(
- getClass().getPackage().getName()
- + "."
- + statement.getSetStatement().getConverterName());
- Constructor> constructor =
- converterClass.getConstructor(String.class);
- this.converter = constructor.newInstance(statement.getSetStatement().getAllowedValues());
- this.method =
- ConnectionStatementExecutor.class.getDeclaredMethod(
- statement.getMethodName(), converter.getParameterClass());
- } catch (Exception e) {
- throw new CompileException(e, statement);
- }
- }
-
- @Override
- public StatementResult execute(ConnectionStatementExecutor connection, String sql)
- throws Exception {
- return (StatementResult) method.invoke(connection, getParameterValue(sql));
- }
-
- T getParameterValue(String sql) {
- Matcher matcher = allowedValuesPattern.matcher(sql);
- if (matcher.find() && matcher.groupCount() >= 1) {
- String value = matcher.group(1);
- T res = converter.convert(value);
- if (res != null) {
- return res;
- }
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.INVALID_ARGUMENT,
- String.format(
- "Unknown value for %s: %s",
- this.statement.getSetStatement().getPropertyName(), value));
- } else {
- Matcher invalidMatcher = this.statement.getPattern().matcher(sql);
- if (invalidMatcher.find() && invalidMatcher.groupCount() == 1) {
- String invalidValue = invalidMatcher.group(1);
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.INVALID_ARGUMENT,
- String.format(
- "Unknown value for %s: %s",
- this.statement.getSetStatement().getPropertyName(), invalidValue));
- }
- }
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.INVALID_ARGUMENT, String.format("Unknown statement: %s", sql));
- }
-}
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/ClientSideStatementValueConverter.java b/src/main/java/com/google/cloud/spanner/jdbc/ClientSideStatementValueConverter.java
deleted file mode 100644
index 13b404cf..00000000
--- a/src/main/java/com/google/cloud/spanner/jdbc/ClientSideStatementValueConverter.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright 2019 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.cloud.spanner.jdbc;
-
-/**
- * Interface for converters that are used by {@link ClientSideStatement} that sets a value that need
- * to be converted from a string to a specific type. Implementing classes must have a public
- * constructor that takes a String parameter. The String parameter will contain a regular expression
- * for the allowed values for the property.
- */
-interface ClientSideStatementValueConverter {
-
- /** The type to convert to. */
- Class getParameterClass();
-
- /**
- * The actual convert method. Should return null
for values that could not be
- * converted.
- */
- T convert(String value);
-}
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/ClientSideStatementValueConverters.java b/src/main/java/com/google/cloud/spanner/jdbc/ClientSideStatementValueConverters.java
deleted file mode 100644
index 92368fec..00000000
--- a/src/main/java/com/google/cloud/spanner/jdbc/ClientSideStatementValueConverters.java
+++ /dev/null
@@ -1,257 +0,0 @@
-/*
- * Copyright 2019 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.cloud.spanner.jdbc;
-
-import com.google.cloud.spanner.ErrorCode;
-import com.google.cloud.spanner.SpannerExceptionFactory;
-import com.google.cloud.spanner.TimestampBound;
-import com.google.cloud.spanner.TimestampBound.Mode;
-import com.google.common.base.Function;
-import com.google.common.base.Preconditions;
-import com.google.protobuf.Duration;
-import com.google.protobuf.util.Durations;
-import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/** Contains all {@link ClientSideStatementValueConverter} implementations. */
-class ClientSideStatementValueConverters {
- /** Map for mapping case-insensitive strings to enums. */
- private static final class CaseInsensitiveEnumMap> {
- private final Map map = new HashMap<>();
-
- /** Create an map using the name of the enum elements as keys. */
- private CaseInsensitiveEnumMap(Class elementType) {
- this(
- elementType,
- new Function() {
- @Override
- public String apply(E input) {
- return input.name();
- }
- });
- }
-
- /** Create a map using the specific function to get the key per enum value. */
- private CaseInsensitiveEnumMap(Class elementType, Function keyFunction) {
- Preconditions.checkNotNull(elementType);
- Preconditions.checkNotNull(keyFunction);
- EnumSet set = EnumSet.allOf(elementType);
- for (E e : set) {
- if (map.put(keyFunction.apply(e).toUpperCase(), e) != null) {
- throw new IllegalArgumentException(
- "Enum contains multiple elements with the same case-insensitive key");
- }
- }
- }
-
- private E get(String value) {
- Preconditions.checkNotNull(value);
- return map.get(value.toUpperCase());
- }
- }
-
- /** Converter from string to {@link Boolean} */
- static class BooleanConverter implements ClientSideStatementValueConverter {
-
- public BooleanConverter(String allowedValues) {}
-
- @Override
- public Class getParameterClass() {
- return Boolean.class;
- }
-
- @Override
- public Boolean convert(String value) {
- if ("true".equalsIgnoreCase(value)) {
- return Boolean.TRUE;
- }
- if ("false".equalsIgnoreCase(value)) {
- return Boolean.FALSE;
- }
- return null;
- }
- }
-
- /** Converter from string to {@link Duration}. */
- static class DurationConverter implements ClientSideStatementValueConverter {
- private final Pattern allowedValues;
-
- public DurationConverter(String allowedValues) {
- // Remove the parentheses from the beginning and end.
- this.allowedValues =
- Pattern.compile(
- "(?is)\\A" + allowedValues.substring(1, allowedValues.length() - 1) + "\\z");
- }
-
- @Override
- public Class getParameterClass() {
- return Duration.class;
- }
-
- @Override
- public Duration convert(String value) {
- Matcher matcher = allowedValues.matcher(value);
- if (matcher.find()) {
- if (matcher.group(0).equalsIgnoreCase("null")) {
- return Durations.fromNanos(0L);
- } else {
- Duration duration =
- ReadOnlyStalenessUtil.createDuration(
- Long.valueOf(matcher.group(1)),
- ReadOnlyStalenessUtil.parseTimeUnit(matcher.group(2)));
- if (duration.getSeconds() == 0L && duration.getNanos() == 0) {
- return null;
- }
- return duration;
- }
- }
- return null;
- }
- }
-
- /** Converter from string to possible values for read only staleness ({@link TimestampBound}). */
- static class ReadOnlyStalenessConverter
- implements ClientSideStatementValueConverter {
- private final Pattern allowedValues;
- private final CaseInsensitiveEnumMap values = new CaseInsensitiveEnumMap<>(Mode.class);
-
- public ReadOnlyStalenessConverter(String allowedValues) {
- // Remove the single quotes at the beginning and end.
- this.allowedValues =
- Pattern.compile(
- "(?is)\\A" + allowedValues.substring(1, allowedValues.length() - 1) + "\\z");
- }
-
- @Override
- public Class getParameterClass() {
- return TimestampBound.class;
- }
-
- @Override
- public TimestampBound convert(String value) {
- Matcher matcher = allowedValues.matcher(value);
- if (matcher.find() && matcher.groupCount() >= 1) {
- Mode mode = null;
- int groupIndex = 0;
- for (int group = 1; group <= matcher.groupCount(); group++) {
- if (matcher.group(group) != null) {
- mode = values.get(matcher.group(group));
- if (mode != null) {
- groupIndex = group;
- break;
- }
- }
- }
- switch (mode) {
- case STRONG:
- return TimestampBound.strong();
- case READ_TIMESTAMP:
- return TimestampBound.ofReadTimestamp(
- ReadOnlyStalenessUtil.parseRfc3339(matcher.group(groupIndex + 1)));
- case MIN_READ_TIMESTAMP:
- return TimestampBound.ofMinReadTimestamp(
- ReadOnlyStalenessUtil.parseRfc3339(matcher.group(groupIndex + 1)));
- case EXACT_STALENESS:
- try {
- return TimestampBound.ofExactStaleness(
- Long.valueOf(matcher.group(groupIndex + 2)),
- ReadOnlyStalenessUtil.parseTimeUnit(matcher.group(groupIndex + 3)));
- } catch (IllegalArgumentException e) {
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.INVALID_ARGUMENT, e.getMessage());
- }
- case MAX_STALENESS:
- try {
- return TimestampBound.ofMaxStaleness(
- Long.valueOf(matcher.group(groupIndex + 2)),
- ReadOnlyStalenessUtil.parseTimeUnit(matcher.group(groupIndex + 3)));
- } catch (IllegalArgumentException e) {
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.INVALID_ARGUMENT, e.getMessage());
- }
- default:
- // fall through to allow the calling method to handle this
- }
- }
- return null;
- }
- }
-
- /** Converter for converting strings to {@link AutocommitDmlMode} values. */
- static class AutocommitDmlModeConverter
- implements ClientSideStatementValueConverter {
- private final CaseInsensitiveEnumMap values =
- new CaseInsensitiveEnumMap<>(AutocommitDmlMode.class);
-
- public AutocommitDmlModeConverter(String allowedValues) {}
-
- @Override
- public Class getParameterClass() {
- return AutocommitDmlMode.class;
- }
-
- @Override
- public AutocommitDmlMode convert(String value) {
- return values.get(value);
- }
- }
-
- static class StringValueConverter implements ClientSideStatementValueConverter {
- public StringValueConverter(String allowedValues) {}
-
- @Override
- public Class getParameterClass() {
- return String.class;
- }
-
- @Override
- public String convert(String value) {
- return value;
- }
- }
-
- /** Converter for converting string values to {@link TransactionMode} values. */
- static class TransactionModeConverter
- implements ClientSideStatementValueConverter {
- private final CaseInsensitiveEnumMap values =
- new CaseInsensitiveEnumMap<>(
- TransactionMode.class,
- new Function() {
- @Override
- public String apply(TransactionMode input) {
- return input.getStatementString();
- }
- });
-
- public TransactionModeConverter(String allowedValues) {}
-
- @Override
- public Class getParameterClass() {
- return TransactionMode.class;
- }
-
- @Override
- public TransactionMode convert(String value) {
- // Transaction mode may contain multiple spaces.
- String valueWithSingleSpaces = value.replaceAll("\\s+", " ");
- return values.get(valueWithSingleSpaces);
- }
- }
-}
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/ClientSideStatements.java b/src/main/java/com/google/cloud/spanner/jdbc/ClientSideStatements.java
deleted file mode 100644
index 3f3d872f..00000000
--- a/src/main/java/com/google/cloud/spanner/jdbc/ClientSideStatements.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright 2019 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.cloud.spanner.jdbc;
-
-import com.google.cloud.spanner.jdbc.ClientSideStatementImpl.CompileException;
-import com.google.gson.Gson;
-import java.io.InputStreamReader;
-import java.util.Set;
-
-/** This class reads and parses the {@link ClientSideStatement}s from the json file. */
-class ClientSideStatements {
- private static final String STATEMENTS_DEFINITION_FILE = "ClientSideStatements.json";
- static final ClientSideStatements INSTANCE = importStatements();
-
- /**
- * Reads statement definitions from ClientSideStatements.json and parses these as Java objects.
- */
- private static ClientSideStatements importStatements() {
- Gson gson = new Gson();
- return gson.fromJson(
- new InputStreamReader(
- ClientSideStatements.class.getResourceAsStream(STATEMENTS_DEFINITION_FILE)),
- ClientSideStatements.class);
- }
-
- private Set statements;
-
- private ClientSideStatements() {}
-
- /** Compiles and returns all statements from the resource file. */
- Set getCompiledStatements() throws CompileException {
- for (ClientSideStatementImpl statement : statements) {
- statement.compile();
- }
- return statements;
- }
-}
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/CloudSpannerJdbcConnection.java b/src/main/java/com/google/cloud/spanner/jdbc/CloudSpannerJdbcConnection.java
index d4c6629b..dc52ca35 100644
--- a/src/main/java/com/google/cloud/spanner/jdbc/CloudSpannerJdbcConnection.java
+++ b/src/main/java/com/google/cloud/spanner/jdbc/CloudSpannerJdbcConnection.java
@@ -152,21 +152,45 @@ public interface CloudSpannerJdbcConnection extends Connection {
/**
* @see
- * com.google.cloud.spanner.jdbc.Connection#addTransactionRetryListener(TransactionRetryListener)
+ * com.google.cloud.spanner.connection.Connection#addTransactionRetryListener(TransactionRetryListener)
* @throws SQLException if the {@link Connection} is closed.
*/
- void addTransactionRetryListener(TransactionRetryListener listener) throws SQLException;
+ void addTransactionRetryListener(
+ com.google.cloud.spanner.connection.TransactionRetryListener listener) throws SQLException;
+
+ /**
+ * Use {@link
+ * #addTransactionRetryListener(com.google.cloud.spanner.jdbc.TransactionRetryListener)}
+ */
+ @Deprecated
+ void addTransactionRetryListener(com.google.cloud.spanner.jdbc.TransactionRetryListener listener)
+ throws SQLException;
/**
* @see
- * com.google.cloud.spanner.jdbc.Connection#removeTransactionRetryListener(TransactionRetryListener)
+ * com.google.cloud.spanner.connection.Connection#removeTransactionRetryListener(TransactionRetryListener)
* @throws SQLException if the {@link Connection} is closed.
*/
- boolean removeTransactionRetryListener(TransactionRetryListener listener) throws SQLException;
+ boolean removeTransactionRetryListener(
+ com.google.cloud.spanner.connection.TransactionRetryListener listener) throws SQLException;
+
+ /**
+ * Use {@link
+ * #removeTransactionRetryListener(com.google.cloud.spanner.jdbc.TransactionRetryListener)}
+ */
+ @Deprecated
+ boolean removeTransactionRetryListener(
+ com.google.cloud.spanner.jdbc.TransactionRetryListener listener) throws SQLException;
+
+ /** Use {@link #getTransactionRetryListenersFromConnection()} */
+ @Deprecated
+ Iterator getTransactionRetryListeners()
+ throws SQLException;
/**
- * @see com.google.cloud.spanner.jdbc.Connection#getTransactionRetryListeners()
+ * @see com.google.cloud.spanner.connection.Connection#getTransactionRetryListeners()
* @throws SQLException if the {@link Connection} is closed.
*/
- Iterator getTransactionRetryListeners() throws SQLException;
+ Iterator
+ getTransactionRetryListenersFromConnection() throws SQLException;
}
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/Connection.java b/src/main/java/com/google/cloud/spanner/jdbc/Connection.java
deleted file mode 100644
index c3b32f8e..00000000
--- a/src/main/java/com/google/cloud/spanner/jdbc/Connection.java
+++ /dev/null
@@ -1,727 +0,0 @@
-/*
- * Copyright 2019 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.cloud.spanner.jdbc;
-
-import com.google.cloud.Timestamp;
-import com.google.cloud.spanner.AbortedException;
-import com.google.cloud.spanner.ErrorCode;
-import com.google.cloud.spanner.Mutation;
-import com.google.cloud.spanner.Options.QueryOption;
-import com.google.cloud.spanner.ReadContext.QueryAnalyzeMode;
-import com.google.cloud.spanner.ResultSet;
-import com.google.cloud.spanner.SpannerBatchUpdateException;
-import com.google.cloud.spanner.SpannerException;
-import com.google.cloud.spanner.Statement;
-import com.google.cloud.spanner.TimestampBound;
-import com.google.cloud.spanner.jdbc.StatementResult.ResultType;
-import java.util.Iterator;
-import java.util.concurrent.TimeUnit;
-
-/**
- * A connection to a Cloud Spanner database. Connections are not designed to be thread-safe. The
- * only exception is the {@link Connection#cancel()} method that may be called by any other thread
- * to stop the execution of the current statement on the connection.
- *
- * Connections accept a number of additional SQL statements for setting or changing the state of
- * a {@link Connection}. These statements can only be executed using the {@link
- * Connection#execute(Statement)} method:
- *
- *
- * SHOW AUTOCOMMIT
: Returns the current value of AUTOCOMMIT
of this
- * connection as a {@link ResultSet}
- * SET AUTOCOMMIT=TRUE|FALSE
: Sets the value of AUTOCOMMIT
for this
- * connection
- * SHOW READONLY
: Returns the current value of READONLY
of this
- * connection as a {@link ResultSet}
- * SET READONLY=TRUE|FALSE
: Sets the value of READONLY
for this
- * connection
- * SHOW RETRY_ABORTS_INTERNALLY
: Returns the current value of
- * RETRY_ABORTS_INTERNALLY
of this connection as a {@link ResultSet}
- * SET RETRY_ABORTS_INTERNALLY=TRUE|FALSE
: Sets the value of
- * RETRY_ABORTS_INTERNALLY
for this connection
- * SHOW AUTOCOMMIT_DML_MODE
: Returns the current value of
- * AUTOCOMMIT_DML_MODE
of this connection as a {@link ResultSet}
- * SET AUTOCOMMIT_DML_MODE='TRANSACTIONAL' | 'PARTITIONED_NON_ATOMIC'
: Sets the
- * value of AUTOCOMMIT_DML_MODE
for this connection
- * SHOW STATEMENT_TIMEOUT
: Returns the current value of STATEMENT_TIMEOUT
- *
of this connection as a {@link ResultSet}
- * SET STATEMENT_TIMEOUT='<int64>s|ms|us|ns' | NULL
: Sets the value of
- * STATEMENT_TIMEOUT
for this connection. The supported {@link TimeUnit}s are:
- *
- * - s - Seconds
- *
- ms - Milliseconds
- *
- us - Microseconds
- *
- ns - Nanoseconds
- *
- * Setting the STATEMENT_TIMEOUT to NULL will clear the value for the STATEMENT_TIMEOUT on the
- * connection.
- * SHOW READ_TIMESTAMP
: Returns the last READ_TIMESTAMP
of this
- * connection as a {@link ResultSet}
- * SHOW COMMIT_TIMESTAMP
: Returns the last COMMIT_TIMESTAMP
of this
- * connection as a {@link ResultSet}
- * SHOW READ_ONLY_STALENESS
: Returns the current value of
- * READ_ONLY_STALENESS
of this connection as a {@link ResultSet}
- *
- * SET READ_ONLY_STALENESS='STRONG' | 'MIN_READ_TIMESTAMP <timestamp>' | 'READ_TIMESTAMP <timestamp>' | 'MAX_STALENESS <int64>s|ms|mus|ns' | 'EXACT_STALENESS (<int64>s|ms|mus|ns)'
- *
: Sets the value of READ_ONLY_STALENESS
for this connection.
- * SHOW OPTIMIZER_VERSION
: Returns the current value of
- * OPTIMIZER_VERSION
of this connection as a {@link ResultSet}
- *
- * SET OPTIMIZER_VERSION='<version>' | 'LATEST'
- *
: Sets the value of OPTIMIZER_VERSION
for this connection.
- * BEGIN [TRANSACTION]
: Begins a new transaction. This statement is optional when
- * the connection is not in autocommit mode, as a new transaction will automatically be
- * started when a query or update statement is issued. In autocommit mode, this statement will
- * temporarily put the connection in transactional mode, and return the connection to
- * autocommit mode when COMMIT [TRANSACTION]
or ROLLBACK [TRANSACTION]
- *
is executed
- * COMMIT [TRANSACTION]
: Commits the current transaction
- * ROLLBACK [TRANSACTION]
: Rollbacks the current transaction
- * SET TRANSACTION READ ONLY|READ WRITE
: Sets the type for the current
- * transaction. May only be executed before a transaction is actually running (i.e. before any
- * statements have been executed in the transaction)
- * START BATCH DDL
: Starts a batch of DDL statements. May only be executed when
- * no transaction has been started and the connection is in read/write mode. The connection
- * will only accept DDL statements while a DDL batch is active.
- * START BATCH DML
: Starts a batch of DML statements. May only be executed when
- * the connection is in read/write mode. The connection will only accept DML statements while
- * a DML batch is active.
- * RUN BATCH
: Ends the current batch, sends the batched DML or DDL statements to
- * Spanner and blocks until all statements have been executed or an error occurs. May only be
- * executed when a (possibly empty) batch is active. The statement will return the update
- * counts of the batched statements as {@link ResultSet} with an ARRAY<INT64> column. In
- * case of a DDL batch, this array will always be empty.
- * ABORT BATCH
: Ends the current batch and removes any DML or DDL statements from
- * the buffer without sending any statements to Spanner. May only be executed when a (possibly
- * empty) batch is active.
- *
- *
- * Note that Cloud Spanner could abort read/write transactions in the background, and that
- * any database call during a read/write transaction could fail with an {@link
- * AbortedException}. This also includes calls to {@link ResultSet#next()}.
- *
- * If {@link Connection#isRetryAbortsInternally()} is true
, then the connection will
- * silently handle any {@link AbortedException}s by internally re-acquiring all transactional locks
- * and verifying (via the use of cryptographic checksums) that no underlying data has changed. If a
- * change to the underlying data is detected, then an {@link
- * AbortedDueToConcurrentModificationException} error will be thrown. If your application already
- * uses retry loops to handle these Aborted errors, then it will be most efficient to set {@link
- * Connection#isRetryAbortsInternally()} to false
.
- *
- *
Use {@link ConnectionOptions} to create a {@link Connection}.
- */
-interface Connection extends AutoCloseable {
-
- /** Closes this connection. This is a no-op if the {@link Connection} has alread been closed. */
- @Override
- void close();
-
- /** @return true
if this connection has been closed. */
- boolean isClosed();
-
- /**
- * Sets autocommit on/off for this {@link Connection}. Connections in autocommit mode will apply
- * any changes to the database directly without waiting for an explicit commit. DDL- and DML
- * statements as well as {@link Mutation}s are sent directly to Spanner, and committed
- * automatically unless the statement caused an error. The statement is retried in case of an
- * {@link AbortedException}. All other errors will cause the underlying transaction to be rolled
- * back.
- *
- *
A {@link Connection} that is in autocommit and read/write mode will allow all types of
- * statements: Queries, DML, DDL, and Mutations (writes). If the connection is in read-only mode,
- * only queries will be allowed.
- *
- *
{@link Connection}s in autocommit mode may also accept partitioned DML statements. See
- * {@link Connection#setAutocommitDmlMode(AutocommitDmlMode)} for more information.
- *
- * @param autocommit true/false to turn autocommit on/off
- */
- void setAutocommit(boolean autocommit);
-
- /** @return true
if this connection is in autocommit mode */
- boolean isAutocommit();
-
- /**
- * Sets this connection to read-only or read-write. This method may only be called when no
- * transaction is active. A connection that is in read-only mode, will never allow any kind of
- * changes to the database to be submitted.
- *
- * @param readOnly true/false to turn read-only mode on/off
- */
- void setReadOnly(boolean readOnly);
-
- /** @return true
if this connection is in read-only mode */
- boolean isReadOnly();
-
- /**
- * Sets the duration the connection should wait before automatically aborting the execution of a
- * statement. The default is no timeout. Statement timeouts are applied all types of statements,
- * both in autocommit and transactional mode. They also apply to {@link Connection#commit()} and
- * {@link Connection#rollback()} statements.
- *
- *
A DML statement in autocommit mode may or may not have actually been applied to the
- * database, depending on when the timeout occurred.
- *
- *
A DML statement in a transaction that times out may still have been applied to the
- * transaction. If you still decide to commit the transaction after such a timeout, the DML
- * statement may or may not have been part of the transaction, depending on whether the timeout
- * occurred before or after the statement was (successfully) sent to Spanner. You should therefore
- * either always rollback a transaction that had a DML statement that timed out, or you should
- * accept that the timed out statement still might have been applied to the database.
- *
- *
DDL statements and DML statements in {@link AutocommitDmlMode#PARTITIONED_NON_ATOMIC} mode
- * cannot be rolled back. If such a statement times out, it may or may not have been applied to
- * the database. The same applies to commit and rollback statements.
- *
- *
Statements that time out will throw a {@link SpannerException} with error code {@link
- * ErrorCode#DEADLINE_EXCEEDED}.
- *
- * @param timeout The number of {@link TimeUnit}s before a statement is automatically aborted by
- * the connection. Zero or negative values are not allowed. The maximum allowed value is
- * 315,576,000,000 seconds. Use {@link Connection#clearStatementTimeout()} to remove a timeout
- * value that has been set.
- * @param unit The {@link TimeUnit} to specify the timeout value in. Must be one of {@link
- * TimeUnit#NANOSECONDS}, {@link TimeUnit#MICROSECONDS}, {@link TimeUnit#MILLISECONDS}, {@link
- * TimeUnit#SECONDS}.
- */
- void setStatementTimeout(long timeout, TimeUnit unit);
-
- /**
- * Clears the statement timeout value for this connection. This is a no-op if there is currently
- * no statement timeout set on this connection.
- */
- void clearStatementTimeout();
-
- /**
- * @param unit The {@link TimeUnit} to get the timeout value in. Must be one of {@link
- * TimeUnit#NANOSECONDS}, {@link TimeUnit#MICROSECONDS}, {@link TimeUnit#MILLISECONDS}, {@link
- * TimeUnit#SECONDS}
- * @return the current statement timeout value or 0 if no timeout value has been set.
- */
- long getStatementTimeout(TimeUnit unit);
-
- /** @return true
if this {@link Connection} has a statement timeout value. */
- boolean hasStatementTimeout();
-
- /**
- * Cancels the currently running statement on this {@link Connection} (if any). If canceling the
- * statement execution succeeds, the statement will be terminated and a {@link SpannerException}
- * with code {@link ErrorCode#CANCELLED} will be thrown. The result of the statement will be the
- * same as when a statement times out (see {@link Connection#setStatementTimeout(long, TimeUnit)}
- * for more information).
- *
- *
Canceling a DDL statement in autocommit mode or a RUN BATCH statement of a DDL batch will
- * cause the connection to try to cancel the execution of the DDL statement(s). This is not
- * guaranteed to cancel the execution of the statement(s) on Cloud Spanner. See
- * https://cloud.google.com/spanner/docs/reference/rpc/google.longrunning#google.longrunning.Operations.CancelOperation
- * for more information.
- *
- *
Canceling a DML statement that is running in {@link
- * AutocommitDmlMode#PARTITIONED_NON_ATOMIC} mode will not cancel a statement on Cloud Spanner
- * that is already being executed, and its effects will still be applied to the database.
- */
- void cancel();
-
- /**
- * Begins a new transaction for this connection.
- *
- *
- * - Calling this method on a connection that has no transaction and that is
- * not in autocommit mode, will register a new transaction that has not yet
- * started on this connection
- *
- Calling this method on a connection that has no transaction and that is
- * in autocommit mode, will register a new transaction that has not yet started on this
- * connection, and temporarily turn off autocommit mode until the next commit/rollback
- *
- Calling this method on a connection that already has a transaction that has not yet
- * started, will cause a {@link SpannerException}
- *
- Calling this method on a connection that already has a transaction that has started, will
- * cause a {@link SpannerException} (no nested transactions)
- *
- */
- void beginTransaction();
-
- /**
- * Sets the transaction mode to use for current transaction. This method may only be called when
- * in a transaction, and before the transaction is actually started, i.e. before any statements
- * have been executed in the transaction.
- *
- * @param transactionMode The transaction mode to use for the current transaction.
- *
- * - {@link TransactionMode#READ_ONLY_TRANSACTION} will create a read-only transaction and
- * prevent any changes to written to the database through this transaction. The read
- * timestamp to be used will be determined based on the current readOnlyStaleness
- * setting of this connection. It is recommended to use {@link
- * TransactionMode#READ_ONLY_TRANSACTION} instead of {@link
- * TransactionMode#READ_WRITE_TRANSACTION} when possible, as read-only transactions do
- * not acquire locks on Cloud Spanner, and read-only transactions never abort.
- *
- {@link TransactionMode#READ_WRITE_TRANSACTION} this value is only allowed when the
- * connection is not in read-only mode and will create a read-write transaction. If
- * {@link Connection#isRetryAbortsInternally()} is
true
, each read/write
- * transaction will keep track of a running SHA256 checksum for each {@link ResultSet}
- * that is returned in order to be able to retry the transaction in case the transaction
- * is aborted by Spanner.
- *
- */
- void setTransactionMode(TransactionMode transactionMode);
-
- /**
- * @return the transaction mode of the current transaction. This method may only be called when
- * the connection is in a transaction.
- */
- TransactionMode getTransactionMode();
-
- /**
- * @return true
if this connection will automatically retry read/write transactions
- * that abort. This method may only be called when the connection is in read/write
- * transactional mode and no transaction has been started yet.
- */
- boolean isRetryAbortsInternally();
-
- /**
- * Sets whether this connection will internally retry read/write transactions that abort. The
- * default is true
. When internal retry is enabled, the {@link Connection} will keep
- * track of a running SHA256 checksum of all {@link ResultSet}s that have been returned from Cloud
- * Spanner. If the checksum that is calculated during an internal retry differs from the original
- * checksum, the transaction will abort with an {@link
- * AbortedDueToConcurrentModificationException}.
- *
- * Note that retries of a read/write transaction that calls a non-deterministic function on
- * Cloud Spanner, such as CURRENT_TIMESTAMP(), will never be successful, as the data returned
- * during the retry will always be different from the original transaction.
- *
- *
It is also highly recommended that all queries in a read/write transaction have an ORDER BY
- * clause that guarantees that the data is returned in the same order as in the original
- * transaction if the transaction is internally retried. The most efficient way to achieve this is
- * to always include the primary key columns at the end of the ORDER BY clause.
- *
- *
This method may only be called when the connection is in read/write transactional mode and
- * no transaction has been started yet.
- *
- * @param retryAbortsInternally Set to true
to internally retry transactions that are
- * aborted by Spanner. When set to false
, any database call on a transaction that
- * has been aborted by Cloud Spanner will throw an {@link AbortedException} instead of being
- * retried. Set this to false if your application already uses retry loops to handle {@link
- * AbortedException}s.
- */
- void setRetryAbortsInternally(boolean retryAbortsInternally);
-
- /**
- * Add a {@link TransactionRetryListener} to this {@link Connection} for testing and logging
- * purposes. The method {@link TransactionRetryListener#retryStarting(Timestamp, long, int)} will
- * be called before an automatic retry is started for a read/write transaction on this connection.
- * The method {@link TransactionRetryListener#retryFinished(Timestamp, long, int,
- * TransactionRetryListener.RetryResult)} will be called after the retry has finished.
- *
- * @param listener The listener to add to this connection.
- */
- void addTransactionRetryListener(TransactionRetryListener listener);
-
- /**
- * Removes one existing {@link TransactionRetryListener} from this {@link Connection}, if it is
- * present (optional operation).
- *
- * @param listener The listener to remove from the connection.
- * @return true
if a listener was removed from the connection.
- */
- boolean removeTransactionRetryListener(TransactionRetryListener listener);
-
- /**
- * @return an unmodifiable iterator of the {@link TransactionRetryListener}s registered for this
- * connection.
- */
- Iterator getTransactionRetryListeners();
-
- /**
- * Sets the mode for executing DML statements in autocommit mode for this connection. This setting
- * is only used when the connection is in autocommit mode, and may only be set while the
- * transaction is in autocommit mode and not in a temporary transaction. The autocommit
- * transaction mode is reset to its default value of {@link AutocommitDmlMode#TRANSACTIONAL} when
- * autocommit mode is changed on the connection.
- *
- * @param mode The DML autocommit mode to use
- *
- * - {@link AutocommitDmlMode#TRANSACTIONAL} DML statements are executed as single
- * read-write transaction. After successful execution, the DML statement is guaranteed
- * to have been applied exactly once to the database
- *
- {@link AutocommitDmlMode#PARTITIONED_NON_ATOMIC} DML statements are executed as
- * partitioned DML transactions. If an error occurs during the execution of the DML
- * statement, it is possible that the statement has been applied to some but not all of
- * the rows specified in the statement.
- *
- */
- void setAutocommitDmlMode(AutocommitDmlMode mode);
-
- /**
- * @return the current {@link AutocommitDmlMode} setting for this connection. This method may only
- * be called on a connection that is in autocommit mode and not while in a temporary
- * transaction.
- */
- AutocommitDmlMode getAutocommitDmlMode();
-
- /**
- * Sets the staleness to use for the current read-only transaction. This method may only be called
- * when the transaction mode of the current transaction is {@link
- * TransactionMode#READ_ONLY_TRANSACTION} and there is no transaction that has started, or when
- * the connection is in read-only and autocommit mode.
- *
- * @param staleness The staleness to use for the current but not yet started read-only transaction
- */
- void setReadOnlyStaleness(TimestampBound staleness);
-
- /**
- * @return the read-only staleness setting for the current read-only transaction. This method may
- * only be called when the current transaction is a read-only transaction, or when the
- * connection is in read-only and autocommit mode.
- */
- TimestampBound getReadOnlyStaleness();
-
- /**
- * Sets the query optimizer version to use for this connection.
- *
- * @param optimizerVersion The query optimizer version to use. Must be a valid optimizer version
- * number, the string LATEST
or an empty string. The empty string will instruct
- * the connection to use the optimizer version that is defined in the environment variable
- * SPANNER_OPTIMIZER_VERSION
. If no value is specified in the environment
- * variable, the default query optimizer of Cloud Spanner is used.
- */
- void setOptimizerVersion(String optimizerVersion);
-
- /**
- * Gets the current query optimizer version of this connection.
- *
- * @return The query optimizer version that is currently used by this connection.
- */
- String getOptimizerVersion();
-
- /**
- * Commits the current transaction of this connection. All mutations that have been buffered
- * during the current transaction will be written to the database.
- *
- * If the connection is in autocommit mode, and there is a temporary transaction active on this
- * connection, calling this method will cause the connection to go back to autocommit mode after
- * calling this method.
- *
- *
This method will throw a {@link SpannerException} with code {@link
- * ErrorCode#DEADLINE_EXCEEDED} if a statement timeout has been set on this connection, and the
- * commit operation takes longer than this timeout.
- *
- *
- * - Calling this method on a connection in autocommit mode and with no temporary transaction,
- * will cause an exception
- *
- Calling this method while a DDL batch is active will cause an exception
- *
- Calling this method on a connection with a transaction that has not yet started, will end
- * that transaction and any properties that might have been set on that transaction, and
- * return the connection to its previous state. This means that if a transaction is created
- * and set to read-only, and then committed before any statements have been executed, the
- * read-only transaction is ended and any subsequent statements will be executed in a new
- * transaction. If the connection is in read-write mode, the default for new transactions
- * will be {@link TransactionMode#READ_WRITE_TRANSACTION}. Committing an empty transaction
- * also does not generate a read timestamp or a commit timestamp, and calling one of the
- * methods {@link Connection#getReadTimestamp()} or {@link Connection#getCommitTimestamp()}
- * will cause an exception.
- *
- Calling this method on a connection with a {@link TransactionMode#READ_ONLY_TRANSACTION}
- * transaction will end that transaction. If the connection is in read-write mode, any
- * subsequent transaction will by default be a {@link
- * TransactionMode#READ_WRITE_TRANSACTION} transaction, unless any following transaction is
- * explicitly set to {@link TransactionMode#READ_ONLY_TRANSACTION}
- *
- Calling this method on a connection with a {@link TransactionMode#READ_WRITE_TRANSACTION}
- * transaction will send all buffered mutations to the database, commit any DML statements
- * that have been executed during this transaction and end the transaction.
- *
- */
- void commit();
-
- /**
- * Rollbacks the current transaction of this connection. All mutations or DDL statements that have
- * been buffered during the current transaction will be removed from the buffer.
- *
- * If the connection is in autocommit mode, and there is a temporary transaction active on this
- * connection, calling this method will cause the connection to go back to autocommit mode after
- * calling this method.
- *
- *
- * - Calling this method on a connection in autocommit mode and with no temporary transaction
- * will cause an exception
- *
- Calling this method while a DDL batch is active will cause an exception
- *
- Calling this method on a connection with a transaction that has not yet started, will end
- * that transaction and any properties that might have been set on that transaction, and
- * return the connection to its previous state. This means that if a transaction is created
- * and set to read-only, and then rolled back before any statements have been executed, the
- * read-only transaction is ended and any subsequent statements will be executed in a new
- * transaction. If the connection is in read-write mode, the default for new transactions
- * will be {@link TransactionMode#READ_WRITE_TRANSACTION}.
- *
- Calling this method on a connection with a {@link TransactionMode#READ_ONLY_TRANSACTION}
- * transaction will end that transaction. If the connection is in read-write mode, any
- * subsequent transaction will by default be a {@link
- * TransactionMode#READ_WRITE_TRANSACTION} transaction, unless any following transaction is
- * explicitly set to {@link TransactionMode#READ_ONLY_TRANSACTION}
- *
- Calling this method on a connection with a {@link TransactionMode#READ_WRITE_TRANSACTION}
- * transaction will clear all buffered mutations, rollback any DML statements that have been
- * executed during this transaction and end the transaction.
- *
- */
- void rollback();
-
- /**
- * @return true
if this connection has a transaction (that has not necessarily
- * started). This method will only return false when the {@link Connection} is in autocommit
- * mode and no explicit transaction has been started by calling {@link
- * Connection#beginTransaction()}. If the {@link Connection} is not in autocommit mode, there
- * will always be a transaction.
- */
- boolean isInTransaction();
-
- /**
- * @return true
if this connection has a transaction that has started. A transaction
- * is automatically started by the first statement that is executed in the transaction.
- */
- boolean isTransactionStarted();
-
- /**
- * Returns the read timestamp of the current/last {@link TransactionMode#READ_ONLY_TRANSACTION}
- * transaction, or the read timestamp of the last query in autocommit mode.
- *
- *
- * - When in autocommit mode: The method will return the read timestamp of the last statement
- * if the last statement was a query.
- *
- When in a {@link TransactionMode#READ_ONLY_TRANSACTION} transaction that has started (a
- * query has been executed), or that has just committed: The read timestamp of the
- * transaction. If the read-only transaction was committed without ever executing a query,
- * calling this method after the commit will also throw a {@link SpannerException}
- *
- In all other cases the method will throw a {@link SpannerException}.
- *
- *
- * @return the read timestamp of the current/last read-only transaction.
- */
- Timestamp getReadTimestamp();
-
- /**
- * @return the commit timestamp of the last {@link TransactionMode#READ_WRITE_TRANSACTION}
- * transaction. This method will throw a {@link SpannerException} if there is no last {@link
- * TransactionMode#READ_WRITE_TRANSACTION} transaction (i.e. the last transaction was a {@link
- * TransactionMode#READ_ONLY_TRANSACTION}), or if the last {@link
- * TransactionMode#READ_WRITE_TRANSACTION} transaction rolled back. It will also throw a
- * {@link SpannerException} if the last {@link TransactionMode#READ_WRITE_TRANSACTION}
- * transaction was empty when committed.
- */
- Timestamp getCommitTimestamp();
-
- /**
- * Starts a new DDL batch on this connection. A DDL batch allows several DDL statements to be
- * grouped into a batch that can be executed as a group. DDL statements that are issued during the
- * batch are buffered locally and will return immediately with an OK. It is not guaranteed that a
- * DDL statement that has been issued during a batch will eventually succeed when running the
- * batch. Aborting a DDL batch will clear the DDL buffer and will have made no changes to the
- * database. Running a DDL batch will send all buffered DDL statements to Spanner, and Spanner
- * will try to execute these. The result will be OK if all the statements executed successfully.
- * If a statement cannot be executed, Spanner will stop execution at that point and return an
- * error message for the statement that could not be executed. Preceding statements of the batch
- * may have been executed.
- *
- * This method may only be called when the connection is in read/write mode, autocommit mode is
- * enabled or no read/write transaction has been started, and there is not already another batch
- * active. The connection will only accept DDL statements while a DDL batch is active.
- */
- void startBatchDdl();
-
- /**
- * Starts a new DML batch on this connection. A DML batch allows several DML statements to be
- * grouped into a batch that can be executed as a group. DML statements that are issued during the
- * batch are buffered locally and will return immediately with an OK. It is not guaranteed that a
- * DML statement that has been issued during a batch will eventually succeed when running the
- * batch. Aborting a DML batch will clear the DML buffer and will have made no changes to the
- * database. Running a DML batch will send all buffered DML statements to Spanner, and Spanner
- * will try to execute these. The result will be OK if all the statements executed successfully.
- * If a statement cannot be executed, Spanner will stop execution at that point and return {@link
- * SpannerBatchUpdateException} for the statement that could not be executed. Preceding statements
- * of the batch will have been executed, and the update counts of those statements can be
- * retrieved through {@link SpannerBatchUpdateException#getUpdateCounts()}.
- *
- *
This method may only be called when the connection is in read/write mode, autocommit mode is
- * enabled or no read/write transaction has been started, and there is not already another batch
- * active. The connection will only accept DML statements while a DML batch is active.
- */
- void startBatchDml();
-
- /**
- * Sends all buffered DML or DDL statements of the current batch to the database, waits for these
- * to be executed and ends the current batch. The method will throw an exception for the first
- * statement that cannot be executed, or return successfully if all statements could be executed.
- * If an exception is thrown for a statement in the batch, the preceding statements in the same
- * batch may still have been applied to the database.
- *
- *
This method may only be called when a (possibly empty) batch is active.
- *
- * @return the update counts in case of a DML batch. Returns an array containing 1 for each
- * successful statement and 0 for each failed statement or statement that was not executed DDL
- * in case of a DDL batch.
- */
- long[] runBatch();
-
- /**
- * Clears all buffered statements in the current batch and ends the batch.
- *
- *
This method may only be called when a (possibly empty) batch is active.
- */
- void abortBatch();
-
- /** @return true
if a DDL batch is active on this connection. */
- boolean isDdlBatchActive();
-
- /** @return true
if a DML batch is active on this connection. */
- boolean isDmlBatchActive();
-
- /**
- * Executes the given statement if allowed in the current {@link TransactionMode} and connection
- * state. The returned value depends on the type of statement:
- *
- *
- * - Queries will return a {@link ResultSet}
- *
- DML statements will return an update count
- *
- DDL statements will return a {@link ResultType#NO_RESULT}
- *
- Connection and transaction statements (SET AUTOCOMMIT=TRUE|FALSE, SHOW AUTOCOMMIT, SET
- * TRANSACTION READ ONLY, etc) will return either a {@link ResultSet} or {@link
- * ResultType#NO_RESULT}, depending on the type of statement (SHOW or SET)
- *
- *
- * @param statement The statement to execute
- * @return the result of the statement
- */
- StatementResult execute(Statement statement);
-
- /**
- * Executes the given statement as a query and returns the result as a {@link ResultSet}. This
- * method blocks and waits for a response from Spanner. If the statement does not contain a valid
- * query, the method will throw a {@link SpannerException}.
- *
- * @param query The query statement to execute
- * @param options the options to configure the query
- * @return a {@link ResultSet} with the results of the query
- */
- ResultSet executeQuery(Statement query, QueryOption... options);
-
- /**
- * Analyzes a query and returns query plan and/or query execution statistics information.
- *
- * The query plan and query statistics information is contained in {@link
- * com.google.spanner.v1.ResultSetStats} that can be accessed by calling {@link
- * ResultSet#getStats()} on the returned {@code ResultSet}.
- *
- *
- *
- * {@code
- * ResultSet resultSet =
- * connection.analyzeQuery(
- * Statement.of("SELECT SingerId, AlbumId, MarketingBudget FROM Albums"),
- * ReadContext.QueryAnalyzeMode.PROFILE);
- * while (resultSet.next()) {
- * // Discard the results. We're only processing because getStats() below requires it.
- * }
- * ResultSetStats stats = resultSet.getStats();
- * }
- *
- *
- *
- * @param query the query statement to execute
- * @param queryMode the mode in which to execute the query
- */
- ResultSet analyzeQuery(Statement query, QueryAnalyzeMode queryMode);
-
- /**
- * Executes the given statement as a DML statement. If the statement does not contain a valid DML
- * statement, the method will throw a {@link SpannerException}.
- *
- * @param update The update statement to execute
- * @return the number of records that were inserted/updated/deleted by this statement
- */
- long executeUpdate(Statement update);
-
- /**
- * Executes a list of DML statements in a single request. The statements will be executed in order
- * and the semantics is the same as if each statement is executed by {@link
- * Connection#executeUpdate(Statement)} in a loop. This method returns an array of long integers,
- * each representing the number of rows modified by each statement.
- *
- * If an individual statement fails, execution stops and a {@code SpannerBatchUpdateException}
- * is returned, which includes the error and the number of rows affected by the statements that
- * are run prior to the error.
- *
- *
For example, if statements contains 3 statements, and the 2nd one is not a valid DML. This
- * method throws a {@code SpannerBatchUpdateException} that contains the error message from the
- * 2nd statement, and an array of length 1 that contains the number of rows modified by the 1st
- * statement. The 3rd statement will not run. Executes the given statements as DML statements in
- * one batch. If one of the statements does not contain a valid DML statement, the method will
- * throw a {@link SpannerException}.
- *
- * @param updates The update statements that will be executed as one batch.
- * @return an array containing the update counts per statement.
- */
- long[] executeBatchUpdate(Iterable updates);
-
- /**
- * Writes the specified mutation directly to the database and commits the change. The value is
- * readable after the successful completion of this method. Writing multiple mutations to a
- * database by calling this method multiple times mode is inefficient, as each call will need a
- * round trip to the database. Instead, you should consider writing the mutations together by
- * calling {@link Connection#write(Iterable)}.
- *
- * Calling this method is only allowed in autocommit mode. See {@link
- * Connection#bufferedWrite(Iterable)} for writing mutations in transactions.
- *
- * @param mutation The {@link Mutation} to write to the database
- * @throws SpannerException if the {@link Connection} is not in autocommit mode
- */
- void write(Mutation mutation);
-
- /**
- * Writes the specified mutations directly to the database and commits the changes. The values are
- * readable after the successful completion of this method.
- *
- *
Calling this method is only allowed in autocommit mode. See {@link
- * Connection#bufferedWrite(Iterable)} for writing mutations in transactions.
- *
- * @param mutations The {@link Mutation}s to write to the database
- * @throws SpannerException if the {@link Connection} is not in autocommit mode
- */
- void write(Iterable mutations);
-
- /**
- * Buffers the given mutation locally on the current transaction of this {@link Connection}. The
- * mutation will be written to the database at the next call to {@link Connection#commit()}. The
- * value will not be readable on this {@link Connection} before the transaction is committed.
- *
- * Calling this method is only allowed when not in autocommit mode. See {@link
- * Connection#write(Mutation)} for writing mutations in autocommit mode.
- *
- * @param mutation the {@link Mutation} to buffer for writing to the database on the next commit
- * @throws SpannerException if the {@link Connection} is in autocommit mode
- */
- void bufferedWrite(Mutation mutation);
-
- /**
- * Buffers the given mutations locally on the current transaction of this {@link Connection}. The
- * mutations will be written to the database at the next call to {@link Connection#commit()}. The
- * values will not be readable on this {@link Connection} before the transaction is committed.
- *
- *
Calling this method is only allowed when not in autocommit mode. See {@link
- * Connection#write(Iterable)} for writing mutations in autocommit mode.
- *
- * @param mutations the {@link Mutation}s to buffer for writing to the database on the next commit
- * @throws SpannerException if the {@link Connection} is in autocommit mode
- */
- void bufferedWrite(Iterable mutations);
-}
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/ConnectionImpl.java b/src/main/java/com/google/cloud/spanner/jdbc/ConnectionImpl.java
deleted file mode 100644
index 2a83fca4..00000000
--- a/src/main/java/com/google/cloud/spanner/jdbc/ConnectionImpl.java
+++ /dev/null
@@ -1,1018 +0,0 @@
-/*
- * Copyright 2019 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.cloud.spanner.jdbc;
-
-import com.google.cloud.Timestamp;
-import com.google.cloud.spanner.DatabaseClient;
-import com.google.cloud.spanner.ErrorCode;
-import com.google.cloud.spanner.Mutation;
-import com.google.cloud.spanner.Options.QueryOption;
-import com.google.cloud.spanner.ReadContext.QueryAnalyzeMode;
-import com.google.cloud.spanner.ResultSet;
-import com.google.cloud.spanner.Spanner;
-import com.google.cloud.spanner.SpannerException;
-import com.google.cloud.spanner.SpannerExceptionFactory;
-import com.google.cloud.spanner.Statement;
-import com.google.cloud.spanner.TimestampBound;
-import com.google.cloud.spanner.TimestampBound.Mode;
-import com.google.cloud.spanner.jdbc.StatementExecutor.StatementTimeout;
-import com.google.cloud.spanner.jdbc.StatementParser.ParsedStatement;
-import com.google.cloud.spanner.jdbc.StatementParser.StatementType;
-import com.google.cloud.spanner.jdbc.UnitOfWork.UnitOfWorkState;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Preconditions;
-import com.google.spanner.v1.ExecuteSqlRequest.QueryOptions;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Stack;
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.TimeUnit;
-import org.threeten.bp.Instant;
-
-/** Implementation for {@link Connection}, the generic Spanner connection API (not JDBC). */
-class ConnectionImpl implements Connection {
- private static final String CLOSED_ERROR_MSG = "This connection is closed";
- private static final String ONLY_ALLOWED_IN_AUTOCOMMIT =
- "This method may only be called while in autocommit mode";
- private static final String NOT_ALLOWED_IN_AUTOCOMMIT =
- "This method may not be called while in autocommit mode";
-
- /**
- * Exception that is used to register the stacktrace of the code that opened a {@link Connection}.
- * This exception is logged if the application closes without first closing the connection.
- */
- static class LeakedConnectionException extends RuntimeException {
- private static final long serialVersionUID = 7119433786832158700L;
-
- private LeakedConnectionException() {
- super("Connection was opened at " + Instant.now());
- }
- }
-
- private volatile LeakedConnectionException leakedException = new LeakedConnectionException();
- private final SpannerPool spannerPool;
- private final StatementParser parser = StatementParser.INSTANCE;
- /**
- * The {@link ConnectionStatementExecutor} is responsible for translating parsed {@link
- * ClientSideStatement}s into actual method calls on this {@link ConnectionImpl}. I.e. the {@link
- * ClientSideStatement} 'SET AUTOCOMMIT ON' will be translated into the method call {@link
- * ConnectionImpl#setAutocommit(boolean)} with value true
.
- */
- private final ConnectionStatementExecutor connectionStatementExecutor =
- new ConnectionStatementExecutorImpl(this);
-
- /** Simple thread factory that is used for fire-and-forget rollbacks. */
- static final class DaemonThreadFactory implements ThreadFactory {
- @Override
- public Thread newThread(Runnable r) {
- Thread t = new Thread(r);
- t.setName("connection-rollback-executor");
- t.setDaemon(true);
- return t;
- }
- }
-
- /**
- * Statements are executed using a separate thread in order to be able to cancel these. Statements
- * are automatically cancelled if the configured {@link ConnectionImpl#statementTimeout} is
- * exceeded. In autocommit mode, the connection will try to rollback the effects of an update
- * statement, but this is not guaranteed to actually succeed.
- */
- private final StatementExecutor statementExecutor;
-
- /**
- * The {@link ConnectionOptions} that were used to create this {@link ConnectionImpl}. This is
- * retained as it is used for getting a {@link Spanner} object and removing this connection from
- * the {@link SpannerPool}.
- */
- private final ConnectionOptions options;
-
- /** The supported batch modes. */
- enum BatchMode {
- NONE,
- DDL,
- DML;
- }
-
- /**
- * This query option is used internally to indicate that a query is executed by the library itself
- * to fetch metadata. These queries are specifically allowed to be executed even when a DDL batch
- * is active.
- */
- static final class InternalMetadataQuery implements QueryOption {
- static final InternalMetadataQuery INSTANCE = new InternalMetadataQuery();
-
- private InternalMetadataQuery() {}
- }
-
- /** The combination of all transaction modes and batch modes. */
- enum UnitOfWorkType {
- READ_ONLY_TRANSACTION {
- @Override
- TransactionMode getTransactionMode() {
- return TransactionMode.READ_ONLY_TRANSACTION;
- }
- },
- READ_WRITE_TRANSACTION {
- @Override
- TransactionMode getTransactionMode() {
- return TransactionMode.READ_WRITE_TRANSACTION;
- }
- },
- DML_BATCH {
- @Override
- TransactionMode getTransactionMode() {
- return TransactionMode.READ_WRITE_TRANSACTION;
- }
- },
- DDL_BATCH {
- @Override
- TransactionMode getTransactionMode() {
- return null;
- }
- };
-
- abstract TransactionMode getTransactionMode();
-
- static UnitOfWorkType of(TransactionMode transactionMode) {
- switch (transactionMode) {
- case READ_ONLY_TRANSACTION:
- return UnitOfWorkType.READ_ONLY_TRANSACTION;
- case READ_WRITE_TRANSACTION:
- return UnitOfWorkType.READ_WRITE_TRANSACTION;
- default:
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.INVALID_ARGUMENT, "Unknown transaction mode: " + transactionMode);
- }
- }
- }
-
- private StatementExecutor.StatementTimeout statementTimeout =
- new StatementExecutor.StatementTimeout();
- private boolean closed = false;
-
- private final Spanner spanner;
- private DdlClient ddlClient;
- private DatabaseClient dbClient;
- private boolean autocommit;
- private boolean readOnly;
-
- private UnitOfWork currentUnitOfWork = null;
- /**
- * The {@link ConnectionImpl#inTransaction} field is only used in autocommit mode to indicate that
- * the user has explicitly started a transaction.
- */
- private boolean inTransaction = false;
- /**
- * This field is used to indicate that a transaction begin has been indicated. This is done by
- * calling beginTransaction or by setting a transaction property while not in autocommit mode.
- */
- private boolean transactionBeginMarked = false;
-
- private BatchMode batchMode;
- private UnitOfWorkType unitOfWorkType;
- private final Stack transactionStack = new Stack<>();
- private boolean retryAbortsInternally;
- private final List transactionRetryListeners = new ArrayList<>();
- private AutocommitDmlMode autocommitDmlMode = AutocommitDmlMode.TRANSACTIONAL;
- private TimestampBound readOnlyStaleness = TimestampBound.strong();
- private QueryOptions queryOptions = QueryOptions.getDefaultInstance();
-
- /** Create a connection and register it in the SpannerPool. */
- ConnectionImpl(ConnectionOptions options) {
- Preconditions.checkNotNull(options);
- this.statementExecutor = new StatementExecutor(options.getStatementExecutionInterceptors());
- this.spannerPool = SpannerPool.INSTANCE;
- this.options = options;
- this.spanner = spannerPool.getSpanner(options, this);
- this.dbClient = spanner.getDatabaseClient(options.getDatabaseId());
- this.retryAbortsInternally = options.isRetryAbortsInternally();
- this.readOnly = options.isReadOnly();
- this.autocommit = options.isAutocommit();
- this.queryOptions = this.queryOptions.toBuilder().mergeFrom(options.getQueryOptions()).build();
- this.ddlClient = createDdlClient();
- setDefaultTransactionOptions();
- }
-
- /** Constructor only for test purposes. */
- @VisibleForTesting
- ConnectionImpl(
- ConnectionOptions options,
- SpannerPool spannerPool,
- DdlClient ddlClient,
- DatabaseClient dbClient) {
- Preconditions.checkNotNull(options);
- Preconditions.checkNotNull(spannerPool);
- Preconditions.checkNotNull(ddlClient);
- Preconditions.checkNotNull(dbClient);
- this.statementExecutor =
- new StatementExecutor(Collections.emptyList());
- this.spannerPool = spannerPool;
- this.options = options;
- this.spanner = spannerPool.getSpanner(options, this);
- this.ddlClient = ddlClient;
- this.dbClient = dbClient;
- setReadOnly(options.isReadOnly());
- setAutocommit(options.isAutocommit());
- setDefaultTransactionOptions();
- }
-
- private DdlClient createDdlClient() {
- return DdlClient.newBuilder()
- .setDatabaseAdminClient(spanner.getDatabaseAdminClient())
- .setInstanceId(options.getInstanceId())
- .setDatabaseName(options.getDatabaseName())
- .build();
- }
-
- @Override
- public void close() {
- if (!isClosed()) {
- try {
- if (isTransactionStarted()) {
- try {
- rollback();
- } catch (Exception e) {
- // Ignore as we are closing the connection.
- }
- }
- statementExecutor.shutdownNow();
- spannerPool.removeConnection(options, this);
- leakedException = null;
- } finally {
- this.closed = true;
- }
- }
- }
-
- /** Get the current unit-of-work type of this connection. */
- UnitOfWorkType getUnitOfWorkType() {
- return unitOfWorkType;
- }
-
- /** Get the current batch mode of this connection. */
- BatchMode getBatchMode() {
- return batchMode;
- }
-
- /** @return true
if this connection is in a batch. */
- boolean isInBatch() {
- return batchMode != BatchMode.NONE;
- }
-
- /** Get the call stack from when the {@link Connection} was opened. */
- LeakedConnectionException getLeakedException() {
- return leakedException;
- }
-
- @Override
- public boolean isClosed() {
- return closed;
- }
-
- @Override
- public void setAutocommit(boolean autocommit) {
- ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
- ConnectionPreconditions.checkState(!isBatchActive(), "Cannot set autocommit while in a batch");
- ConnectionPreconditions.checkState(
- !isTransactionStarted(), "Cannot set autocommit while a transaction is active");
- ConnectionPreconditions.checkState(
- !(isAutocommit() && isInTransaction()),
- "Cannot set autocommit while in a temporary transaction");
- ConnectionPreconditions.checkState(
- !transactionBeginMarked, "Cannot set autocommit when a transaction has begun");
- this.autocommit = autocommit;
- clearLastTransactionAndSetDefaultTransactionOptions();
- // Reset the readOnlyStaleness value if it is no longer compatible with the new autocommit
- // value.
- if (!autocommit
- && (readOnlyStaleness.getMode() == Mode.MAX_STALENESS
- || readOnlyStaleness.getMode() == Mode.MIN_READ_TIMESTAMP)) {
- readOnlyStaleness = TimestampBound.strong();
- }
- }
-
- @Override
- public boolean isAutocommit() {
- ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
- return internalIsAutocommit();
- }
-
- private boolean internalIsAutocommit() {
- return this.autocommit;
- }
-
- @Override
- public void setReadOnly(boolean readOnly) {
- ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
- ConnectionPreconditions.checkState(!isBatchActive(), "Cannot set read-only while in a batch");
- ConnectionPreconditions.checkState(
- !isTransactionStarted(), "Cannot set read-only while a transaction is active");
- ConnectionPreconditions.checkState(
- !(isAutocommit() && isInTransaction()),
- "Cannot set read-only while in a temporary transaction");
- ConnectionPreconditions.checkState(
- !transactionBeginMarked, "Cannot set read-only when a transaction has begun");
- this.readOnly = readOnly;
- clearLastTransactionAndSetDefaultTransactionOptions();
- }
-
- @Override
- public boolean isReadOnly() {
- ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
- return this.readOnly;
- }
-
- private void clearLastTransactionAndSetDefaultTransactionOptions() {
- setDefaultTransactionOptions();
- this.currentUnitOfWork = null;
- }
-
- @Override
- public void setAutocommitDmlMode(AutocommitDmlMode mode) {
- Preconditions.checkNotNull(mode);
- ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
- ConnectionPreconditions.checkState(
- !isBatchActive(), "Cannot set autocommit DML mode while in a batch");
- ConnectionPreconditions.checkState(
- !isInTransaction() && isAutocommit(),
- "Cannot set autocommit DML mode while not in autocommit mode or while a transaction is active");
- ConnectionPreconditions.checkState(
- !isReadOnly(), "Cannot set autocommit DML mode for a read-only connection");
- this.autocommitDmlMode = mode;
- }
-
- @Override
- public AutocommitDmlMode getAutocommitDmlMode() {
- ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
- ConnectionPreconditions.checkState(
- !isBatchActive(), "Cannot get autocommit DML mode while in a batch");
- return this.autocommitDmlMode;
- }
-
- @Override
- public void setReadOnlyStaleness(TimestampBound staleness) {
- Preconditions.checkNotNull(staleness);
- ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
- ConnectionPreconditions.checkState(!isBatchActive(), "Cannot set read-only while in a batch");
- ConnectionPreconditions.checkState(
- !isTransactionStarted(),
- "Cannot set read-only staleness when a transaction has been started");
- if (staleness.getMode() == Mode.MAX_STALENESS
- || staleness.getMode() == Mode.MIN_READ_TIMESTAMP) {
- // These values are only allowed in autocommit mode.
- ConnectionPreconditions.checkState(
- isAutocommit() && !inTransaction,
- "MAX_STALENESS and MIN_READ_TIMESTAMP are only allowed in autocommit mode");
- }
- this.readOnlyStaleness = staleness;
- }
-
- @Override
- public TimestampBound getReadOnlyStaleness() {
- ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
- ConnectionPreconditions.checkState(!isBatchActive(), "Cannot get read-only while in a batch");
- return this.readOnlyStaleness;
- }
-
- @Override
- public void setOptimizerVersion(String optimizerVersion) {
- Preconditions.checkNotNull(optimizerVersion);
- ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
- this.queryOptions = queryOptions.toBuilder().setOptimizerVersion(optimizerVersion).build();
- }
-
- @Override
- public String getOptimizerVersion() {
- ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
- return this.queryOptions.getOptimizerVersion();
- }
-
- @Override
- public void setStatementTimeout(long timeout, TimeUnit unit) {
- Preconditions.checkArgument(timeout > 0L, "Zero or negative timeout values are not allowed");
- Preconditions.checkArgument(
- StatementTimeout.isValidTimeoutUnit(unit),
- "Time unit must be one of NANOSECONDS, MICROSECONDS, MILLISECONDS or SECONDS");
- ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
- this.statementTimeout.setTimeoutValue(timeout, unit);
- }
-
- @Override
- public void clearStatementTimeout() {
- ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
- this.statementTimeout.clearTimeoutValue();
- }
-
- @Override
- public long getStatementTimeout(TimeUnit unit) {
- ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
- Preconditions.checkArgument(
- StatementTimeout.isValidTimeoutUnit(unit),
- "Time unit must be one of NANOSECONDS, MICROSECONDS, MILLISECONDS or SECONDS");
- return this.statementTimeout.getTimeoutValue(unit);
- }
-
- @Override
- public boolean hasStatementTimeout() {
- ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
- return this.statementTimeout.hasTimeout();
- }
-
- @Override
- public void cancel() {
- ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
- if (this.currentUnitOfWork != null) {
- currentUnitOfWork.cancel();
- }
- }
-
- @Override
- public TransactionMode getTransactionMode() {
- ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
- ConnectionPreconditions.checkState(!isDdlBatchActive(), "This connection is in a DDL batch");
- ConnectionPreconditions.checkState(isInTransaction(), "This connection has no transaction");
- return unitOfWorkType.getTransactionMode();
- }
-
- @Override
- public void setTransactionMode(TransactionMode transactionMode) {
- Preconditions.checkNotNull(transactionMode);
- ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
- ConnectionPreconditions.checkState(
- !isBatchActive(), "Cannot set transaction mode while in a batch");
- ConnectionPreconditions.checkState(isInTransaction(), "This connection has no transaction");
- ConnectionPreconditions.checkState(
- !isTransactionStarted(),
- "The transaction mode cannot be set after the transaction has started");
- ConnectionPreconditions.checkState(
- !isReadOnly() || transactionMode == TransactionMode.READ_ONLY_TRANSACTION,
- "The transaction mode can only be READ_ONLY when the connection is in read_only mode");
-
- this.transactionBeginMarked = true;
- this.unitOfWorkType = UnitOfWorkType.of(transactionMode);
- }
-
- /**
- * Throws an {@link SpannerException} with code {@link ErrorCode#FAILED_PRECONDITION} if the
- * current state of this connection does not allow changing the setting for retryAbortsInternally.
- */
- private void checkSetRetryAbortsInternallyAvailable() {
- ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
- ConnectionPreconditions.checkState(isInTransaction(), "This connection has no transaction");
- ConnectionPreconditions.checkState(
- getTransactionMode() == TransactionMode.READ_WRITE_TRANSACTION,
- "RetryAbortsInternally is only available for read-write transactions");
- ConnectionPreconditions.checkState(
- !isTransactionStarted(),
- "RetryAbortsInternally cannot be set after the transaction has started");
- }
-
- @Override
- public boolean isRetryAbortsInternally() {
- ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
- return retryAbortsInternally;
- }
-
- @Override
- public void setRetryAbortsInternally(boolean retryAbortsInternally) {
- checkSetRetryAbortsInternallyAvailable();
- this.retryAbortsInternally = retryAbortsInternally;
- }
-
- @Override
- public void addTransactionRetryListener(TransactionRetryListener listener) {
- Preconditions.checkNotNull(listener);
- transactionRetryListeners.add(listener);
- }
-
- @Override
- public boolean removeTransactionRetryListener(TransactionRetryListener listener) {
- Preconditions.checkNotNull(listener);
- return transactionRetryListeners.remove(listener);
- }
-
- @Override
- public Iterator getTransactionRetryListeners() {
- return Collections.unmodifiableList(transactionRetryListeners).iterator();
- }
-
- @Override
- public boolean isInTransaction() {
- ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
- return internalIsInTransaction();
- }
-
- /** Returns true if this connection currently is in a transaction (and not a batch). */
- private boolean internalIsInTransaction() {
- return !isDdlBatchActive() && (!internalIsAutocommit() || inTransaction);
- }
-
- @Override
- public boolean isTransactionStarted() {
- ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
- return internalIsTransactionStarted();
- }
-
- private boolean internalIsTransactionStarted() {
- if (internalIsAutocommit() && !inTransaction) {
- return false;
- }
- return internalIsInTransaction()
- && this.currentUnitOfWork != null
- && this.currentUnitOfWork.getState() == UnitOfWorkState.STARTED;
- }
-
- @Override
- public Timestamp getReadTimestamp() {
- ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
- ConnectionPreconditions.checkState(
- this.currentUnitOfWork != null, "There is no transaction on this connection");
- return this.currentUnitOfWork.getReadTimestamp();
- }
-
- Timestamp getReadTimestampOrNull() {
- ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
- return this.currentUnitOfWork == null ? null : this.currentUnitOfWork.getReadTimestampOrNull();
- }
-
- @Override
- public Timestamp getCommitTimestamp() {
- ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
- ConnectionPreconditions.checkState(
- this.currentUnitOfWork != null, "There is no transaction on this connection");
- return this.currentUnitOfWork.getCommitTimestamp();
- }
-
- Timestamp getCommitTimestampOrNull() {
- ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
- return this.currentUnitOfWork == null
- ? null
- : this.currentUnitOfWork.getCommitTimestampOrNull();
- }
-
- /** Resets this connection to its default transaction options. */
- private void setDefaultTransactionOptions() {
- if (transactionStack.isEmpty()) {
- unitOfWorkType =
- isReadOnly()
- ? UnitOfWorkType.READ_ONLY_TRANSACTION
- : UnitOfWorkType.READ_WRITE_TRANSACTION;
- batchMode = BatchMode.NONE;
- } else {
- popUnitOfWorkFromTransactionStack();
- }
- }
-
- @Override
- public void beginTransaction() {
- ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
- ConnectionPreconditions.checkState(
- !isBatchActive(), "This connection has an active batch and cannot begin a transaction");
- ConnectionPreconditions.checkState(
- !isTransactionStarted(),
- "Beginning a new transaction is not allowed when a transaction is already running");
- ConnectionPreconditions.checkState(!transactionBeginMarked, "A transaction has already begun");
-
- transactionBeginMarked = true;
- clearLastTransactionAndSetDefaultTransactionOptions();
- if (isAutocommit()) {
- inTransaction = true;
- }
- }
-
- /** Internal interface for ending a transaction (commit/rollback). */
- private static interface EndTransactionMethod {
- public void end(UnitOfWork t);
- }
-
- private static final class Commit implements EndTransactionMethod {
- @Override
- public void end(UnitOfWork t) {
- t.commit();
- }
- }
-
- private final Commit commit = new Commit();
-
- @Override
- public void commit() {
- ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
- endCurrentTransaction(commit);
- }
-
- private static final class Rollback implements EndTransactionMethod {
- @Override
- public void end(UnitOfWork t) {
- t.rollback();
- }
- }
-
- private final Rollback rollback = new Rollback();
-
- @Override
- public void rollback() {
- ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
- endCurrentTransaction(rollback);
- }
-
- private void endCurrentTransaction(EndTransactionMethod endTransactionMethod) {
- ConnectionPreconditions.checkState(!isBatchActive(), "This connection has an active batch");
- ConnectionPreconditions.checkState(isInTransaction(), "This connection has no transaction");
- try {
- if (isTransactionStarted()) {
- endTransactionMethod.end(getCurrentUnitOfWorkOrStartNewUnitOfWork());
- } else {
- this.currentUnitOfWork = null;
- }
- } finally {
- transactionBeginMarked = false;
- if (isAutocommit()) {
- inTransaction = false;
- }
- setDefaultTransactionOptions();
- }
- }
-
- @Override
- public StatementResult execute(Statement statement) {
- Preconditions.checkNotNull(statement);
- ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
- ParsedStatement parsedStatement = parser.parse(statement, this.queryOptions);
- switch (parsedStatement.getType()) {
- case CLIENT_SIDE:
- return parsedStatement
- .getClientSideStatement()
- .execute(connectionStatementExecutor, parsedStatement.getSqlWithoutComments());
- case QUERY:
- return StatementResultImpl.of(internalExecuteQuery(parsedStatement, AnalyzeMode.NONE));
- case UPDATE:
- return StatementResultImpl.of(internalExecuteUpdate(parsedStatement));
- case DDL:
- executeDdl(parsedStatement);
- return StatementResultImpl.noResult();
- case UNKNOWN:
- default:
- }
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.INVALID_ARGUMENT,
- "Unknown statement: " + parsedStatement.getSqlWithoutComments());
- }
-
- @Override
- public ResultSet executeQuery(Statement query, QueryOption... options) {
- return parseAndExecuteQuery(query, AnalyzeMode.NONE, options);
- }
-
- @Override
- public ResultSet analyzeQuery(Statement query, QueryAnalyzeMode queryMode) {
- Preconditions.checkNotNull(queryMode);
- return parseAndExecuteQuery(query, AnalyzeMode.of(queryMode));
- }
-
- /**
- * Parses the given statement as a query and executes it. Throws a {@link SpannerException} if the
- * statement is not a query.
- */
- private ResultSet parseAndExecuteQuery(
- Statement query, AnalyzeMode analyzeMode, QueryOption... options) {
- Preconditions.checkNotNull(query);
- Preconditions.checkNotNull(analyzeMode);
- ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
- ParsedStatement parsedStatement = parser.parse(query, this.queryOptions);
- if (parsedStatement.isQuery()) {
- switch (parsedStatement.getType()) {
- case CLIENT_SIDE:
- return parsedStatement
- .getClientSideStatement()
- .execute(connectionStatementExecutor, parsedStatement.getSqlWithoutComments())
- .getResultSet();
- case QUERY:
- return internalExecuteQuery(parsedStatement, analyzeMode, options);
- case UPDATE:
- case DDL:
- case UNKNOWN:
- default:
- }
- }
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.INVALID_ARGUMENT,
- "Statement is not a query: " + parsedStatement.getSqlWithoutComments());
- }
-
- @Override
- public long executeUpdate(Statement update) {
- Preconditions.checkNotNull(update);
- ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
- ParsedStatement parsedStatement = parser.parse(update);
- if (parsedStatement.isUpdate()) {
- switch (parsedStatement.getType()) {
- case UPDATE:
- return internalExecuteUpdate(parsedStatement);
- case CLIENT_SIDE:
- case QUERY:
- case DDL:
- case UNKNOWN:
- default:
- }
- }
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.INVALID_ARGUMENT,
- "Statement is not an update statement: " + parsedStatement.getSqlWithoutComments());
- }
-
- @Override
- public long[] executeBatchUpdate(Iterable updates) {
- Preconditions.checkNotNull(updates);
- ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
- // Check that there are only DML statements in the input.
- List parsedStatements = new LinkedList<>();
- for (Statement update : updates) {
- ParsedStatement parsedStatement = parser.parse(update);
- if (parsedStatement.isUpdate()) {
- switch (parsedStatement.getType()) {
- case UPDATE:
- parsedStatements.add(parsedStatement);
- break;
- case CLIENT_SIDE:
- case QUERY:
- case DDL:
- case UNKNOWN:
- default:
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.INVALID_ARGUMENT,
- "The batch update list contains a statement that is not an update statement: "
- + parsedStatement.getSqlWithoutComments());
- }
- }
- }
- return internalExecuteBatchUpdate(parsedStatements);
- }
-
- private ResultSet internalExecuteQuery(
- final ParsedStatement statement,
- final AnalyzeMode analyzeMode,
- final QueryOption... options) {
- Preconditions.checkArgument(
- statement.getType() == StatementType.QUERY, "Statement must be a query");
- UnitOfWork transaction = getCurrentUnitOfWorkOrStartNewUnitOfWork();
- try {
- return transaction.executeQuery(statement, analyzeMode, options);
- } catch (SpannerException e) {
- // In case of a timed out or cancelled query we need to replace the executor to ensure that we
- // have an executor that is not busy executing a statement. Although we try to cancel the
- // current statement, it is not guaranteed to actually stop the execution directly.
- if (e.getErrorCode() == ErrorCode.DEADLINE_EXCEEDED
- || e.getErrorCode() == ErrorCode.CANCELLED) {
- this.statementExecutor.recreate();
- }
- throw e;
- }
- }
-
- private long internalExecuteUpdate(final ParsedStatement update) {
- Preconditions.checkArgument(
- update.getType() == StatementType.UPDATE, "Statement must be an update");
- UnitOfWork transaction = getCurrentUnitOfWorkOrStartNewUnitOfWork();
- try {
- return transaction.executeUpdate(update);
- } catch (SpannerException e) {
- // In case of a timed out or cancelled query we need to replace the executor to ensure that we
- // have an executor that is not busy executing a statement. Although we try to cancel the
- // current statement, it is not guaranteed to actually stop the execution directly.
- if (e.getErrorCode() == ErrorCode.DEADLINE_EXCEEDED
- || e.getErrorCode() == ErrorCode.CANCELLED) {
- this.statementExecutor.recreate();
- }
- throw e;
- }
- }
-
- private long[] internalExecuteBatchUpdate(final List updates) {
- UnitOfWork transaction = getCurrentUnitOfWorkOrStartNewUnitOfWork();
- try {
- return transaction.executeBatchUpdate(updates);
- } catch (SpannerException e) {
- // In case of a timed out or cancelled query we need to replace the executor to ensure that we
- // have an executor that is not busy executing a statement. Although we try to cancel the
- // current statement, it is not guaranteed to actually stop the execution directly.
- if (e.getErrorCode() == ErrorCode.DEADLINE_EXCEEDED
- || e.getErrorCode() == ErrorCode.CANCELLED) {
- this.statementExecutor.recreate();
- }
- throw e;
- }
- }
-
- /**
- * Returns the current {@link UnitOfWork} of this connection, or creates a new one based on the
- * current transaction settings of the connection and returns that.
- */
- @VisibleForTesting
- UnitOfWork getCurrentUnitOfWorkOrStartNewUnitOfWork() {
- if (this.currentUnitOfWork == null || !this.currentUnitOfWork.isActive()) {
- this.currentUnitOfWork = createNewUnitOfWork();
- }
- return this.currentUnitOfWork;
- }
-
- private UnitOfWork createNewUnitOfWork() {
- if (isAutocommit() && !isInTransaction() && !isInBatch()) {
- return SingleUseTransaction.newBuilder()
- .setDdlClient(ddlClient)
- .setDatabaseClient(dbClient)
- .setReadOnly(isReadOnly())
- .setReadOnlyStaleness(readOnlyStaleness)
- .setAutocommitDmlMode(autocommitDmlMode)
- .setStatementTimeout(statementTimeout)
- .withStatementExecutor(statementExecutor)
- .build();
- } else {
- switch (getUnitOfWorkType()) {
- case READ_ONLY_TRANSACTION:
- return ReadOnlyTransaction.newBuilder()
- .setDatabaseClient(dbClient)
- .setReadOnlyStaleness(readOnlyStaleness)
- .setStatementTimeout(statementTimeout)
- .withStatementExecutor(statementExecutor)
- .build();
- case READ_WRITE_TRANSACTION:
- return ReadWriteTransaction.newBuilder()
- .setDatabaseClient(dbClient)
- .setRetryAbortsInternally(retryAbortsInternally)
- .setTransactionRetryListeners(transactionRetryListeners)
- .setStatementTimeout(statementTimeout)
- .withStatementExecutor(statementExecutor)
- .build();
- case DML_BATCH:
- // A DML batch can run inside the current transaction. It should therefore only
- // temporarily replace the current transaction.
- pushCurrentUnitOfWorkToTransactionStack();
- return DmlBatch.newBuilder()
- .setTransaction(currentUnitOfWork)
- .setStatementTimeout(statementTimeout)
- .withStatementExecutor(statementExecutor)
- .build();
- case DDL_BATCH:
- return DdlBatch.newBuilder()
- .setDdlClient(ddlClient)
- .setDatabaseClient(dbClient)
- .setStatementTimeout(statementTimeout)
- .withStatementExecutor(statementExecutor)
- .build();
- default:
- }
- }
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.FAILED_PRECONDITION,
- "This connection does not have an active transaction and the state of this connection does not allow any new transactions to be started");
- }
-
- /** Pushes the current unit of work to the stack of nested transactions. */
- private void pushCurrentUnitOfWorkToTransactionStack() {
- Preconditions.checkState(currentUnitOfWork != null, "There is no current transaction");
- transactionStack.push(currentUnitOfWork);
- }
-
- /** Set the {@link UnitOfWork} of this connection back to the previous {@link UnitOfWork}. */
- private void popUnitOfWorkFromTransactionStack() {
- Preconditions.checkState(
- !transactionStack.isEmpty(), "There is no unit of work in the transaction stack");
- this.currentUnitOfWork = transactionStack.pop();
- }
-
- private void executeDdl(ParsedStatement ddl) {
- getCurrentUnitOfWorkOrStartNewUnitOfWork().executeDdl(ddl);
- }
-
- @Override
- public void write(Mutation mutation) {
- Preconditions.checkNotNull(mutation);
- ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
- ConnectionPreconditions.checkState(isAutocommit(), ONLY_ALLOWED_IN_AUTOCOMMIT);
- getCurrentUnitOfWorkOrStartNewUnitOfWork().write(mutation);
- }
-
- @Override
- public void write(Iterable mutations) {
- Preconditions.checkNotNull(mutations);
- ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
- ConnectionPreconditions.checkState(isAutocommit(), ONLY_ALLOWED_IN_AUTOCOMMIT);
- getCurrentUnitOfWorkOrStartNewUnitOfWork().write(mutations);
- }
-
- @Override
- public void bufferedWrite(Mutation mutation) {
- Preconditions.checkNotNull(mutation);
- ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
- ConnectionPreconditions.checkState(!isAutocommit(), NOT_ALLOWED_IN_AUTOCOMMIT);
- getCurrentUnitOfWorkOrStartNewUnitOfWork().write(mutation);
- }
-
- @Override
- public void bufferedWrite(Iterable mutations) {
- Preconditions.checkNotNull(mutations);
- ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
- ConnectionPreconditions.checkState(!isAutocommit(), NOT_ALLOWED_IN_AUTOCOMMIT);
- getCurrentUnitOfWorkOrStartNewUnitOfWork().write(mutations);
- }
-
- @Override
- public void startBatchDdl() {
- ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
- ConnectionPreconditions.checkState(
- !isBatchActive(), "Cannot start a DDL batch when a batch is already active");
- ConnectionPreconditions.checkState(
- !isReadOnly(), "Cannot start a DDL batch when the connection is in read-only mode");
- ConnectionPreconditions.checkState(
- !isTransactionStarted(), "Cannot start a DDL batch while a transaction is active");
- ConnectionPreconditions.checkState(
- !(isAutocommit() && isInTransaction()),
- "Cannot start a DDL batch while in a temporary transaction");
- ConnectionPreconditions.checkState(
- !transactionBeginMarked, "Cannot start a DDL batch when a transaction has begun");
- this.batchMode = BatchMode.DDL;
- this.unitOfWorkType = UnitOfWorkType.DDL_BATCH;
- this.currentUnitOfWork = createNewUnitOfWork();
- }
-
- @Override
- public void startBatchDml() {
- ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
- ConnectionPreconditions.checkState(
- !isBatchActive(), "Cannot start a DML batch when a batch is already active");
- ConnectionPreconditions.checkState(
- !isReadOnly(), "Cannot start a DML batch when the connection is in read-only mode");
- ConnectionPreconditions.checkState(
- !(isInTransaction() && getTransactionMode() == TransactionMode.READ_ONLY_TRANSACTION),
- "Cannot start a DML batch when a read-only transaction is in progress");
- // Make sure that there is a current unit of work that the batch can use.
- getCurrentUnitOfWorkOrStartNewUnitOfWork();
- // Then create the DML batch.
- this.batchMode = BatchMode.DML;
- this.unitOfWorkType = UnitOfWorkType.DML_BATCH;
- this.currentUnitOfWork = createNewUnitOfWork();
- }
-
- @Override
- public long[] runBatch() {
- ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
- ConnectionPreconditions.checkState(isBatchActive(), "This connection has no active batch");
- try {
- if (this.currentUnitOfWork != null) {
- return this.currentUnitOfWork.runBatch();
- }
- return new long[0];
- } finally {
- this.batchMode = BatchMode.NONE;
- setDefaultTransactionOptions();
- }
- }
-
- @Override
- public void abortBatch() {
- ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
- ConnectionPreconditions.checkState(isBatchActive(), "This connection has no active batch");
- try {
- if (this.currentUnitOfWork != null) {
- this.currentUnitOfWork.abortBatch();
- }
- } finally {
- this.batchMode = BatchMode.NONE;
- setDefaultTransactionOptions();
- }
- }
-
- private boolean isBatchActive() {
- return isDdlBatchActive() || isDmlBatchActive();
- }
-
- @Override
- public boolean isDdlBatchActive() {
- ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
- return this.batchMode == BatchMode.DDL;
- }
-
- @Override
- public boolean isDmlBatchActive() {
- ConnectionPreconditions.checkState(!isClosed(), CLOSED_ERROR_MSG);
- return this.batchMode == BatchMode.DML;
- }
-}
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/ConnectionOptions.java b/src/main/java/com/google/cloud/spanner/jdbc/ConnectionOptions.java
deleted file mode 100644
index 4ec727cd..00000000
--- a/src/main/java/com/google/cloud/spanner/jdbc/ConnectionOptions.java
+++ /dev/null
@@ -1,666 +0,0 @@
-/*
- * Copyright 2019 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.cloud.spanner.jdbc;
-
-import com.google.auth.Credentials;
-import com.google.auth.oauth2.AccessToken;
-import com.google.auth.oauth2.GoogleCredentials;
-import com.google.auth.oauth2.ServiceAccountCredentials;
-import com.google.cloud.NoCredentials;
-import com.google.cloud.ServiceOptions;
-import com.google.cloud.spanner.DatabaseId;
-import com.google.cloud.spanner.ErrorCode;
-import com.google.cloud.spanner.Spanner;
-import com.google.cloud.spanner.SpannerException;
-import com.google.cloud.spanner.SpannerExceptionFactory;
-import com.google.cloud.spanner.SpannerOptions;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Preconditions;
-import com.google.common.collect.Sets;
-import com.google.spanner.v1.ExecuteSqlRequest.QueryOptions;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Options for creating a {@link Connection} to a Google Cloud Spanner database.
- *
- * Usage:
- *
- *
- *
- * {@code
- * ConnectionOptions options = ConnectionOptions.newBuilder()
- * .setUri("cloudspanner:/projects/my_project_id/instances/my_instance_id/databases/my_database_name?autocommit=false")
- * .setCredentialsUrl("/home/cloudspanner-keys/my-key.json")
- * .build();
- * try(Connection connection = options.getConnection()) {
- * try(ResultSet rs = connection.executeQuery(Statement.of("SELECT SingerId, AlbumId, MarketingBudget FROM Albums"))) {
- * while(rs.next()) {
- * // do something
- * }
- * }
- * }
- * }
- *
- *
- */
-class ConnectionOptions {
- /** Supported connection properties that can be included in the connection URI. */
- public static class ConnectionProperty {
- private static final String[] BOOLEAN_VALUES = new String[] {"true", "false"};
- private final String name;
- private final String description;
- private final String defaultValue;
- private final String[] validValues;
- private final int hashCode;
-
- private static ConnectionProperty createStringProperty(String name, String description) {
- return new ConnectionProperty(name, description, "", null);
- }
-
- private static ConnectionProperty createBooleanProperty(
- String name, String description, boolean defaultValue) {
- return new ConnectionProperty(
- name, description, String.valueOf(defaultValue), BOOLEAN_VALUES);
- }
-
- private static ConnectionProperty createEmptyProperty(String name) {
- return new ConnectionProperty(name, "", "", null);
- }
-
- private ConnectionProperty(
- String name, String description, String defaultValue, String[] validValues) {
- Preconditions.checkNotNull(name);
- Preconditions.checkNotNull(description);
- Preconditions.checkNotNull(defaultValue);
- this.name = name;
- this.description = description;
- this.defaultValue = defaultValue;
- this.validValues = validValues;
- this.hashCode = name.toLowerCase().hashCode();
- }
-
- @Override
- public int hashCode() {
- return hashCode;
- }
-
- @Override
- public boolean equals(Object o) {
- if (!(o instanceof ConnectionProperty)) {
- return false;
- }
- return ((ConnectionProperty) o).name.equalsIgnoreCase(this.name);
- }
-
- /** @return the name of this connection property. */
- public String getName() {
- return name;
- }
-
- /** @return the description of this connection property. */
- public String getDescription() {
- return description;
- }
-
- /** @return the default value of this connection property. */
- public String getDefaultValue() {
- return defaultValue;
- }
-
- /**
- * @return the valid values for this connection property. null
indicates no
- * restriction.
- */
- public String[] getValidValues() {
- return validValues;
- }
- }
-
- private static final boolean DEFAULT_USE_PLAIN_TEXT = false;
- static final boolean DEFAULT_AUTOCOMMIT = true;
- static final boolean DEFAULT_READONLY = false;
- static final boolean DEFAULT_RETRY_ABORTS_INTERNALLY = true;
- private static final String DEFAULT_CREDENTIALS = null;
- private static final String DEFAULT_OAUTH_TOKEN = null;
- private static final String DEFAULT_NUM_CHANNELS = null;
- private static final String DEFAULT_USER_AGENT = null;
- private static final String DEFAULT_OPTIMIZER_VERSION = "";
-
- private static final String PLAIN_TEXT_PROTOCOL = "http:";
- private static final String HOST_PROTOCOL = "https:";
- private static final String DEFAULT_HOST = "https://spanner.googleapis.com";
- /** Use plain text is only for local testing purposes. */
- private static final String USE_PLAIN_TEXT_PROPERTY_NAME = "usePlainText";
- /** Name of the 'autocommit' connection property. */
- public static final String AUTOCOMMIT_PROPERTY_NAME = "autocommit";
- /** Name of the 'readonly' connection property. */
- public static final String READONLY_PROPERTY_NAME = "readonly";
- /** Name of the 'retry aborts internally' connection property. */
- public static final String RETRY_ABORTS_INTERNALLY_PROPERTY_NAME = "retryAbortsInternally";
- /** Name of the 'credentials' connection property. */
- public static final String CREDENTIALS_PROPERTY_NAME = "credentials";
- /**
- * OAuth token to use for authentication. Cannot be used in combination with a credentials file.
- */
- public static final String OAUTH_TOKEN_PROPERTY_NAME = "oauthToken";
- /** Name of the 'numChannels' connection property. */
- public static final String NUM_CHANNELS_PROPERTY_NAME = "numChannels";
- /** Custom user agent string is only for other Google libraries. */
- private static final String USER_AGENT_PROPERTY_NAME = "userAgent";
- /** Query optimizer version to use for a connection. */
- private static final String OPTIMIZER_VERSION_PROPERTY_NAME = "optimizerVersion";
-
- /** All valid connection properties. */
- public static final Set VALID_PROPERTIES =
- Collections.unmodifiableSet(
- new HashSet<>(
- Arrays.asList(
- ConnectionProperty.createBooleanProperty(
- AUTOCOMMIT_PROPERTY_NAME, "", DEFAULT_AUTOCOMMIT),
- ConnectionProperty.createBooleanProperty(
- READONLY_PROPERTY_NAME, "", DEFAULT_READONLY),
- ConnectionProperty.createBooleanProperty(
- RETRY_ABORTS_INTERNALLY_PROPERTY_NAME, "", DEFAULT_RETRY_ABORTS_INTERNALLY),
- ConnectionProperty.createStringProperty(CREDENTIALS_PROPERTY_NAME, ""),
- ConnectionProperty.createStringProperty(OAUTH_TOKEN_PROPERTY_NAME, ""),
- ConnectionProperty.createStringProperty(NUM_CHANNELS_PROPERTY_NAME, ""),
- ConnectionProperty.createBooleanProperty(
- USE_PLAIN_TEXT_PROPERTY_NAME, "", DEFAULT_USE_PLAIN_TEXT),
- ConnectionProperty.createStringProperty(USER_AGENT_PROPERTY_NAME, ""),
- ConnectionProperty.createStringProperty(OPTIMIZER_VERSION_PROPERTY_NAME, ""))));
-
- private static final Set INTERNAL_PROPERTIES =
- Collections.unmodifiableSet(
- new HashSet<>(
- Arrays.asList(
- ConnectionProperty.createStringProperty(USER_AGENT_PROPERTY_NAME, ""))));
- private static final Set INTERNAL_VALID_PROPERTIES =
- Sets.union(VALID_PROPERTIES, INTERNAL_PROPERTIES);
-
- /**
- * Gets the default project-id for the current environment as defined by {@link
- * ServiceOptions#getDefaultProjectId()}, and if none could be found, the project-id of the given
- * credentials if it contains any.
- *
- * @param credentials The credentials to use to get the default project-id if none could be found
- * in the environment.
- * @return the default project-id.
- */
- public static String getDefaultProjectId(Credentials credentials) {
- String projectId = SpannerOptions.getDefaultProjectId();
- if (projectId == null
- && credentials != null
- && credentials instanceof ServiceAccountCredentials) {
- projectId = ((ServiceAccountCredentials) credentials).getProjectId();
- }
- return projectId;
- }
-
- /**
- * Closes all {@link Spanner} instances that have been opened by connections
- * during the lifetime of this JVM. Call this method at the end of your application to free up
- * resources. You must close all {@link Connection}s that have been opened by your application
- * before calling this method. Failing to do so, will cause this method to throw a {@link
- * SpannerException}.
- *
- * This method is also automatically called by a shutdown hook (see {@link
- * Runtime#addShutdownHook(Thread)}) when the JVM is shutdown gracefully.
- */
- public static void closeSpanner() {
- SpannerPool.INSTANCE.checkAndCloseSpanners();
- }
-
- /** Builder for {@link ConnectionOptions} instances. */
- public static class Builder {
- private String uri;
- private String credentialsUrl;
- private String oauthToken;
- private Credentials credentials;
- private List statementExecutionInterceptors =
- Collections.emptyList();
-
- private Builder() {}
-
- /** Spanner {@link ConnectionOptions} URI format. */
- public static final String SPANNER_URI_FORMAT =
- "(?:cloudspanner:)(?//[\\w.-]+(?:\\.[\\w\\.-]+)*[\\w\\-\\._~:/?#\\[\\]@!\\$&'\\(\\)\\*\\+,;=.]+)?/projects/(?(([a-z]|[-.:]|[0-9])+|(DEFAULT_PROJECT_ID)))(/instances/(?([a-z]|[-]|[0-9])+)(/databases/(?([a-z]|[-]|[_]|[0-9])+))?)?(?:[?|;].*)?";
-
- private static final String SPANNER_URI_REGEX = "(?is)^" + SPANNER_URI_FORMAT + "$";
- private static final Pattern SPANNER_URI_PATTERN = Pattern.compile(SPANNER_URI_REGEX);
- private static final String HOST_GROUP = "HOSTGROUP";
- private static final String PROJECT_GROUP = "PROJECTGROUP";
- private static final String INSTANCE_GROUP = "INSTANCEGROUP";
- private static final String DATABASE_GROUP = "DATABASEGROUP";
- private static final String DEFAULT_PROJECT_ID_PLACEHOLDER = "DEFAULT_PROJECT_ID";
-
- private boolean isValidUri(String uri) {
- return SPANNER_URI_PATTERN.matcher(uri).matches();
- }
-
- /**
- * Sets the URI of the Cloud Spanner database to connect to. A connection URI must be specified
- * in this format:
- *
- *
- * cloudspanner:[//host[:port]]/projects/project-id[/instances/instance-id[/databases/database-name]][\?property-name=property-value[;property-name=property-value]*]?
- *
- *
- * The property-value strings should be url-encoded.
- *
- * The project-id part of the URI may be filled with the placeholder DEFAULT_PROJECT_ID. This
- * placeholder will be replaced by the default project id of the environment that is requesting
- * a connection.
- *
- *
The supported properties are:
- *
- *
- * - credentials (String): URL for the credentials file to use for the connection. This
- * property is only used if no credentials have been specified using the {@link
- * ConnectionOptions.Builder#setCredentialsUrl(String)} method. If you do not specify any
- * credentials at all, the default credentials of the environment as returned by {@link
- * GoogleCredentials#getApplicationDefault()} will be used.
- *
- autocommit (boolean): Sets the initial autocommit mode for the connection. Default is
- * true.
- *
- readonly (boolean): Sets the initial readonly mode for the connection. Default is
- * false.
- *
- retryAbortsInternally (boolean): Sets the initial retryAbortsInternally mode for the
- * connection. Default is true.
- *
- optimizerVersion (string): Sets the query optimizer version to use for the connection.
- *
- *
- * @param uri The URI of the Spanner database to connect to.
- * @return this builder
- */
- public Builder setUri(String uri) {
- Preconditions.checkArgument(
- isValidUri(uri),
- "The specified URI is not a valid Cloud Spanner connection URI. Please specify a URI in the format \"cloudspanner:[//host[:port]]/projects/project-id[/instances/instance-id[/databases/database-name]][\\?property-name=property-value[;property-name=property-value]*]?\"");
- checkValidProperties(uri);
- this.uri = uri;
- return this;
- }
-
- /**
- * Sets the URL of the credentials file to use for this connection. The URL may be a reference
- * to a file on the local file system, or to a file on Google Cloud Storage. References to
- * Google Cloud Storage files are only allowed when the application is running on Google Cloud
- * and the environment has access to the specified storage location. It also requires that the
- * Google Cloud Storage client library is present on the class path. The Google Cloud Storage
- * library is not automatically added as a dependency by the JDBC driver.
- *
- * If you do not specify a credentialsUrl (either by using this setter, or by specifying on
- * the connection URI), the credentials returned by {@link
- * GoogleCredentials#getApplicationDefault()} will be used for the connection.
- *
- * @param credentialsUrl A valid file or Google Cloud Storage URL for the credentials file to be
- * used.
- * @return this builder
- */
- public Builder setCredentialsUrl(String credentialsUrl) {
- this.credentialsUrl = credentialsUrl;
- return this;
- }
-
- /**
- * Sets the OAuth token to use with this connection. The token must be a valid token with access
- * to the resources (project/instance/database) that the connection will be accessing. This
- * authentication method cannot be used in combination with a credentials file. If both an OAuth
- * token and a credentials file is specified, the {@link #build()} method will throw an
- * exception.
- *
- * @param oauthToken A valid OAuth token for the Google Cloud project that is used by this
- * connection.
- * @return this builder
- */
- public Builder setOAuthToken(String oauthToken) {
- this.oauthToken = oauthToken;
- return this;
- }
-
- @VisibleForTesting
- Builder setStatementExecutionInterceptors(List interceptors) {
- this.statementExecutionInterceptors = interceptors;
- return this;
- }
-
- @VisibleForTesting
- Builder setCredentials(Credentials credentials) {
- this.credentials = credentials;
- return this;
- }
-
- /** @return the {@link ConnectionOptions} */
- public ConnectionOptions build() {
- Preconditions.checkState(this.uri != null, "Connection URI is required");
- return new ConnectionOptions(this);
- }
- }
-
- /**
- * Create a {@link Builder} for {@link ConnectionOptions}. Use this method to create {@link
- * ConnectionOptions} that can be used to obtain a {@link Connection}.
- *
- * @return a new {@link Builder}
- */
- public static Builder newBuilder() {
- return new Builder();
- }
-
- private final String uri;
- private final String credentialsUrl;
- private final String oauthToken;
-
- private final boolean usePlainText;
- private final String host;
- private final String projectId;
- private final String instanceId;
- private final String databaseName;
- private final Credentials credentials;
- private final Integer numChannels;
- private final String userAgent;
- private final QueryOptions queryOptions;
-
- private final boolean autocommit;
- private final boolean readOnly;
- private final boolean retryAbortsInternally;
- private final List statementExecutionInterceptors;
-
- private ConnectionOptions(Builder builder) {
- Matcher matcher = Builder.SPANNER_URI_PATTERN.matcher(builder.uri);
- Preconditions.checkArgument(
- matcher.find(), String.format("Invalid connection URI specified: %s", builder.uri));
- checkValidProperties(builder.uri);
-
- this.uri = builder.uri;
- this.credentialsUrl =
- builder.credentialsUrl != null ? builder.credentialsUrl : parseCredentials(builder.uri);
- this.oauthToken =
- builder.oauthToken != null ? builder.oauthToken : parseOAuthToken(builder.uri);
- // Check that not both credentials and an OAuth token have been specified.
- Preconditions.checkArgument(
- (builder.credentials == null && this.credentialsUrl == null) || this.oauthToken == null,
- "Cannot specify both credentials and an OAuth token.");
-
- this.usePlainText = parseUsePlainText(this.uri);
- this.userAgent = parseUserAgent(this.uri);
- QueryOptions.Builder queryOptionsBuilder = QueryOptions.newBuilder();
- queryOptionsBuilder.setOptimizerVersion(parseOptimizerVersion(this.uri));
- this.queryOptions = queryOptionsBuilder.build();
-
- this.host =
- matcher.group(Builder.HOST_GROUP) == null
- ? DEFAULT_HOST
- : (usePlainText ? PLAIN_TEXT_PROTOCOL : HOST_PROTOCOL)
- + matcher.group(Builder.HOST_GROUP);
- this.instanceId = matcher.group(Builder.INSTANCE_GROUP);
- this.databaseName = matcher.group(Builder.DATABASE_GROUP);
- // Using credentials on a plain text connection is not allowed, so if the user has not specified
- // any credentials and is using a plain text connection, we should not try to get the
- // credentials from the environment, but default to NoCredentials.
- if (builder.credentials == null
- && this.credentialsUrl == null
- && this.oauthToken == null
- && this.usePlainText) {
- this.credentials = NoCredentials.getInstance();
- } else if (this.oauthToken != null) {
- this.credentials = new GoogleCredentials(new AccessToken(oauthToken, null));
- } else {
- this.credentials =
- builder.credentials == null
- ? getCredentialsService().createCredentials(this.credentialsUrl)
- : builder.credentials;
- }
- String numChannelsValue = parseNumChannels(builder.uri);
- if (numChannelsValue != null) {
- try {
- this.numChannels = Integer.valueOf(numChannelsValue);
- } catch (NumberFormatException e) {
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.INVALID_ARGUMENT,
- "Invalid numChannels value specified: " + numChannelsValue,
- e);
- }
- } else {
- this.numChannels = null;
- }
-
- String projectId = matcher.group(Builder.PROJECT_GROUP);
- if (Builder.DEFAULT_PROJECT_ID_PLACEHOLDER.equalsIgnoreCase(projectId)) {
- projectId = getDefaultProjectId(this.credentials);
- }
- this.projectId = projectId;
-
- this.autocommit = parseAutocommit(this.uri);
- this.readOnly = parseReadOnly(this.uri);
- this.retryAbortsInternally = parseRetryAbortsInternally(this.uri);
- this.statementExecutionInterceptors =
- Collections.unmodifiableList(builder.statementExecutionInterceptors);
- }
-
- @VisibleForTesting
- CredentialsService getCredentialsService() {
- return CredentialsService.INSTANCE;
- }
-
- @VisibleForTesting
- static boolean parseUsePlainText(String uri) {
- String value = parseUriProperty(uri, USE_PLAIN_TEXT_PROPERTY_NAME);
- return value != null ? Boolean.valueOf(value) : DEFAULT_USE_PLAIN_TEXT;
- }
-
- @VisibleForTesting
- static boolean parseAutocommit(String uri) {
- String value = parseUriProperty(uri, AUTOCOMMIT_PROPERTY_NAME);
- return value != null ? Boolean.valueOf(value) : DEFAULT_AUTOCOMMIT;
- }
-
- @VisibleForTesting
- static boolean parseReadOnly(String uri) {
- String value = parseUriProperty(uri, READONLY_PROPERTY_NAME);
- return value != null ? Boolean.valueOf(value) : DEFAULT_READONLY;
- }
-
- @VisibleForTesting
- static boolean parseRetryAbortsInternally(String uri) {
- String value = parseUriProperty(uri, RETRY_ABORTS_INTERNALLY_PROPERTY_NAME);
- return value != null ? Boolean.valueOf(value) : DEFAULT_RETRY_ABORTS_INTERNALLY;
- }
-
- @VisibleForTesting
- static String parseCredentials(String uri) {
- String value = parseUriProperty(uri, CREDENTIALS_PROPERTY_NAME);
- return value != null ? value : DEFAULT_CREDENTIALS;
- }
-
- @VisibleForTesting
- static String parseOAuthToken(String uri) {
- String value = parseUriProperty(uri, OAUTH_TOKEN_PROPERTY_NAME);
- return value != null ? value : DEFAULT_OAUTH_TOKEN;
- }
-
- @VisibleForTesting
- static String parseNumChannels(String uri) {
- String value = parseUriProperty(uri, NUM_CHANNELS_PROPERTY_NAME);
- return value != null ? value : DEFAULT_NUM_CHANNELS;
- }
-
- @VisibleForTesting
- static String parseUserAgent(String uri) {
- String value = parseUriProperty(uri, USER_AGENT_PROPERTY_NAME);
- return value != null ? value : DEFAULT_USER_AGENT;
- }
-
- @VisibleForTesting
- static String parseOptimizerVersion(String uri) {
- String value = parseUriProperty(uri, OPTIMIZER_VERSION_PROPERTY_NAME);
- return value != null ? value : DEFAULT_OPTIMIZER_VERSION;
- }
-
- @VisibleForTesting
- static String parseUriProperty(String uri, String property) {
- Pattern pattern = Pattern.compile(String.format("(?is)(?:;|\\?)%s=(.*?)(?:;|$)", property));
- Matcher matcher = pattern.matcher(uri);
- if (matcher.find() && matcher.groupCount() == 1) {
- return matcher.group(1);
- }
- return null;
- }
-
- /** Check that only valid properties have been specified. */
- @VisibleForTesting
- static void checkValidProperties(String uri) {
- String invalidProperties = "";
- List properties = parseProperties(uri);
- for (String property : properties) {
- if (!INTERNAL_VALID_PROPERTIES.contains(ConnectionProperty.createEmptyProperty(property))) {
- if (invalidProperties.length() > 0) {
- invalidProperties = invalidProperties + ", ";
- }
- invalidProperties = invalidProperties + property;
- }
- }
- Preconditions.checkArgument(
- invalidProperties.isEmpty(),
- "Invalid properties found in connection URI: " + invalidProperties.toString());
- }
-
- @VisibleForTesting
- static List parseProperties(String uri) {
- Pattern pattern = Pattern.compile("(?is)(?:\\?|;)(?.*?)=(?:.*?)");
- Matcher matcher = pattern.matcher(uri);
- List res = new ArrayList<>();
- while (matcher.find() && matcher.group("PROPERTY") != null) {
- res.add(matcher.group("PROPERTY"));
- }
- return res;
- }
-
- /**
- * Create a new {@link Connection} from this {@link ConnectionOptions}. Calling this method
- * multiple times for the same {@link ConnectionOptions} will return multiple instances of {@link
- * Connection}s to the same database.
- *
- * @return a new {@link Connection} to the database referenced by this {@link ConnectionOptions}
- */
- public Connection getConnection() {
- return new ConnectionImpl(this);
- }
-
- /** The URI of this {@link ConnectionOptions} */
- public String getUri() {
- return uri;
- }
-
- /** The credentials URL of this {@link ConnectionOptions} */
- public String getCredentialsUrl() {
- return credentialsUrl;
- }
-
- /** The number of channels to use for the connection. */
- public Integer getNumChannels() {
- return numChannels;
- }
-
- /** The host and port number that this {@link ConnectionOptions} will connect to */
- public String getHost() {
- return host;
- }
-
- /** The Google Project ID that this {@link ConnectionOptions} will connect to */
- public String getProjectId() {
- return projectId;
- }
-
- /** The Spanner Instance ID that this {@link ConnectionOptions} will connect to */
- public String getInstanceId() {
- return instanceId;
- }
-
- /** The Spanner database name that this {@link ConnectionOptions} will connect to */
- public String getDatabaseName() {
- return databaseName;
- }
-
- /** The Spanner {@link DatabaseId} that this {@link ConnectionOptions} will connect to */
- public DatabaseId getDatabaseId() {
- Preconditions.checkState(projectId != null, "Project ID is not specified");
- Preconditions.checkState(instanceId != null, "Instance ID is not specified");
- Preconditions.checkState(databaseName != null, "Database name is not specified");
- return DatabaseId.of(projectId, instanceId, databaseName);
- }
-
- /**
- * The {@link Credentials} of this {@link ConnectionOptions}. This is either the credentials
- * specified in the credentialsUrl or the default Google application credentials
- */
- public Credentials getCredentials() {
- return credentials;
- }
-
- /** The initial autocommit value for connections created by this {@link ConnectionOptions} */
- public boolean isAutocommit() {
- return autocommit;
- }
-
- /** The initial readonly value for connections created by this {@link ConnectionOptions} */
- public boolean isReadOnly() {
- return readOnly;
- }
-
- /**
- * The initial retryAbortsInternally value for connections created by this {@link
- * ConnectionOptions}
- */
- public boolean isRetryAbortsInternally() {
- return retryAbortsInternally;
- }
-
- /** Use http instead of https. Only valid for (local) test servers. */
- boolean isUsePlainText() {
- return usePlainText;
- }
-
- /**
- * The (custom) user agent string to use for this connection. If null
, then the
- * default JDBC user agent string will be used.
- */
- String getUserAgent() {
- return userAgent;
- }
-
- /** The {@link QueryOptions} to use for the connection. */
- QueryOptions getQueryOptions() {
- return queryOptions;
- }
-
- /** Interceptors that should be executed after each statement */
- List getStatementExecutionInterceptors() {
- return statementExecutionInterceptors;
- }
-
- @Override
- public String toString() {
- return getUri();
- }
-}
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/ConnectionPreconditions.java b/src/main/java/com/google/cloud/spanner/jdbc/ConnectionPreconditions.java
deleted file mode 100644
index 02f39533..00000000
--- a/src/main/java/com/google/cloud/spanner/jdbc/ConnectionPreconditions.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright 2019 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.cloud.spanner.jdbc;
-
-import com.google.cloud.spanner.ErrorCode;
-import com.google.cloud.spanner.SpannerException;
-import com.google.cloud.spanner.SpannerExceptionFactory;
-import javax.annotation.Nullable;
-
-/**
- * Static convenience methods that help a method or constructor in the Connection API to check
- * whether it was invoked correctly.
- */
-class ConnectionPreconditions {
- /**
- * Ensures the truth of an expression involving the state of the calling instance, but not
- * involving any parameters to the calling method.
- *
- * @param expression a boolean expression
- * @param errorMessage the exception message to use if the check fails; will be converted to a
- * string using {@link String#valueOf(Object)}.
- * @throws SpannerException with {@link ErrorCode#FAILED_PRECONDITION} if {@code expression} is
- * false.
- */
- static void checkState(boolean expression, @Nullable Object errorMessage) {
- if (!expression) {
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.FAILED_PRECONDITION, String.valueOf(errorMessage));
- }
- }
-}
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/ConnectionStatementExecutor.java b/src/main/java/com/google/cloud/spanner/jdbc/ConnectionStatementExecutor.java
deleted file mode 100644
index 4de1f3f0..00000000
--- a/src/main/java/com/google/cloud/spanner/jdbc/ConnectionStatementExecutor.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright 2019 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.cloud.spanner.jdbc;
-
-import com.google.cloud.spanner.TimestampBound;
-import com.google.protobuf.Duration;
-
-/**
- * The Cloud Spanner JDBC driver supports a number of client side statements that are interpreted by
- * the driver and that can modify the current state of a connection, or report the current state of
- * a connection. Each of the methods in this interface correspond with one such client side
- * statement.
- *
- * The methods in this interface are called by the different {@link ClientSideStatement}s. These
- * method calls are then forwarded into the appropriate method of a {@link Connection} instance.
- *
- *
The client side statements are defined in the ClientSideStatements.json file.
- */
-interface ConnectionStatementExecutor {
-
- StatementResult statementSetAutocommit(Boolean autocommit);
-
- StatementResult statementShowAutocommit();
-
- StatementResult statementSetReadOnly(Boolean readOnly);
-
- StatementResult statementShowReadOnly();
-
- StatementResult statementSetRetryAbortsInternally(Boolean retryAbortsInternally);
-
- StatementResult statementShowRetryAbortsInternally();
-
- StatementResult statementSetAutocommitDmlMode(AutocommitDmlMode mode);
-
- StatementResult statementShowAutocommitDmlMode();
-
- StatementResult statementSetStatementTimeout(Duration duration);
-
- StatementResult statementShowStatementTimeout();
-
- StatementResult statementShowReadTimestamp();
-
- StatementResult statementShowCommitTimestamp();
-
- StatementResult statementSetReadOnlyStaleness(TimestampBound staleness);
-
- StatementResult statementShowReadOnlyStaleness();
-
- StatementResult statementSetOptimizerVersion(String optimizerVersion);
-
- StatementResult statementShowOptimizerVersion();
-
- StatementResult statementBeginTransaction();
-
- StatementResult statementCommit();
-
- StatementResult statementRollback();
-
- StatementResult statementSetTransactionMode(TransactionMode mode);
-
- StatementResult statementStartBatchDdl();
-
- StatementResult statementStartBatchDml();
-
- StatementResult statementRunBatch();
-
- StatementResult statementAbortBatch();
-}
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/ConnectionStatementExecutorImpl.java b/src/main/java/com/google/cloud/spanner/jdbc/ConnectionStatementExecutorImpl.java
deleted file mode 100644
index c8aa40d7..00000000
--- a/src/main/java/com/google/cloud/spanner/jdbc/ConnectionStatementExecutorImpl.java
+++ /dev/null
@@ -1,247 +0,0 @@
-/*
- * Copyright 2019 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.cloud.spanner.jdbc;
-
-import static com.google.cloud.spanner.jdbc.StatementResult.ClientSideStatementType.ABORT_BATCH;
-import static com.google.cloud.spanner.jdbc.StatementResult.ClientSideStatementType.BEGIN;
-import static com.google.cloud.spanner.jdbc.StatementResult.ClientSideStatementType.COMMIT;
-import static com.google.cloud.spanner.jdbc.StatementResult.ClientSideStatementType.ROLLBACK;
-import static com.google.cloud.spanner.jdbc.StatementResult.ClientSideStatementType.RUN_BATCH;
-import static com.google.cloud.spanner.jdbc.StatementResult.ClientSideStatementType.SET_AUTOCOMMIT;
-import static com.google.cloud.spanner.jdbc.StatementResult.ClientSideStatementType.SET_AUTOCOMMIT_DML_MODE;
-import static com.google.cloud.spanner.jdbc.StatementResult.ClientSideStatementType.SET_OPTIMIZER_VERSION;
-import static com.google.cloud.spanner.jdbc.StatementResult.ClientSideStatementType.SET_READONLY;
-import static com.google.cloud.spanner.jdbc.StatementResult.ClientSideStatementType.SET_READ_ONLY_STALENESS;
-import static com.google.cloud.spanner.jdbc.StatementResult.ClientSideStatementType.SET_RETRY_ABORTS_INTERNALLY;
-import static com.google.cloud.spanner.jdbc.StatementResult.ClientSideStatementType.SET_STATEMENT_TIMEOUT;
-import static com.google.cloud.spanner.jdbc.StatementResult.ClientSideStatementType.SET_TRANSACTION_MODE;
-import static com.google.cloud.spanner.jdbc.StatementResult.ClientSideStatementType.SHOW_AUTOCOMMIT;
-import static com.google.cloud.spanner.jdbc.StatementResult.ClientSideStatementType.SHOW_AUTOCOMMIT_DML_MODE;
-import static com.google.cloud.spanner.jdbc.StatementResult.ClientSideStatementType.SHOW_COMMIT_TIMESTAMP;
-import static com.google.cloud.spanner.jdbc.StatementResult.ClientSideStatementType.SHOW_OPTIMIZER_VERSION;
-import static com.google.cloud.spanner.jdbc.StatementResult.ClientSideStatementType.SHOW_READONLY;
-import static com.google.cloud.spanner.jdbc.StatementResult.ClientSideStatementType.SHOW_READ_ONLY_STALENESS;
-import static com.google.cloud.spanner.jdbc.StatementResult.ClientSideStatementType.SHOW_READ_TIMESTAMP;
-import static com.google.cloud.spanner.jdbc.StatementResult.ClientSideStatementType.SHOW_RETRY_ABORTS_INTERNALLY;
-import static com.google.cloud.spanner.jdbc.StatementResult.ClientSideStatementType.SHOW_STATEMENT_TIMEOUT;
-import static com.google.cloud.spanner.jdbc.StatementResult.ClientSideStatementType.START_BATCH_DDL;
-import static com.google.cloud.spanner.jdbc.StatementResult.ClientSideStatementType.START_BATCH_DML;
-import static com.google.cloud.spanner.jdbc.StatementResultImpl.noResult;
-import static com.google.cloud.spanner.jdbc.StatementResultImpl.resultSet;
-
-import com.google.cloud.spanner.TimestampBound;
-import com.google.cloud.spanner.jdbc.ReadOnlyStalenessUtil.DurationValueGetter;
-import com.google.common.base.Preconditions;
-import com.google.protobuf.Duration;
-import java.util.concurrent.TimeUnit;
-
-/**
- * The methods in this class are called by the different {@link ClientSideStatement}s. These method
- * calls are then forwarded into a {@link Connection}.
- */
-class ConnectionStatementExecutorImpl implements ConnectionStatementExecutor {
- static final class StatementTimeoutGetter implements DurationValueGetter {
- private final Connection connection;
-
- public StatementTimeoutGetter(Connection connection) {
- this.connection = connection;
- }
-
- @Override
- public long getDuration(TimeUnit unit) {
- return connection.getStatementTimeout(unit);
- }
-
- @Override
- public boolean hasDuration() {
- return connection.hasStatementTimeout();
- }
- }
-
- /** The connection to execute the statements on. */
- private final ConnectionImpl connection;
-
- ConnectionStatementExecutorImpl(ConnectionImpl connection) {
- this.connection = connection;
- }
-
- ConnectionImpl getConnection() {
- return connection;
- }
-
- @Override
- public StatementResult statementSetAutocommit(Boolean autocommit) {
- Preconditions.checkNotNull(autocommit);
- getConnection().setAutocommit(autocommit);
- return noResult(SET_AUTOCOMMIT);
- }
-
- @Override
- public StatementResult statementShowAutocommit() {
- return resultSet("AUTOCOMMIT", getConnection().isAutocommit(), SHOW_AUTOCOMMIT);
- }
-
- @Override
- public StatementResult statementSetReadOnly(Boolean readOnly) {
- Preconditions.checkNotNull(readOnly);
- getConnection().setReadOnly(readOnly);
- return noResult(SET_READONLY);
- }
-
- @Override
- public StatementResult statementShowReadOnly() {
- return StatementResultImpl.resultSet("READONLY", getConnection().isReadOnly(), SHOW_READONLY);
- }
-
- @Override
- public StatementResult statementSetRetryAbortsInternally(Boolean retryAbortsInternally) {
- Preconditions.checkNotNull(retryAbortsInternally);
- getConnection().setRetryAbortsInternally(retryAbortsInternally);
- return noResult(SET_RETRY_ABORTS_INTERNALLY);
- }
-
- @Override
- public StatementResult statementShowRetryAbortsInternally() {
- return StatementResultImpl.resultSet(
- "RETRY_ABORTS_INTERNALLY",
- getConnection().isRetryAbortsInternally(),
- SHOW_RETRY_ABORTS_INTERNALLY);
- }
-
- @Override
- public StatementResult statementSetAutocommitDmlMode(AutocommitDmlMode mode) {
- getConnection().setAutocommitDmlMode(mode);
- return noResult(SET_AUTOCOMMIT_DML_MODE);
- }
-
- @Override
- public StatementResult statementShowAutocommitDmlMode() {
- return resultSet(
- "AUTOCOMMIT_DML_MODE", getConnection().getAutocommitDmlMode(), SHOW_AUTOCOMMIT_DML_MODE);
- }
-
- @Override
- public StatementResult statementSetStatementTimeout(Duration duration) {
- if (duration.getSeconds() == 0L && duration.getNanos() == 0) {
- getConnection().clearStatementTimeout();
- } else {
- TimeUnit unit =
- ReadOnlyStalenessUtil.getAppropriateTimeUnit(
- new ReadOnlyStalenessUtil.DurationGetter(duration));
- getConnection()
- .setStatementTimeout(ReadOnlyStalenessUtil.durationToUnits(duration, unit), unit);
- }
- return noResult(SET_STATEMENT_TIMEOUT);
- }
-
- @Override
- public StatementResult statementShowStatementTimeout() {
- return resultSet(
- "STATEMENT_TIMEOUT",
- getConnection().hasStatementTimeout()
- ? ReadOnlyStalenessUtil.durationToString(new StatementTimeoutGetter(getConnection()))
- : null,
- SHOW_STATEMENT_TIMEOUT);
- }
-
- @Override
- public StatementResult statementShowReadTimestamp() {
- return resultSet(
- "READ_TIMESTAMP", getConnection().getReadTimestampOrNull(), SHOW_READ_TIMESTAMP);
- }
-
- @Override
- public StatementResult statementShowCommitTimestamp() {
- return resultSet(
- "COMMIT_TIMESTAMP", getConnection().getCommitTimestampOrNull(), SHOW_COMMIT_TIMESTAMP);
- }
-
- @Override
- public StatementResult statementSetReadOnlyStaleness(TimestampBound staleness) {
- getConnection().setReadOnlyStaleness(staleness);
- return noResult(SET_READ_ONLY_STALENESS);
- }
-
- @Override
- public StatementResult statementShowReadOnlyStaleness() {
- TimestampBound staleness = getConnection().getReadOnlyStaleness();
- return resultSet(
- "READ_ONLY_STALENESS",
- ReadOnlyStalenessUtil.timestampBoundToString(staleness),
- SHOW_READ_ONLY_STALENESS);
- }
-
- @Override
- public StatementResult statementSetOptimizerVersion(String optimizerVersion) {
- getConnection().setOptimizerVersion(optimizerVersion);
- return noResult(SET_OPTIMIZER_VERSION);
- }
-
- @Override
- public StatementResult statementShowOptimizerVersion() {
- return resultSet(
- "OPTIMIZER_VERSION", getConnection().getOptimizerVersion(), SHOW_OPTIMIZER_VERSION);
- }
-
- @Override
- public StatementResult statementBeginTransaction() {
- getConnection().beginTransaction();
- return noResult(BEGIN);
- }
-
- @Override
- public StatementResult statementCommit() {
- getConnection().commit();
- return noResult(COMMIT);
- }
-
- @Override
- public StatementResult statementRollback() {
- getConnection().rollback();
- return noResult(ROLLBACK);
- }
-
- @Override
- public StatementResult statementSetTransactionMode(TransactionMode mode) {
- getConnection().setTransactionMode(mode);
- return noResult(SET_TRANSACTION_MODE);
- }
-
- @Override
- public StatementResult statementStartBatchDdl() {
- getConnection().startBatchDdl();
- return noResult(START_BATCH_DDL);
- }
-
- @Override
- public StatementResult statementStartBatchDml() {
- getConnection().startBatchDml();
- return noResult(START_BATCH_DML);
- }
-
- @Override
- public StatementResult statementRunBatch() {
- long[] updateCounts = getConnection().runBatch();
- return resultSet("UPDATE_COUNTS", updateCounts, RUN_BATCH);
- }
-
- @Override
- public StatementResult statementAbortBatch() {
- getConnection().abortBatch();
- return noResult(ABORT_BATCH);
- }
-}
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/CredentialsService.java b/src/main/java/com/google/cloud/spanner/jdbc/CredentialsService.java
deleted file mode 100644
index 1bb314e7..00000000
--- a/src/main/java/com/google/cloud/spanner/jdbc/CredentialsService.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright 2019 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.cloud.spanner.jdbc;
-
-import com.google.auth.oauth2.GoogleCredentials;
-import com.google.cloud.spanner.ErrorCode;
-import com.google.cloud.spanner.SpannerException;
-import com.google.cloud.spanner.SpannerExceptionFactory;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Preconditions;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
-/** Service class for getting credentials from key files. */
-class CredentialsService {
- static final String GCS_NOT_SUPPORTED_MSG =
- "Credentials that is stored on Google Cloud Storage is no longer supported. Download the credentials to a local file and reference the local file in the connection URL.";
- static final CredentialsService INSTANCE = new CredentialsService();
-
- CredentialsService() {}
-
- /**
- * Create credentials from the given URL pointing to a credentials json file. This may be a local
- * file or a file on Google Cloud Storage. Credentials on Google Cloud Storage can only be used if
- * the application is running in an environment where application default credentials have been
- * set.
- *
- * @param credentialsUrl The URL of the credentials file to read. If null
, then this
- * method will return the application default credentials of the environment.
- * @return the {@link GoogleCredentials} object pointed to by the URL.
- * @throws SpannerException If the URL does not point to a valid credentials file, or if the file
- * cannot be accessed.
- */
- GoogleCredentials createCredentials(String credentialsUrl) {
- try {
- if (credentialsUrl == null) {
- return internalGetApplicationDefault();
- } else {
- return getCredentialsFromUrl(credentialsUrl);
- }
- } catch (IOException e) {
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.INVALID_ARGUMENT, "Invalid credentials path specified", e);
- }
- }
-
- @VisibleForTesting
- GoogleCredentials internalGetApplicationDefault() throws IOException {
- return GoogleCredentials.getApplicationDefault();
- }
-
- private GoogleCredentials getCredentialsFromUrl(String credentialsUrl) throws IOException {
- Preconditions.checkNotNull(credentialsUrl);
- Preconditions.checkArgument(
- credentialsUrl.length() > 0, "credentialsUrl may not be an empty string");
- if (credentialsUrl.startsWith("gs://")) {
- throw new IOException(GCS_NOT_SUPPORTED_MSG);
- } else {
- return getCredentialsFromLocalFile(credentialsUrl);
- }
- }
-
- private GoogleCredentials getCredentialsFromLocalFile(String filePath) throws IOException {
- File credentialsFile = new File(filePath);
- if (!credentialsFile.isFile()) {
- throw new IOException(
- String.format("Error reading credential file %s: File does not exist", filePath));
- }
- try (InputStream credentialsStream = new FileInputStream(credentialsFile)) {
- return GoogleCredentials.fromStream(credentialsStream);
- }
- }
-}
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/DdlBatch.java b/src/main/java/com/google/cloud/spanner/jdbc/DdlBatch.java
deleted file mode 100644
index 3431b296..00000000
--- a/src/main/java/com/google/cloud/spanner/jdbc/DdlBatch.java
+++ /dev/null
@@ -1,303 +0,0 @@
-/*
- * Copyright 2019 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.cloud.spanner.jdbc;
-
-import com.google.api.gax.longrunning.OperationFuture;
-import com.google.cloud.Timestamp;
-import com.google.cloud.spanner.DatabaseClient;
-import com.google.cloud.spanner.ErrorCode;
-import com.google.cloud.spanner.Mutation;
-import com.google.cloud.spanner.Options.QueryOption;
-import com.google.cloud.spanner.ResultSet;
-import com.google.cloud.spanner.SpannerException;
-import com.google.cloud.spanner.SpannerExceptionFactory;
-import com.google.cloud.spanner.Statement;
-import com.google.cloud.spanner.jdbc.ConnectionImpl.InternalMetadataQuery;
-import com.google.cloud.spanner.jdbc.StatementParser.ParsedStatement;
-import com.google.cloud.spanner.jdbc.StatementParser.StatementType;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Preconditions;
-import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
-import org.apache.commons.lang3.ArrayUtils;
-
-/**
- * {@link UnitOfWork} that is used when a DDL batch is started. These batches only accept DDL
- * statements. All DDL statements are buffered locally and sent to Spanner when runBatch() is
- * called. Running a {@link DdlBatch} is not an atomic operation. If the execution fails, then some
- * (possibly empty) prefix of the statements in the batch have been successfully applied to the
- * database, and the others have not. Note that the statements that succeed may not all happen at
- * the same time, but they will always happen in order.
- */
-class DdlBatch extends AbstractBaseUnitOfWork {
- private final DdlClient ddlClient;
- private final DatabaseClient dbClient;
- private final List statements = new ArrayList<>();
- private UnitOfWorkState state = UnitOfWorkState.STARTED;
-
- static class Builder extends AbstractBaseUnitOfWork.Builder {
- private DdlClient ddlClient;
- private DatabaseClient dbClient;
-
- private Builder() {}
-
- Builder setDdlClient(DdlClient client) {
- Preconditions.checkNotNull(client);
- this.ddlClient = client;
- return this;
- }
-
- Builder setDatabaseClient(DatabaseClient client) {
- Preconditions.checkNotNull(client);
- this.dbClient = client;
- return this;
- }
-
- @Override
- DdlBatch build() {
- Preconditions.checkState(ddlClient != null, "No DdlClient specified");
- Preconditions.checkState(dbClient != null, "No DbClient specified");
- return new DdlBatch(this);
- }
- }
-
- static Builder newBuilder() {
- return new Builder();
- }
-
- private DdlBatch(Builder builder) {
- super(builder);
- this.ddlClient = builder.ddlClient;
- this.dbClient = builder.dbClient;
- }
-
- @Override
- public Type getType() {
- return Type.BATCH;
- }
-
- @Override
- public UnitOfWorkState getState() {
- return this.state;
- }
-
- @Override
- public boolean isActive() {
- return getState().isActive();
- }
-
- @Override
- public boolean isReadOnly() {
- return false;
- }
-
- @Override
- public ResultSet executeQuery(
- final ParsedStatement statement, AnalyzeMode analyzeMode, QueryOption... options) {
- if (options != null) {
- for (int i = 0; i < options.length; i++) {
- if (options[i] instanceof InternalMetadataQuery) {
- Preconditions.checkNotNull(statement);
- Preconditions.checkArgument(statement.isQuery(), "Statement is not a query");
- Preconditions.checkArgument(
- analyzeMode == AnalyzeMode.NONE, "Analyze is not allowed for DDL batch");
- // Queries marked with internal metadata queries are allowed during a DDL batch.
- // These can only be generated by library internal methods and may be used to check
- // whether a database object such as table or an index exists.
- final QueryOption[] internalOptions = ArrayUtils.remove(options, i);
- Callable callable =
- new Callable() {
- @Override
- public ResultSet call() throws Exception {
- return DirectExecuteResultSet.ofResultSet(
- dbClient.singleUse().executeQuery(statement.getStatement(), internalOptions));
- }
- };
- return asyncExecuteStatement(statement, callable);
- }
- }
- }
- // Queries are by default not allowed on DDL batches.
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.FAILED_PRECONDITION, "Executing queries is not allowed for DDL batches.");
- }
-
- @Override
- public Timestamp getReadTimestamp() {
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.FAILED_PRECONDITION, "There is no read timestamp available for DDL batches.");
- }
-
- @Override
- public Timestamp getReadTimestampOrNull() {
- return null;
- }
-
- @Override
- public Timestamp getCommitTimestamp() {
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.FAILED_PRECONDITION, "There is no commit timestamp available for DDL batches.");
- }
-
- @Override
- public Timestamp getCommitTimestampOrNull() {
- return null;
- }
-
- @Override
- public void executeDdl(ParsedStatement ddl) {
- ConnectionPreconditions.checkState(
- state == UnitOfWorkState.STARTED,
- "The batch is no longer active and cannot be used for further statements");
- Preconditions.checkArgument(
- ddl.getType() == StatementType.DDL,
- "Only DDL statements are allowed. \""
- + ddl.getSqlWithoutComments()
- + "\" is not a DDL-statement.");
- statements.add(ddl.getSqlWithoutComments());
- }
-
- @Override
- public long executeUpdate(ParsedStatement update) {
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.FAILED_PRECONDITION, "Executing updates is not allowed for DDL batches.");
- }
-
- @Override
- public long[] executeBatchUpdate(Iterable updates) {
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.FAILED_PRECONDITION, "Executing batch updates is not allowed for DDL batches.");
- }
-
- @Override
- public void write(Mutation mutation) {
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.FAILED_PRECONDITION, "Writing mutations is not allowed for DDL batches.");
- }
-
- @Override
- public void write(Iterable mutations) {
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.FAILED_PRECONDITION, "Writing mutations is not allowed for DDL batches.");
- }
-
- /**
- * Create a {@link ParsedStatement} that we can use as input for the generic execute method when
- * the {@link #runBatch()} method is executed. This method uses the generic execute method that
- * allows statements to be cancelled and to timeout, which requires the input to be a {@link
- * ParsedStatement}.
- */
- private static final ParsedStatement RUN_BATCH =
- StatementParser.INSTANCE.parse(Statement.of("RUN BATCH"));
-
- @Override
- public long[] runBatch() {
- ConnectionPreconditions.checkState(
- state == UnitOfWorkState.STARTED, "The batch is no longer active and cannot be ran");
- try {
- if (!statements.isEmpty()) {
- // create a statement that can be passed in to the execute method
- Callable callable =
- new Callable() {
- @Override
- public UpdateDatabaseDdlMetadata call() throws Exception {
- OperationFuture operation =
- ddlClient.executeDdl(statements);
- try {
- // Wait until the operation has finished.
- operation.get();
- // Return metadata.
- return operation.getMetadata().get();
- } catch (ExecutionException e) {
- SpannerException spannerException = extractSpannerCause(e);
- long[] updateCounts = extractUpdateCounts(operation.getMetadata().get());
- throw SpannerExceptionFactory.newSpannerBatchUpdateException(
- spannerException == null
- ? ErrorCode.UNKNOWN
- : spannerException.getErrorCode(),
- e.getMessage(),
- updateCounts);
- } catch (InterruptedException e) {
- long[] updateCounts = extractUpdateCounts(operation.getMetadata().get());
- throw SpannerExceptionFactory.newSpannerBatchUpdateException(
- ErrorCode.CANCELLED, e.getMessage(), updateCounts);
- }
- }
- };
- asyncExecuteStatement(RUN_BATCH, callable);
- }
- this.state = UnitOfWorkState.RAN;
- long[] updateCounts = new long[statements.size()];
- Arrays.fill(updateCounts, 1L);
- return updateCounts;
- } catch (SpannerException e) {
- this.state = UnitOfWorkState.RUN_FAILED;
- throw e;
- }
- }
-
- private SpannerException extractSpannerCause(ExecutionException e) {
- Throwable cause = e.getCause();
- Set causes = new HashSet<>();
- while (cause != null && !causes.contains(cause)) {
- if (cause instanceof SpannerException) {
- return (SpannerException) cause;
- }
- causes.add(cause);
- cause = cause.getCause();
- }
- return null;
- }
-
- @VisibleForTesting
- long[] extractUpdateCounts(UpdateDatabaseDdlMetadata metadata) {
- long[] updateCounts = new long[metadata.getStatementsCount()];
- for (int i = 0; i < updateCounts.length; i++) {
- if (metadata.getCommitTimestampsCount() > i && metadata.getCommitTimestamps(i) != null) {
- updateCounts[i] = 1L;
- } else {
- updateCounts[i] = 0L;
- }
- }
- return updateCounts;
- }
-
- @Override
- public void abortBatch() {
- ConnectionPreconditions.checkState(
- state == UnitOfWorkState.STARTED, "The batch is no longer active and cannot be aborted.");
- this.state = UnitOfWorkState.ABORTED;
- }
-
- @Override
- public void commit() {
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.FAILED_PRECONDITION, "Commit is not allowed for DDL batches.");
- }
-
- @Override
- public void rollback() {
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.FAILED_PRECONDITION, "Rollback is not allowed for DDL batches.");
- }
-}
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/DdlClient.java b/src/main/java/com/google/cloud/spanner/jdbc/DdlClient.java
deleted file mode 100644
index 5f921d7b..00000000
--- a/src/main/java/com/google/cloud/spanner/jdbc/DdlClient.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright 2019 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.cloud.spanner.jdbc;
-
-import com.google.api.gax.longrunning.OperationFuture;
-import com.google.cloud.spanner.DatabaseAdminClient;
-import com.google.common.base.Preconditions;
-import com.google.common.base.Strings;
-import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata;
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * Convenience class for executing Data Definition Language statements on transactions that support
- * DDL statements, i.e. DdlBatchTransaction and SingleUseTransaction.
- */
-class DdlClient {
- private final DatabaseAdminClient dbAdminClient;
- private final String instanceId;
- private final String databaseName;
-
- static class Builder {
- private DatabaseAdminClient dbAdminClient;
- private String instanceId;
- private String databaseName;
-
- private Builder() {}
-
- Builder setDatabaseAdminClient(DatabaseAdminClient client) {
- Preconditions.checkNotNull(client);
- this.dbAdminClient = client;
- return this;
- }
-
- Builder setInstanceId(String instanceId) {
- Preconditions.checkArgument(
- !Strings.isNullOrEmpty(instanceId), "Empty instanceId is not allowed");
- this.instanceId = instanceId;
- return this;
- }
-
- Builder setDatabaseName(String name) {
- Preconditions.checkArgument(
- !Strings.isNullOrEmpty(name), "Empty database name is not allowed");
- this.databaseName = name;
- return this;
- }
-
- DdlClient build() {
- Preconditions.checkState(dbAdminClient != null, "No DatabaseAdminClient specified");
- Preconditions.checkState(!Strings.isNullOrEmpty(instanceId), "No InstanceId specified");
- Preconditions.checkArgument(
- !Strings.isNullOrEmpty(databaseName), "No database name specified");
- return new DdlClient(this);
- }
- }
-
- static Builder newBuilder() {
- return new Builder();
- }
-
- private DdlClient(Builder builder) {
- this.dbAdminClient = builder.dbAdminClient;
- this.instanceId = builder.instanceId;
- this.databaseName = builder.databaseName;
- }
-
- /** Execute a single DDL statement. */
- OperationFuture executeDdl(String ddl) {
- return executeDdl(Arrays.asList(ddl));
- }
-
- /** Execute a list of DDL statements as one operation. */
- OperationFuture executeDdl(List statements) {
- return dbAdminClient.updateDatabaseDdl(instanceId, databaseName, statements, null);
- }
-}
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/DirectExecuteResultSet.java b/src/main/java/com/google/cloud/spanner/jdbc/DirectExecuteResultSet.java
deleted file mode 100644
index 90ca0441..00000000
--- a/src/main/java/com/google/cloud/spanner/jdbc/DirectExecuteResultSet.java
+++ /dev/null
@@ -1,365 +0,0 @@
-/*
- * Copyright 2019 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.cloud.spanner.jdbc;
-
-import com.google.cloud.ByteArray;
-import com.google.cloud.Date;
-import com.google.cloud.Timestamp;
-import com.google.cloud.spanner.ResultSet;
-import com.google.cloud.spanner.SpannerException;
-import com.google.cloud.spanner.Struct;
-import com.google.cloud.spanner.Type;
-import com.google.common.base.Preconditions;
-import com.google.spanner.v1.ResultSetStats;
-import java.util.List;
-
-/**
- * {@link ResultSet} implementation used by the Spanner connection API to ensure that the query for
- * a {@link ResultSet} is executed directly when it is created. This is done by calling {@link
- * ResultSet#next()} directly after creation. This ensures that a statement timeout can be applied
- * to the actual query execution. It also ensures that any invalid query will throw an exception at
- * execution instead of the first next() call by a client.
- */
-class DirectExecuteResultSet implements ResultSet {
- private static final String MISSING_NEXT_CALL = "Must be preceded by a next() call";
- private final ResultSet delegate;
- private boolean nextCalledByClient = false;
- private final boolean initialNextResult;
- private boolean nextHasReturnedFalse = false;
-
- /**
- * Creates a new {@link DirectExecuteResultSet} from the given delegate {@link ResultSet}. This
- * automatically executes the query of the given delegate {@link ResultSet} by calling next() on
- * the delegate. The delegate must not have been used (i.e. next() must not have been called on
- * it).
- *
- * @param delegate The underlying {@link ResultSet} for this {@link DirectExecuteResultSet}.
- * @return a {@link DirectExecuteResultSet} that has already executed the query associated with
- * the delegate {@link ResultSet}.
- */
- static DirectExecuteResultSet ofResultSet(ResultSet delegate) {
- return new DirectExecuteResultSet(delegate);
- }
-
- DirectExecuteResultSet(ResultSet delegate) {
- Preconditions.checkNotNull(delegate);
- this.delegate = delegate;
- initialNextResult = delegate.next();
- }
-
- @Override
- public boolean next() throws SpannerException {
- if (nextCalledByClient) {
- boolean res = delegate.next();
- nextHasReturnedFalse = !res;
- return res;
- }
- nextCalledByClient = true;
- nextHasReturnedFalse = !initialNextResult;
- return initialNextResult;
- }
-
- @Override
- public Struct getCurrentRowAsStruct() {
- Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL);
- return delegate.getCurrentRowAsStruct();
- }
-
- @Override
- public void close() {
- delegate.close();
- }
-
- @Override
- public ResultSetStats getStats() {
- if (nextHasReturnedFalse) {
- return delegate.getStats();
- }
- return null;
- }
-
- @Override
- public Type getType() {
- Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL);
- return delegate.getType();
- }
-
- @Override
- public int getColumnCount() {
- Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL);
- return delegate.getColumnCount();
- }
-
- @Override
- public int getColumnIndex(String columnName) {
- Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL);
- return delegate.getColumnIndex(columnName);
- }
-
- @Override
- public Type getColumnType(int columnIndex) {
- Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL);
- return delegate.getColumnType(columnIndex);
- }
-
- @Override
- public Type getColumnType(String columnName) {
- Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL);
- return delegate.getColumnType(columnName);
- }
-
- @Override
- public boolean isNull(int columnIndex) {
- Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL);
- return delegate.isNull(columnIndex);
- }
-
- @Override
- public boolean isNull(String columnName) {
- Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL);
- return delegate.isNull(columnName);
- }
-
- @Override
- public boolean getBoolean(int columnIndex) {
- Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL);
- return delegate.getBoolean(columnIndex);
- }
-
- @Override
- public boolean getBoolean(String columnName) {
- Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL);
- return delegate.getBoolean(columnName);
- }
-
- @Override
- public long getLong(int columnIndex) {
- Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL);
- return delegate.getLong(columnIndex);
- }
-
- @Override
- public long getLong(String columnName) {
- Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL);
- return delegate.getLong(columnName);
- }
-
- @Override
- public double getDouble(int columnIndex) {
- Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL);
- return delegate.getDouble(columnIndex);
- }
-
- @Override
- public double getDouble(String columnName) {
- Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL);
- return delegate.getDouble(columnName);
- }
-
- @Override
- public String getString(int columnIndex) {
- Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL);
- return delegate.getString(columnIndex);
- }
-
- @Override
- public String getString(String columnName) {
- Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL);
- return delegate.getString(columnName);
- }
-
- @Override
- public ByteArray getBytes(int columnIndex) {
- Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL);
- return delegate.getBytes(columnIndex);
- }
-
- @Override
- public ByteArray getBytes(String columnName) {
- Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL);
- return delegate.getBytes(columnName);
- }
-
- @Override
- public Timestamp getTimestamp(int columnIndex) {
- Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL);
- return delegate.getTimestamp(columnIndex);
- }
-
- @Override
- public Timestamp getTimestamp(String columnName) {
- Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL);
- return delegate.getTimestamp(columnName);
- }
-
- @Override
- public Date getDate(int columnIndex) {
- Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL);
- return delegate.getDate(columnIndex);
- }
-
- @Override
- public Date getDate(String columnName) {
- Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL);
- return delegate.getDate(columnName);
- }
-
- @Override
- public boolean[] getBooleanArray(int columnIndex) {
- Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL);
- return delegate.getBooleanArray(columnIndex);
- }
-
- @Override
- public boolean[] getBooleanArray(String columnName) {
- Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL);
- return delegate.getBooleanArray(columnName);
- }
-
- @Override
- public List getBooleanList(int columnIndex) {
- Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL);
- return delegate.getBooleanList(columnIndex);
- }
-
- @Override
- public List getBooleanList(String columnName) {
- Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL);
- return delegate.getBooleanList(columnName);
- }
-
- @Override
- public long[] getLongArray(int columnIndex) {
- Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL);
- return delegate.getLongArray(columnIndex);
- }
-
- @Override
- public long[] getLongArray(String columnName) {
- Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL);
- return delegate.getLongArray(columnName);
- }
-
- @Override
- public List getLongList(int columnIndex) {
- Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL);
- return delegate.getLongList(columnIndex);
- }
-
- @Override
- public List getLongList(String columnName) {
- Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL);
- return delegate.getLongList(columnName);
- }
-
- @Override
- public double[] getDoubleArray(int columnIndex) {
- Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL);
- return delegate.getDoubleArray(columnIndex);
- }
-
- @Override
- public double[] getDoubleArray(String columnName) {
- Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL);
- return delegate.getDoubleArray(columnName);
- }
-
- @Override
- public List getDoubleList(int columnIndex) {
- Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL);
- return delegate.getDoubleList(columnIndex);
- }
-
- @Override
- public List getDoubleList(String columnName) {
- Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL);
- return delegate.getDoubleList(columnName);
- }
-
- @Override
- public List getStringList(int columnIndex) {
- Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL);
- return delegate.getStringList(columnIndex);
- }
-
- @Override
- public List getStringList(String columnName) {
- Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL);
- return delegate.getStringList(columnName);
- }
-
- @Override
- public List getBytesList(int columnIndex) {
- Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL);
- return delegate.getBytesList(columnIndex);
- }
-
- @Override
- public List getBytesList(String columnName) {
- Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL);
- return delegate.getBytesList(columnName);
- }
-
- @Override
- public List getTimestampList(int columnIndex) {
- Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL);
- return delegate.getTimestampList(columnIndex);
- }
-
- @Override
- public List getTimestampList(String columnName) {
- Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL);
- return delegate.getTimestampList(columnName);
- }
-
- @Override
- public List getDateList(int columnIndex) {
- Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL);
- return delegate.getDateList(columnIndex);
- }
-
- @Override
- public List getDateList(String columnName) {
- Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL);
- return delegate.getDateList(columnName);
- }
-
- @Override
- public List getStructList(int columnIndex) {
- Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL);
- return delegate.getStructList(columnIndex);
- }
-
- @Override
- public List getStructList(String columnName) {
- Preconditions.checkState(nextCalledByClient, MISSING_NEXT_CALL);
- return delegate.getStructList(columnName);
- }
-
- @Override
- public boolean equals(Object o) {
- if (!(o instanceof DirectExecuteResultSet)) {
- return false;
- }
- return ((DirectExecuteResultSet) o).delegate.equals(delegate);
- }
-
- @Override
- public int hashCode() {
- return delegate.hashCode();
- }
-}
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/DmlBatch.java b/src/main/java/com/google/cloud/spanner/jdbc/DmlBatch.java
deleted file mode 100644
index 55da9fb1..00000000
--- a/src/main/java/com/google/cloud/spanner/jdbc/DmlBatch.java
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
- * Copyright 2019 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.cloud.spanner.jdbc;
-
-import com.google.cloud.Timestamp;
-import com.google.cloud.spanner.ErrorCode;
-import com.google.cloud.spanner.Mutation;
-import com.google.cloud.spanner.Options.QueryOption;
-import com.google.cloud.spanner.ResultSet;
-import com.google.cloud.spanner.SpannerException;
-import com.google.cloud.spanner.SpannerExceptionFactory;
-import com.google.cloud.spanner.jdbc.StatementParser.ParsedStatement;
-import com.google.cloud.spanner.jdbc.StatementParser.StatementType;
-import com.google.common.base.Preconditions;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * {@link UnitOfWork} that is used when a DML batch is started. These batches only accept DML
- * statements. All DML statements are buffered locally and sent to Spanner when runBatch() is
- * called.
- */
-class DmlBatch extends AbstractBaseUnitOfWork {
- private final UnitOfWork transaction;
- private final List statements = new ArrayList<>();
- private UnitOfWorkState state = UnitOfWorkState.STARTED;
-
- static class Builder extends AbstractBaseUnitOfWork.Builder {
- private UnitOfWork transaction;
-
- private Builder() {}
-
- Builder setTransaction(UnitOfWork transaction) {
- Preconditions.checkNotNull(transaction);
- this.transaction = transaction;
- return this;
- }
-
- @Override
- DmlBatch build() {
- Preconditions.checkState(transaction != null, "No transaction specified");
- return new DmlBatch(this);
- }
- }
-
- static Builder newBuilder() {
- return new Builder();
- }
-
- private DmlBatch(Builder builder) {
- super(builder);
- this.transaction = builder.transaction;
- }
-
- @Override
- public Type getType() {
- return Type.BATCH;
- }
-
- @Override
- public UnitOfWorkState getState() {
- return this.state;
- }
-
- @Override
- public boolean isActive() {
- return getState().isActive();
- }
-
- @Override
- public boolean isReadOnly() {
- return false;
- }
-
- @Override
- public ResultSet executeQuery(
- ParsedStatement statement, AnalyzeMode analyzeMode, QueryOption... options) {
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.FAILED_PRECONDITION, "Executing queries is not allowed for DML batches.");
- }
-
- @Override
- public Timestamp getReadTimestamp() {
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.FAILED_PRECONDITION, "There is no read timestamp available for DML batches.");
- }
-
- @Override
- public Timestamp getReadTimestampOrNull() {
- return null;
- }
-
- @Override
- public Timestamp getCommitTimestamp() {
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.FAILED_PRECONDITION, "There is no commit timestamp available for DML batches.");
- }
-
- @Override
- public Timestamp getCommitTimestampOrNull() {
- return null;
- }
-
- @Override
- public void executeDdl(ParsedStatement ddl) {
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.FAILED_PRECONDITION, "Executing DDL statements is not allowed for DML batches.");
- }
-
- @Override
- public long executeUpdate(ParsedStatement update) {
- ConnectionPreconditions.checkState(
- state == UnitOfWorkState.STARTED,
- "The batch is no longer active and cannot be used for further statements");
- Preconditions.checkArgument(
- update.getType() == StatementType.UPDATE,
- "Only DML statements are allowed. \""
- + update.getSqlWithoutComments()
- + "\" is not a DML-statement.");
- statements.add(update);
- return -1L;
- }
-
- @Override
- public long[] executeBatchUpdate(Iterable updates) {
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.FAILED_PRECONDITION, "Executing batch updates is not allowed for DML batches.");
- }
-
- @Override
- public void write(Mutation mutation) {
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.FAILED_PRECONDITION, "Writing mutations is not allowed for DML batches.");
- }
-
- @Override
- public void write(Iterable mutations) {
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.FAILED_PRECONDITION, "Writing mutations is not allowed for DML batches.");
- }
-
- @Override
- public long[] runBatch() {
- ConnectionPreconditions.checkState(
- state == UnitOfWorkState.STARTED, "The batch is no longer active and cannot be ran");
- try {
- long[] res;
- if (statements.isEmpty()) {
- res = new long[0];
- } else {
- res = transaction.executeBatchUpdate(statements);
- }
- this.state = UnitOfWorkState.RAN;
- return res;
- } catch (SpannerException e) {
- this.state = UnitOfWorkState.RUN_FAILED;
- throw e;
- }
- }
-
- @Override
- public void abortBatch() {
- ConnectionPreconditions.checkState(
- state == UnitOfWorkState.STARTED, "The batch is no longer active and cannot be aborted.");
- this.state = UnitOfWorkState.ABORTED;
- }
-
- @Override
- public void commit() {
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.FAILED_PRECONDITION, "Commit is not allowed for DML batches.");
- }
-
- @Override
- public void rollback() {
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.FAILED_PRECONDITION, "Rollback is not allowed for DML batches.");
- }
-}
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/FailedBatchUpdate.java b/src/main/java/com/google/cloud/spanner/jdbc/FailedBatchUpdate.java
deleted file mode 100644
index d96d98a8..00000000
--- a/src/main/java/com/google/cloud/spanner/jdbc/FailedBatchUpdate.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright 2019 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.cloud.spanner.jdbc;
-
-import com.google.cloud.spanner.AbortedException;
-import com.google.cloud.spanner.SpannerBatchUpdateException;
-import com.google.cloud.spanner.SpannerException;
-import com.google.cloud.spanner.SpannerExceptionFactory;
-import com.google.cloud.spanner.Statement;
-import com.google.cloud.spanner.jdbc.ReadWriteTransaction.RetriableStatement;
-import com.google.common.base.Preconditions;
-import java.util.Arrays;
-import java.util.Objects;
-
-/**
- * A batch update that failed with a {@link SpannerException} on a {@link ReadWriteTransaction}. The
- * batch update can be retried if the transaction is aborted, and should throw the same exception
- * during retry as during the original transaction.
- */
-final class FailedBatchUpdate implements RetriableStatement {
- private final ReadWriteTransaction transaction;
- private final SpannerException exception;
- private final Iterable statements;
-
- FailedBatchUpdate(
- ReadWriteTransaction transaction,
- SpannerException exception,
- Iterable statements) {
- Preconditions.checkNotNull(transaction);
- Preconditions.checkNotNull(exception);
- Preconditions.checkNotNull(statements);
- this.transaction = transaction;
- this.exception = exception;
- this.statements = statements;
- }
-
- @Override
- public void retry(AbortedException aborted) throws AbortedException {
- transaction
- .getStatementExecutor()
- .invokeInterceptors(
- ReadWriteTransaction.EXECUTE_BATCH_UPDATE_STATEMENT,
- StatementExecutionStep.RETRY_STATEMENT,
- transaction);
- try {
- transaction.getReadContext().batchUpdate(statements);
- } catch (SpannerBatchUpdateException e) {
- // Check that we got the same exception as in the original transaction.
- if (exception instanceof SpannerBatchUpdateException
- && e.getErrorCode() == exception.getErrorCode()
- && Objects.equals(e.getMessage(), exception.getMessage())) {
- // Check that the returned update counts are equal.
- if (Arrays.equals(
- e.getUpdateCounts(), ((SpannerBatchUpdateException) exception).getUpdateCounts())) {
- return;
- }
- }
- throw SpannerExceptionFactory.newAbortedDueToConcurrentModificationException(aborted, e);
- } catch (SpannerException e) {
- // Check that we got the same exception as in the original transaction.
- if (e.getErrorCode() == exception.getErrorCode()
- && Objects.equals(e.getMessage(), exception.getMessage())) {
- return;
- }
- throw SpannerExceptionFactory.newAbortedDueToConcurrentModificationException(aborted, e);
- }
- throw SpannerExceptionFactory.newAbortedDueToConcurrentModificationException(aborted);
- }
-}
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/FailedQuery.java b/src/main/java/com/google/cloud/spanner/jdbc/FailedQuery.java
deleted file mode 100644
index 4e2a3ecd..00000000
--- a/src/main/java/com/google/cloud/spanner/jdbc/FailedQuery.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright 2019 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.cloud.spanner.jdbc;
-
-import com.google.cloud.spanner.AbortedException;
-import com.google.cloud.spanner.Options.QueryOption;
-import com.google.cloud.spanner.ResultSet;
-import com.google.cloud.spanner.SpannerException;
-import com.google.cloud.spanner.SpannerExceptionFactory;
-import com.google.cloud.spanner.jdbc.ReadWriteTransaction.RetriableStatement;
-import com.google.cloud.spanner.jdbc.StatementParser.ParsedStatement;
-import com.google.common.base.Preconditions;
-import java.util.Objects;
-
-/**
- * A query that failed with a {@link SpannerException} on a {@link ReadWriteTransaction}. The query
- * can be retried if the transaction is aborted, and should throw the same exception during retry as
- * during the original transaction.
- */
-final class FailedQuery implements RetriableStatement {
- private final ReadWriteTransaction transaction;
- private final SpannerException exception;
- private final ParsedStatement statement;
- private final AnalyzeMode analyzeMode;
- private final QueryOption[] options;
-
- FailedQuery(
- ReadWriteTransaction transaction,
- SpannerException exception,
- ParsedStatement statement,
- AnalyzeMode analyzeMode,
- QueryOption... options) {
- Preconditions.checkNotNull(transaction);
- Preconditions.checkNotNull(exception);
- Preconditions.checkNotNull(statement);
- this.transaction = transaction;
- this.exception = exception;
- this.statement = statement;
- this.analyzeMode = analyzeMode;
- this.options = options;
- }
-
- @Override
- public void retry(AbortedException aborted) throws AbortedException {
- transaction
- .getStatementExecutor()
- .invokeInterceptors(statement, StatementExecutionStep.RETRY_STATEMENT, transaction);
- try {
- transaction
- .getStatementExecutor()
- .invokeInterceptors(statement, StatementExecutionStep.RETRY_STATEMENT, transaction);
- try (ResultSet rs =
- DirectExecuteResultSet.ofResultSet(
- transaction.internalExecuteQuery(statement, analyzeMode, options))) {
- // Do nothing with the results, we are only interested in whether the statement throws the
- // same exception as in the original transaction.
- }
- } catch (SpannerException e) {
- // Check that we got the same exception as in the original transaction
- if (e.getErrorCode() == exception.getErrorCode()
- && Objects.equals(e.getMessage(), exception.getMessage())) {
- return;
- }
- throw SpannerExceptionFactory.newAbortedDueToConcurrentModificationException(aborted, e);
- }
- throw SpannerExceptionFactory.newAbortedDueToConcurrentModificationException(aborted);
- }
-}
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/FailedUpdate.java b/src/main/java/com/google/cloud/spanner/jdbc/FailedUpdate.java
deleted file mode 100644
index 1974f0cc..00000000
--- a/src/main/java/com/google/cloud/spanner/jdbc/FailedUpdate.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright 2019 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.cloud.spanner.jdbc;
-
-import com.google.cloud.spanner.AbortedException;
-import com.google.cloud.spanner.SpannerException;
-import com.google.cloud.spanner.SpannerExceptionFactory;
-import com.google.cloud.spanner.jdbc.ReadWriteTransaction.RetriableStatement;
-import com.google.cloud.spanner.jdbc.StatementParser.ParsedStatement;
-import com.google.common.base.Preconditions;
-import java.util.Objects;
-
-/**
- * An update that failed with a {@link SpannerException} on a {@link ReadWriteTransaction}. The
- * update can be retried if the transaction is aborted, and should throw the same exception during
- * retry as during the original transaction.
- */
-final class FailedUpdate implements RetriableStatement {
- private final ReadWriteTransaction transaction;
- private final SpannerException exception;
- private final ParsedStatement statement;
-
- FailedUpdate(
- ReadWriteTransaction transaction, SpannerException exception, ParsedStatement statement) {
- Preconditions.checkNotNull(transaction);
- Preconditions.checkNotNull(exception);
- Preconditions.checkNotNull(statement);
- this.transaction = transaction;
- this.exception = exception;
- this.statement = statement;
- }
-
- @Override
- public void retry(AbortedException aborted) throws AbortedException {
- transaction
- .getStatementExecutor()
- .invokeInterceptors(statement, StatementExecutionStep.RETRY_STATEMENT, transaction);
- try {
- transaction
- .getStatementExecutor()
- .invokeInterceptors(statement, StatementExecutionStep.RETRY_STATEMENT, transaction);
- transaction.getReadContext().executeUpdate(statement.getStatement());
- } catch (SpannerException e) {
- // Check that we got the same exception as in the original transaction.
- if (e.getErrorCode() == exception.getErrorCode()
- && Objects.equals(e.getMessage(), exception.getMessage())) {
- return;
- }
- throw SpannerExceptionFactory.newAbortedDueToConcurrentModificationException(aborted, e);
- }
- throw SpannerExceptionFactory.newAbortedDueToConcurrentModificationException(aborted);
- }
-}
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/JdbcConnection.java b/src/main/java/com/google/cloud/spanner/jdbc/JdbcConnection.java
index 08b2a823..5aefd35b 100644
--- a/src/main/java/com/google/cloud/spanner/jdbc/JdbcConnection.java
+++ b/src/main/java/com/google/cloud/spanner/jdbc/JdbcConnection.java
@@ -16,8 +16,13 @@
package com.google.cloud.spanner.jdbc;
+import com.google.api.client.util.Preconditions;
import com.google.cloud.spanner.Mutation;
import com.google.cloud.spanner.SpannerException;
+import com.google.cloud.spanner.connection.ConnectionOptions;
+import com.google.cloud.spanner.connection.StatementParser;
+import com.google.common.base.Function;
+import com.google.common.collect.Iterators;
import java.sql.Array;
import java.sql.Blob;
import java.sql.Clob;
@@ -383,22 +388,104 @@ public void bufferedWrite(Iterable mutations) throws SQLException {
}
}
+ @SuppressWarnings("deprecation")
+ private static final class JdbcToSpannerTransactionRetryListener
+ implements com.google.cloud.spanner.connection.TransactionRetryListener {
+ private final TransactionRetryListener delegate;
+
+ JdbcToSpannerTransactionRetryListener(TransactionRetryListener delegate) {
+ this.delegate = Preconditions.checkNotNull(delegate);
+ }
+
+ @Override
+ public void retryStarting(
+ com.google.cloud.Timestamp transactionStarted, long transactionId, int retryAttempt) {
+ delegate.retryStarting(transactionStarted, transactionId, retryAttempt);
+ }
+
+ @Override
+ public void retryFinished(
+ com.google.cloud.Timestamp transactionStarted,
+ long transactionId,
+ int retryAttempt,
+ RetryResult result) {
+ delegate.retryFinished(
+ transactionStarted,
+ transactionId,
+ retryAttempt,
+ TransactionRetryListener.RetryResult.valueOf(result.name()));
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof JdbcToSpannerTransactionRetryListener)) {
+ return false;
+ }
+ JdbcToSpannerTransactionRetryListener other = (JdbcToSpannerTransactionRetryListener) o;
+ return this.delegate.equals(other.delegate);
+ }
+
+ @Override
+ public int hashCode() {
+ return delegate.hashCode();
+ }
+ }
+
+ @SuppressWarnings("deprecation")
@Override
public void addTransactionRetryListener(TransactionRetryListener listener) throws SQLException {
checkClosed();
+ getSpannerConnection()
+ .addTransactionRetryListener(new JdbcToSpannerTransactionRetryListener(listener));
+ }
+
+ @Override
+ public void addTransactionRetryListener(
+ com.google.cloud.spanner.connection.TransactionRetryListener listener) throws SQLException {
+ checkClosed();
getSpannerConnection().addTransactionRetryListener(listener);
}
+ @SuppressWarnings("deprecation")
@Override
public boolean removeTransactionRetryListener(TransactionRetryListener listener)
throws SQLException {
checkClosed();
+ return getSpannerConnection()
+ .removeTransactionRetryListener(new JdbcToSpannerTransactionRetryListener(listener));
+ }
+
+ @Override
+ public boolean removeTransactionRetryListener(
+ com.google.cloud.spanner.connection.TransactionRetryListener listener) throws SQLException {
+ checkClosed();
return getSpannerConnection().removeTransactionRetryListener(listener);
}
+ @SuppressWarnings("deprecation")
@Override
public Iterator getTransactionRetryListeners() throws SQLException {
checkClosed();
+ return Iterators.transform(
+ getSpannerConnection().getTransactionRetryListeners(),
+ new Function<
+ com.google.cloud.spanner.connection.TransactionRetryListener,
+ TransactionRetryListener>() {
+ @Override
+ public TransactionRetryListener apply(
+ com.google.cloud.spanner.connection.TransactionRetryListener input) {
+ if (input instanceof JdbcToSpannerTransactionRetryListener) {
+ return ((JdbcToSpannerTransactionRetryListener) input).delegate;
+ }
+ return null;
+ }
+ });
+ }
+
+ @Override
+ public Iterator
+ getTransactionRetryListenersFromConnection() throws SQLException {
+ checkClosed();
return getSpannerConnection().getTransactionRetryListeners();
}
}
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/JdbcConstants.java b/src/main/java/com/google/cloud/spanner/jdbc/JdbcConstants.java
index a8cca8ab..58c9e5bb 100644
--- a/src/main/java/com/google/cloud/spanner/jdbc/JdbcConstants.java
+++ b/src/main/java/com/google/cloud/spanner/jdbc/JdbcConstants.java
@@ -16,7 +16,6 @@
package com.google.cloud.spanner.jdbc;
-import com.google.cloud.spanner.jdbc.StatementResult.ResultType;
import java.sql.ResultSet;
import java.sql.Statement;
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/JdbcDataSource.java b/src/main/java/com/google/cloud/spanner/jdbc/JdbcDataSource.java
index 31a4ad44..72a641cc 100644
--- a/src/main/java/com/google/cloud/spanner/jdbc/JdbcDataSource.java
+++ b/src/main/java/com/google/cloud/spanner/jdbc/JdbcDataSource.java
@@ -16,6 +16,7 @@
package com.google.cloud.spanner.jdbc;
+import com.google.cloud.spanner.connection.ConnectionOptions;
import com.google.rpc.Code;
import java.io.PrintWriter;
import java.sql.Connection;
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/JdbcDatabaseMetaData.java b/src/main/java/com/google/cloud/spanner/jdbc/JdbcDatabaseMetaData.java
index a15ad721..22176595 100644
--- a/src/main/java/com/google/cloud/spanner/jdbc/JdbcDatabaseMetaData.java
+++ b/src/main/java/com/google/cloud/spanner/jdbc/JdbcDatabaseMetaData.java
@@ -23,7 +23,7 @@
import com.google.cloud.spanner.Struct;
import com.google.cloud.spanner.Type;
import com.google.cloud.spanner.Type.StructField;
-import com.google.cloud.spanner.jdbc.ConnectionImpl.InternalMetadataQuery;
+import com.google.cloud.spanner.connection.Connection.InternalMetadataQuery;
import com.google.common.annotations.VisibleForTesting;
import java.io.BufferedReader;
import java.io.InputStream;
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/JdbcDriver.java b/src/main/java/com/google/cloud/spanner/jdbc/JdbcDriver.java
index 229c0a6f..2d83a34d 100644
--- a/src/main/java/com/google/cloud/spanner/jdbc/JdbcDriver.java
+++ b/src/main/java/com/google/cloud/spanner/jdbc/JdbcDriver.java
@@ -19,7 +19,8 @@
import com.google.api.core.InternalApi;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.cloud.spanner.SpannerException;
-import com.google.cloud.spanner.jdbc.ConnectionOptions.ConnectionProperty;
+import com.google.cloud.spanner.connection.ConnectionOptions;
+import com.google.cloud.spanner.connection.ConnectionOptions.ConnectionProperty;
import com.google.rpc.Code;
import java.sql.Connection;
import java.sql.Driver;
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/JdbcPreparedStatement.java b/src/main/java/com/google/cloud/spanner/jdbc/JdbcPreparedStatement.java
index 4d5cfe88..7a0e8423 100644
--- a/src/main/java/com/google/cloud/spanner/jdbc/JdbcPreparedStatement.java
+++ b/src/main/java/com/google/cloud/spanner/jdbc/JdbcPreparedStatement.java
@@ -18,6 +18,7 @@
import com.google.cloud.spanner.Options.QueryOption;
import com.google.cloud.spanner.Statement;
+import com.google.cloud.spanner.connection.StatementParser;
import com.google.cloud.spanner.jdbc.JdbcParameterStore.ParametersInfo;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/JdbcStatement.java b/src/main/java/com/google/cloud/spanner/jdbc/JdbcStatement.java
index e87558e6..b4eaa0d1 100644
--- a/src/main/java/com/google/cloud/spanner/jdbc/JdbcStatement.java
+++ b/src/main/java/com/google/cloud/spanner/jdbc/JdbcStatement.java
@@ -24,6 +24,8 @@
import com.google.cloud.spanner.Struct;
import com.google.cloud.spanner.Type;
import com.google.cloud.spanner.Type.StructField;
+import com.google.cloud.spanner.connection.StatementParser;
+import com.google.cloud.spanner.connection.StatementResult;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.rpc.Code;
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/ReadOnlyStalenessUtil.java b/src/main/java/com/google/cloud/spanner/jdbc/ReadOnlyStalenessUtil.java
deleted file mode 100644
index 29166586..00000000
--- a/src/main/java/com/google/cloud/spanner/jdbc/ReadOnlyStalenessUtil.java
+++ /dev/null
@@ -1,261 +0,0 @@
-/*
- * Copyright 2019 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.cloud.spanner.jdbc;
-
-import com.google.api.client.util.DateTime;
-import com.google.api.client.util.DateTime.SecondsAndNanos;
-import com.google.cloud.Timestamp;
-import com.google.cloud.spanner.ErrorCode;
-import com.google.cloud.spanner.SpannerException;
-import com.google.cloud.spanner.SpannerExceptionFactory;
-import com.google.cloud.spanner.TimestampBound;
-import com.google.cloud.spanner.TimestampBound.Mode;
-import com.google.protobuf.Duration;
-import com.google.protobuf.util.Durations;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Util class for parsing and converting ReadOnlyStaleness values to/from strings. This util is used
- * to parse client side statements and values for read only staleness for read-only transactions on
- * Cloud Spanner.
- */
-class ReadOnlyStalenessUtil {
- /**
- * Parses an RFC3339 date/time value with nanosecond precision and returns this as a {@link
- * Timestamp}.
- */
- static Timestamp parseRfc3339(String str) throws SpannerException {
- try {
- SecondsAndNanos secondsAndNanos = DateTime.parseRfc3339ToSecondsAndNanos(str);
- return Timestamp.ofTimeSecondsAndNanos(
- secondsAndNanos.getSeconds(), secondsAndNanos.getNanos());
- } catch (NumberFormatException e) {
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.INVALID_ARGUMENT, String.format("Invalid timestamp: %s", str), e);
- }
- }
-
- /** The abbreviations for time units that may be used for client side statements. */
- enum TimeUnitAbbreviation {
- NANOSECONDS("ns", TimeUnit.NANOSECONDS),
- MICROSECONDS("us", TimeUnit.MICROSECONDS),
- MILLISECONDS("ms", TimeUnit.MILLISECONDS),
- SECONDS("s", TimeUnit.SECONDS);
-
- private final String abbreviation;
- private final TimeUnit unit;
-
- private TimeUnitAbbreviation(String abbreviation, TimeUnit unit) {
- this.abbreviation = abbreviation;
- this.unit = unit;
- }
-
- String getAbbreviation() {
- return abbreviation;
- }
-
- TimeUnit getUnit() {
- return unit;
- }
- }
-
- /** Get the abbreviation for the given {@link TimeUnit}. */
- static String getTimeUnitAbbreviation(TimeUnit unit) {
- for (TimeUnitAbbreviation abb : TimeUnitAbbreviation.values()) {
- if (abb.unit == unit) return abb.abbreviation;
- }
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.INVALID_ARGUMENT, "Invalid option for time unit: " + unit);
- }
-
- /** Get the {@link TimeUnit} corresponding with the given abbreviation. */
- static TimeUnit parseTimeUnit(String unit) {
- for (TimeUnitAbbreviation abb : TimeUnitAbbreviation.values()) {
- if (abb.abbreviation.equalsIgnoreCase(unit)) return abb.unit;
- }
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.INVALID_ARGUMENT, "Invalid option for time unit: " + unit);
- }
-
- /**
- * Internal interface that is used to generalize getting a time duration from Cloud Spanner
- * read-only staleness settings.
- */
- static interface DurationValueGetter {
- long getDuration(TimeUnit unit);
-
- boolean hasDuration();
- }
-
- static final class GetExactStaleness implements DurationValueGetter {
- private final TimestampBound staleness;
-
- public GetExactStaleness(TimestampBound staleness) {
- this.staleness = staleness;
- }
-
- @Override
- public long getDuration(TimeUnit unit) {
- return staleness.getExactStaleness(unit);
- }
-
- @Override
- public boolean hasDuration() {
- return staleness.getMode() == Mode.EXACT_STALENESS;
- }
- }
-
- static final class MaxStalenessGetter implements DurationValueGetter {
- private final TimestampBound staleness;
-
- public MaxStalenessGetter(TimestampBound staleness) {
- this.staleness = staleness;
- }
-
- @Override
- public long getDuration(TimeUnit unit) {
- return staleness.getMaxStaleness(unit);
- }
-
- @Override
- public boolean hasDuration() {
- return staleness.getMode() == Mode.MAX_STALENESS;
- }
- }
-
- static final class DurationGetter implements DurationValueGetter {
- private final Duration duration;
-
- public DurationGetter(Duration duration) {
- this.duration = duration;
- }
-
- @Override
- public long getDuration(TimeUnit unit) {
- return durationToUnits(duration, unit);
- }
-
- @Override
- public boolean hasDuration() {
- return duration.getNanos() > 0 || duration.getSeconds() > 0L;
- }
- }
-
- /**
- * Converts a {@link TimestampBound} to a human readable string representation.
- *
- * @param staleness The staleness to convert
- * @return a human readable representation of the staleness.
- */
- static String timestampBoundToString(TimestampBound staleness) {
- switch (staleness.getMode()) {
- case STRONG:
- return "STRONG";
- case READ_TIMESTAMP:
- return "READ_TIMESTAMP " + staleness.getReadTimestamp().toString();
- case MIN_READ_TIMESTAMP:
- return "MIN_READ_TIMESTAMP " + staleness.getMinReadTimestamp().toString();
- case EXACT_STALENESS:
- return "EXACT_STALENESS " + durationToString(new GetExactStaleness(staleness));
- case MAX_STALENESS:
- return "MAX_STALENESS " + durationToString(new MaxStalenessGetter(staleness));
- default:
- throw new IllegalStateException("Unknown mode: " + staleness.getMode());
- }
- }
-
- /** The {@link TimeUnit}s that are supported for timeout and staleness durations. */
- static final TimeUnit[] SUPPORTED_UNITS =
- new TimeUnit[] {
- TimeUnit.SECONDS, TimeUnit.MILLISECONDS, TimeUnit.MICROSECONDS, TimeUnit.NANOSECONDS
- };
-
- /**
- * Converts a duration value to a human readable string. The method will search for the most
- * appropriate {@link TimeUnit} to use to represent the value.
- *
- * @param function The function that should be called to get the duration in a specific {@link
- * TimeUnit}.
- * @return a human readable value of the duration.
- */
- static String durationToString(DurationValueGetter function) {
- TimeUnit unit = getAppropriateTimeUnit(function);
- return String.valueOf(function.getDuration(unit)) + getTimeUnitAbbreviation(unit);
- }
-
- /**
- * Calculates the most appropriate {@link TimeUnit} to use to represent the duration that is
- * returned by the given function. The most appropriate {@link TimeUnit} is the unit with the
- * least precision that still retains all information of the given input.
- *
- * @param durationGetter The function that will return the duration in different {@link
- * TimeUnit}s.
- * @return the most appropriate {@link TimeUnit} to represent the duration.
- */
- static TimeUnit getAppropriateTimeUnit(DurationValueGetter durationGetter) {
- int index = 0;
- if (durationGetter.hasDuration()) {
- for (TimeUnit unit : SUPPORTED_UNITS) {
- long duration = durationGetter.getDuration(unit);
- if (index + 1 < SUPPORTED_UNITS.length) {
- if (duration > 0L
- && duration * 1000 == durationGetter.getDuration(SUPPORTED_UNITS[index + 1])) {
- return unit;
- }
- } else {
- // last unit, we have to use this one
- return unit;
- }
- index++;
- }
- throw new IllegalStateException("Unsupported duration");
- }
- return TimeUnit.NANOSECONDS;
- }
-
- /** Converts a value into a duration using the specified {@link TimeUnit}. */
- static Duration createDuration(long num, TimeUnit units) {
- switch (units) {
- case NANOSECONDS:
- return Durations.fromNanos(num);
- case MICROSECONDS:
- return Durations.fromMicros(num);
- case MILLISECONDS:
- return Durations.fromMillis(num);
- case SECONDS:
- return Durations.fromSeconds(num);
- default:
- return Durations.fromMillis(units.toMillis(num));
- }
- }
-
- /** Converts a duration to a number using the specified {@link TimeUnit}. */
- static long durationToUnits(Duration duration, TimeUnit units) {
- switch (units) {
- case NANOSECONDS:
- return Durations.toNanos(duration);
- case MICROSECONDS:
- return Durations.toMicros(duration);
- case MILLISECONDS:
- return Durations.toMillis(duration);
- case SECONDS:
- return Durations.toSeconds(duration);
- default:
- throw new IllegalArgumentException();
- }
- }
-}
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/ReadOnlyTransaction.java b/src/main/java/com/google/cloud/spanner/jdbc/ReadOnlyTransaction.java
deleted file mode 100644
index e0c40897..00000000
--- a/src/main/java/com/google/cloud/spanner/jdbc/ReadOnlyTransaction.java
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Copyright 2019 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.cloud.spanner.jdbc;
-
-import com.google.cloud.Timestamp;
-import com.google.cloud.spanner.DatabaseClient;
-import com.google.cloud.spanner.ErrorCode;
-import com.google.cloud.spanner.Mutation;
-import com.google.cloud.spanner.ReadContext;
-import com.google.cloud.spanner.SpannerException;
-import com.google.cloud.spanner.SpannerExceptionFactory;
-import com.google.cloud.spanner.TimestampBound;
-import com.google.cloud.spanner.jdbc.StatementParser.ParsedStatement;
-import com.google.common.base.Preconditions;
-
-/**
- * Transaction that is used when a {@link Connection} is in read-only mode or when the transaction
- * mode is set to read-only. This transaction can only be used to execute queries.
- */
-class ReadOnlyTransaction extends AbstractMultiUseTransaction {
- private final DatabaseClient dbClient;
- private final TimestampBound readOnlyStaleness;
- private com.google.cloud.spanner.ReadOnlyTransaction transaction;
- private UnitOfWorkState state = UnitOfWorkState.STARTED;
-
- static class Builder extends AbstractBaseUnitOfWork.Builder {
- private DatabaseClient dbClient;
- private TimestampBound readOnlyStaleness;
-
- private Builder() {}
-
- Builder setDatabaseClient(DatabaseClient client) {
- Preconditions.checkNotNull(client);
- this.dbClient = client;
- return this;
- }
-
- Builder setReadOnlyStaleness(TimestampBound staleness) {
- Preconditions.checkNotNull(staleness);
- this.readOnlyStaleness = staleness;
- return this;
- }
-
- @Override
- ReadOnlyTransaction build() {
- Preconditions.checkState(dbClient != null, "No DatabaseClient client specified");
- Preconditions.checkState(readOnlyStaleness != null, "No ReadOnlyStaleness specified");
- return new ReadOnlyTransaction(this);
- }
- }
-
- static Builder newBuilder() {
- return new Builder();
- }
-
- private ReadOnlyTransaction(Builder builder) {
- super(builder);
- this.dbClient = builder.dbClient;
- this.readOnlyStaleness = builder.readOnlyStaleness;
- }
-
- @Override
- public UnitOfWorkState getState() {
- return this.state;
- }
-
- @Override
- public boolean isReadOnly() {
- return true;
- }
-
- @Override
- void checkValidTransaction() {
- if (transaction == null) {
- transaction = dbClient.readOnlyTransaction(readOnlyStaleness);
- }
- }
-
- @Override
- ReadContext getReadContext() {
- ConnectionPreconditions.checkState(transaction != null, "Missing read-only transaction");
- return transaction;
- }
-
- @Override
- public Timestamp getReadTimestamp() {
- ConnectionPreconditions.checkState(
- transaction != null, "There is no read timestamp available for this transaction.");
- ConnectionPreconditions.checkState(
- state != UnitOfWorkState.ROLLED_BACK, "This transaction was rolled back");
- return transaction.getReadTimestamp();
- }
-
- @Override
- public Timestamp getReadTimestampOrNull() {
- if (transaction != null && state != UnitOfWorkState.ROLLED_BACK) {
- try {
- return transaction.getReadTimestamp();
- } catch (SpannerException e) {
- // ignore
- }
- }
- return null;
- }
-
- @Override
- public Timestamp getCommitTimestamp() {
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.FAILED_PRECONDITION,
- "There is no commit timestamp available for this transaction.");
- }
-
- @Override
- public Timestamp getCommitTimestampOrNull() {
- return null;
- }
-
- @Override
- public void executeDdl(ParsedStatement ddl) {
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.FAILED_PRECONDITION, "DDL statements are not allowed for read-only transactions");
- }
-
- @Override
- public long executeUpdate(ParsedStatement update) {
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.FAILED_PRECONDITION,
- "Update statements are not allowed for read-only transactions");
- }
-
- @Override
- public long[] executeBatchUpdate(Iterable updates) {
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.FAILED_PRECONDITION, "Batch updates are not allowed for read-only transactions.");
- }
-
- @Override
- public void write(Mutation mutation) {
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.FAILED_PRECONDITION, "Mutations are not allowed for read-only transactions");
- }
-
- @Override
- public void write(Iterable mutations) {
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.FAILED_PRECONDITION, "Mutations are not allowed for read-only transactions");
- }
-
- @Override
- public void commit() {
- if (this.transaction != null) {
- this.transaction.close();
- }
- this.state = UnitOfWorkState.COMMITTED;
- }
-
- @Override
- public void rollback() {
- if (this.transaction != null) {
- this.transaction.close();
- }
- this.state = UnitOfWorkState.ROLLED_BACK;
- }
-}
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/ReadWriteTransaction.java b/src/main/java/com/google/cloud/spanner/jdbc/ReadWriteTransaction.java
deleted file mode 100644
index a9fcbc53..00000000
--- a/src/main/java/com/google/cloud/spanner/jdbc/ReadWriteTransaction.java
+++ /dev/null
@@ -1,762 +0,0 @@
-/*
- * Copyright 2019 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.cloud.spanner.jdbc;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-
-import com.google.cloud.Timestamp;
-import com.google.cloud.spanner.AbortedDueToConcurrentModificationException;
-import com.google.cloud.spanner.AbortedException;
-import com.google.cloud.spanner.DatabaseClient;
-import com.google.cloud.spanner.ErrorCode;
-import com.google.cloud.spanner.Mutation;
-import com.google.cloud.spanner.Options.QueryOption;
-import com.google.cloud.spanner.ResultSet;
-import com.google.cloud.spanner.SpannerException;
-import com.google.cloud.spanner.SpannerExceptionFactory;
-import com.google.cloud.spanner.Statement;
-import com.google.cloud.spanner.TransactionContext;
-import com.google.cloud.spanner.TransactionManager;
-import com.google.cloud.spanner.jdbc.StatementParser.ParsedStatement;
-import com.google.cloud.spanner.jdbc.TransactionRetryListener.RetryResult;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Preconditions;
-import java.util.ArrayList;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.concurrent.Callable;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-/**
- * Transaction that is used when a {@link Connection} is normal read/write mode (i.e. not autocommit
- * and not read-only). These transactions can be automatically retried if an {@link
- * AbortedException} is thrown. The transaction will keep track of a running checksum of all {@link
- * ResultSet}s that have been returned, and the update counts returned by any DML statement executed
- * during the transaction. As long as these checksums and update counts are equal for both the
- * original transaction and the retried transaction, the retry can safely be assumed to have the
- * exact same results as the original transaction.
- */
-class ReadWriteTransaction extends AbstractMultiUseTransaction {
- private static final Logger logger = Logger.getLogger(ReadWriteTransaction.class.getName());
- private static final AtomicLong ID_GENERATOR = new AtomicLong();
- private static final String MAX_INTERNAL_RETRIES_EXCEEDED =
- "Internal transaction retry maximum exceeded";
- private static final int MAX_INTERNAL_RETRIES = 50;
- private final long transactionId;
- private final DatabaseClient dbClient;
- private TransactionManager txManager;
- private final boolean retryAbortsInternally;
- private int transactionRetryAttempts;
- private int successfulRetries;
- private final List transactionRetryListeners;
- private volatile TransactionContext txContext;
- private volatile UnitOfWorkState state = UnitOfWorkState.STARTED;
- private boolean timedOutOrCancelled = false;
- private final List statements = new ArrayList<>();
- private final List mutations = new ArrayList<>();
- private Timestamp transactionStarted;
-
- static class Builder extends AbstractMultiUseTransaction.Builder {
- private DatabaseClient dbClient;
- private Boolean retryAbortsInternally;
- private List transactionRetryListeners;
-
- private Builder() {}
-
- Builder setDatabaseClient(DatabaseClient client) {
- Preconditions.checkNotNull(client);
- this.dbClient = client;
- return this;
- }
-
- Builder setRetryAbortsInternally(boolean retryAbortsInternally) {
- this.retryAbortsInternally = retryAbortsInternally;
- return this;
- }
-
- Builder setTransactionRetryListeners(List listeners) {
- Preconditions.checkNotNull(listeners);
- this.transactionRetryListeners = listeners;
- return this;
- }
-
- @Override
- ReadWriteTransaction build() {
- Preconditions.checkState(dbClient != null, "No DatabaseClient client specified");
- Preconditions.checkState(
- retryAbortsInternally != null, "RetryAbortsInternally is not specified");
- Preconditions.checkState(
- transactionRetryListeners != null, "TransactionRetryListeners are not specified");
- return new ReadWriteTransaction(this);
- }
- }
-
- static Builder newBuilder() {
- return new Builder();
- }
-
- private ReadWriteTransaction(Builder builder) {
- super(builder);
- this.transactionId = ID_GENERATOR.incrementAndGet();
- this.dbClient = builder.dbClient;
- this.retryAbortsInternally = builder.retryAbortsInternally;
- this.transactionRetryListeners = builder.transactionRetryListeners;
- this.txManager = dbClient.transactionManager();
- }
-
- @Override
- public String toString() {
- return new StringBuilder()
- .append("ReadWriteTransaction - ID: ")
- .append(transactionId)
- .append("; Status: ")
- .append(internalGetStateName())
- .append("; Started: ")
- .append(internalGetTimeStarted())
- .append("; Retry attempts: ")
- .append(transactionRetryAttempts)
- .append("; Successful retries: ")
- .append(successfulRetries)
- .toString();
- }
-
- private String internalGetStateName() {
- return transactionStarted == null ? "Not yet started" : getState().toString();
- }
-
- private String internalGetTimeStarted() {
- return transactionStarted == null ? "Not yet started" : transactionStarted.toString();
- }
-
- @Override
- public UnitOfWorkState getState() {
- return this.state;
- }
-
- @Override
- public boolean isReadOnly() {
- return false;
- }
-
- @Override
- void checkValidTransaction() {
- ConnectionPreconditions.checkState(
- state == UnitOfWorkState.STARTED,
- "This transaction has status "
- + state.name()
- + ", only "
- + UnitOfWorkState.STARTED
- + " is allowed.");
- ConnectionPreconditions.checkState(
- !timedOutOrCancelled,
- "The last statement of this transaction timed out or was cancelled. "
- + "The transaction is no longer usable. "
- + "Rollback the transaction and start a new one.");
- if (txManager.getState() == null) {
- transactionStarted = Timestamp.now();
- txContext = txManager.begin();
- }
- if (txManager.getState()
- != com.google.cloud.spanner.TransactionManager.TransactionState.STARTED) {
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.FAILED_PRECONDITION,
- String.format("Invalid transaction state: %s", txManager.getState()));
- }
- }
-
- @Override
- TransactionContext getReadContext() {
- ConnectionPreconditions.checkState(txContext != null, "Missing transaction context");
- return txContext;
- }
-
- @Override
- public Timestamp getReadTimestamp() {
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.FAILED_PRECONDITION,
- "There is no read timestamp available for read/write transactions.");
- }
-
- @Override
- public Timestamp getReadTimestampOrNull() {
- return null;
- }
-
- private boolean hasCommitTimestamp() {
- return txManager.getState()
- == com.google.cloud.spanner.TransactionManager.TransactionState.COMMITTED;
- }
-
- @Override
- public Timestamp getCommitTimestamp() {
- ConnectionPreconditions.checkState(hasCommitTimestamp(), "This transaction has not committed.");
- return txManager.getCommitTimestamp();
- }
-
- @Override
- public Timestamp getCommitTimestampOrNull() {
- return hasCommitTimestamp() ? txManager.getCommitTimestamp() : null;
- }
-
- @Override
- public void executeDdl(ParsedStatement ddl) {
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.FAILED_PRECONDITION,
- "DDL-statements are not allowed inside a read/write transaction.");
- }
-
- private void handlePossibleInvalidatingException(SpannerException e) {
- if (e.getErrorCode() == ErrorCode.DEADLINE_EXCEEDED
- || e.getErrorCode() == ErrorCode.CANCELLED) {
- this.timedOutOrCancelled = true;
- }
- }
-
- @Override
- public ResultSet executeQuery(
- final ParsedStatement statement,
- final AnalyzeMode analyzeMode,
- final QueryOption... options) {
- Preconditions.checkArgument(statement.isQuery(), "Statement is not a query");
- checkValidTransaction();
- try {
- if (retryAbortsInternally) {
- return asyncExecuteStatement(
- statement,
- new Callable() {
- @Override
- public ResultSet call() throws Exception {
- return runWithRetry(
- new Callable() {
- @Override
- public ResultSet call() throws Exception {
- try {
- getStatementExecutor()
- .invokeInterceptors(
- statement,
- StatementExecutionStep.EXECUTE_STATEMENT,
- ReadWriteTransaction.this);
- ResultSet delegate =
- DirectExecuteResultSet.ofResultSet(
- internalExecuteQuery(statement, analyzeMode, options));
- return createAndAddRetryResultSet(
- delegate, statement, analyzeMode, options);
- } catch (AbortedException e) {
- throw e;
- } catch (SpannerException e) {
- createAndAddFailedQuery(e, statement, analyzeMode, options);
- throw e;
- }
- }
- });
- }
- },
- InterceptorsUsage
- .IGNORE_INTERCEPTORS); // ignore interceptors here as they are invoked in the
- // Callable.
- } else {
- return super.executeQuery(statement, analyzeMode, options);
- }
- } catch (SpannerException e) {
- handlePossibleInvalidatingException(e);
- throw e;
- }
- }
-
- @Override
- public long executeUpdate(final ParsedStatement update) {
- Preconditions.checkNotNull(update);
- Preconditions.checkArgument(update.isUpdate(), "The statement is not an update statement");
- checkValidTransaction();
- try {
- if (retryAbortsInternally) {
- return asyncExecuteStatement(
- update,
- new Callable() {
- @Override
- public Long call() throws Exception {
- return runWithRetry(
- new Callable() {
- @Override
- public Long call() throws Exception {
- try {
- getStatementExecutor()
- .invokeInterceptors(
- update,
- StatementExecutionStep.EXECUTE_STATEMENT,
- ReadWriteTransaction.this);
- long updateCount = txContext.executeUpdate(update.getStatement());
- createAndAddRetriableUpdate(update, updateCount);
- return updateCount;
- } catch (AbortedException e) {
- throw e;
- } catch (SpannerException e) {
- createAndAddFailedUpdate(e, update);
- throw e;
- }
- }
- });
- }
- },
- InterceptorsUsage
- .IGNORE_INTERCEPTORS); // ignore interceptors here as they are invoked in the
- // Callable.
- } else {
- return asyncExecuteStatement(
- update,
- new Callable() {
- @Override
- public Long call() throws Exception {
- return txContext.executeUpdate(update.getStatement());
- }
- });
- }
- } catch (SpannerException e) {
- handlePossibleInvalidatingException(e);
- throw e;
- }
- }
-
- /**
- * Create a RUN BATCH statement to use with the {@link #executeBatchUpdate(Iterable)} method to
- * allow it to be cancelled, time out or retried.
- *
- * {@link ReadWriteTransaction} uses the generic methods {@link #executeAsync(ParsedStatement,
- * Callable)} and {@link #runWithRetry(Callable)} to allow statements to be cancelled, to timeout
- * and to be retried. These methods require a {@link ParsedStatement} as input. When the {@link
- * #executeBatchUpdate(Iterable)} method is called, we do not have one {@link ParsedStatement},
- * and the method uses this statement instead in order to use the same logic as the other
- * statements.
- */
- static final ParsedStatement EXECUTE_BATCH_UPDATE_STATEMENT =
- StatementParser.INSTANCE.parse(Statement.of("RUN BATCH"));
-
- @Override
- public long[] executeBatchUpdate(final Iterable updates) {
- Preconditions.checkNotNull(updates);
- final List updateStatements = new LinkedList<>();
- for (ParsedStatement update : updates) {
- Preconditions.checkArgument(
- update.isUpdate(),
- "Statement is not an update statement: " + update.getSqlWithoutComments());
- updateStatements.add(update.getStatement());
- }
- checkValidTransaction();
- try {
- if (retryAbortsInternally) {
- return asyncExecuteStatement(
- EXECUTE_BATCH_UPDATE_STATEMENT,
- new Callable() {
- @Override
- public long[] call() throws Exception {
- return runWithRetry(
- new Callable() {
- @Override
- public long[] call() throws Exception {
- try {
- getStatementExecutor()
- .invokeInterceptors(
- EXECUTE_BATCH_UPDATE_STATEMENT,
- StatementExecutionStep.EXECUTE_STATEMENT,
- ReadWriteTransaction.this);
- long[] updateCounts = txContext.batchUpdate(updateStatements);
- createAndAddRetriableBatchUpdate(updateStatements, updateCounts);
- return updateCounts;
- } catch (AbortedException e) {
- throw e;
- } catch (SpannerException e) {
- createAndAddFailedBatchUpdate(e, updateStatements);
- throw e;
- }
- }
- });
- }
- },
- InterceptorsUsage
- .IGNORE_INTERCEPTORS); // ignore interceptors here as they are invoked in the
- // Callable.
- } else {
- return asyncExecuteStatement(
- EXECUTE_BATCH_UPDATE_STATEMENT,
- new Callable() {
- @Override
- public long[] call() throws Exception {
- return txContext.batchUpdate(updateStatements);
- }
- });
- }
- } catch (SpannerException e) {
- handlePossibleInvalidatingException(e);
- throw e;
- }
- }
-
- @Override
- public void write(Mutation mutation) {
- Preconditions.checkNotNull(mutation);
- checkValidTransaction();
- mutations.add(mutation);
- }
-
- @Override
- public void write(Iterable mutations) {
- Preconditions.checkNotNull(mutations);
- checkValidTransaction();
- for (Mutation mutation : mutations) {
- this.mutations.add(checkNotNull(mutation));
- }
- }
-
- /**
- * Create a COMMIT statement to use with the {@link #commit()} method to allow it to be cancelled,
- * time out or retried.
- *
- * {@link ReadWriteTransaction} uses the generic methods {@link #executeAsync(ParsedStatement,
- * Callable)} and {@link #runWithRetry(Callable)} to allow statements to be cancelled, to timeout
- * and to be retried. These methods require a {@link ParsedStatement} as input. When the {@link
- * #commit()} method is called directly, we do not have a {@link ParsedStatement}, and the method
- * uses this statement instead in order to use the same logic as the other statements.
- */
- private static final ParsedStatement COMMIT_STATEMENT =
- StatementParser.INSTANCE.parse(Statement.of("COMMIT"));
-
- private final Callable commitCallable =
- new Callable() {
- @Override
- public Void call() throws Exception {
- txContext.buffer(mutations);
- txManager.commit();
- return null;
- }
- };
-
- @Override
- public void commit() {
- checkValidTransaction();
- try {
- if (retryAbortsInternally) {
- asyncExecuteStatement(
- COMMIT_STATEMENT,
- new Callable() {
- @Override
- public Void call() throws Exception {
- return runWithRetry(
- new Callable() {
- @Override
- public Void call() throws Exception {
- getStatementExecutor()
- .invokeInterceptors(
- COMMIT_STATEMENT,
- StatementExecutionStep.EXECUTE_STATEMENT,
- ReadWriteTransaction.this);
- commitCallable.call();
- return null;
- }
- });
- }
- },
- InterceptorsUsage.IGNORE_INTERCEPTORS);
- } else {
- asyncExecuteStatement(COMMIT_STATEMENT, commitCallable);
- }
- ReadWriteTransaction.this.state = UnitOfWorkState.COMMITTED;
- } catch (SpannerException e) {
- try {
- txManager.close();
- } catch (Throwable t) {
- // ignore
- }
- this.state = UnitOfWorkState.COMMIT_FAILED;
- throw e;
- }
- }
-
- /**
- * Executes a database call that could throw an {@link AbortedException}. If an {@link
- * AbortedException} is thrown, the transaction will automatically be retried and the checksums of
- * all {@link ResultSet}s and update counts of DML statements will be checked against the original
- * values of the original transaction. If the checksums and/or update counts do not match, the
- * method will throw an {@link AbortedException} that cannot be retried, as the underlying data
- * have actually changed.
- *
- * If {@link ReadWriteTransaction#retryAbortsInternally} has been set to false
,
- * this method will throw an exception instead of retrying the transaction if the transaction was
- * aborted.
- *
- * @param callable The actual database calls.
- * @return the results of the database calls.
- * @throws SpannerException if the database calls threw an exception, an {@link
- * AbortedDueToConcurrentModificationException} if a retry of the transaction yielded
- * different results than the original transaction, or an {@link AbortedException} if the
- * maximum number of retries has been exceeded.
- */
- T runWithRetry(Callable callable) throws SpannerException {
- while (true) {
- try {
- return callable.call();
- } catch (final AbortedException aborted) {
- if (retryAbortsInternally) {
- handleAborted(aborted);
- } else {
- throw aborted;
- }
- } catch (SpannerException e) {
- throw e;
- } catch (Exception e) {
- throw SpannerExceptionFactory.newSpannerException(ErrorCode.UNKNOWN, e.getMessage(), e);
- }
- }
- }
-
- /**
- * Registers a {@link ResultSet} on this transaction that must be checked during a retry, and
- * returns a retryable {@link ResultSet}.
- */
- private ResultSet createAndAddRetryResultSet(
- ResultSet resultSet,
- ParsedStatement statement,
- AnalyzeMode analyzeMode,
- QueryOption... options) {
- if (retryAbortsInternally) {
- ChecksumResultSet checksumResultSet =
- createChecksumResultSet(resultSet, statement, analyzeMode, options);
- addRetryStatement(checksumResultSet);
- return checksumResultSet;
- }
- return resultSet;
- }
-
- /** Registers the statement as a query that should return an error during a retry. */
- private void createAndAddFailedQuery(
- SpannerException e,
- ParsedStatement statement,
- AnalyzeMode analyzeMode,
- QueryOption... options) {
- if (retryAbortsInternally) {
- addRetryStatement(new FailedQuery(this, e, statement, analyzeMode, options));
- }
- }
-
- private void createAndAddRetriableUpdate(ParsedStatement update, long updateCount) {
- if (retryAbortsInternally) {
- addRetryStatement(new RetriableUpdate(this, update, updateCount));
- }
- }
-
- private void createAndAddRetriableBatchUpdate(Iterable updates, long[] updateCounts) {
- if (retryAbortsInternally) {
- addRetryStatement(new RetriableBatchUpdate(this, updates, updateCounts));
- }
- }
-
- /** Registers the statement as an update that should return an error during a retry. */
- private void createAndAddFailedUpdate(SpannerException e, ParsedStatement update) {
- if (retryAbortsInternally) {
- addRetryStatement(new FailedUpdate(this, e, update));
- }
- }
-
- /** Registers the statements as a batch of updates that should return an error during a retry. */
- private void createAndAddFailedBatchUpdate(SpannerException e, Iterable updates) {
- if (retryAbortsInternally) {
- addRetryStatement(new FailedBatchUpdate(this, e, updates));
- }
- }
-
- /**
- * Adds a statement to the list of statements that should be retried if this transaction aborts.
- */
- private void addRetryStatement(RetriableStatement statement) {
- Preconditions.checkState(
- retryAbortsInternally, "retryAbortsInternally is not enabled for this transaction");
- statements.add(statement);
- }
-
- /**
- * Handles an aborted exception by checking whether the transaction may be retried internally, and
- * if so, does the retry. If retry is not allowed, or if the retry fails, the method will throw an
- * {@link AbortedException}.
- */
- private void handleAborted(AbortedException aborted) {
- if (transactionRetryAttempts >= MAX_INTERNAL_RETRIES) {
- // If the same statement in transaction keeps aborting, then we need to abort here.
- throwAbortWithRetryAttemptsExceeded();
- } else if (retryAbortsInternally) {
- logger.fine(toString() + ": Starting internal transaction retry");
- while (true) {
- // First back off and then restart the transaction.
- try {
- Thread.sleep(aborted.getRetryDelayInMillis() / 1000);
- } catch (InterruptedException ie) {
- Thread.currentThread().interrupt();
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.CANCELLED, "The statement was cancelled");
- }
- try {
- txContext = txManager.resetForRetry();
- // Inform listeners about the transaction retry that is about to start.
- invokeTransactionRetryListenersOnStart();
- // Then retry all transaction statements.
- transactionRetryAttempts++;
- for (RetriableStatement statement : statements) {
- statement.retry(aborted);
- }
- successfulRetries++;
- invokeTransactionRetryListenersOnFinish(RetryResult.RETRY_SUCCESSFUL);
- logger.fine(
- toString()
- + ": Internal transaction retry succeeded. Starting retry of original statement.");
- // Retry succeeded, return and continue the original transaction.
- break;
- } catch (AbortedDueToConcurrentModificationException e) {
- // Retry failed because of a concurrent modification, we have to abort.
- invokeTransactionRetryListenersOnFinish(
- RetryResult.RETRY_ABORTED_DUE_TO_CONCURRENT_MODIFICATION);
- logger.fine(
- toString() + ": Internal transaction retry aborted due to a concurrent modification");
- // Try to rollback the new transaction and ignore any exceptions.
- try {
- txManager.rollback();
- } catch (Throwable t) {
- // ignore
- }
- this.state = UnitOfWorkState.ABORTED;
- throw e;
- } catch (AbortedException e) {
- // Retry aborted, do another retry of the transaction.
- if (transactionRetryAttempts >= MAX_INTERNAL_RETRIES) {
- throwAbortWithRetryAttemptsExceeded();
- }
- invokeTransactionRetryListenersOnFinish(RetryResult.RETRY_ABORTED_AND_RESTARTING);
- logger.fine(toString() + ": Internal transaction retry aborted, trying again");
- } catch (SpannerException e) {
- // unexpected exception
- logger.log(
- Level.FINE,
- toString() + ": Internal transaction retry failed due to an unexpected exception",
- e);
- // Try to rollback the new transaction and ignore any exceptions.
- try {
- txManager.rollback();
- } catch (Throwable t) {
- // ignore
- }
- // Set transaction state to aborted as the retry failed.
- this.state = UnitOfWorkState.ABORTED;
- // Re-throw underlying exception.
- throw e;
- }
- }
- } else {
- try {
- txManager.close();
- } catch (Throwable t) {
- // ignore
- }
- // Internal retry is not enabled.
- this.state = UnitOfWorkState.ABORTED;
- throw aborted;
- }
- }
-
- private void throwAbortWithRetryAttemptsExceeded() throws SpannerException {
- invokeTransactionRetryListenersOnFinish(RetryResult.RETRY_ABORTED_AND_MAX_ATTEMPTS_EXCEEDED);
- logger.fine(
- toString()
- + ": Internal transaction retry aborted and max number of retry attempts has been exceeded");
- // Try to rollback the transaction and ignore any exceptions.
- // Normally it should not be necessary to do this, but in order to be sure we never leak
- // any sessions it is better to do so.
- try {
- txManager.rollback();
- } catch (Throwable t) {
- // ignore
- }
- this.state = UnitOfWorkState.ABORTED;
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.ABORTED, MAX_INTERNAL_RETRIES_EXCEEDED);
- }
-
- private void invokeTransactionRetryListenersOnStart() {
- for (TransactionRetryListener listener : transactionRetryListeners) {
- listener.retryStarting(transactionStarted, transactionId, transactionRetryAttempts);
- }
- }
-
- private void invokeTransactionRetryListenersOnFinish(RetryResult result) {
- for (TransactionRetryListener listener : transactionRetryListeners) {
- listener.retryFinished(transactionStarted, transactionId, transactionRetryAttempts, result);
- }
- }
-
- /** The {@link Statement} and {@link Callable} for rollbacks */
- private final ParsedStatement rollbackStatement =
- StatementParser.INSTANCE.parse(Statement.of("ROLLBACK"));
-
- private final Callable rollbackCallable =
- new Callable() {
- @Override
- public Void call() throws Exception {
- txManager.rollback();
- return null;
- }
- };
-
- @Override
- public void rollback() {
- ConnectionPreconditions.checkState(
- state == UnitOfWorkState.STARTED, "This transaction has status " + state.name());
- try {
- asyncExecuteStatement(rollbackStatement, rollbackCallable);
- } finally {
- // Whatever happens, we should always call close in order to return the underlying session to
- // the session pool to avoid any session leaks.
- try {
- txManager.close();
- } catch (Throwable e) {
- // ignore
- }
- this.state = UnitOfWorkState.ROLLED_BACK;
- }
- }
-
- /**
- * A retriable statement is a query or DML statement during a read/write transaction that can be
- * retried if the original transaction aborted.
- */
- interface RetriableStatement {
- /**
- * Retry this statement in a new transaction. Throws an {@link
- * AbortedDueToConcurrentModificationException} if the retry could not successfully be executed
- * because of an actual concurrent modification of the underlying data. This {@link
- * AbortedDueToConcurrentModificationException} cannot be retried.
- */
- void retry(AbortedException aborted) throws AbortedException;
- }
-
- /** Creates a {@link ChecksumResultSet} for this {@link ReadWriteTransaction}. */
- @VisibleForTesting
- ChecksumResultSet createChecksumResultSet(
- ResultSet delegate,
- ParsedStatement statement,
- AnalyzeMode analyzeMode,
- QueryOption... options) {
- return new ChecksumResultSet(this, delegate, statement, analyzeMode, options);
- }
-}
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/ReplaceableForwardingResultSet.java b/src/main/java/com/google/cloud/spanner/jdbc/ReplaceableForwardingResultSet.java
deleted file mode 100644
index 0a72b369..00000000
--- a/src/main/java/com/google/cloud/spanner/jdbc/ReplaceableForwardingResultSet.java
+++ /dev/null
@@ -1,352 +0,0 @@
-/*
- * Copyright 2019 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.cloud.spanner.jdbc;
-
-import com.google.cloud.ByteArray;
-import com.google.cloud.Date;
-import com.google.cloud.Timestamp;
-import com.google.cloud.spanner.ErrorCode;
-import com.google.cloud.spanner.ResultSet;
-import com.google.cloud.spanner.SpannerException;
-import com.google.cloud.spanner.SpannerExceptionFactory;
-import com.google.cloud.spanner.Struct;
-import com.google.cloud.spanner.Type;
-import com.google.common.base.Preconditions;
-import com.google.spanner.v1.ResultSetStats;
-import java.util.List;
-
-/**
- * Forwarding implementation of {@link ResultSet} that forwards all calls to a delegate that can be
- * replaced. This is used by the JDBC Driver when a read/write transaction is successfully retried.
- * Any {@link ResultSet} that is open during a transaction retry, must be replaced by a result set
- * that is fetched using the new transaction. This is achieved by wrapping the returned result sets
- * in a {@link ReplaceableForwardingResultSet} that replaces its delegate after a transaction retry.
- */
-class ReplaceableForwardingResultSet implements ResultSet {
- private ResultSet delegate;
- private boolean closed;
-
- ReplaceableForwardingResultSet(ResultSet delegate) {
- this.delegate = Preconditions.checkNotNull(delegate);
- }
-
- /** Replace the underlying delegate {@link ResultSet} with a new one. */
- void replaceDelegate(ResultSet delegate) {
- Preconditions.checkNotNull(delegate);
- checkClosed();
- if (this.delegate != null) {
- this.delegate.close();
- }
- this.delegate = delegate;
- }
-
- private void checkClosed() {
- if (closed) {
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.FAILED_PRECONDITION, "This ResultSet is closed");
- }
- }
-
- boolean isClosed() {
- return closed;
- }
-
- @Override
- public boolean next() throws SpannerException {
- checkClosed();
- return delegate.next();
- }
-
- @Override
- public Struct getCurrentRowAsStruct() {
- checkClosed();
- return delegate.getCurrentRowAsStruct();
- }
-
- @Override
- public void close() {
- if (delegate != null) {
- delegate.close();
- delegate = null;
- }
- closed = true;
- }
-
- @Override
- public ResultSetStats getStats() {
- checkClosed();
- return delegate.getStats();
- }
-
- @Override
- public Type getType() {
- checkClosed();
- return delegate.getType();
- }
-
- @Override
- public int getColumnCount() {
- checkClosed();
- return delegate.getColumnCount();
- }
-
- @Override
- public int getColumnIndex(String columnName) {
- checkClosed();
- return delegate.getColumnIndex(columnName);
- }
-
- @Override
- public Type getColumnType(int columnIndex) {
- checkClosed();
- return delegate.getColumnType(columnIndex);
- }
-
- @Override
- public Type getColumnType(String columnName) {
- checkClosed();
- return delegate.getColumnType(columnName);
- }
-
- @Override
- public boolean isNull(int columnIndex) {
- checkClosed();
- return delegate.isNull(columnIndex);
- }
-
- @Override
- public boolean isNull(String columnName) {
- checkClosed();
- return delegate.isNull(columnName);
- }
-
- @Override
- public boolean getBoolean(int columnIndex) {
- checkClosed();
- return delegate.getBoolean(columnIndex);
- }
-
- @Override
- public boolean getBoolean(String columnName) {
- checkClosed();
- return delegate.getBoolean(columnName);
- }
-
- @Override
- public long getLong(int columnIndex) {
- checkClosed();
- return delegate.getLong(columnIndex);
- }
-
- @Override
- public long getLong(String columnName) {
- checkClosed();
- return delegate.getLong(columnName);
- }
-
- @Override
- public double getDouble(int columnIndex) {
- checkClosed();
- return delegate.getDouble(columnIndex);
- }
-
- @Override
- public double getDouble(String columnName) {
- checkClosed();
- return delegate.getDouble(columnName);
- }
-
- @Override
- public String getString(int columnIndex) {
- checkClosed();
- return delegate.getString(columnIndex);
- }
-
- @Override
- public String getString(String columnName) {
- checkClosed();
- return delegate.getString(columnName);
- }
-
- @Override
- public ByteArray getBytes(int columnIndex) {
- checkClosed();
- return delegate.getBytes(columnIndex);
- }
-
- @Override
- public ByteArray getBytes(String columnName) {
- checkClosed();
- return delegate.getBytes(columnName);
- }
-
- @Override
- public Timestamp getTimestamp(int columnIndex) {
- checkClosed();
- return delegate.getTimestamp(columnIndex);
- }
-
- @Override
- public Timestamp getTimestamp(String columnName) {
- checkClosed();
- return delegate.getTimestamp(columnName);
- }
-
- @Override
- public Date getDate(int columnIndex) {
- checkClosed();
- return delegate.getDate(columnIndex);
- }
-
- @Override
- public Date getDate(String columnName) {
- checkClosed();
- return delegate.getDate(columnName);
- }
-
- @Override
- public boolean[] getBooleanArray(int columnIndex) {
- checkClosed();
- return delegate.getBooleanArray(columnIndex);
- }
-
- @Override
- public boolean[] getBooleanArray(String columnName) {
- checkClosed();
- return delegate.getBooleanArray(columnName);
- }
-
- @Override
- public List getBooleanList(int columnIndex) {
- checkClosed();
- return delegate.getBooleanList(columnIndex);
- }
-
- @Override
- public List getBooleanList(String columnName) {
- checkClosed();
- return delegate.getBooleanList(columnName);
- }
-
- @Override
- public long[] getLongArray(int columnIndex) {
- checkClosed();
- return delegate.getLongArray(columnIndex);
- }
-
- @Override
- public long[] getLongArray(String columnName) {
- checkClosed();
- return delegate.getLongArray(columnName);
- }
-
- @Override
- public List getLongList(int columnIndex) {
- checkClosed();
- return delegate.getLongList(columnIndex);
- }
-
- @Override
- public List getLongList(String columnName) {
- checkClosed();
- return delegate.getLongList(columnName);
- }
-
- @Override
- public double[] getDoubleArray(int columnIndex) {
- checkClosed();
- return delegate.getDoubleArray(columnIndex);
- }
-
- @Override
- public double[] getDoubleArray(String columnName) {
- checkClosed();
- return delegate.getDoubleArray(columnName);
- }
-
- @Override
- public List getDoubleList(int columnIndex) {
- checkClosed();
- return delegate.getDoubleList(columnIndex);
- }
-
- @Override
- public List getDoubleList(String columnName) {
- checkClosed();
- return delegate.getDoubleList(columnName);
- }
-
- @Override
- public List getStringList(int columnIndex) {
- checkClosed();
- return delegate.getStringList(columnIndex);
- }
-
- @Override
- public List getStringList(String columnName) {
- checkClosed();
- return delegate.getStringList(columnName);
- }
-
- @Override
- public List getBytesList(int columnIndex) {
- checkClosed();
- return delegate.getBytesList(columnIndex);
- }
-
- @Override
- public List getBytesList(String columnName) {
- checkClosed();
- return delegate.getBytesList(columnName);
- }
-
- @Override
- public List getTimestampList(int columnIndex) {
- checkClosed();
- return delegate.getTimestampList(columnIndex);
- }
-
- @Override
- public List getTimestampList(String columnName) {
- checkClosed();
- return delegate.getTimestampList(columnName);
- }
-
- @Override
- public List getDateList(int columnIndex) {
- checkClosed();
- return delegate.getDateList(columnIndex);
- }
-
- @Override
- public List getDateList(String columnName) {
- checkClosed();
- return delegate.getDateList(columnName);
- }
-
- @Override
- public List getStructList(int columnIndex) {
- checkClosed();
- return delegate.getStructList(columnIndex);
- }
-
- @Override
- public List getStructList(String columnName) {
- checkClosed();
- return delegate.getStructList(columnName);
- }
-}
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/RetriableBatchUpdate.java b/src/main/java/com/google/cloud/spanner/jdbc/RetriableBatchUpdate.java
deleted file mode 100644
index 73609e8b..00000000
--- a/src/main/java/com/google/cloud/spanner/jdbc/RetriableBatchUpdate.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright 2019 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.cloud.spanner.jdbc;
-
-import com.google.cloud.spanner.AbortedException;
-import com.google.cloud.spanner.SpannerException;
-import com.google.cloud.spanner.SpannerExceptionFactory;
-import com.google.cloud.spanner.Statement;
-import com.google.cloud.spanner.jdbc.ReadWriteTransaction.RetriableStatement;
-import com.google.common.base.Preconditions;
-import java.util.Arrays;
-
-/**
- * Retriable batch of DML statements. The check whether the statements had the same effect during
- * retry is done by comparing the number of records affected.
- */
-final class RetriableBatchUpdate implements RetriableStatement {
- private final ReadWriteTransaction transaction;
- private final Iterable statements;
- private final long[] updateCounts;
-
- RetriableBatchUpdate(
- ReadWriteTransaction transaction, Iterable statements, long[] updateCounts) {
- Preconditions.checkNotNull(transaction);
- Preconditions.checkNotNull(statements);
- this.transaction = transaction;
- this.statements = statements;
- this.updateCounts = updateCounts;
- }
-
- @Override
- public void retry(AbortedException aborted) throws AbortedException {
- long[] newCount = null;
- try {
- transaction
- .getStatementExecutor()
- .invokeInterceptors(
- ReadWriteTransaction.EXECUTE_BATCH_UPDATE_STATEMENT,
- StatementExecutionStep.RETRY_STATEMENT,
- transaction);
- newCount = transaction.getReadContext().batchUpdate(statements);
- } catch (AbortedException e) {
- // Just re-throw the AbortedException and let the retry logic determine whether another try
- // should be executed or not.
- throw e;
- } catch (SpannerException e) {
- // Unexpected database error that is different from the original transaction.
- throw SpannerExceptionFactory.newAbortedDueToConcurrentModificationException(aborted, e);
- }
- if (newCount == null || !Arrays.equals(updateCounts, newCount)) {
- // The update counts do not match, we cannot retry the transaction.
- throw SpannerExceptionFactory.newAbortedDueToConcurrentModificationException(aborted);
- }
- }
-}
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/RetriableUpdate.java b/src/main/java/com/google/cloud/spanner/jdbc/RetriableUpdate.java
deleted file mode 100644
index ac2b8242..00000000
--- a/src/main/java/com/google/cloud/spanner/jdbc/RetriableUpdate.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright 2019 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.cloud.spanner.jdbc;
-
-import com.google.cloud.spanner.AbortedException;
-import com.google.cloud.spanner.SpannerException;
-import com.google.cloud.spanner.SpannerExceptionFactory;
-import com.google.cloud.spanner.jdbc.ReadWriteTransaction.RetriableStatement;
-import com.google.cloud.spanner.jdbc.StatementParser.ParsedStatement;
-import com.google.common.base.Preconditions;
-
-/**
- * Retriable DML statement. The check whether the statement had the same effect during retry is done
- * by comparing the number of records affected.
- */
-final class RetriableUpdate implements RetriableStatement {
- private final ReadWriteTransaction transaction;
- private final ParsedStatement statement;
- private final long updateCount;
-
- RetriableUpdate(ReadWriteTransaction transaction, ParsedStatement statement, long updateCount) {
- Preconditions.checkNotNull(transaction);
- Preconditions.checkNotNull(statement);
- this.transaction = transaction;
- this.statement = statement;
- this.updateCount = updateCount;
- }
-
- @Override
- public void retry(AbortedException aborted) throws AbortedException {
- long newCount = -1;
- try {
- transaction
- .getStatementExecutor()
- .invokeInterceptors(statement, StatementExecutionStep.RETRY_STATEMENT, transaction);
- newCount = transaction.getReadContext().executeUpdate(statement.getStatement());
- } catch (AbortedException e) {
- // Just re-throw the AbortedException and let the retry logic determine whether another try
- // should be executed or not.
- throw e;
- } catch (SpannerException e) {
- // Unexpected database error that is different from the original transaction.
- throw SpannerExceptionFactory.newAbortedDueToConcurrentModificationException(aborted, e);
- }
- if (newCount != updateCount) {
- // The update counts do not match, we cannot retry the transaction.
- throw SpannerExceptionFactory.newAbortedDueToConcurrentModificationException(aborted);
- }
- }
-}
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/SingleUseTransaction.java b/src/main/java/com/google/cloud/spanner/jdbc/SingleUseTransaction.java
deleted file mode 100644
index a5d61d5b..00000000
--- a/src/main/java/com/google/cloud/spanner/jdbc/SingleUseTransaction.java
+++ /dev/null
@@ -1,505 +0,0 @@
-/*
- * Copyright 2019 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.cloud.spanner.jdbc;
-
-import com.google.api.gax.longrunning.OperationFuture;
-import com.google.cloud.Timestamp;
-import com.google.cloud.spanner.AbortedException;
-import com.google.cloud.spanner.DatabaseClient;
-import com.google.cloud.spanner.ErrorCode;
-import com.google.cloud.spanner.Mutation;
-import com.google.cloud.spanner.Options.QueryOption;
-import com.google.cloud.spanner.ReadOnlyTransaction;
-import com.google.cloud.spanner.ResultSet;
-import com.google.cloud.spanner.SpannerBatchUpdateException;
-import com.google.cloud.spanner.SpannerException;
-import com.google.cloud.spanner.SpannerExceptionFactory;
-import com.google.cloud.spanner.Statement;
-import com.google.cloud.spanner.TimestampBound;
-import com.google.cloud.spanner.TransactionContext;
-import com.google.cloud.spanner.TransactionManager;
-import com.google.cloud.spanner.TransactionRunner;
-import com.google.cloud.spanner.TransactionRunner.TransactionCallable;
-import com.google.cloud.spanner.jdbc.StatementParser.ParsedStatement;
-import com.google.cloud.spanner.jdbc.StatementParser.StatementType;
-import com.google.common.base.Preconditions;
-import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata;
-import java.util.Arrays;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.concurrent.Callable;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Transaction that is used when a {@link Connection} is in autocommit mode. Each method on this
- * transaction actually starts a new transaction on Spanner. The type of transaction that is started
- * depends on the type of statement that is being executed. A {@link SingleUseTransaction} will
- * always try to choose the most efficient type of one-time transaction that is available for the
- * statement.
- *
- * A {@link SingleUseTransaction} can be used to execute any type of statement on Cloud Spanner:
- *
- *
- * - Client side statements, e.g. SHOW VARIABLE AUTOCOMMIT
- *
- Queries, e.g. SELECT * FROM FOO
- *
- DML statements, e.g. UPDATE FOO SET BAR=1
- *
- DDL statements, e.g. CREATE TABLE FOO (...)
- *
- */
-class SingleUseTransaction extends AbstractBaseUnitOfWork {
- private final boolean readOnly;
- private final DdlClient ddlClient;
- private final DatabaseClient dbClient;
- private final TimestampBound readOnlyStaleness;
- private final AutocommitDmlMode autocommitDmlMode;
- private Timestamp readTimestamp = null;
- private volatile TransactionManager txManager;
- private TransactionRunner writeTransaction;
- private boolean used = false;
- private UnitOfWorkState state = UnitOfWorkState.STARTED;
-
- static class Builder extends AbstractBaseUnitOfWork.Builder {
- private DdlClient ddlClient;
- private DatabaseClient dbClient;
- private boolean readOnly;
- private TimestampBound readOnlyStaleness;
- private AutocommitDmlMode autocommitDmlMode;
-
- private Builder() {}
-
- Builder setDdlClient(DdlClient ddlClient) {
- Preconditions.checkNotNull(ddlClient);
- this.ddlClient = ddlClient;
- return this;
- }
-
- Builder setDatabaseClient(DatabaseClient client) {
- Preconditions.checkNotNull(client);
- this.dbClient = client;
- return this;
- }
-
- Builder setReadOnly(boolean readOnly) {
- this.readOnly = readOnly;
- return this;
- }
-
- Builder setReadOnlyStaleness(TimestampBound staleness) {
- Preconditions.checkNotNull(staleness);
- this.readOnlyStaleness = staleness;
- return this;
- }
-
- Builder setAutocommitDmlMode(AutocommitDmlMode dmlMode) {
- Preconditions.checkNotNull(dmlMode);
- this.autocommitDmlMode = dmlMode;
- return this;
- }
-
- @Override
- SingleUseTransaction build() {
- Preconditions.checkState(ddlClient != null, "No DDL client specified");
- Preconditions.checkState(dbClient != null, "No DatabaseClient client specified");
- Preconditions.checkState(readOnlyStaleness != null, "No read-only staleness specified");
- Preconditions.checkState(autocommitDmlMode != null, "No autocommit dml mode specified");
- return new SingleUseTransaction(this);
- }
- }
-
- static Builder newBuilder() {
- return new Builder();
- }
-
- private SingleUseTransaction(Builder builder) {
- super(builder);
- this.ddlClient = builder.ddlClient;
- this.dbClient = builder.dbClient;
- this.readOnly = builder.readOnly;
- this.readOnlyStaleness = builder.readOnlyStaleness;
- this.autocommitDmlMode = builder.autocommitDmlMode;
- }
-
- @Override
- public Type getType() {
- return Type.TRANSACTION;
- }
-
- @Override
- public UnitOfWorkState getState() {
- return state;
- }
-
- @Override
- public boolean isActive() {
- // Single-use transactions are never active as they can be used only once.
- return false;
- }
-
- @Override
- public boolean isReadOnly() {
- return readOnly;
- }
-
- private void checkAndMarkUsed() {
- Preconditions.checkState(!used, "This single-use transaction has already been used");
- used = true;
- }
-
- @Override
- public ResultSet executeQuery(
- final ParsedStatement statement,
- final AnalyzeMode analyzeMode,
- final QueryOption... options) {
- Preconditions.checkNotNull(statement);
- Preconditions.checkArgument(statement.isQuery(), "Statement is not a query");
- checkAndMarkUsed();
-
- final ReadOnlyTransaction currentTransaction =
- dbClient.singleUseReadOnlyTransaction(readOnlyStaleness);
- Callable callable =
- new Callable() {
- @Override
- public ResultSet call() throws Exception {
- try {
- ResultSet rs;
- if (analyzeMode == AnalyzeMode.NONE) {
- rs = currentTransaction.executeQuery(statement.getStatement(), options);
- } else {
- rs =
- currentTransaction.analyzeQuery(
- statement.getStatement(), analyzeMode.getQueryAnalyzeMode());
- }
- // Return a DirectExecuteResultSet, which will directly do a next() call in order to
- // ensure that the query is actually sent to Spanner.
- return DirectExecuteResultSet.ofResultSet(rs);
- } finally {
- currentTransaction.close();
- }
- }
- };
- try {
- ResultSet res = asyncExecuteStatement(statement, callable);
- readTimestamp = currentTransaction.getReadTimestamp();
- state = UnitOfWorkState.COMMITTED;
- return res;
- } catch (Throwable e) {
- state = UnitOfWorkState.COMMIT_FAILED;
- throw e;
- } finally {
- currentTransaction.close();
- }
- }
-
- @Override
- public Timestamp getReadTimestamp() {
- ConnectionPreconditions.checkState(
- readTimestamp != null, "There is no read timestamp available for this transaction.");
- return readTimestamp;
- }
-
- @Override
- public Timestamp getReadTimestampOrNull() {
- return readTimestamp;
- }
-
- private boolean hasCommitTimestamp() {
- return writeTransaction != null
- || (txManager != null
- && txManager.getState()
- == com.google.cloud.spanner.TransactionManager.TransactionState.COMMITTED);
- }
-
- @Override
- public Timestamp getCommitTimestamp() {
- ConnectionPreconditions.checkState(
- hasCommitTimestamp(), "There is no commit timestamp available for this transaction.");
- return writeTransaction != null
- ? writeTransaction.getCommitTimestamp()
- : txManager.getCommitTimestamp();
- }
-
- @Override
- public Timestamp getCommitTimestampOrNull() {
- if (hasCommitTimestamp()) {
- try {
- return writeTransaction != null
- ? writeTransaction.getCommitTimestamp()
- : txManager.getCommitTimestamp();
- } catch (SpannerException e) {
- // ignore
- }
- }
- return null;
- }
-
- @Override
- public void executeDdl(final ParsedStatement ddl) {
- Preconditions.checkNotNull(ddl);
- Preconditions.checkArgument(
- ddl.getType() == StatementType.DDL, "Statement is not a ddl statement");
- ConnectionPreconditions.checkState(
- !isReadOnly(), "DDL statements are not allowed in read-only mode");
- checkAndMarkUsed();
-
- try {
- Callable callable =
- new Callable() {
- @Override
- public Void call() throws Exception {
- OperationFuture operation =
- ddlClient.executeDdl(ddl.getSqlWithoutComments());
- return operation.get();
- }
- };
- asyncExecuteStatement(ddl, callable);
- state = UnitOfWorkState.COMMITTED;
- } catch (Throwable e) {
- state = UnitOfWorkState.COMMIT_FAILED;
- throw e;
- }
- }
-
- @Override
- public long executeUpdate(final ParsedStatement update) {
- Preconditions.checkNotNull(update);
- Preconditions.checkArgument(update.isUpdate(), "Statement is not an update statement");
- ConnectionPreconditions.checkState(
- !isReadOnly(), "Update statements are not allowed in read-only mode");
- checkAndMarkUsed();
-
- long res;
- try {
- switch (autocommitDmlMode) {
- case TRANSACTIONAL:
- res = executeAsyncTransactionalUpdate(update, new TransactionalUpdateCallable(update));
- break;
- case PARTITIONED_NON_ATOMIC:
- res = executeAsyncPartitionedUpdate(update);
- break;
- default:
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.FAILED_PRECONDITION, "Unknown dml mode: " + autocommitDmlMode);
- }
- } catch (Throwable e) {
- state = UnitOfWorkState.COMMIT_FAILED;
- throw e;
- }
- state = UnitOfWorkState.COMMITTED;
- return res;
- }
-
- /** Execute an update statement as a partitioned DML statement. */
- private long executeAsyncPartitionedUpdate(final ParsedStatement update) {
- Callable callable =
- new Callable() {
- @Override
- public Long call() throws Exception {
- return dbClient.executePartitionedUpdate(update.getStatement());
- }
- };
- return asyncExecuteStatement(update, callable);
- }
-
- private final ParsedStatement executeBatchUpdateStatement =
- StatementParser.INSTANCE.parse(Statement.of("RUN BATCH"));
-
- @Override
- public long[] executeBatchUpdate(Iterable updates) {
- Preconditions.checkNotNull(updates);
- for (ParsedStatement update : updates) {
- Preconditions.checkArgument(
- update.isUpdate(),
- "Statement is not an update statement: " + update.getSqlWithoutComments());
- }
- ConnectionPreconditions.checkState(
- !isReadOnly(), "Batch update statements are not allowed in read-only mode");
- checkAndMarkUsed();
-
- long[] res;
- try {
- switch (autocommitDmlMode) {
- case TRANSACTIONAL:
- res =
- executeAsyncTransactionalUpdate(
- executeBatchUpdateStatement, new TransactionalBatchUpdateCallable(updates));
- break;
- case PARTITIONED_NON_ATOMIC:
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.FAILED_PRECONDITION,
- "Batch updates are not allowed in " + autocommitDmlMode);
- default:
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.FAILED_PRECONDITION, "Unknown dml mode: " + autocommitDmlMode);
- }
- } catch (SpannerBatchUpdateException e) {
- // Batch update exceptions does not cause a rollback.
- state = UnitOfWorkState.COMMITTED;
- throw e;
- } catch (Throwable e) {
- state = UnitOfWorkState.COMMIT_FAILED;
- throw e;
- }
- state = UnitOfWorkState.COMMITTED;
- return res;
- }
-
- /** Base class for executing DML updates (both single statements and batches). */
- private abstract class AbstractUpdateCallable implements Callable {
- abstract T executeUpdate(TransactionContext txContext);
-
- @Override
- public T call() throws Exception {
- try {
- txManager = dbClient.transactionManager();
- // Check the interrupted state after each (possible) round-trip to the db to allow the
- // statement to be cancelled.
- checkInterrupted();
- try (TransactionContext txContext = txManager.begin()) {
- checkInterrupted();
- T res = executeUpdate(txContext);
- checkInterrupted();
- txManager.commit();
- checkInterrupted();
- return res;
- }
- } finally {
- if (txManager != null) {
- // Calling txManager.close() will rollback the transaction if it is still active, i.e. if
- // an error occurred before the commit() call returned successfully.
- txManager.close();
- }
- }
- }
- }
-
- /** {@link Callable} for a single update statement. */
- private final class TransactionalUpdateCallable extends AbstractUpdateCallable {
- private final ParsedStatement update;
-
- private TransactionalUpdateCallable(ParsedStatement update) {
- this.update = update;
- }
-
- @Override
- Long executeUpdate(TransactionContext txContext) {
- return txContext.executeUpdate(update.getStatement());
- }
- }
-
- /** {@link Callable} for a batch update. */
- private final class TransactionalBatchUpdateCallable extends AbstractUpdateCallable {
- private final List updates;
-
- private TransactionalBatchUpdateCallable(Iterable updates) {
- this.updates = new LinkedList<>();
- for (ParsedStatement update : updates) {
- this.updates.add(update.getStatement());
- }
- }
-
- @Override
- long[] executeUpdate(TransactionContext txContext) {
- return txContext.batchUpdate(updates);
- }
- }
-
- private T executeAsyncTransactionalUpdate(
- final ParsedStatement update, final AbstractUpdateCallable callable) {
- long startedTime = System.currentTimeMillis();
- // This method uses a TransactionManager instead of the TransactionRunner in order to be able to
- // handle timeouts and canceling of a statement.
- while (true) {
- try {
- return asyncExecuteStatement(update, callable);
- } catch (AbortedException e) {
- try {
- Thread.sleep(e.getRetryDelayInMillis() / 1000);
- } catch (InterruptedException e1) {
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.CANCELLED, "Statement execution was interrupted", e1);
- }
- // Check whether the timeout time has been exceeded.
- long executionTime = System.currentTimeMillis() - startedTime;
- if (getStatementTimeout().hasTimeout()
- && executionTime > getStatementTimeout().getTimeoutValue(TimeUnit.MILLISECONDS)) {
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.DEADLINE_EXCEEDED,
- "Statement execution timeout occurred for " + update.getSqlWithoutComments());
- }
- }
- }
- }
-
- private void checkInterrupted() throws InterruptedException {
- if (Thread.currentThread().isInterrupted()) {
- throw new InterruptedException();
- }
- }
-
- @Override
- public void write(final Mutation mutation) {
- write(Arrays.asList(mutation));
- }
-
- @Override
- public void write(final Iterable mutations) {
- Preconditions.checkNotNull(mutations);
- ConnectionPreconditions.checkState(
- !isReadOnly(), "Update statements are not allowed in read-only mode");
- checkAndMarkUsed();
-
- writeTransaction = dbClient.readWriteTransaction();
- try {
- writeTransaction.run(
- new TransactionCallable() {
- @Override
- public Void run(TransactionContext transaction) throws Exception {
- transaction.buffer(mutations);
- return null;
- }
- });
- } catch (Throwable e) {
- state = UnitOfWorkState.COMMIT_FAILED;
- throw e;
- }
- state = UnitOfWorkState.COMMITTED;
- }
-
- @Override
- public void commit() {
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.FAILED_PRECONDITION, "Commit is not supported for single-use transactions");
- }
-
- @Override
- public void rollback() {
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.FAILED_PRECONDITION, "Rollback is not supported for single-use transactions");
- }
-
- @Override
- public long[] runBatch() {
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.FAILED_PRECONDITION, "Run batch is not supported for single-use transactions");
- }
-
- @Override
- public void abortBatch() {
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.FAILED_PRECONDITION, "Run batch is not supported for single-use transactions");
- }
-}
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/SpannerPool.java b/src/main/java/com/google/cloud/spanner/jdbc/SpannerPool.java
index 771ad505..8bcbcc19 100644
--- a/src/main/java/com/google/cloud/spanner/jdbc/SpannerPool.java
+++ b/src/main/java/com/google/cloud/spanner/jdbc/SpannerPool.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 Google LLC
+ * Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,413 +16,14 @@
package com.google.cloud.spanner.jdbc;
-import com.google.api.core.ApiFunction;
-import com.google.auth.Credentials;
-import com.google.cloud.NoCredentials;
-import com.google.cloud.spanner.ErrorCode;
-import com.google.cloud.spanner.Spanner;
-import com.google.cloud.spanner.SpannerException;
-import com.google.cloud.spanner.SpannerExceptionFactory;
-import com.google.cloud.spanner.SpannerOptions;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.MoreObjects;
-import com.google.common.base.Preconditions;
-import io.grpc.ManagedChannelBuilder;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Objects;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.TimeUnit;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-import javax.annotation.concurrent.GuardedBy;
-
-/**
- * Pool for keeping track of {@link Spanner} instances needed by JDBC connections.
- *
- * When a JDBC connection is opened for a Google Cloud Spanner database, a {@link Spanner} object
- * can be opened in the background. The {@link SpannerPool} keeps track of which {@link Spanner}
- * objects have been opened by connections during the lifetime of the JVM, which connections are
- * still opened and closed, and which {@link Spanner} objects could be closed.
- *
- *
Call the method {@link SpannerPool#closeSpannerPool()} at the end of your application to
- * gracefully shutdown all instances in the pool.
- */
+/** @see com.google.cloud.spanner.connection.SpannerPool */
+@Deprecated
public class SpannerPool {
- private static final Logger logger = Logger.getLogger(SpannerPool.class.getName());
+ private SpannerPool() {}
- /**
- * Closes the default {@link SpannerPool} and all {@link Spanner} instances that have been opened
- * by connections and that are still open. Call this method at the end of your application to
- * gracefully close all {@link Spanner} instances in the pool. Failing to call this method will
- * keep your application running for 60 seconds after you close the last {@link
- * java.sql.Connection} to Cloud Spanner, as this is the default timeout before the {@link
- * SpannerPool} closes the unused {@link Spanner} instances.
- */
+ /** @see com.google.cloud.spanner.connection.SpannerPool#closeSpannerPool() */
+ @Deprecated
public static void closeSpannerPool() {
- INSTANCE.checkAndCloseSpanners();
- }
-
- /**
- * The minimum number of milliseconds a {@link Spanner} should not have been used for a connection
- * before it is closed.
- */
- private static final long DEFAULT_CLOSE_SPANNER_AFTER_MILLISECONDS_UNUSED = 60000L;
-
- static final SpannerPool INSTANCE =
- new SpannerPool(DEFAULT_CLOSE_SPANNER_AFTER_MILLISECONDS_UNUSED);
-
- @VisibleForTesting
- enum CheckAndCloseSpannersMode {
- WARN,
- ERROR;
- }
-
- private final class CloseSpannerRunnable implements Runnable {
- @Override
- public void run() {
- try {
- checkAndCloseSpanners(CheckAndCloseSpannersMode.WARN);
- } catch (Exception e) {
- // ignore
- }
- }
- }
-
- private final class CloseUnusedSpannersRunnable implements Runnable {
- @Override
- public void run() {
- try {
- closeUnusedSpanners(SpannerPool.this.closeSpannerAfterMillisecondsUnused);
- } catch (Throwable e) {
- logger.log(Level.FINE, "Scheduled call to closeUnusedSpanners failed", e);
- }
- }
- }
-
- static class SpannerPoolKey {
- private final String host;
- private final String projectId;
- private final Credentials credentials;
- private final Integer numChannels;
- private final boolean usePlainText;
- private final String userAgent;
-
- private static SpannerPoolKey of(ConnectionOptions options) {
- return new SpannerPoolKey(options);
- }
-
- private SpannerPoolKey(ConnectionOptions options) {
- this.host = options.getHost();
- this.projectId = options.getProjectId();
- this.credentials = options.getCredentials();
- this.numChannels = options.getNumChannels();
- this.usePlainText = options.isUsePlainText();
- this.userAgent = options.getUserAgent();
- }
-
- @Override
- public boolean equals(Object o) {
- if (!(o instanceof SpannerPoolKey)) {
- return false;
- }
- SpannerPoolKey other = (SpannerPoolKey) o;
- return Objects.equals(this.host, other.host)
- && Objects.equals(this.projectId, other.projectId)
- && Objects.equals(this.credentials, other.credentials)
- && Objects.equals(this.numChannels, other.numChannels)
- && Objects.equals(this.usePlainText, other.usePlainText)
- && Objects.equals(this.userAgent, other.userAgent);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(
- this.host,
- this.projectId,
- this.credentials,
- this.numChannels,
- this.usePlainText,
- this.userAgent);
- }
- }
-
- /**
- * The management threads of a {@link SpannerPool} are lazily initialized to prevent unnecessary
- * threads to be created when the connection API is not used.
- */
- private boolean initialized = false;
- /**
- * Thread that will be run as a shutdown hook on closing the application. This thread will close
- * any Spanner instances opened by the Connection API that are still open.
- */
- private Thread shutdownThread = null;
-
- /**
- * Keep unused {@link Spanner} instances open and in the pool for this duration after all its
- * {@link Connection}s have been closed. This prevents unnecessary opening and closing of {@link
- * Spanner} instances.
- */
- private final long closeSpannerAfterMillisecondsUnused;
-
- /**
- * This scheduled task will close all {@link Spanner} objects that have not been used for an open
- * connection for at least {@link SpannerPool#DEFAULT_CLOSE_SPANNER_AFTER_MILLISECONDS_UNUSED}
- * milliseconds.
- */
- private ScheduledExecutorService closerService;
-
- @GuardedBy("this")
- private final Map spanners = new HashMap<>();
-
- @GuardedBy("this")
- private final Map> connections = new HashMap<>();
-
- /**
- * Keep track of the moment that the last connection for a specific {@link SpannerPoolKey} was
- * closed, so that we can use this to determine whether a {@link Spanner} instance should be
- * closed and removed from the pool. As {@link Spanner} instances are expensive to create and
- * close, we do not want to do that unnecessarily. By adding a delay between the moment the last
- * {@link Connection} for a {@link Spanner} was closed and the moment we close the {@link Spanner}
- * instance, we prevent applications that open one or more connections for a process and close all
- * these connections at the end of the process from getting a severe performance penalty from
- * opening and closing {@link Spanner} instances all the time.
- *
- * {@link Spanner} instances are closed and removed from the pool when the last connection was
- * closed more than {@link #closeSpannerAfterMillisecondsUnused} milliseconds ago.
- */
- @GuardedBy("this")
- private final Map lastConnectionClosedAt = new HashMap<>();
-
- @VisibleForTesting
- SpannerPool() {
- this(0L);
- }
-
- @VisibleForTesting
- SpannerPool(long closeSpannerAfterMillisecondsUnused) {
- this.closeSpannerAfterMillisecondsUnused = closeSpannerAfterMillisecondsUnused;
- }
-
- /**
- * Gets a Spanner object for a connection with the properties specified in the {@link
- * ConnectionOptions} object. The {@link SpannerPool} will manage a pool of opened Spanner objects
- * for the different connections, and reuse Spanner objects whenever possible. Spanner objects
- * will also be closed down when the application is closing.
- *
- * @param options The specification of the Spanner database to connect to.
- * @param connection The {@link ConnectionImpl} that will be created. This {@link ConnectionImpl}
- * will be tracked by the pool to know when a {@link Spanner} object can be closed.
- * @return an opened {@link Spanner} object that can be used by a connection to communicate with
- * the Spanner database.
- */
- Spanner getSpanner(ConnectionOptions options, ConnectionImpl connection) {
- Preconditions.checkNotNull(options);
- Preconditions.checkNotNull(connection);
- SpannerPoolKey key = SpannerPoolKey.of(options);
- Spanner spanner;
- synchronized (this) {
- if (!initialized) {
- initialize();
- }
- if (spanners.get(key) != null) {
- spanner = spanners.get(key);
- } else {
- spanner = createSpanner(key);
- spanners.put(key, spanner);
- }
- List registeredConnectionsForSpanner = connections.get(key);
- if (registeredConnectionsForSpanner == null) {
- registeredConnectionsForSpanner = new ArrayList<>();
- connections.put(key, registeredConnectionsForSpanner);
- }
- registeredConnectionsForSpanner.add(connection);
- lastConnectionClosedAt.remove(key);
- return spanner;
- }
- }
-
- private void initialize() {
- shutdownThread = new Thread(new CloseSpannerRunnable(), "SpannerPool shutdown hook");
- Runtime.getRuntime().addShutdownHook(shutdownThread);
- if (this.closeSpannerAfterMillisecondsUnused > 0) {
- this.closerService =
- Executors.newSingleThreadScheduledExecutor(
- new ThreadFactory() {
- @Override
- public Thread newThread(Runnable r) {
- Thread thread = new Thread(r, "close-unused-spanners-worker");
- thread.setDaemon(true);
- return thread;
- }
- });
- this.closerService.scheduleAtFixedRate(
- new CloseUnusedSpannersRunnable(),
- this.closeSpannerAfterMillisecondsUnused,
- this.closeSpannerAfterMillisecondsUnused,
- TimeUnit.MILLISECONDS);
- }
- initialized = true;
- }
-
- @SuppressWarnings("rawtypes")
- @VisibleForTesting
- Spanner createSpanner(SpannerPoolKey key) {
- SpannerOptions.Builder builder = SpannerOptions.newBuilder();
- builder
- .setClientLibToken(MoreObjects.firstNonNull(key.userAgent, JdbcDriver.getClientLibToken()))
- .setHost(key.host)
- .setProjectId(key.projectId)
- .setCredentials(key.credentials);
- if (key.numChannels != null) {
- builder.setNumChannels(key.numChannels);
- }
- if (key.usePlainText) {
- // Credentials may not be sent over a plain text channel.
- builder.setCredentials(NoCredentials.getInstance());
- // Set a custom channel configurator to allow http instead of https.
- builder.setChannelConfigurator(
- new ApiFunction() {
- @Override
- public ManagedChannelBuilder apply(ManagedChannelBuilder input) {
- input.usePlaintext();
- return input;
- }
- });
- }
- return builder.build().getService();
- }
-
- /**
- * Remove the given {@link ConnectionImpl} from the list of connections that should be monitored
- * by this pool.
- *
- * @param options The {@link ConnectionOptions} that were used to create the connection.
- * @param connection The {@link ConnectionImpl} to remove from this pool..
- */
- void removeConnection(ConnectionOptions options, ConnectionImpl connection) {
- Preconditions.checkNotNull(options);
- Preconditions.checkNotNull(connection);
- SpannerPoolKey key = SpannerPoolKey.of(options);
- synchronized (this) {
- if (spanners.containsKey(key) && connections.containsKey(key)) {
- List registeredConnections = connections.get(key);
- // Remove the connection from the pool.
- if (registeredConnections == null || !registeredConnections.remove(connection)) {
- logger.log(
- Level.WARNING,
- "There are no connections registered for ConnectionOptions " + options.toString());
- } else {
- // Check if this was the last connection for this spanner key.
- if (registeredConnections.isEmpty()) {
- // Register the moment the last connection for this Spanner key was removed, so we know
- // which Spanner objects we could close.
- lastConnectionClosedAt.put(key, System.currentTimeMillis());
- }
- }
- } else {
- logger.log(
- Level.WARNING,
- "There is no Spanner registered for ConnectionOptions " + options.toString());
- }
- }
- }
-
- /**
- * Checks that there are no {@link Connection}s that have been created by this {@link SpannerPool}
- * that are still open, and then closes all {@link Spanner} instances in the pool. If there is at
- * least one unclosed {@link Connection} left in the pool, the method will throw a {@link
- * SpannerException} and no {@link Spanner} instances will be closed.
- */
- void checkAndCloseSpanners() {
- checkAndCloseSpanners(CheckAndCloseSpannersMode.ERROR);
- }
-
- @VisibleForTesting
- void checkAndCloseSpanners(CheckAndCloseSpannersMode mode) {
- List keysStillInUse = new ArrayList<>();
- synchronized (this) {
- for (Entry entry : spanners.entrySet()) {
- if (!lastConnectionClosedAt.containsKey(entry.getKey())) {
- keysStillInUse.add(entry.getKey());
- }
- }
- if (keysStillInUse.isEmpty() || mode == CheckAndCloseSpannersMode.WARN) {
- if (!keysStillInUse.isEmpty()) {
- logLeakedConnections(keysStillInUse);
- logger.log(
- Level.WARNING,
- "There is/are "
- + keysStillInUse.size()
- + " connection(s) still open."
- + " Close all connections before stopping the application");
- }
- // Force close all Spanner instances by passing in a value that will always be less than the
- // difference between the current time and the close time of a connection.
- closeUnusedSpanners(Long.MIN_VALUE);
- } else {
- logLeakedConnections(keysStillInUse);
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.FAILED_PRECONDITION,
- "There is/are "
- + keysStillInUse.size()
- + " connection(s) still open. Close all connections before calling closeSpanner()");
- }
- }
- }
-
- private void logLeakedConnections(List keysStillInUse) {
- synchronized (this) {
- for (SpannerPoolKey key : keysStillInUse) {
- for (ConnectionImpl con : connections.get(key)) {
- if (!con.isClosed() && con.getLeakedException() != null) {
- logger.log(Level.WARNING, "Leaked connection", con.getLeakedException());
- }
- }
- }
- }
- }
-
- /**
- * Closes Spanner objects that are no longer in use by connections, and where the last connection
- * that used it was closed more than closeSpannerAfterMillisecondsUnused
seconds ago.
- * The delay ensures that Spanner objects are not closed unless there's a good reason for it.
- *
- * @param closeSpannerAfterMillisecondsUnused The number of milliseconds a {@link Spanner} object
- * should not have been used for a {@link Connection} before it is closed by this method.
- */
- @VisibleForTesting
- void closeUnusedSpanners(long closeSpannerAfterMillisecondsUnused) {
- List keysToBeRemoved = new ArrayList<>();
- synchronized (this) {
- for (Entry entry : lastConnectionClosedAt.entrySet()) {
- Long closedAt = entry.getValue();
- // Check whether the last connection was closed more than
- // closeSpannerAfterMillisecondsUnused milliseconds ago.
- if (closedAt != null
- && ((System.currentTimeMillis() - closedAt.longValue()))
- > closeSpannerAfterMillisecondsUnused) {
- Spanner spanner = spanners.get(entry.getKey());
- if (spanner != null) {
- try {
- spanner.close();
- } finally {
- // Even if the close operation failed, we should remove the spanner object as it is no
- // longer valid.
- spanners.remove(entry.getKey());
- keysToBeRemoved.add(entry.getKey());
- }
- }
- }
- }
- for (SpannerPoolKey key : keysToBeRemoved) {
- lastConnectionClosedAt.remove(key);
- }
- }
+ com.google.cloud.spanner.connection.SpannerPool.closeSpannerPool();
}
}
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/StatementExecutionInterceptor.java b/src/main/java/com/google/cloud/spanner/jdbc/StatementExecutionInterceptor.java
deleted file mode 100644
index 227bc629..00000000
--- a/src/main/java/com/google/cloud/spanner/jdbc/StatementExecutionInterceptor.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright 2019 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.cloud.spanner.jdbc;
-
-import com.google.cloud.spanner.ResultSet;
-import com.google.cloud.spanner.jdbc.StatementParser.ParsedStatement;
-
-/** Interface for interceptors that are invoked before a statement is executed. */
-interface StatementExecutionInterceptor {
- void intercept(ParsedStatement statement, StatementExecutionStep step, UnitOfWork transaction);
-}
-
-/**
- * Enum passed in to a {@link StatementExecutionInterceptor} to determine what/why a statement is
- * being executed.
- */
-enum StatementExecutionStep {
- /** The initial execution of a statement (DML/Query). */
- EXECUTE_STATEMENT,
- /** A call to {@link ResultSet#next()}. */
- CALL_NEXT_ON_RESULT_SET,
- /** Execution of the statement during an internal transaction retry. */
- RETRY_STATEMENT,
- /** A call to {@link ResultSet#next()} during internal transaction retry. */
- RETRY_NEXT_ON_RESULT_SET;
-}
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/StatementExecutor.java b/src/main/java/com/google/cloud/spanner/jdbc/StatementExecutor.java
deleted file mode 100644
index 994c4c44..00000000
--- a/src/main/java/com/google/cloud/spanner/jdbc/StatementExecutor.java
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
- * Copyright 2019 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.cloud.spanner.jdbc;
-
-import com.google.cloud.spanner.jdbc.ReadOnlyStalenessUtil.DurationValueGetter;
-import com.google.cloud.spanner.jdbc.StatementParser.ParsedStatement;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Preconditions;
-import com.google.common.util.concurrent.MoreExecutors;
-import com.google.common.util.concurrent.ThreadFactoryBuilder;
-import com.google.protobuf.Duration;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Future;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-
-/**
- * {@link StatementExecutor} is responsible for executing statements on a {@link Connection}.
- * Statements are executed using a separate executor to allow timeouts and cancellation of
- * statements.
- */
-class StatementExecutor {
-
- /** Simple holder class for statement timeout that allows us to pass the value by reference. */
- static class StatementTimeout {
- /**
- * Only {@link TimeUnit#NANOSECONDS}, {@link TimeUnit#MICROSECONDS}, {@link
- * TimeUnit#MILLISECONDS} and {@link TimeUnit#SECONDS} may be used to specify a statement
- * timeout.
- */
- static boolean isValidTimeoutUnit(TimeUnit unit) {
- return unit == TimeUnit.NANOSECONDS
- || unit == TimeUnit.MICROSECONDS
- || unit == TimeUnit.MILLISECONDS
- || unit == TimeUnit.SECONDS;
- }
-
- /** The statement timeout. */
- private Duration duration = null;
-
- /** Creates a {@link StatementTimeout} that will never timeout. */
- @VisibleForTesting
- static StatementTimeout nullTimeout() {
- return new StatementTimeout();
- }
-
- /** Creates a {@link StatementTimeout} with the given duration. */
- @VisibleForTesting
- static StatementTimeout of(long timeout, TimeUnit unit) {
- Preconditions.checkArgument(timeout > 0L);
- Preconditions.checkArgument(isValidTimeoutUnit(unit));
- StatementTimeout res = new StatementTimeout();
- res.duration = ReadOnlyStalenessUtil.createDuration(timeout, unit);
- return res;
- }
-
- /**
- * Does this {@link StatementTimeout} have an actual timeout (i.e. it will eventually timeout).
- */
- boolean hasTimeout() {
- return duration != null;
- }
-
- void clearTimeoutValue() {
- this.duration = null;
- }
-
- void setTimeoutValue(long timeout, TimeUnit unit) {
- Preconditions.checkArgument(timeout > 0L);
- Preconditions.checkArgument(isValidTimeoutUnit(unit));
- this.duration = ReadOnlyStalenessUtil.createDuration(timeout, unit);
- }
-
- long getTimeoutValue(TimeUnit unit) {
- Preconditions.checkArgument(isValidTimeoutUnit(unit));
- return duration == null ? 0L : ReadOnlyStalenessUtil.durationToUnits(duration, unit);
- }
-
- /**
- * Returns the {@link TimeUnit} with the least precision that could be used to represent this
- * {@link StatementTimeout} without loss of precision.
- */
- TimeUnit getAppropriateTimeUnit() {
- ConnectionPreconditions.checkState(
- duration != null, "This StatementTimeout has no timeout value");
- return ReadOnlyStalenessUtil.getAppropriateTimeUnit(
- new DurationValueGetter() {
- @Override
- public long getDuration(TimeUnit unit) {
- return StatementTimeout.this.getTimeoutValue(unit);
- }
-
- @Override
- public boolean hasDuration() {
- return StatementTimeout.this.hasTimeout();
- }
- });
- }
- }
-
- /**
- * Use a {@link ThreadFactory} that produces daemon threads and sets recognizable name on the
- * threads.
- */
- private static final ThreadFactory THREAD_FACTORY =
- new ThreadFactoryBuilder()
- .setDaemon(true)
- .setNameFormat("connection-executor-%d")
- .setThreadFactory(MoreExecutors.platformThreadFactory())
- .build();
-
- /** Creates an {@link ExecutorService} for a {@link StatementExecutor}. */
- private static ExecutorService createExecutorService() {
- return new ThreadPoolExecutor(
- 1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(), THREAD_FACTORY);
- }
-
- private ExecutorService executor = createExecutorService();
-
- /**
- * Interceptors that should be invoked before or after a statement is executed can be registered
- * for a connection. This are added to this list. The interceptors are intended for test usage.
- */
- private final List interceptors;
-
- @VisibleForTesting
- StatementExecutor() {
- this.interceptors = Collections.emptyList();
- }
-
- StatementExecutor(List interceptors) {
- this.interceptors = Collections.unmodifiableList(interceptors);
- }
-
- /**
- * Recreates this {@link StatementExecutor} and its {@link ExecutorService}. This can be necessary
- * if a statement times out or is cancelled, and it cannot be guaranteed that the statement
- * execution can be terminated. In order to prevent the single threaded {@link ExecutorService} to
- * continue to block on the timed out/cancelled statement, a new {@link ExecutorService} is
- * created.
- */
- void recreate() {
- executor.shutdown();
- executor = createExecutorService();
- }
-
- /**
- * Shutdown this executor now and do not wait for any statement that is being executed to finish.
- */
- List shutdownNow() {
- return executor.shutdownNow();
- }
-
- /** Execute a statement on this {@link StatementExecutor}. */
- Future submit(Callable callable) {
- return executor.submit(callable);
- }
-
- /**
- * Invoke the interceptors that have been registered for this {@link StatementExecutor} for the
- * given step.
- */
- void invokeInterceptors(
- ParsedStatement statement, StatementExecutionStep step, UnitOfWork transaction) {
- for (StatementExecutionInterceptor interceptor : interceptors) {
- interceptor.intercept(statement, step, transaction);
- }
- }
-}
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/StatementParser.java b/src/main/java/com/google/cloud/spanner/jdbc/StatementParser.java
deleted file mode 100644
index e3e998a2..00000000
--- a/src/main/java/com/google/cloud/spanner/jdbc/StatementParser.java
+++ /dev/null
@@ -1,458 +0,0 @@
-/*
- * Copyright 2019 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.cloud.spanner.jdbc;
-
-import com.google.cloud.spanner.ErrorCode;
-import com.google.cloud.spanner.SpannerExceptionFactory;
-import com.google.cloud.spanner.Statement;
-import com.google.cloud.spanner.jdbc.ClientSideStatementImpl.CompileException;
-import com.google.cloud.spanner.jdbc.StatementParser.ParsedStatement;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Preconditions;
-import com.google.common.collect.ImmutableSet;
-import com.google.spanner.v1.ExecuteSqlRequest.QueryOptions;
-import java.util.Collections;
-import java.util.Objects;
-import java.util.Set;
-
-/**
- * Internal class for the Spanner Connection API.
- *
- * Parses {@link ClientSideStatement}s and normal SQL statements. The parser is able to recognize
- * the type of statement, allowing the connection API to know which method on Spanner should be
- * called. The parser does not validate the validity of statements, except for {@link
- * ClientSideStatement}s. This means that an invalid DML statement could be accepted by the {@link
- * StatementParser} and sent to Spanner, and Spanner will then reject it with some error message.
- */
-class StatementParser {
- /** Singleton instance of {@link StatementParser}. */
- public static final StatementParser INSTANCE = new StatementParser();
-
- /** The type of statement that has been recognized by the parser. */
- enum StatementType {
- CLIENT_SIDE,
- DDL,
- QUERY,
- UPDATE,
- UNKNOWN;
- }
-
- /** A statement that has been parsed */
- static class ParsedStatement {
- private final StatementType type;
- private final ClientSideStatementImpl clientSideStatement;
- private final Statement statement;
- private final String sqlWithoutComments;
-
- private static ParsedStatement clientSideStatement(
- ClientSideStatementImpl clientSideStatement,
- Statement statement,
- String sqlWithoutComments) {
- return new ParsedStatement(clientSideStatement, statement, sqlWithoutComments);
- }
-
- private static ParsedStatement ddl(Statement statement, String sqlWithoutComments) {
- return new ParsedStatement(StatementType.DDL, statement, sqlWithoutComments);
- }
-
- private static ParsedStatement query(
- Statement statement, String sqlWithoutComments, QueryOptions defaultQueryOptions) {
- return new ParsedStatement(
- StatementType.QUERY, statement, sqlWithoutComments, defaultQueryOptions);
- }
-
- private static ParsedStatement update(Statement statement, String sqlWithoutComments) {
- return new ParsedStatement(StatementType.UPDATE, statement, sqlWithoutComments);
- }
-
- private static ParsedStatement unknown(Statement statement, String sqlWithoutComments) {
- return new ParsedStatement(StatementType.UNKNOWN, statement, sqlWithoutComments);
- }
-
- private ParsedStatement(
- ClientSideStatementImpl clientSideStatement,
- Statement statement,
- String sqlWithoutComments) {
- Preconditions.checkNotNull(clientSideStatement);
- Preconditions.checkNotNull(statement);
- this.type = StatementType.CLIENT_SIDE;
- this.clientSideStatement = clientSideStatement;
- this.statement = statement;
- this.sqlWithoutComments = sqlWithoutComments;
- }
-
- private ParsedStatement(StatementType type, Statement statement, String sqlWithoutComments) {
- this(type, statement, sqlWithoutComments, null);
- }
-
- private ParsedStatement(
- StatementType type,
- Statement statement,
- String sqlWithoutComments,
- QueryOptions defaultQueryOptions) {
- Preconditions.checkNotNull(type);
- Preconditions.checkNotNull(statement);
- this.type = type;
- this.clientSideStatement = null;
- this.statement = mergeQueryOptions(statement, defaultQueryOptions);
- this.sqlWithoutComments = sqlWithoutComments;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(
- this.type, this.clientSideStatement, this.statement, this.sqlWithoutComments);
- }
-
- @Override
- public boolean equals(Object other) {
- if (!(other instanceof ParsedStatement)) {
- return false;
- }
- ParsedStatement o = (ParsedStatement) other;
- return Objects.equals(this.type, o.type)
- && Objects.equals(this.clientSideStatement, o.clientSideStatement)
- && Objects.equals(this.statement, o.statement)
- && Objects.equals(this.sqlWithoutComments, o.sqlWithoutComments);
- }
-
- StatementType getType() {
- return type;
- }
-
- boolean isQuery() {
- switch (type) {
- case CLIENT_SIDE:
- return getClientSideStatement().isQuery();
- case QUERY:
- return true;
- case UPDATE:
- case DDL:
- case UNKNOWN:
- default:
- }
- return false;
- }
-
- boolean isUpdate() {
- switch (type) {
- case CLIENT_SIDE:
- return getClientSideStatement().isUpdate();
- case UPDATE:
- return true;
- case QUERY:
- case DDL:
- case UNKNOWN:
- default:
- }
- return false;
- }
-
- boolean isDdl() {
- switch (type) {
- case DDL:
- return true;
- case CLIENT_SIDE:
- case UPDATE:
- case QUERY:
- case UNKNOWN:
- default:
- }
- return false;
- }
-
- Statement getStatement() {
- return statement;
- }
-
- /**
- * Merges the {@link QueryOptions} of the {@link Statement} with the current {@link
- * QueryOptions} of this connection. The {@link QueryOptions} that are already present on the
- * statement take precedence above the connection {@link QueryOptions}.
- */
- Statement mergeQueryOptions(Statement statement, QueryOptions defaultQueryOptions) {
- if (defaultQueryOptions == null
- || defaultQueryOptions.equals(QueryOptions.getDefaultInstance())) {
- return statement;
- }
- if (statement.getQueryOptions() == null) {
- return statement.toBuilder().withQueryOptions(defaultQueryOptions).build();
- }
- return statement
- .toBuilder()
- .withQueryOptions(
- defaultQueryOptions.toBuilder().mergeFrom(statement.getQueryOptions()).build())
- .build();
- }
-
- String getSqlWithoutComments() {
- return sqlWithoutComments;
- }
-
- ClientSideStatement getClientSideStatement() {
- Preconditions.checkState(
- clientSideStatement != null,
- "This ParsedStatement does not contain a ClientSideStatement");
- return clientSideStatement;
- }
- }
-
- private final Set ddlStatements = ImmutableSet.of("CREATE", "DROP", "ALTER");
- private final Set selectStatements = ImmutableSet.of("SELECT", "WITH");
- private final Set dmlStatements = ImmutableSet.of("INSERT", "UPDATE", "DELETE");
- private final Set statements;
-
- /** Private constructor for singleton instance. */
- private StatementParser() {
- try {
- statements =
- Collections.unmodifiableSet(ClientSideStatements.INSTANCE.getCompiledStatements());
- } catch (CompileException e) {
- throw new RuntimeException(e);
- }
- }
-
- /**
- * Parses the given statement and categorizes it as one of the possible {@link StatementType}s.
- * The validity of the statement is not checked, unless it is a client-side statement.
- *
- * @param statement The statement to parse.
- * @return the parsed and categorized statement.
- */
- ParsedStatement parse(Statement statement) {
- return parse(statement, null);
- }
-
- ParsedStatement parse(Statement statement, QueryOptions defaultQueryOptions) {
- 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);
- } else if (isUpdateStatement(sql)) {
- return ParsedStatement.update(statement, sql);
- } else if (isDdlStatement(sql)) {
- return ParsedStatement.ddl(statement, sql);
- }
- return ParsedStatement.unknown(statement, sql);
- }
-
- /**
- * Parses the given statement as a client-side statement. Client-side statements are statements
- * that are never sent to Cloud Spanner, but that are interpreted by the Connection API and then
- * translated into some action, such as for example starting a transaction or getting the last
- * commit timestamp.
- *
- * @param sql The statement to try to parse as a client-side statement (without any comments).
- * @return a valid {@link ClientSideStatement} or null if the statement is not a client-side
- * statement.
- */
- @VisibleForTesting
- ClientSideStatementImpl parseClientSideStatement(String sql) {
- for (ClientSideStatementImpl css : statements) {
- if (css.matches(sql)) {
- return css;
- }
- }
- return null;
- }
-
- /**
- * Checks whether the given statement is (probably) a DDL statement. The method does not check the
- * validity of the statement, only if it is a DDL statement based on the first word in the
- * statement.
- *
- * @param sql The statement to check (without any comments).
- * @return true
if the statement is a DDL statement (i.e. starts with 'CREATE',
- * 'ALTER' or 'DROP').
- */
- boolean isDdlStatement(String sql) {
- return statementStartsWith(sql, ddlStatements);
- }
-
- /**
- * Checks whether the given statement is (probably) a SELECT query. The method does not check the
- * validity of the statement, only if it is a SELECT statement based on the first word in the
- * statement.
- *
- * @param sql The statement to check (without any comments).
- * @return true
if the statement is a SELECT statement (i.e. starts with 'SELECT').
- */
- boolean isQuery(String sql) {
- // Skip any query hints at the beginning of the query.
- if (sql.startsWith("@")) {
- sql = removeStatementHint(sql);
- }
- return statementStartsWith(sql, selectStatements);
- }
-
- /**
- * Checks whether the given statement is (probably) an update statement. The method does not check
- * the validity of the statement, only if it is an update statement based on the first word in the
- * statement.
- *
- * @param sql The statement to check (without any comments).
- * @return true
if the statement is a DML update statement (i.e. starts with
- * 'INSERT', 'UPDATE' or 'DELETE').
- */
- boolean isUpdateStatement(String sql) {
- return statementStartsWith(sql, dmlStatements);
- }
-
- private boolean statementStartsWith(String sql, Iterable checkStatements) {
- Preconditions.checkNotNull(sql);
- String[] tokens = sql.split("\\s+", 2);
- if (tokens.length > 0) {
- for (String check : checkStatements) {
- if (tokens[0].equalsIgnoreCase(check)) {
- return true;
- }
- }
- }
- return false;
- }
-
- /**
- * Removes comments from and trims the given sql statement. Spanner supports three types of
- * comments:
- *
- *
- * - Single line comments starting with '--'
- *
- Single line comments starting with '#'
- *
- Multi line comments between '/*' and '*/'
- *
- *
- * Reference: https://cloud.google.com/spanner/docs/lexical#comments
- *
- * @param sql The sql statement to remove comments from and to trim.
- * @return the sql statement without the comments and leading and trailing spaces.
- */
- static String removeCommentsAndTrim(String sql) {
- Preconditions.checkNotNull(sql);
- final char SINGLE_QUOTE = '\'';
- final char DOUBLE_QUOTE = '"';
- final char BACKTICK_QUOTE = '`';
- final char HYPHEN = '-';
- final char DASH = '#';
- final char SLASH = '/';
- final char ASTERIKS = '*';
- boolean isInQuoted = false;
- boolean isInSingleLineComment = false;
- boolean isInMultiLineComment = false;
- char startQuote = 0;
- boolean lastCharWasEscapeChar = false;
- boolean isTripleQuoted = false;
- StringBuilder res = new StringBuilder(sql.length());
- int index = 0;
- while (index < sql.length()) {
- char c = sql.charAt(index);
- if (isInQuoted) {
- if ((c == '\n' || c == '\r') && !isTripleQuoted) {
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.INVALID_ARGUMENT, "SQL statement contains an unclosed literal: " + sql);
- } else if (c == startQuote) {
- if (lastCharWasEscapeChar) {
- lastCharWasEscapeChar = false;
- } else if (isTripleQuoted) {
- if (sql.length() > index + 2
- && sql.charAt(index + 1) == startQuote
- && sql.charAt(index + 2) == startQuote) {
- isInQuoted = false;
- startQuote = 0;
- isTripleQuoted = false;
- res.append(c).append(c);
- index += 2;
- }
- } else {
- isInQuoted = false;
- startQuote = 0;
- }
- } else if (c == '\\') {
- lastCharWasEscapeChar = true;
- } else {
- lastCharWasEscapeChar = false;
- }
- res.append(c);
- } else {
- // We are not in a quoted string.
- if (isInSingleLineComment) {
- if (c == '\n') {
- isInSingleLineComment = false;
- // Include the line feed in the result.
- res.append(c);
- }
- } else if (isInMultiLineComment) {
- if (sql.length() > index + 1 && c == ASTERIKS && sql.charAt(index + 1) == SLASH) {
- isInMultiLineComment = false;
- index++;
- }
- } else {
- if (c == DASH
- || (sql.length() > index + 1 && c == HYPHEN && sql.charAt(index + 1) == HYPHEN)) {
- // This is a single line comment.
- isInSingleLineComment = true;
- } else if (sql.length() > index + 1 && c == SLASH && sql.charAt(index + 1) == ASTERIKS) {
- isInMultiLineComment = true;
- index++;
- } else {
- if (c == SINGLE_QUOTE || c == DOUBLE_QUOTE || c == BACKTICK_QUOTE) {
- isInQuoted = true;
- startQuote = c;
- // Check whether it is a triple-quote.
- if (sql.length() > index + 2
- && sql.charAt(index + 1) == startQuote
- && sql.charAt(index + 2) == startQuote) {
- isTripleQuoted = true;
- res.append(c).append(c);
- index += 2;
- }
- }
- res.append(c);
- }
- }
- }
- index++;
- }
- if (isInQuoted) {
- throw SpannerExceptionFactory.newSpannerException(
- ErrorCode.INVALID_ARGUMENT, "SQL statement contains an unclosed literal: " + sql);
- }
- if (res.length() > 0 && res.charAt(res.length() - 1) == ';') {
- res.deleteCharAt(res.length() - 1);
- }
- return res.toString().trim();
- }
-
- /** Removes any statement hints at the beginning of the statement. */
- static String removeStatementHint(String sql) {
- // Valid statement hints at the beginning of a SQL statement can only contain a fixed set of
- // possible values. Although it is possible to add a @{FORCE_INDEX=...} as a statement hint, the
- // only allowed value is _BASE_TABLE. This means that we can safely assume that the statement
- // hint will not contain any special characters, for example a closing curly brace, and
- // that we can keep the check simple by just searching for the first occurrence of a closing
- // curly brace at the end of the statement hint.
- int startStatementHintIndex = sql.indexOf('{');
- int endStatementHintIndex = sql.indexOf('}');
- if (startStatementHintIndex == -1 || startStatementHintIndex > endStatementHintIndex) {
- // Looks like an invalid statement hint. Just ignore at this point and let the caller handle
- // the invalid query.
- return sql;
- }
- return removeCommentsAndTrim(sql.substring(endStatementHintIndex + 1));
- }
-}
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/StatementResult.java b/src/main/java/com/google/cloud/spanner/jdbc/StatementResult.java
deleted file mode 100644
index 9820716a..00000000
--- a/src/main/java/com/google/cloud/spanner/jdbc/StatementResult.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright 2019 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.cloud.spanner.jdbc;
-
-import com.google.cloud.spanner.ResultSet;
-
-/**
- * A result of the execution of a statement. Statements that are executed by the {@link
- * Connection#execute(com.google.cloud.spanner.Statement)} method could have different types of
- * return values. These are wrapped in a {@link StatementResult}.
- */
-interface StatementResult {
-
- /**
- * Enum indicating the type of result that was returned by {@link
- * Connection#execute(com.google.cloud.spanner.Statement)}
- */
- enum ResultType {
- /**
- * A result set either returned by a query on Cloud Spanner or a local result set generated by a
- * client side statement.
- */
- RESULT_SET,
- /** An update count returned by Cloud Spanner. */
- UPDATE_COUNT,
- /**
- * DDL statements and client side statements that set the state of a connection return no
- * result.
- */
- NO_RESULT;
- }
-
- /** The type of client side statement that was executed. */
- enum ClientSideStatementType {
- SHOW_AUTOCOMMIT,
- SET_AUTOCOMMIT,
- SHOW_READONLY,
- SET_READONLY,
- SHOW_RETRY_ABORTS_INTERNALLY,
- SET_RETRY_ABORTS_INTERNALLY,
- SHOW_AUTOCOMMIT_DML_MODE,
- SET_AUTOCOMMIT_DML_MODE,
- SHOW_STATEMENT_TIMEOUT,
- SET_STATEMENT_TIMEOUT,
- SHOW_READ_TIMESTAMP,
- SHOW_COMMIT_TIMESTAMP,
- SHOW_READ_ONLY_STALENESS,
- SET_READ_ONLY_STALENESS,
- SHOW_OPTIMIZER_VERSION,
- SET_OPTIMIZER_VERSION,
- BEGIN,
- COMMIT,
- ROLLBACK,
- SET_TRANSACTION_MODE,
- START_BATCH_DDL,
- START_BATCH_DML,
- RUN_BATCH,
- ABORT_BATCH;
- }
-
- /**
- * Returns the {@link ResultType} of this result.
- *
- * @return the result type.
- */
- ResultType getResultType();
-
- /**
- * @return the {@link ClientSideStatementType} that was executed, or null if no such statement was
- * executed.
- */
- ClientSideStatementType getClientSideStatementType();
-
- /**
- * Returns the {@link ResultSet} held by this result. May only be called if the type of this
- * result is {@link ResultType#RESULT_SET}.
- *
- * @return the {@link ResultSet} held by this result.
- */
- ResultSet getResultSet();
-
- /**
- * Returns the update count held by this result. May only be called if the type of this result is
- * {@link ResultType#UPDATE_COUNT}.
- *
- * @return the update count held by this result.
- */
- Long getUpdateCount();
-}
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/StatementResultImpl.java b/src/main/java/com/google/cloud/spanner/jdbc/StatementResultImpl.java
deleted file mode 100644
index 6748311e..00000000
--- a/src/main/java/com/google/cloud/spanner/jdbc/StatementResultImpl.java
+++ /dev/null
@@ -1,187 +0,0 @@
-/*
- * Copyright 2019 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.cloud.spanner.jdbc;
-
-import com.google.cloud.Timestamp;
-import com.google.cloud.spanner.ResultSet;
-import com.google.cloud.spanner.ResultSets;
-import com.google.cloud.spanner.Struct;
-import com.google.cloud.spanner.Type;
-import com.google.cloud.spanner.Type.StructField;
-import java.util.Arrays;
-
-/** Implementation of {@link StatementResult} */
-class StatementResultImpl implements StatementResult {
-
- /** {@link StatementResult} containing a {@link ResultSet} returned by Cloud Spanner. */
- static StatementResult of(ResultSet resultSet) {
- return new StatementResultImpl(resultSet, null);
- }
-
- /**
- * {@link StatementResult} containing a {@link ResultSet} created by a {@link
- * ClientSideStatement}.
- */
- static StatementResult of(ResultSet resultSet, ClientSideStatementType clientSideStatementType) {
- return new StatementResultImpl(resultSet, clientSideStatementType);
- }
-
- /** {@link StatementResult} containing an update count returned by Cloud Spanner. */
- static StatementResult of(Long updateCount) {
- return new StatementResultImpl(updateCount);
- }
-
- /**
- * Convenience method for creating a {@link StatementResult} containing a {@link ResultSet} with
- * one BOOL column and one row that is created by a {@link ClientSideStatement}.
- */
- static StatementResult resultSet(
- String name, Boolean value, ClientSideStatementType clientSideStatementType) {
- return of(
- ResultSets.forRows(
- Type.struct(StructField.of(name, Type.bool())),
- Arrays.asList(Struct.newBuilder().set(name).to(value).build())),
- clientSideStatementType);
- }
-
- /**
- * Convenience method for creating a {@link StatementResult} containing a {@link ResultSet} with
- * one INT64 column and one row that is created by a {@link ClientSideStatement}.
- */
- static StatementResult resultSet(
- String name, Long value, ClientSideStatementType clientSideStatementType) {
- return of(
- ResultSets.forRows(
- Type.struct(StructField.of(name, Type.int64())),
- Arrays.asList(Struct.newBuilder().set(name).to(value).build())),
- clientSideStatementType);
- }
-
- /**
- * Convenience method for creating a {@link StatementResult} containing a {@link ResultSet} with
- * one ARRAY column and one row that is created by a {@link ClientSideStatement}.
- */
- static StatementResult resultSet(
- String name, long[] values, ClientSideStatementType clientSideStatementType) {
- return of(
- ResultSets.forRows(
- Type.struct(StructField.of(name, Type.array(Type.int64()))),
- Arrays.asList(Struct.newBuilder().set(name).toInt64Array(values).build())),
- clientSideStatementType);
- }
-
- /**
- * Convenience method for creating a {@link StatementResult} containing a {@link ResultSet} with
- * one STRING column and one row that is created by a {@link ClientSideStatement}.
- */
- static StatementResult resultSet(
- String name, String value, ClientSideStatementType clientSideStatementType) {
- return of(
- ResultSets.forRows(
- Type.struct(StructField.of(name, Type.string())),
- Arrays.asList(Struct.newBuilder().set(name).to(value).build())),
- clientSideStatementType);
- }
-
- /**
- * Convenience method for creating a {@link StatementResult} containing a {@link ResultSet} with
- * one STRING column containing an {@link Enum} value and one row that is created by a {@link
- * ClientSideStatement}.
- */
- static StatementResult resultSet(
- String name, Enum> value, ClientSideStatementType clientSideStatementType) {
- return of(
- ResultSets.forRows(
- Type.struct(StructField.of(name, Type.string())),
- Arrays.asList(Struct.newBuilder().set(name).to(value.toString()).build())),
- clientSideStatementType);
- }
-
- /**
- * Convenience method for creating a {@link StatementResult} containing a {@link ResultSet} with
- * one TIMESTAMP column and one row that is created by a {@link ClientSideStatement}.
- */
- static StatementResult resultSet(
- String name, Timestamp value, ClientSideStatementType clientSideStatementType) {
- return of(
- ResultSets.forRows(
- Type.struct(StructField.of(name, Type.timestamp())),
- Arrays.asList(Struct.newBuilder().set(name).to(value).build())),
- clientSideStatementType);
- }
-
- /** {@link StatementResult} containing no results. */
- static StatementResult noResult() {
- return new StatementResultImpl((ClientSideStatementType) null);
- }
-
- /** {@link StatementResult} containing no results created by a {@link ClientSideStatement}. */
- static StatementResult noResult(ClientSideStatementType clientSideStatementType) {
- return new StatementResultImpl(clientSideStatementType);
- }
-
- private final ResultType type;
- private final ClientSideStatementType clientSideStatementType;
- private final ResultSet resultSet;
- private final Long updateCount;
-
- private StatementResultImpl(
- ResultSet resultSet, ClientSideStatementType clientSideStatementType) {
- this.type = ResultType.RESULT_SET;
- this.clientSideStatementType = clientSideStatementType;
- this.resultSet = resultSet;
- this.updateCount = null;
- }
-
- private StatementResultImpl(Long updateCount) {
- this.type = ResultType.UPDATE_COUNT;
- this.clientSideStatementType = null;
- this.resultSet = null;
- this.updateCount = updateCount;
- }
-
- private StatementResultImpl(ClientSideStatementType clientSideStatementType) {
- this.type = ResultType.NO_RESULT;
- this.clientSideStatementType = clientSideStatementType;
- this.resultSet = null;
- this.updateCount = null;
- }
-
- @Override
- public ResultType getResultType() {
- return type;
- }
-
- @Override
- public ClientSideStatementType getClientSideStatementType() {
- return clientSideStatementType;
- }
-
- @Override
- public ResultSet getResultSet() {
- ConnectionPreconditions.checkState(
- resultSet != null, "This result does not contain a ResultSet");
- return resultSet;
- }
-
- @Override
- public Long getUpdateCount() {
- ConnectionPreconditions.checkState(
- updateCount != null, "This result does not contain an update count");
- return updateCount;
- }
-}
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/TransactionMode.java b/src/main/java/com/google/cloud/spanner/jdbc/TransactionMode.java
deleted file mode 100644
index ae98118c..00000000
--- a/src/main/java/com/google/cloud/spanner/jdbc/TransactionMode.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 2019 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.cloud.spanner.jdbc;
-
-/** Enum used to define the transaction type of a {@link Connection} */
-enum TransactionMode {
- READ_ONLY_TRANSACTION("READ ONLY"),
- READ_WRITE_TRANSACTION("READ WRITE");
-
- private final String statementString;
-
- private TransactionMode(String statement) {
- this.statementString = statement;
- }
-
- /**
- * Use this method to get the correct format for use in a SQL statement. The SQL statement for
- * setting the mode to read-only should for example be without the underscore:
- * SET TRANSACTION READ ONLY
- *
- * @return a string representation of this {@link TransactionMode} that can be used in a SQL
- * statement to set the transaction mode.
- */
- public String getStatementString() {
- return statementString;
- }
-
- @Override
- public String toString() {
- return statementString;
- }
-}
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/TransactionRetryListener.java b/src/main/java/com/google/cloud/spanner/jdbc/TransactionRetryListener.java
index 546cd4f2..6d85dc7c 100644
--- a/src/main/java/com/google/cloud/spanner/jdbc/TransactionRetryListener.java
+++ b/src/main/java/com/google/cloud/spanner/jdbc/TransactionRetryListener.java
@@ -16,24 +16,18 @@
package com.google.cloud.spanner.jdbc;
+import com.google.api.core.InternalApi;
import com.google.cloud.Timestamp;
+import com.google.cloud.spanner.AbortedDueToConcurrentModificationException;
import com.google.cloud.spanner.AbortedException;
-/**
- * Cloud Spanner can abort any read/write transaction because of potential deadlocks or other
- * internal reasons. When a transaction is aborted, the entire transaction should be retried. A
- * {@link Connection} can automatically retry a transaction internally and check whether the results
- * that are returned during a retry attempt are equal to the results during the original
- * transaction. This is done by keeping track of a SHA-256 checksum of all the results that are
- * returned by Spanner during both transactions.
- *
- * This listener class for internal transaction retries allow client applications to do
- * additional testing or logging of transaction retries. Transaction retry listeners of a {@link
- * Connection} can be added using {@link
- * Connection#addTransactionRetryListener(TransactionRetryListener)}.
- */
+/** Use {@link com.google.cloud.spanner.connection.TransactionRetryListener} */
+@InternalApi
+@Deprecated
public interface TransactionRetryListener {
- /** The result of a retry. */
+ /** Use {@link com.google.cloud.spanner.connection.TransactionRetryListener.RetryResult} */
+ @InternalApi
+ @Deprecated
public enum RetryResult {
/** The retry executed successfully and the transaction will continue. */
RETRY_SUCCESSFUL,
diff --git a/src/main/java/com/google/cloud/spanner/jdbc/UnitOfWork.java b/src/main/java/com/google/cloud/spanner/jdbc/UnitOfWork.java
deleted file mode 100644
index 4287981d..00000000
--- a/src/main/java/com/google/cloud/spanner/jdbc/UnitOfWork.java
+++ /dev/null
@@ -1,182 +0,0 @@
-/*
- * Copyright 2019 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.cloud.spanner.jdbc;
-
-import com.google.api.core.InternalApi;
-import com.google.cloud.Timestamp;
-import com.google.cloud.spanner.Mutation;
-import com.google.cloud.spanner.Options.QueryOption;
-import com.google.cloud.spanner.ReadContext;
-import com.google.cloud.spanner.ResultSet;
-import com.google.cloud.spanner.SpannerException;
-import com.google.cloud.spanner.TransactionContext;
-import com.google.cloud.spanner.jdbc.StatementParser.ParsedStatement;
-import com.google.spanner.v1.ResultSetStats;
-
-/** Internal interface for transactions and batches on {@link Connection}s. */
-@InternalApi
-interface UnitOfWork {
-
- /** A unit of work can be either a transaction or a DDL/DML batch. */
- enum Type {
- TRANSACTION,
- BATCH;
- }
-
- enum UnitOfWorkState {
- STARTED,
- COMMITTED,
- COMMIT_FAILED,
- ROLLED_BACK,
- RAN,
- RUN_FAILED,
- ABORTED;
-
- public boolean isActive() {
- return this == STARTED;
- }
- }
-
- /** Cancel the currently running statement (if any and the statement may be cancelled). */
- void cancel();
-
- /** @return the type of unit of work. */
- Type getType();
-
- /** @return the current state of this unit of work. */
- UnitOfWorkState getState();
-
- /** @return true
if this unit of work is still active. */
- boolean isActive();
-
- /**
- * Commits the changes in this unit of work to the database. For read-only transactions, this only
- * closes the {@link ReadContext}. This method will throw a {@link SpannerException} if called for
- * a {@link Type#BATCH}.
- */
- void commit();
-
- /**
- * Rollbacks any changes in this unit of work. For read-only transactions, this only closes the
- * {@link ReadContext}. This method will throw a {@link SpannerException} if called for a {@link
- * Type#BATCH}.
- */
- void rollback();
-
- /**
- * Sends the currently buffered statements in this unit of work to the database and ends the
- * batch. This method will throw a {@link SpannerException} if called for a {@link
- * Type#TRANSACTION}.
- *
- * @return the update counts in case of a DML batch. Returns an array containing 1 for each
- * successful statement and 0 for each failed statement or statement that was not executed DDL
- * in case of a DDL batch.
- */
- long[] runBatch();
-
- /**
- * Clears the currently buffered statements in this unit of work and ends the batch. This method
- * will throw a {@link SpannerException} if called for a {@link Type#TRANSACTION}.
- */
- void abortBatch();
-
- /** @return true
if this unit of work is read-only. */
- boolean isReadOnly();
-
- /**
- * Executes a query with the given options. If {@link AnalyzeMode} is set to {@link
- * AnalyzeMode#PLAN} or {@link AnalyzeMode#PROFILE}, the returned {@link ResultSet} will include
- * {@link ResultSetStats}.
- *
- * @param statement The statement to execute.
- * @param analyzeMode Indicates whether to include {@link ResultSetStats} in the returned {@link
- * ResultSet} or not. Cannot be used in combination with {@link QueryOption}s.
- * @param options the options to configure the query. May only be set if analyzeMode is set to
- * {@link AnalyzeMode#NONE}.
- * @return a {@link ResultSet} with the results of the query.
- * @throws SpannerException if the query is not allowed on this {@link UnitOfWork}, or if a
- * database error occurs.
- */
- ResultSet executeQuery(
- ParsedStatement statement, AnalyzeMode analyzeMode, QueryOption... options);
-
- /**
- * @return the read timestamp of this transaction. Will throw a {@link SpannerException} if there
- * is no read timestamp.
- */
- Timestamp getReadTimestamp();
-
- /** @return the read timestamp of this transaction or null if there is no read timestamp. */
- Timestamp getReadTimestampOrNull();
-
- /**
- * @return the commit timestamp of this transaction. Will throw a {@link SpannerException} if
- * there is no commit timestamp.
- */
- Timestamp getCommitTimestamp();
-
- /** @return the commit timestamp of this transaction or null if there is no commit timestamp. */
- Timestamp getCommitTimestampOrNull();
-
- /**
- * Executes the specified DDL statements in this unit of work. For DDL batches, this will mean
- * that the statements are buffered locally and will be sent to Spanner when {@link
- * UnitOfWork#commit()} is called. For {@link SingleUseTransaction}s, this will execute the DDL
- * statement directly on Spanner.
- *
- * @param ddl The DDL statement to execute.
- */
- void executeDdl(ParsedStatement ddl);
-
- /**
- * Execute a DML statement on Spanner.
- *
- * @param update The DML statement to execute.
- * @return the number of records that were inserted/updated/deleted by this statement.
- */
- long executeUpdate(ParsedStatement update);
-
- /**
- * Execute a batch of DML statements on Spanner.
- *
- * @param updates The DML statements to execute.
- * @return an array containing the number of records that were inserted/updated/deleted per
- * statement.
- * @see TransactionContext#batchUpdate(Iterable)
- */
- long[] executeBatchUpdate(Iterable updates);
-
- /**
- * Writes a {@link Mutation} to Spanner. For {@link ReadWriteTransaction}s, this means buffering
- * the {@link Mutation} locally and writing the {@link Mutation} to Spanner upon {@link
- * UnitOfWork#commit()}. For {@link SingleUseTransaction}s, the {@link Mutation} will be sent
- * directly to Spanner.
- *
- * @param mutation The mutation to write.
- */
- void write(Mutation mutation);
-
- /**
- * Writes a batch of {@link Mutation}s to Spanner. For {@link ReadWriteTransaction}s, this means
- * buffering the {@link Mutation}s locally and writing the {@link Mutation}s to Spanner upon
- * {@link UnitOfWork#commit()}. For {@link SingleUseTransaction}s, the {@link Mutation}s will be
- * sent directly to Spanner.
- *
- * @param mutations The mutations to write.
- */
- void write(Iterable mutations);
-}
diff --git a/src/test/java/com/google/cloud/spanner/jdbc/AbortedTest.java b/src/test/java/com/google/cloud/spanner/jdbc/AbortedTest.java
deleted file mode 100644
index d10508bb..00000000
--- a/src/test/java/com/google/cloud/spanner/jdbc/AbortedTest.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright 2019 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.cloud.spanner.jdbc;
-
-import static org.hamcrest.CoreMatchers.equalTo;
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.MatcherAssert.assertThat;
-
-import com.google.cloud.spanner.MockSpannerServiceImpl.StatementResult;
-import com.google.cloud.spanner.ResultSet;
-import com.google.cloud.spanner.Statement;
-import com.google.cloud.spanner.jdbc.ITAbstractSpannerTest.AbortInterceptor;
-import com.google.cloud.spanner.jdbc.ITAbstractSpannerTest.ITConnection;
-import com.google.cloud.spanner.jdbc.it.ITTransactionRetryTest.CountTransactionRetryListener;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-@RunWith(JUnit4.class)
-public class AbortedTest extends AbstractMockServerTest {
-
- @Test
- public void testCommitAborted() {
- // Do two iterations to ensure that each iteration gets its own transaction, and that each
- // transaction is the most recent transaction of that session.
- for (int i = 0; i < 2; i++) {
- mockSpanner.putStatementResult(
- StatementResult.query(SELECT_COUNT_STATEMENT, SELECT_COUNT_RESULTSET_BEFORE_INSERT));
- mockSpanner.putStatementResult(StatementResult.update(INSERT_STATEMENT, UPDATE_COUNT));
- AbortInterceptor interceptor = new AbortInterceptor(0);
- try (ITConnection connection =
- createConnection(interceptor, new CountTransactionRetryListener())) {
- // verify that the there is no test record
- try (ResultSet rs =
- connection.executeQuery(Statement.of("SELECT COUNT(*) AS C FROM TEST WHERE ID=1"))) {
- assertThat(rs.next(), is(true));
- assertThat(rs.getLong("C"), is(equalTo(0L)));
- assertThat(rs.next(), is(false));
- }
- // do an insert
- connection.executeUpdate(
- Statement.of("INSERT INTO TEST (ID, NAME) VALUES (1, 'test aborted')"));
- // indicate that the next statement should abort
- interceptor.setProbability(1.0);
- interceptor.setOnlyInjectOnce(true);
- // do a commit that will first abort, and then on retry will succeed
- connection.commit();
- mockSpanner.putStatementResult(
- StatementResult.query(SELECT_COUNT_STATEMENT, SELECT_COUNT_RESULTSET_AFTER_INSERT));
- // verify that the insert succeeded
- try (ResultSet rs =
- connection.executeQuery(Statement.of("SELECT COUNT(*) AS C FROM TEST WHERE ID=1"))) {
- assertThat(rs.next(), is(true));
- assertThat(rs.getLong("C"), is(equalTo(1L)));
- assertThat(rs.next(), is(false));
- }
- }
- }
- }
-}
diff --git a/src/test/java/com/google/cloud/spanner/jdbc/AbstractConnectionImplTest.java b/src/test/java/com/google/cloud/spanner/jdbc/AbstractConnectionImplTest.java
deleted file mode 100644
index f12f5e88..00000000
--- a/src/test/java/com/google/cloud/spanner/jdbc/AbstractConnectionImplTest.java
+++ /dev/null
@@ -1,963 +0,0 @@
-/*
- * Copyright 2019 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.cloud.spanner.jdbc;
-
-import static com.google.cloud.spanner.jdbc.ReadOnlyStalenessUtil.getTimeUnitAbbreviation;
-import static com.google.cloud.spanner.jdbc.SpannerExceptionMatcher.matchCode;
-import static org.hamcrest.CoreMatchers.equalTo;
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.CoreMatchers.notNullValue;
-import static org.hamcrest.CoreMatchers.nullValue;
-import static org.hamcrest.MatcherAssert.assertThat;
-
-import com.google.cloud.Timestamp;
-import com.google.cloud.spanner.ErrorCode;
-import com.google.cloud.spanner.Mutation;
-import com.google.cloud.spanner.ReadContext.QueryAnalyzeMode;
-import com.google.cloud.spanner.ResultSet;
-import com.google.cloud.spanner.SpannerException;
-import com.google.cloud.spanner.Statement;
-import com.google.cloud.spanner.TimestampBound;
-import com.google.cloud.spanner.jdbc.StatementParser.StatementType;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStreamWriter;
-import java.io.PrintWriter;
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-/**
- * This test class and all its subclasses are used to generate the file
- * ConnectionImplGeneratedSqlScriptTest.sql.
- */
-@RunWith(JUnit4.class)
-public abstract class AbstractConnectionImplTest {
- public static final String UPDATE = "UPDATE foo SET bar=1";
- public static final String SELECT = "SELECT 1 AS TEST";
- public static final String DDL =
- "CREATE TABLE foo (id INT64 NOT NULL, name STRING(100)) PRIMARY KEY (id)";
-
- static interface ConnectionConsumer {
- void accept(Connection connection);
- }
-
- @Rule public ExpectedException exception = ExpectedException.none();
-
- /**
- * This test class can generate a large sql file that represents all the statements and
- * verifications that are executed by this test class. This file can be fed into other test cases
- * (in other programming languages) to execute the same tests as the tests covered by all the
- * subclasses of {@link AbstractConnectionImplTest}.
- */
- private static final String LOG_FILE =
- "src/test/resources/com/google/cloud/spanner/jdbc/ConnectionImplGeneratedSqlScriptTest.sql";
-
- private static final String DO_LOG_PROPERTY = "do_log_statements";
- private static boolean doLog;
- private static PrintWriter writer;
-
- abstract Connection getConnection();
-
- static void expectSpannerException(
- String reason, ConnectionConsumer consumer, Connection connection) {
- expectSpannerException(reason, consumer, connection, ErrorCode.FAILED_PRECONDITION);
- }
-
- static void expectSpannerException(
- String reason, ConnectionConsumer consumer, Connection connection, ErrorCode errorCode) {
- SpannerException exception = null;
- try {
- consumer.accept(connection);
- } catch (SpannerException e) {
- exception = e;
- }
- assertThat(reason, exception, is(notNullValue()));
- assertThat(reason, exception.getErrorCode(), is(equalTo(errorCode)));
- }
-
- AbstractConnectionImplTest() {}
-
- /** Makes an empty test script. Can be called before a new script is to be generated. */
- static void emptyScript() {
- openLog(false);
- closeLog();
- }
-
- void log(String statement) {
- if (doLog) {
- writer.println(statement);
- }
- }
-
- @BeforeClass
- public static void openLog() {
- doLog = Boolean.valueOf(System.getProperty(DO_LOG_PROPERTY, "false"));
- if (doLog) {
- openLog(true);
- } else {
- writer = null;
- }
- }
-
- private static void openLog(boolean append) {
- try {
- writer =
- new PrintWriter(
- new OutputStreamWriter(new FileOutputStream(LOG_FILE, append), "UTF8"), true);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-
- @AfterClass
- public static void closeLog() {
- if (writer != null) {
- writer.close();
- }
- }
-
- @Test
- public void testClose() {
- getConnection().close();
- }
-
- @Test
- public void testIsClosed() {
- Connection connection = getConnection();
- assertThat(connection.isClosed(), is(false));
- connection.close();
- assertThat(connection.isClosed(), is(true));
- }
-
- abstract boolean isSetAutocommitAllowed();
-
- @Test
- public void testSetAutocommit() {
- try (Connection connection = getConnection()) {
- if (isSetAutocommitAllowed()) {
- log("SET AUTOCOMMIT=FALSE;");
- connection.setAutocommit(false);
-
- log("@EXPECT RESULT_SET 'AUTOCOMMIT',FALSE");
- log("SHOW VARIABLE AUTOCOMMIT;");
- assertThat(connection.isAutocommit(), is(false));
-
- log("SET AUTOCOMMIT=TRUE;");
- connection.setAutocommit(true);
-
- log("@EXPECT RESULT_SET 'AUTOCOMMIT',TRUE");
- log("SHOW VARIABLE AUTOCOMMIT;");
- assertThat(connection.isAutocommit(), is(true));
- } else {
- log("@EXPECT EXCEPTION FAILED_PRECONDITION");
- log("SET AUTOCOMMIT=" + (connection.isAutocommit() ? "FALSE;" : "TRUE;"));
- exception.expect(matchCode(ErrorCode.FAILED_PRECONDITION));
- connection.setAutocommit(!connection.isAutocommit());
- }
- }
- }
-
- abstract boolean isSetReadOnlyAllowed();
-
- @Test
- public void testSetReadOnly() {
- try (Connection connection = getConnection()) {
- if (isSetReadOnlyAllowed()) {
- log("SET READONLY=FALSE;");
- connection.setReadOnly(false);
-
- log("@EXPECT RESULT_SET 'READONLY',FALSE");
- log("SHOW VARIABLE READONLY;");
- assertThat(connection.isReadOnly(), is(false));
-
- log("SET READONLY=TRUE;");
- connection.setReadOnly(true);
-
- log("@EXPECT RESULT_SET 'READONLY',TRUE");
- log("SHOW VARIABLE READONLY;");
- assertThat(connection.isReadOnly(), is(true));
- } else {
- log("@EXPECT EXCEPTION FAILED_PRECONDITION");
- log("SET READONLY=" + (connection.isAutocommit() ? "FALSE;" : "TRUE;"));
- exception.expect(matchCode(ErrorCode.FAILED_PRECONDITION));
- connection.setReadOnly(!connection.isReadOnly());
- }
- }
- }
-
- @Test
- public void testSetStatementTimeout() {
- try (Connection connection = getConnection()) {
- for (TimeUnit unit : ReadOnlyStalenessUtil.SUPPORTED_UNITS) {
- log(String.format("SET STATEMENT_TIMEOUT='1%s';", getTimeUnitAbbreviation(unit)));
- connection.setStatementTimeout(1L, unit);
-
- log(
- String.format(
- "@EXPECT RESULT_SET 'STATEMENT_TIMEOUT','1%s'", getTimeUnitAbbreviation(unit)));
- log("SHOW VARIABLE STATEMENT_TIMEOUT;");
- assertThat(connection.getStatementTimeout(unit), is(equalTo(1L)));
-
- log("SET STATEMENT_TIMEOUT=null;");
- connection.clearStatementTimeout();
-
- log("@EXPECT RESULT_SET 'STATEMENT_TIMEOUT',null");
- log("SHOW VARIABLE STATEMENT_TIMEOUT;");
- assertThat(connection.getStatementTimeout(unit), is(equalTo(0L)));
- assertThat(connection.hasStatementTimeout(), is(false));
- boolean gotException = false;
- try {
- log("@EXPECT EXCEPTION INVALID_ARGUMENT");
- log(String.format("SET STATEMENT_TIMEOUT='0%s';", getTimeUnitAbbreviation(unit)));
- connection.setStatementTimeout(0L, unit);
- } catch (IllegalArgumentException e) {
- gotException = true;
- }
- assertThat(gotException, is(true));
- log("@EXPECT RESULT_SET 'STATEMENT_TIMEOUT',null");
- log("SHOW VARIABLE STATEMENT_TIMEOUT;");
- assertThat(connection.getStatementTimeout(unit), is(equalTo(0L)));
- assertThat(connection.hasStatementTimeout(), is(false));
- }
- }
- }
-
- abstract boolean isStartBatchDmlAllowed();
-
- @Test
- public void testStartBatchDml() {
- try (Connection connection = getConnection()) {
- if (isStartBatchDmlAllowed()) {
- assertThat(connection.isReadOnly(), is(false));
- assertThat(connection.isDdlBatchActive() || connection.isDmlBatchActive(), is(false));
-
- log("START BATCH DML;");
- connection.startBatchDml();
- assertThat(connection.isDmlBatchActive(), is(true));
-
- expectSpannerException(
- "Select should not be allowed after startBatchDml()",
- new ConnectionConsumer() {
- @Override
- public void accept(Connection t) {
- log("@EXPECT EXCEPTION FAILED_PRECONDITION");
- log(SELECT + ";");
- t.execute(Statement.of(SELECT));
- }
- },
- connection);
- expectSpannerException(
- "DDL should not be allowed after startBatchDml()",
- new ConnectionConsumer() {
- @Override
- public void accept(Connection t) {
- log("@EXPECT EXCEPTION FAILED_PRECONDITION");
- log(DDL + ";");
- t.execute(Statement.of(DDL));
- }
- },
- connection);
- log(UPDATE + ";");
- connection.execute(Statement.of(UPDATE));
- assertThat(connection.isDmlBatchActive(), is(true));
- }
- // startBatchDml is not allowed as a batch has already been started.
- log("@EXPECT EXCEPTION FAILED_PRECONDITION");
- log("START BATCH DML;");
- exception.expect(matchCode(ErrorCode.FAILED_PRECONDITION));
- connection.startBatchDml();
- }
- }
-
- abstract boolean isStartBatchDdlAllowed();
-
- @Test
- public void testStartBatchDdl() {
- try (Connection connection = getConnection()) {
- if (isStartBatchDdlAllowed()) {
- assertThat(connection.isTransactionStarted(), is(false));
- assertThat(connection.isInTransaction(), is(equalTo(!connection.isAutocommit())));
- assertThat(connection.isDdlBatchActive() || connection.isDmlBatchActive(), is(false));
-
- log("START BATCH DDL;");
- connection.startBatchDdl();
- assertThat(connection.isTransactionStarted(), is(false));
- assertThat(connection.isInTransaction(), is(false));
- assertThat(connection.isDdlBatchActive(), is(true));
-
- expectSpannerException(
- "Select should not be allowed after startBatchDdl()",
- new ConnectionConsumer() {
- @Override
- public void accept(Connection t) {
- log("@EXPECT EXCEPTION FAILED_PRECONDITION");
- log(SELECT + ";");
- t.execute(Statement.of(SELECT));
- }
- },
- connection);
- expectSpannerException(
- "Update should not be allowed after startBatchDdl()",
- new ConnectionConsumer() {
- @Override
- public void accept(Connection t) {
- log("@EXPECT EXCEPTION FAILED_PRECONDITION");
- log(UPDATE + ";");
- t.execute(Statement.of(UPDATE));
- }
- },
- connection);
- log(DDL + ";");
- connection.execute(Statement.of(DDL));
- assertThat(connection.isTransactionStarted(), is(false));
- assertThat(connection.isDdlBatchActive(), is(true));
- }
- // startBatchDdl is no longer allowed as a batch has already been started
- log("@EXPECT EXCEPTION FAILED_PRECONDITION");
- log("START BATCH DDL;");
- exception.expect(matchCode(ErrorCode.FAILED_PRECONDITION));
- connection.startBatchDdl();
- }
- }
-
- abstract boolean isRunBatchAllowed();
-
- @Test
- public void testRunBatch() {
- try (Connection connection = getConnection()) {
- if (!isRunBatchAllowed()) {
- log("@EXPECT EXCEPTION FAILED_PRECONDITION");
- exception.expect(matchCode(ErrorCode.FAILED_PRECONDITION));
- }
- log("RUN BATCH;");
- connection.runBatch();
- }
- }
-
- abstract boolean isAbortBatchAllowed();
-
- @Test
- public void testAbortBatch() {
- try (Connection connection = getConnection()) {
- if (!isAbortBatchAllowed()) {
- log("@EXPECT EXCEPTION FAILED_PRECONDITION");
- exception.expect(matchCode(ErrorCode.FAILED_PRECONDITION));
- }
- log("ABORT BATCH;");
- connection.abortBatch();
- }
- }
-
- abstract boolean isBeginTransactionAllowed();
-
- abstract boolean isSelectAllowedAfterBeginTransaction();
-
- abstract boolean isDmlAllowedAfterBeginTransaction();
-
- abstract boolean isDdlAllowedAfterBeginTransaction();
-
- @Test
- public void testBeginTransaction() {
- try (Connection connection = getConnection()) {
- if (isBeginTransactionAllowed()) {
- assertThat(connection.isTransactionStarted(), is(false));
- assertThat(connection.isInTransaction(), is(equalTo(!connection.isAutocommit())));
-
- log("BEGIN TRANSACTION;");
- connection.beginTransaction();
- assertThat(connection.isTransactionStarted(), is(false));
- assertThat(connection.isInTransaction(), is(true));
-
- if (isSelectAllowedAfterBeginTransaction()) {
- log(SELECT + ";");
- connection.execute(Statement.of(SELECT));
- } else {
- expectSpannerException(
- "Select should not be allowed after beginTransaction",
- new ConnectionConsumer() {
- @Override
- public void accept(Connection t) {
- log("@EXPECT EXCEPTION FAILED_PRECONDITION");
- log(SELECT + ";");
- t.execute(Statement.of(SELECT));
- }
- },
- connection);
- }
- if (isDmlAllowedAfterBeginTransaction()) {
- log(UPDATE + ";");
- connection.execute(Statement.of(UPDATE));
- } else {
- expectSpannerException(
- "Update should not be allowed after beginTransaction",
- new ConnectionConsumer() {
- @Override
- public void accept(Connection t) {
- log("@EXPECT EXCEPTION FAILED_PRECONDITION");
- log(UPDATE + ";");
- t.execute(Statement.of(UPDATE));
- }
- },
- connection);
- }
- if (isDdlAllowedAfterBeginTransaction()) {
- log(DDL + ";");
- connection.execute(Statement.of(DDL));
- } else {
- expectSpannerException(
- "DDL should not be allowed after beginTransaction",
- new ConnectionConsumer() {
- @Override
- public void accept(Connection t) {
- log("@EXPECT EXCEPTION FAILED_PRECONDITION");
- log(DDL + ";");
- t.execute(Statement.of(DDL));
- }
- },
- connection);
- }
- assertThat(connection.isTransactionStarted(), is(true));
- }
- // beginTransaction is no longer allowed as the transaction has already started
- log("@EXPECT EXCEPTION FAILED_PRECONDITION");
- log("BEGIN TRANSACTION;");
- exception.expect(matchCode(ErrorCode.FAILED_PRECONDITION));
- connection.beginTransaction();
- }
- }
-
- abstract boolean isSetTransactionModeAllowed(TransactionMode mode);
-
- @Test
- public void testSetTransactionMode() {
- for (TransactionMode mode : TransactionMode.values()) {
- testSetTransactionMode(mode);
- }
- }
-
- private void testSetTransactionMode(final TransactionMode mode) {
- try (Connection connection = getConnection()) {
- if (isSetTransactionModeAllowed(mode)) {
- log("SET TRANSACTION " + mode.toString() + ";");
- connection.setTransactionMode(mode);
- assertThat(connection.getTransactionMode(), is(equalTo(mode)));
- } else {
- expectSpannerException(
- mode + " should not be allowed",
- new ConnectionConsumer() {
- @Override
- public void accept(Connection t) {
- log("@EXPECT EXCEPTION FAILED_PRECONDITION");
- log("SET TRANSACTION " + mode.getStatementString() + ";");
- t.setTransactionMode(mode);
- }
- },
- connection);
- }
- }
- }
-
- abstract boolean isGetTransactionModeAllowed();
-
- @Test
- public void testGetTransactionMode() {
- try (Connection connection = getConnection()) {
- if (isGetTransactionModeAllowed()) {
- assertThat(connection.getTransactionMode(), is(notNullValue()));
- } else {
- exception.expect(matchCode(ErrorCode.FAILED_PRECONDITION));
- connection.getTransactionMode();
- }
- }
- }
-
- abstract boolean isSetAutocommitDmlModeAllowed();
-
- @Test
- public void testSetAutocommitDmlMode() {
- try (Connection connection = getConnection()) {
- if (isSetAutocommitDmlModeAllowed()) {
- for (AutocommitDmlMode mode : AutocommitDmlMode.values()) {
- log("SET AUTOCOMMIT_DML_MODE='" + mode.toString() + "';");
- connection.setAutocommitDmlMode(mode);
-
- log("@EXPECT RESULT_SET 'AUTOCOMMIT_DML_MODE','" + mode.toString() + "'");
- log("SHOW VARIABLE AUTOCOMMIT_DML_MODE;");
- assertThat(connection.getAutocommitDmlMode(), is(equalTo(mode)));
- }
- } else {
- log("@EXPECT EXCEPTION FAILED_PRECONDITION");
- log(
- "SET AUTOCOMMIT_DML_MODE='"
- + AutocommitDmlMode.PARTITIONED_NON_ATOMIC.toString()
- + "';");
- exception.expect(matchCode(ErrorCode.FAILED_PRECONDITION));
- connection.setAutocommitDmlMode(AutocommitDmlMode.PARTITIONED_NON_ATOMIC);
- }
- }
- }
-
- abstract boolean isGetAutocommitDmlModeAllowed();
-
- @Test
- public void testGetAutocommitDmlMode() {
- try (Connection connection = getConnection()) {
- if (isGetAutocommitDmlModeAllowed()) {
- log("@EXPECT RESULT_SET 'AUTOCOMMIT_DML_MODE'");
- log("SHOW VARIABLE AUTOCOMMIT_DML_MODE;");
- assertThat(connection.getAutocommitDmlMode(), is(notNullValue()));
- } else {
- log("@EXPECT EXCEPTION FAILED_PRECONDITION");
- log("SHOW VARIABLE AUTOCOMMIT_DML_MODE;");
- exception.expect(matchCode(ErrorCode.FAILED_PRECONDITION));
- connection.getAutocommitDmlMode();
- }
- }
- }
-
- abstract boolean isSetReadOnlyStalenessAllowed(TimestampBound.Mode mode);
-
- @Test
- public void testSetReadOnlyStaleness() {
- for (TimestampBound staleness : getTestTimestampBounds()) {
- testSetReadOnlyStaleness(staleness);
- }
- }
-
- private List getTestTimestampBounds() {
- return Arrays.asList(
- TimestampBound.strong(),
- TimestampBound.ofReadTimestamp(Timestamp.now()),
- TimestampBound.ofMinReadTimestamp(Timestamp.now()),
- TimestampBound.ofExactStaleness(1L, TimeUnit.SECONDS),
- TimestampBound.ofMaxStaleness(100L, TimeUnit.MILLISECONDS),
- TimestampBound.ofExactStaleness(100L, TimeUnit.MICROSECONDS));
- }
-
- private void testSetReadOnlyStaleness(final TimestampBound staleness) {
- try (Connection connection = getConnection()) {
- if (isSetReadOnlyStalenessAllowed(staleness.getMode())) {
- log(
- "SET READ_ONLY_STALENESS='"
- + ReadOnlyStalenessUtil.timestampBoundToString(staleness)
- + "';");
- connection.setReadOnlyStaleness(staleness);
-
- log(
- "@EXPECT RESULT_SET 'READ_ONLY_STALENESS','"
- + ReadOnlyStalenessUtil.timestampBoundToString(staleness)
- + "'");
- log("SHOW VARIABLE READ_ONLY_STALENESS;");
- assertThat(connection.getReadOnlyStaleness(), is(equalTo(staleness)));
- } else {
- expectSpannerException(
- staleness.getMode() + " should not be allowed",
- new ConnectionConsumer() {
- @Override
- public void accept(Connection t) {
- log("@EXPECT EXCEPTION FAILED_PRECONDITION");
- log(
- "SET READ_ONLY_STALENESS='"
- + ReadOnlyStalenessUtil.timestampBoundToString(staleness)
- + "';");
- t.setReadOnlyStaleness(staleness);
- }
- },
- connection);
- }
- }
- }
-
- abstract boolean isGetReadOnlyStalenessAllowed();
-
- @Test
- public void testGetReadOnlyStaleness() {
- try (Connection connection = getConnection()) {
- if (isGetReadOnlyStalenessAllowed()) {
- log("@EXPECT RESULT_SET 'READ_ONLY_STALENESS'");
- log("SHOW VARIABLE READ_ONLY_STALENESS;");
- assertThat(connection.getReadOnlyStaleness(), is(notNullValue()));
- } else {
- log("@EXPECT EXCEPTION FAILED_PRECONDITION");
- log("SHOW VARIABLE READ_ONLY_STALENESS;");
- exception.expect(matchCode(ErrorCode.FAILED_PRECONDITION));
- connection.getReadOnlyStaleness();
- }
- }
- }
-
- boolean isSetOptimizerVersionAllowed() {
- return !getConnection().isClosed();
- }
-
- @Test
- public void testSetOptimizerVersion() {
- try (Connection connection = getConnection()) {
- if (isSetOptimizerVersionAllowed()) {
- for (String version : new String[] {"1", "2", "latest", ""}) {
- log("SET OPTIMIZER_VERSION='" + version + "';");
- connection.setOptimizerVersion(version);
-
- log("@EXPECT RESULT_SET 'OPTIMIZER_VERSION','" + version + "'");
- log("SHOW VARIABLE OPTIMIZER_VERSION;");
- assertThat(connection.getOptimizerVersion(), is(equalTo(version)));
- }
- } else {
- log("@EXPECT EXCEPTION FAILED_PRECONDITION");
- log("SET OPTIMIZER_VERSION='1';");
- exception.expect(matchCode(ErrorCode.FAILED_PRECONDITION));
- connection.setOptimizerVersion("1");
- }
- }
- }
-
- boolean isGetOptimizerVersionAllowed() {
- return !getConnection().isClosed();
- }
-
- @Test
- public void testGetOptimizerVersion() {
- try (Connection connection = getConnection()) {
- if (isGetOptimizerVersionAllowed()) {
- log("@EXPECT RESULT_SET 'OPTIMIZER_VERSION'");
- log("SHOW VARIABLE OPTIMIZER_VERSION;");
- assertThat(connection.getOptimizerVersion(), is(notNullValue()));
- } else {
- log("@EXPECT EXCEPTION FAILED_PRECONDITION");
- log("SHOW VARIABLE OPTIMIZER_VERSION;");
- exception.expect(matchCode(ErrorCode.FAILED_PRECONDITION));
- connection.getOptimizerVersion();
- }
- }
- }
-
- abstract boolean isCommitAllowed();
-
- @Test
- public void testCommit() {
- try (Connection connection = getConnection()) {
- if (!isCommitAllowed()) {
- log("@EXPECT EXCEPTION FAILED_PRECONDITION");
- exception.expect(matchCode(ErrorCode.FAILED_PRECONDITION));
- }
- log("COMMIT;");
- connection.commit();
- }
- }
-
- abstract boolean isRollbackAllowed();
-
- @Test
- public void testRollback() {
- try (Connection connection = getConnection()) {
- if (!isRollbackAllowed()) {
- log("@EXPECT EXCEPTION FAILED_PRECONDITION");
- exception.expect(matchCode(ErrorCode.FAILED_PRECONDITION));
- }
- log("ROLLBACK;");
- connection.rollback();
- }
- }
-
- abstract boolean expectedIsInTransaction();
-
- @Test
- public void testIsInTransaction() {
- try (Connection connection = getConnection()) {
- assertThat(connection.isInTransaction(), is(expectedIsInTransaction()));
- }
- }
-
- abstract boolean expectedIsTransactionStarted();
-
- @Test
- public void testIsTransactionStarted() {
- try (Connection connection = getConnection()) {
- assertThat(connection.isTransactionStarted(), is(expectedIsTransactionStarted()));
- }
- }
-
- abstract boolean isGetReadTimestampAllowed();
-
- @Test
- public void testGetReadTimestamp() {
- try (Connection connection = getConnection()) {
- if (isGetReadTimestampAllowed()) {
- log("@EXPECT RESULT_SET 'READ_TIMESTAMP'");
- log("SHOW VARIABLE READ_TIMESTAMP;");
- assertThat(connection.getReadTimestamp(), is(notNullValue()));
- } else {
- log("@EXPECT RESULT_SET 'READ_TIMESTAMP',null");
- log("SHOW VARIABLE READ_TIMESTAMP;");
- exception.expect(matchCode(ErrorCode.FAILED_PRECONDITION));
- connection.getReadTimestamp();
- }
- }
- }
-
- abstract boolean isGetCommitTimestampAllowed();
-
- @Test
- public void testGetCommitTimestamp() {
- try (Connection connection = getConnection()) {
- if (isGetCommitTimestampAllowed()) {
- log("@EXPECT RESULT_SET 'COMMIT_TIMESTAMP'");
- log("SHOW VARIABLE COMMIT_TIMESTAMP;");
- assertThat(connection.getCommitTimestamp(), is(notNullValue()));
- } else {
- log("@EXPECT RESULT_SET 'COMMIT_TIMESTAMP',null");
- log("SHOW VARIABLE COMMIT_TIMESTAMP;");
- exception.expect(matchCode(ErrorCode.FAILED_PRECONDITION));
- connection.getCommitTimestamp();
- }
- }
- }
-
- abstract boolean isExecuteAllowed(StatementType type);
-
- @Test
- public void testExecute() {
- for (StatementType type :
- new StatementType[] {StatementType.QUERY, StatementType.UPDATE, StatementType.DDL}) {
- testExecute(type);
- }
- }
-
- private void testExecute(final StatementType type) {
- try (Connection connection = getConnection()) {
- if (isExecuteAllowed(type)) {
- log(getTestStatement(type).getSql() + ";");
- assertThat(connection.execute(getTestStatement(type)), is(notNullValue()));
- } else {
- expectSpannerException(
- type + " should not be allowed",
- new ConnectionConsumer() {
- @Override
- public void accept(Connection t) {
- log("@EXPECT EXCEPTION FAILED_PRECONDITION");
- log(getTestStatement(type).getSql() + ";");
- t.execute(getTestStatement(type));
- }
- },
- connection);
- }
- }
- }
-
- private Statement getTestStatement(StatementType type) {
- switch (type) {
- case QUERY:
- return Statement.of(SELECT);
- case UPDATE:
- return Statement.of(UPDATE);
- case DDL:
- return Statement.of(DDL);
- case CLIENT_SIDE:
- case UNKNOWN:
- default:
- throw new IllegalArgumentException("Unsupported type: " + type);
- }
- }
-
- @Test
- public void testExecuteQuery() {
- for (StatementType type :
- new StatementType[] {StatementType.QUERY, StatementType.UPDATE, StatementType.DDL}) {
- testExecuteQuery(type);
- }
- }
-
- private void testExecuteQuery(final StatementType type) {
- try (Connection connection = getConnection()) {
- if (type == StatementType.QUERY && isExecuteAllowed(StatementType.QUERY)) {
- log("@EXPECT RESULT_SET 'TEST',1");
- log(getTestStatement(type).getSql() + ";");
- ResultSet rs = connection.executeQuery(getTestStatement(type));
- assertThat(rs, is(notNullValue()));
- assertThat(rs.getStats(), is(nullValue()));
- } else if (type == StatementType.QUERY) {
- // it is a query, but queries are not allowed for this connection state
- expectSpannerException(
- type + " should not be allowed",
- new ConnectionConsumer() {
- @Override
- public void accept(Connection t) {
- log("@EXPECT EXCEPTION FAILED_PRECONDITION");
- log(getTestStatement(type).getSql() + ";");
- t.executeQuery(getTestStatement(type));
- }
- },
- connection,
- ErrorCode.FAILED_PRECONDITION);
- } else {
- expectSpannerException(
- type + " should be an invalid argument",
- new ConnectionConsumer() {
- @Override
- public void accept(Connection t) {
- t.executeQuery(getTestStatement(type));
- }
- },
- connection,
- ErrorCode.INVALID_ARGUMENT);
- }
- }
- }
-
- @Test
- public void testAnalyzeQuery() {
- for (StatementType type :
- new StatementType[] {StatementType.QUERY, StatementType.UPDATE, StatementType.DDL}) {
- testAnalyzeQuery(type);
- }
- }
-
- private void testAnalyzeQuery(final StatementType type) {
- // TODO: add log statements when ANALYZE ... sql statements are supported
- try (Connection connection = getConnection()) {
- for (QueryAnalyzeMode mode : QueryAnalyzeMode.values()) {
- final QueryAnalyzeMode currentMode = mode;
- if (type == StatementType.QUERY && isExecuteAllowed(StatementType.QUERY)) {
- ResultSet rs = connection.analyzeQuery(getTestStatement(type), currentMode);
- assertThat(rs, is(notNullValue()));
- while (rs.next()) {}
- assertThat(rs.getStats(), is(notNullValue()));
- } else if (type == StatementType.QUERY) {
- // it is a query, but queries are not allowed for this connection state
- expectSpannerException(
- type + " should not be allowed",
- new ConnectionConsumer() {
- @Override
- public void accept(Connection t) {
- t.analyzeQuery(getTestStatement(type), currentMode);
- }
- },
- connection,
- ErrorCode.FAILED_PRECONDITION);
- } else {
- expectSpannerException(
- type + " should be an invalid argument",
- new ConnectionConsumer() {
- @Override
- public void accept(Connection t) {
- t.analyzeQuery(getTestStatement(type), currentMode);
- }
- },
- connection,
- ErrorCode.INVALID_ARGUMENT);
- }
- }
- }
- }
-
- @Test
- public void testExecuteUpdate() {
- for (StatementType type :
- new StatementType[] {StatementType.QUERY, StatementType.UPDATE, StatementType.DDL}) {
- testExecuteUpdate(type);
- }
- }
-
- private void testExecuteUpdate(final StatementType type) {
- try (Connection connection = getConnection()) {
- if (type == StatementType.UPDATE && isExecuteAllowed(StatementType.UPDATE)) {
- log("@EXPECT UPDATE_COUNT 1");
- log(getTestStatement(type).getSql() + ";");
- assertThat(connection.executeUpdate(getTestStatement(type)), is(notNullValue()));
- } else if (type == StatementType.UPDATE) {
- // it is an update statement, but updates are not allowed for this connection state
- expectSpannerException(
- type + "should not be allowed",
- new ConnectionConsumer() {
- @Override
- public void accept(Connection t) {
- log("@EXPECT EXCEPTION FAILED_PRECONDITION");
- log(getTestStatement(type).getSql() + ";");
- t.executeUpdate(getTestStatement(type));
- }
- },
- connection,
- ErrorCode.FAILED_PRECONDITION);
- } else {
- expectSpannerException(
- type + " should be an invalid argument",
- new ConnectionConsumer() {
- @Override
- public void accept(Connection t) {
- t.executeUpdate(getTestStatement(type));
- }
- },
- connection,
- ErrorCode.INVALID_ARGUMENT);
- }
- }
- }
-
- abstract boolean isWriteAllowed();
-
- @Test
- public void testWrite() {
- try (Connection connection = getConnection()) {
- if (!isWriteAllowed() || !connection.isAutocommit()) {
- exception.expect(matchCode(ErrorCode.FAILED_PRECONDITION));
- }
- connection.write(createTestMutation());
- }
- }
-
- @Test
- public void testWriteIterable() {
- try (Connection connection = getConnection()) {
- if (!isWriteAllowed() || !connection.isAutocommit()) {
- exception.expect(matchCode(ErrorCode.FAILED_PRECONDITION));
- }
- connection.write(Arrays.asList(createTestMutation()));
- }
- }
-
- @Test
- public void testBufferedWrite() {
- try (Connection connection = getConnection()) {
- if (!isWriteAllowed() || connection.isAutocommit()) {
- exception.expect(matchCode(ErrorCode.FAILED_PRECONDITION));
- }
- connection.bufferedWrite(createTestMutation());
- }
- }
-
- @Test
- public void testBufferedWriteIterable() {
- try (Connection connection = getConnection()) {
- if (!isWriteAllowed() || connection.isAutocommit()) {
- exception.expect(matchCode(ErrorCode.FAILED_PRECONDITION));
- }
- connection.bufferedWrite(Arrays.asList(createTestMutation()));
- }
- }
-
- private Mutation createTestMutation() {
- return Mutation.newInsertBuilder("foo").set("id").to(1L).set("name").to("bar").build();
- }
-}
diff --git a/src/test/java/com/google/cloud/spanner/jdbc/AbstractMockServerTest.java b/src/test/java/com/google/cloud/spanner/jdbc/AbstractMockServerTest.java
deleted file mode 100644
index 4117eed2..00000000
--- a/src/test/java/com/google/cloud/spanner/jdbc/AbstractMockServerTest.java
+++ /dev/null
@@ -1,186 +0,0 @@
-/*
- * Copyright 2020 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.cloud.spanner.jdbc;
-
-import com.google.cloud.spanner.MockSpannerServiceImpl;
-import com.google.cloud.spanner.MockSpannerServiceImpl.StatementResult;
-import com.google.cloud.spanner.Statement;
-import com.google.cloud.spanner.admin.database.v1.MockDatabaseAdminImpl;
-import com.google.cloud.spanner.admin.instance.v1.MockInstanceAdminImpl;
-import com.google.cloud.spanner.jdbc.ITAbstractSpannerTest.AbortInterceptor;
-import com.google.cloud.spanner.jdbc.ITAbstractSpannerTest.ITConnection;
-import com.google.protobuf.AbstractMessage;
-import com.google.protobuf.ListValue;
-import com.google.protobuf.Value;
-import com.google.spanner.v1.ExecuteSqlRequest;
-import com.google.spanner.v1.ResultSetMetadata;
-import com.google.spanner.v1.StructType;
-import com.google.spanner.v1.StructType.Field;
-import com.google.spanner.v1.Type;
-import com.google.spanner.v1.TypeCode;
-import io.grpc.Server;
-import io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder;
-import java.io.IOException;
-import java.net.InetSocketAddress;
-import java.sql.DriverManager;
-import java.sql.SQLException;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import org.junit.After;
-import org.junit.AfterClass;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-@RunWith(JUnit4.class)
-public abstract class AbstractMockServerTest {
- static final long COUNT_BEFORE_INSERT = 0L;
- static final long COUNT_AFTER_INSERT = 1L;
- static final Statement SELECT_COUNT_STATEMENT =
- Statement.of("SELECT COUNT(*) AS C FROM TEST WHERE ID=1");
- private static final ResultSetMetadata SELECT_COUNT_METADATA =
- ResultSetMetadata.newBuilder()
- .setRowType(
- StructType.newBuilder()
- .addFields(
- Field.newBuilder()
- .setName("C")
- .setType(Type.newBuilder().setCode(TypeCode.INT64).build())
- .build())
- .build())
- .build();
- static final com.google.spanner.v1.ResultSet SELECT_COUNT_RESULTSET_BEFORE_INSERT =
- com.google.spanner.v1.ResultSet.newBuilder()
- .addRows(
- ListValue.newBuilder()
- .addValues(
- Value.newBuilder()
- .setStringValue(String.valueOf(COUNT_BEFORE_INSERT))
- .build())
- .build())
- .setMetadata(SELECT_COUNT_METADATA)
- .build();
- static final com.google.spanner.v1.ResultSet SELECT_COUNT_RESULTSET_AFTER_INSERT =
- com.google.spanner.v1.ResultSet.newBuilder()
- .addRows(
- ListValue.newBuilder()
- .addValues(
- Value.newBuilder().setStringValue(String.valueOf(COUNT_AFTER_INSERT)).build())
- .build())
- .setMetadata(SELECT_COUNT_METADATA)
- .build();
- static final Statement INSERT_STATEMENT =
- Statement.of("INSERT INTO TEST (ID, NAME) VALUES (1, 'test aborted')");
- static final int UPDATE_COUNT = 1;
-
- static MockSpannerServiceImpl mockSpanner;
- static MockInstanceAdminImpl mockInstanceAdmin;
- static MockDatabaseAdminImpl mockDatabaseAdmin;
- private static Server server;
- private static InetSocketAddress address;
-
- @BeforeClass
- public static void startStaticServer() throws IOException {
- mockSpanner = new MockSpannerServiceImpl();
- mockSpanner.setAbortProbability(0.0D); // We don't want any unpredictable aborted transactions.
- mockInstanceAdmin = new MockInstanceAdminImpl();
- mockDatabaseAdmin = new MockDatabaseAdminImpl();
- address = new InetSocketAddress("localhost", 0);
- server =
- NettyServerBuilder.forAddress(address)
- .addService(mockSpanner)
- .addService(mockInstanceAdmin)
- .addService(mockDatabaseAdmin)
- .build()
- .start();
- mockSpanner.putStatementResult(
- StatementResult.query(SELECT_COUNT_STATEMENT, SELECT_COUNT_RESULTSET_BEFORE_INSERT));
- mockSpanner.putStatementResult(StatementResult.update(INSERT_STATEMENT, UPDATE_COUNT));
- }
-
- @AfterClass
- public static void stopServer() throws Exception {
- SpannerPool.closeSpannerPool();
- server.shutdown();
- server.awaitTermination();
- }
-
- @Before
- public void setupResults() {
- mockSpanner.reset();
- }
-
- @After
- public void closeSpannerPool() {
- SpannerPool.closeSpannerPool();
- }
-
- java.sql.Connection createJdbcConnection() throws SQLException {
- return DriverManager.getConnection("jdbc:" + getBaseUrl());
- }
-
- ITConnection createConnection() {
- return createConnection(
- Collections.emptyList(),
- Collections.emptyList());
- }
-
- ITConnection createConnection(
- AbortInterceptor interceptor, TransactionRetryListener transactionRetryListener) {
- return createConnection(
- Arrays.asList(interceptor),
- Arrays.asList(transactionRetryListener));
- }
-
- ITConnection createConnection(
- List interceptors,
- List transactionRetryListeners) {
- StringBuilder url = new StringBuilder(getBaseUrl());
- ConnectionOptions.Builder builder =
- ConnectionOptions.newBuilder()
- .setUri(url.toString())
- .setStatementExecutionInterceptors(interceptors);
- ConnectionOptions options = builder.build();
- ITConnection connection = createITConnection(options);
- for (TransactionRetryListener listener : transactionRetryListeners) {
- connection.addTransactionRetryListener(listener);
- }
- return connection;
- }
-
- String getBaseUrl() {
- return String.format(
- "cloudspanner://localhost:%d/projects/proj/instances/inst/databases/db?usePlainText=true;autocommit=false;retryAbortsInternally=true",
- server.getPort());
- }
-
- ExecuteSqlRequest getLastExecuteSqlRequest() {
- List requests = mockSpanner.getRequests();
- for (int i = requests.size() - 1; i >= 0; i--) {
- if (requests.get(i) instanceof ExecuteSqlRequest) {
- return (ExecuteSqlRequest) requests.get(i);
- }
- }
- throw new IllegalStateException("No ExecuteSqlRequest found in requests");
- }
-
- private ITConnection createITConnection(ConnectionOptions options) {
- return new ITConnectionImpl(options);
- }
-}
diff --git a/src/test/java/com/google/cloud/spanner/jdbc/AbstractSqlScriptVerifier.java b/src/test/java/com/google/cloud/spanner/jdbc/AbstractSqlScriptVerifier.java
deleted file mode 100644
index 3e854d3e..00000000
--- a/src/test/java/com/google/cloud/spanner/jdbc/AbstractSqlScriptVerifier.java
+++ /dev/null
@@ -1,453 +0,0 @@
-/*
- * Copyright 2019 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.cloud.spanner.jdbc;
-
-import static org.hamcrest.CoreMatchers.equalTo;
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.CoreMatchers.not;
-import static org.hamcrest.CoreMatchers.notNullValue;
-import static org.hamcrest.CoreMatchers.nullValue;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-
-import com.google.cloud.Timestamp;
-import com.google.cloud.spanner.ResultSet;
-import com.google.cloud.spanner.SpannerException;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Scanner;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Base class for SQL Script verifiers for both the generic Connection API and JDBC connections
- *
- * Simple parser/verifier for sql statements. This verifier is able to parse additional @EXPECT
- * statements that defines the expected behavior of a sql statement. Possible uses are:
- *
- *
- * - @EXPECT NO_RESULT: The following statement should not return a result (no {@link ResultSet}
- * and no update count)
- *
- @EXPECT UPDATE_COUNT count: The following statement should return the specified
- * update count
- *
- @EXPECT RESULT_SET: The following statement should return a {@link ResultSet} with two
- * columns with the names ACTUAL and EXPECTED and containing at least one row. For each row,
- * the values of ACTUAL and EXPECTED must be equal
- *
- @EXPECT RESULT_SET 'columnName': The following statement should return a {@link ResultSet}
- * with a column with the specified name and containing at least one row (additional columns
- * in the {@link ResultSet} are allowed). For each row, the value of the column must be not
- * null
- *
- @EXPECT RESULT_SET 'columnName',value: The following statement should return a {@link
- * ResultSet} with a column with the specified name and containing at least one row
- * (additional columns in the {@link ResultSet} are allowed). For each row, the value of the
- * column must be equal to the specified value
- *
- @EXPECT EXCEPTION code ['messagePrefix']: The following statement should throw a {@link
- * SpannerException} with the specified code and starting with the (optional) message prefix
- *
- @EXPECT EQUAL 'variable1','variable2': The values of the two given variables should be
- * equal. The value of a variable can be set using a @PUT statement.
- *
- *
- * The parser can set a temporary variable value using a @PUT statement:
- * @PUT 'variable_name'\nSQL statement
The SQL statement must be a statement that returns a
- * {@link ResultSet} containing exactly one row and one column.
- *
- * In addition the verifier can create new connections if the script contains NEW_CONNECTION;
- * statements and the verifier has been created with a {@link GenericConnectionProvider}. See {@link
- * ConnectionImplGeneratedSqlScriptTest} for an example for this.
- */
-public abstract class AbstractSqlScriptVerifier {
- private static final Pattern VERIFY_PATTERN =
- Pattern.compile(
- "(?is)\\s*(?:@EXPECT)\\s+"
- + "(?NO_RESULT"
- + "|RESULT_SET\\s*(?'.*?'(?,.*?)?)?"
- + "|UPDATE_COUNT\\s*(?-?\\d{1,19})"
- + "|EXCEPTION\\s*(?(?CANCELLED|UNKNOWN|INVALID_ARGUMENT|DEADLINE_EXCEEDED|NOT_FOUND|ALREADY_EXISTS|PERMISSION_DENIED|UNAUTHENTICATED|RESOURCE_EXHAUSTED|FAILED_PRECONDITION|ABORTED|OUT_OF_RANGE|UNIMPLEMENTED|INTERNAL|UNAVAILABLE|DATA_LOSS)(?:\\s*)(?'.*?')?)"
- + "|EQUAL\\s+(?'.+?')\\s*,\\s*(?'.+?')"
- + ")"
- + "(\\n(?.*))?");
-
- private static final String PUT_CONDITION =
- "@PUT can only be used in combination with a statement that returns a"
- + " result set containing exactly one row and one column";
- private static final Pattern PUT_PATTERN =
- Pattern.compile("(?is)\\s*(?:@PUT)\\s+(?