nl.topicus.jdbc.transaction.CloudSpannerTransaction Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of spanner-jdbc Show documentation
Show all versions of spanner-jdbc Show documentation
JDBC Driver for Google Cloud Spanner
package nl.topicus.jdbc.transaction;
import java.sql.SQLException;
import java.sql.Savepoint;
import java.util.List;
import nl.topicus.jdbc.shaded.com.google.cloud.Timestamp;
import nl.topicus.jdbc.shaded.com.google.cloud.spanner.BatchClient;
import nl.topicus.jdbc.shaded.com.google.cloud.spanner.BatchReadOnlyTransaction;
import nl.topicus.jdbc.shaded.com.google.cloud.spanner.BatchTransactionId;
import nl.topicus.jdbc.shaded.com.google.cloud.spanner.DatabaseClient;
import nl.topicus.jdbc.shaded.com.google.cloud.spanner.ErrorCode;
import nl.topicus.jdbc.shaded.com.google.cloud.spanner.Key;
import nl.topicus.jdbc.shaded.com.google.cloud.spanner.KeySet;
import nl.topicus.jdbc.shaded.com.google.cloud.spanner.Mutation;
import nl.topicus.jdbc.shaded.com.google.cloud.spanner.Options.QueryOption;
import nl.topicus.jdbc.shaded.com.google.cloud.spanner.Options.ReadOption;
import nl.topicus.jdbc.shaded.com.google.cloud.spanner.Partition;
import nl.topicus.jdbc.shaded.com.google.cloud.spanner.PartitionOptions;
import nl.topicus.jdbc.shaded.com.google.cloud.spanner.ReadOnlyTransaction;
import nl.topicus.jdbc.shaded.com.google.cloud.spanner.ResultSet;
import nl.topicus.jdbc.shaded.com.google.cloud.spanner.SpannerException;
import nl.topicus.jdbc.shaded.com.google.cloud.spanner.SpannerExceptionFactory;
import nl.topicus.jdbc.shaded.com.google.cloud.spanner.Statement;
import nl.topicus.jdbc.shaded.com.google.cloud.spanner.Struct;
import nl.topicus.jdbc.shaded.com.google.cloud.spanner.TimestampBound;
import nl.topicus.jdbc.shaded.com.google.cloud.spanner.TransactionContext;
import nl.topicus.jdbc.shaded.com.google.common.base.Preconditions;
import nl.topicus.jdbc.shaded.com.google.rpc.Code;
import nl.topicus.jdbc.CloudSpannerConnection;
import nl.topicus.jdbc.exception.CloudSpannerSQLException;
/**
* An abstraction of transactions on Google Cloud Spanner JDBC connections.
*
* @author loite
*
*/
public class CloudSpannerTransaction implements TransactionContext, BatchReadOnlyTransaction {
private static final String SAVEPOINTS_NOT_IN_READ_ONLY =
"Savepoints are not allowed in read-only mode";
private static final String METHOD_NOT_IMPLEMENTED = "This method is not implemented";
private static final String METHOD_ONLY_IN_BATCH_READONLY =
"This method may only be called when in batch read-only mode";
public static class TransactionException extends RuntimeException {
private static final long serialVersionUID = 1L;
private TransactionException(String message, SQLException cause) {
super(message, cause);
}
}
private TransactionThread transactionThread;
private ReadOnlyTransaction readOnlyTransaction;
private BatchReadOnlyTransaction batchReadOnlyTransaction;
private DatabaseClient dbClient;
private BatchClient batchClient;
private CloudSpannerConnection connection;
public CloudSpannerTransaction(DatabaseClient dbClient, BatchClient batchClient,
CloudSpannerConnection connection) {
this.dbClient = dbClient;
this.batchClient = batchClient;
this.connection = connection;
}
public boolean isRunning() {
return batchReadOnlyTransaction != null || readOnlyTransaction != null
|| transactionThread != null;
}
public boolean hasBufferedMutations() {
return transactionThread != null && transactionThread.hasBufferedMutations();
}
public int getNumberOfBufferedMutations() {
return transactionThread == null ? 0 : transactionThread.numberOfBufferedMutations();
}
public void begin() throws SQLException {
if (connection.isBatchReadOnly()) {
if (batchReadOnlyTransaction == null) {
batchReadOnlyTransaction = batchClient.batchReadOnlyTransaction(TimestampBound.strong());
}
} else if (connection.isReadOnly()) {
if (readOnlyTransaction == null) {
readOnlyTransaction = dbClient.readOnlyTransaction();
}
} else {
if (transactionThread == null) {
transactionThread = new TransactionThread(dbClient);
transactionThread.start();
}
}
}
public Timestamp commit() throws SQLException {
Timestamp res = null;
try {
if (connection.isBatchReadOnly()) {
if (batchReadOnlyTransaction != null) {
batchReadOnlyTransaction.close();
}
} else if (connection.isReadOnly()) {
if (readOnlyTransaction != null) {
readOnlyTransaction.close();
}
} else {
if (transactionThread != null) {
res = transactionThread.commit();
}
}
} finally {
transactionThread = null;
readOnlyTransaction = null;
batchReadOnlyTransaction = null;
}
return res;
}
public void rollback() throws SQLException {
try {
if (connection.isBatchReadOnly()) {
if (batchReadOnlyTransaction != null) {
batchReadOnlyTransaction.close();
}
} else if (connection.isReadOnly()) {
if (readOnlyTransaction != null) {
readOnlyTransaction.close();
}
} else {
if (transactionThread != null) {
transactionThread.rollback();
}
}
} finally {
transactionThread = null;
readOnlyTransaction = null;
batchReadOnlyTransaction = null;
}
}
public void setSavepoint(Savepoint savepoint) throws SQLException {
Preconditions.checkNotNull(savepoint);
checkTransaction();
if (transactionThread == null)
throw new CloudSpannerSQLException(SAVEPOINTS_NOT_IN_READ_ONLY, Code.FAILED_PRECONDITION);
transactionThread.setSavepoint(savepoint);
}
public void rollbackSavepoint(Savepoint savepoint) throws SQLException {
Preconditions.checkNotNull(savepoint);
checkTransaction();
if (transactionThread == null)
throw new CloudSpannerSQLException(SAVEPOINTS_NOT_IN_READ_ONLY, Code.FAILED_PRECONDITION);
transactionThread.rollbackSavepoint(savepoint);
}
public void releaseSavepoint(Savepoint savepoint) throws SQLException {
Preconditions.checkNotNull(savepoint);
checkTransaction();
if (transactionThread == null)
throw new CloudSpannerSQLException(SAVEPOINTS_NOT_IN_READ_ONLY, Code.FAILED_PRECONDITION);
transactionThread.releaseSavepoint(savepoint);
}
@FunctionalInterface
private static interface TransactionAction {
public void apply(String xid) throws SQLException;
}
public void prepareTransaction(String xid) throws SQLException {
checkTransaction();
preparedTransactionAction(xid, transactionThread::prepareTransaction);
}
public void commitPreparedTransaction(String xid) throws SQLException {
checkTransaction();
preparedTransactionAction(xid, transactionThread::commitPreparedTransaction);
}
public void rollbackPreparedTransaction(String xid) throws SQLException {
checkTransaction();
preparedTransactionAction(xid, transactionThread::rollbackPreparedTransaction);
}
private void preparedTransactionAction(String xid, TransactionAction action) throws SQLException {
try {
if (connection.isReadOnly()) {
throw new CloudSpannerSQLException(
"Connection is in read-only mode and cannot be used for prepared transactions",
Code.FAILED_PRECONDITION);
} else {
action.apply(xid);
}
} finally {
transactionThread = null;
readOnlyTransaction = null;
batchReadOnlyTransaction = null;
}
}
private void checkTransaction() {
if (transactionThread == null && readOnlyTransaction == null
&& batchReadOnlyTransaction == null) {
try {
begin();
} catch (SQLException e) {
throw new TransactionException("Failed to start new transaction", e);
}
}
}
@Override
public void buffer(Mutation mutation) {
checkTransaction();
if (transactionThread == null)
throw new IllegalStateException("Mutations are not allowed in read-only mode");
transactionThread.buffer(mutation);
}
@Override
public void buffer(Iterable mutations) {
checkTransaction();
if (transactionThread == null)
throw new IllegalStateException("Mutations are not allowed in read-only mode");
transactionThread.buffer(mutations);
}
@Override
public ResultSet executeQuery(Statement statement, QueryOption... options) {
checkTransaction();
if (batchReadOnlyTransaction != null)
return batchReadOnlyTransaction.executeQuery(statement, options);
else if (readOnlyTransaction != null)
return readOnlyTransaction.executeQuery(statement, options);
else if (transactionThread != null)
return transactionThread.executeQuery(statement);
throw new IllegalStateException("No transaction found (this should not happen)");
}
@Override
public ResultSet read(String table, KeySet keys, Iterable columns,
ReadOption... options) {
throw SpannerExceptionFactory.newSpannerException(ErrorCode.UNIMPLEMENTED,
METHOD_NOT_IMPLEMENTED);
}
@Override
public ResultSet readUsingIndex(String table, String index, KeySet keys, Iterable columns,
ReadOption... options) {
throw SpannerExceptionFactory.newSpannerException(ErrorCode.UNIMPLEMENTED,
METHOD_NOT_IMPLEMENTED);
}
@Override
public Struct readRow(String table, Key key, Iterable columns) {
throw SpannerExceptionFactory.newSpannerException(ErrorCode.UNIMPLEMENTED,
METHOD_NOT_IMPLEMENTED);
}
@Override
public Struct readRowUsingIndex(String table, String index, Key key, Iterable columns) {
throw SpannerExceptionFactory.newSpannerException(ErrorCode.UNIMPLEMENTED,
METHOD_NOT_IMPLEMENTED);
}
@Override
public ResultSet analyzeQuery(Statement statement, QueryAnalyzeMode queryMode) {
throw SpannerExceptionFactory.newSpannerException(ErrorCode.UNIMPLEMENTED,
METHOD_NOT_IMPLEMENTED);
}
/**
* Close method is needed for the interface, but does not do anything
*/
@Override
public void close() {
// no-op as there is nothing to close or throw away
}
@Override
public Timestamp getReadTimestamp() {
if (batchReadOnlyTransaction != null)
return batchReadOnlyTransaction.getReadTimestamp();
if (readOnlyTransaction != null)
return readOnlyTransaction.getReadTimestamp();
return null;
}
@Override
public List partitionRead(PartitionOptions partitionOptions, String table, KeySet keys,
Iterable columns, ReadOption... options) throws SpannerException {
throw SpannerExceptionFactory.newSpannerException(ErrorCode.UNIMPLEMENTED,
METHOD_NOT_IMPLEMENTED);
}
@Override
public List partitionReadUsingIndex(PartitionOptions partitionOptions, String table,
String index, KeySet keys, Iterable columns, ReadOption... options)
throws SpannerException {
throw SpannerExceptionFactory.newSpannerException(ErrorCode.UNIMPLEMENTED,
METHOD_NOT_IMPLEMENTED);
}
@Override
public List partitionQuery(PartitionOptions partitionOptions, Statement statement,
QueryOption... options) throws SpannerException {
checkTransaction();
if (batchReadOnlyTransaction != null) {
return batchReadOnlyTransaction.partitionQuery(partitionOptions, statement, options);
}
throw SpannerExceptionFactory.newSpannerException(ErrorCode.FAILED_PRECONDITION,
METHOD_ONLY_IN_BATCH_READONLY);
}
@Override
public ResultSet execute(Partition partition) throws SpannerException {
checkTransaction();
if (batchReadOnlyTransaction != null) {
return batchReadOnlyTransaction.execute(partition);
}
throw SpannerExceptionFactory.newSpannerException(ErrorCode.FAILED_PRECONDITION,
METHOD_ONLY_IN_BATCH_READONLY);
}
@Override
public BatchTransactionId getBatchTransactionId() {
checkTransaction();
if (batchReadOnlyTransaction != null) {
return batchReadOnlyTransaction.getBatchTransactionId();
}
throw SpannerExceptionFactory.newSpannerException(ErrorCode.FAILED_PRECONDITION,
METHOD_ONLY_IN_BATCH_READONLY);
}
public BatchReadOnlyTransaction getBatchReadOnlyTransaction() {
return batchReadOnlyTransaction;
}
}