All Downloads are FREE. Search and download functionalities are using the official Maven repository.

nl.topicus.jdbc.transaction.CloudSpannerTransaction Maven / Gradle / Ivy

There is a newer version: 1.1.6
Show newest version
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;
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy