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