com.google.cloud.spanner.DatabaseClient Maven / Gradle / Ivy
Show all versions of google-cloud-spanner Show documentation
/*
* Copyright 2017 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;
import com.google.api.gax.rpc.ServerStream;
import com.google.cloud.Timestamp;
import com.google.cloud.spanner.Options.RpcPriority;
import com.google.cloud.spanner.Options.TransactionOption;
import com.google.cloud.spanner.Options.UpdateOption;
import com.google.spanner.v1.BatchWriteResponse;
/**
* Interface for all the APIs that are used to read/write data into a Cloud Spanner database. An
* instance of this is tied to a specific database.
*/
public interface DatabaseClient {
/**
* Returns the SQL dialect that is used by the database.
*
* @return the SQL dialect that is used by the database.
*/
default Dialect getDialect() {
throw new UnsupportedOperationException("method should be overwritten");
}
/**
* Returns the {@link DatabaseRole} used by the client connection. The database role that is used
* determines the access permissions that a connection has. This can for example be used to create
* connections that are only permitted to access certain tables.
*
* @return the {@link DatabaseRole} used by the client connection.
*/
default String getDatabaseRole() {
throw new UnsupportedOperationException("method should be overwritten");
}
/**
* Writes the given mutations atomically to the database.
*
* This method uses retries and replay protection internally, which means that the mutations
* are applied exactly once on success, or not at all if an error is returned, regardless of any
* failures in the underlying network. Note that if the call is cancelled or reaches deadline, it
* is not possible to know whether the mutations were applied without performing a subsequent
* database operation, but the mutations will have been applied at most once.
*
*
Example of blind write.
*
*
{@code
* long singerId = my_singer_id;
* Mutation mutation = Mutation.newInsertBuilder("Singer")
* .set("SingerId")
* .to(singerId)
* .set("FirstName")
* .to("Billy")
* .set("LastName")
* .to("Joel")
* .build();
* dbClient.write(Collections.singletonList(mutation));
* }
*
* @return the timestamp at which the write was committed
*/
Timestamp write(Iterable mutations) throws SpannerException;
/**
* Writes the given mutations atomically to the database with the given options.
*
* This method uses retries and replay protection internally, which means that the mutations
* are applied exactly once on success, or not at all if an error is returned, regardless of any
* failures in the underlying network. Note that if the call is cancelled or reaches deadline, it
* is not possible to know whether the mutations were applied without performing a subsequent
* database operation, but the mutations will have been applied at most once.
*
*
Example of blind write.
*
*
{@code
* long singerId = my_singer_id;
* Mutation mutation = Mutation.newInsertBuilder("Singer")
* .set("SingerId")
* .to(singerId)
* .set("FirstName")
* .to("Billy")
* .set("LastName")
* .to("Joel")
* .build();
* dbClient.writeWithOptions(
* Collections.singletonList(mutation),
* Options.priority(RpcPriority.HIGH));
* }
*
* Options for a transaction can include:
*
*
* - {@link Options#priority(com.google.cloud.spanner.Options.RpcPriority)}: The {@link
* RpcPriority} to use for the commit request of the transaction. The priority will not be
* applied to any other requests on the transaction.
*
- {@link Options#commitStats()}: Request that the server includes commit statistics in the
* {@link CommitResponse}.
*
*
* @return a response with the timestamp at which the write was committed
*/
CommitResponse writeWithOptions(Iterable mutations, TransactionOption... options)
throws SpannerException;
/**
* Writes the given mutations atomically to the database without replay protection.
*
* Since this method does not feature replay protection, it may attempt to apply {@code
* mutations} more than once; if the mutations are not idempotent, this may lead to a failure
* being reported when the mutation was applied once. For example, an insert may fail with {@link
* ErrorCode#ALREADY_EXISTS} even though the row did not exist before this method was called. For
* this reason, most users of the library will prefer to use {@link #write(Iterable)} instead.
* However, {@code writeAtLeastOnce()} requires only a single RPC, whereas {@code write()}
* requires two RPCs (one of which may be performed in advance), and so this method may be
* appropriate for latency sensitive and/or high throughput blind writing.
*
*
Example of unprotected blind write.
*
*
{@code
* long singerId = my_singer_id;
* Mutation mutation = Mutation.newInsertBuilder("Singers")
* .set("SingerId")
* .to(singerId)
* .set("FirstName")
* .to("Billy")
* .set("LastName")
* .to("Joel")
* .build();
* dbClient.writeAtLeastOnce(Collections.singletonList(mutation));
* }
*
* @return the timestamp at which the write was committed
*/
Timestamp writeAtLeastOnce(Iterable mutations) throws SpannerException;
/**
* Writes the given mutations atomically to the database without replay protection.
*
* Since this method does not feature replay protection, it may attempt to apply {@code
* mutations} more than once; if the mutations are not idempotent, this may lead to a failure
* being reported when the mutation was applied once. For example, an insert may fail with {@link
* ErrorCode#ALREADY_EXISTS} even though the row did not exist before this method was called. For
* this reason, most users of the library will prefer to use {@link #write(Iterable)} instead.
* However, {@code writeAtLeastOnce()} requires only a single RPC, whereas {@code write()}
* requires two RPCs (one of which may be performed in advance), and so this method may be
* appropriate for latency sensitive and/or high throughput blind writing.
*
*
Example of unprotected blind write.
*
*
{@code
* long singerId = my_singer_id;
* Mutation mutation = Mutation.newInsertBuilder("Singers")
* .set("SingerId")
* .to(singerId)
* .set("FirstName")
* .to("Billy")
* .set("LastName")
* .to("Joel")
* .build();
* dbClient.writeAtLeastOnceWithOptions(
* Collections.singletonList(mutation),
* Options.priority(RpcPriority.LOW));
* }
*
* Options for a transaction can include:
*
*
* - {@link Options#priority(com.google.cloud.spanner.Options.RpcPriority)}: The {@link
* RpcPriority} to use for the commit request of the transaction. The priority will not be
* applied to any other requests on the transaction.
*
- {@link Options#commitStats()}: Request that the server includes commit statistics in the
* {@link CommitResponse}.
*
*
* @return a response with the timestamp at which the write was committed
*/
CommitResponse writeAtLeastOnceWithOptions(
Iterable mutations, TransactionOption... options) throws SpannerException;
/**
* Applies batch of mutation groups in a collection of efficient transactions. The mutation groups
* are applied non-atomically in an unspecified order and thus, they must be independent of each
* other. Partial failure is possible, i.e., some mutation groups may have been applied
* successfully, while some may have failed. The results of individual batches are streamed into
* the response as and when the batches are applied.
*
* One BatchWriteResponse can contain the results for multiple MutationGroups. Inspect the
* indexes field to determine the MutationGroups that the BatchWriteResponse is for.
*
*
The mutation groups may be applied more than once. This can lead to failures if the mutation
* groups are non-idempotent. For example, an insert that is replayed can return an {@link
* ErrorCode#ALREADY_EXISTS} error. For this reason, users of the library may prefer to use {@link
* #write(Iterable)} instead. However, {@code batchWriteAtLeastOnce()} method may be appropriate
* for non-atomically committing multiple mutation groups in a single RPC with low latency.
*
*
Example of BatchWriteAtLeastOnce
*
*
{@code
* Iterable mutationGroups =
* ImmutableList.of(
* MutationGroup.of(
* Mutation.newInsertBuilder("FOO1").set("ID").to(1L).set("NAME").to("Bar1").build(),
* Mutation.newInsertBuilder("FOO2").set("ID").to(2L).set("NAME").to("Bar2").build()),
* MutationGroup.of(
* Mutation.newInsertBuilder("FOO3").set("ID").to(3L).set("NAME").to("Bar3").build(),
* Mutation.newInsertBuilder("FOO4").set("ID").to(4L).set("NAME").to("Bar4").build()),
* MutationGroup.of(
* Mutation.newInsertBuilder("FOO4").set("ID").to(4L).set("NAME").to("Bar4").build(),
* Mutation.newInsertBuilder("FOO5").set("ID").to(5L).set("NAME").to("Bar5").build()),
* MutationGroup.of(
* Mutation.newInsertBuilder("FOO6").set("ID").to(6L).set("NAME").to("Bar6").build()));
* ServerStream responses =
* dbClient.batchWriteAtLeastOnce(mutationGroups, Options.tag("batch-write-tag"));
* for (BatchWriteResponse response : responses) {
* // Do something when a response is received.
* }
* }
*
* Options for a transaction can include:
*
*
* - {@link Options#priority(com.google.cloud.spanner.Options.RpcPriority)}: The {@link
* RpcPriority} to use for the batch write request.
*
- {@link Options#tag(String)}: The transaction tag to use for the batch write request.
*
*/
ServerStream batchWriteAtLeastOnce(
Iterable mutationGroups, TransactionOption... options) throws SpannerException;
/**
* Returns a context in which a single read can be performed using {@link TimestampBound#strong()}
* concurrency. This method will return a {@link ReadContext} that will not return the read
* timestamp that was used by Cloud Spanner. If you want to be able to access the read timestamp,
* you should use the method {@link #singleUseReadOnlyTransaction()}.
*
* Example of single use.
*
*
{@code
* long singerId = my_singer_id;
* String column = "FirstName";
* Struct row =
* dbClient.singleUse().readRow("Singers", Key.of(singerId), Collections.singleton(column));
* String firstName = row.getString(column);
* }
*/
ReadContext singleUse();
/**
* Returns a context in which a single read can be performed at the given timestamp bound. This
* method will return a {@link ReadContext} that will not return the read timestamp that was used
* by Cloud Spanner. If you want to be able to access the read timestamp, you should use the
* method {@link #singleUseReadOnlyTransaction()}.
*
* Example of single use with timestamp bound.
*
*
{@code
* long singerId = my_singer_id;
* String column = "FirstName";
* Struct row = dbClient.singleUse(TimestampBound.ofMaxStaleness(10, TimeUnit.SECONDS))
* .readRow("Singers", Key.of(singerId), Collections.singleton(column));
* String firstName = row.getString(column);
* }
*
* @param bound the timestamp bound at which to perform the read
*/
ReadContext singleUse(TimestampBound bound);
/**
* Returns a read-only transaction context in which a single read or query can be performed using
* {@link TimestampBound#strong()} concurrency. This method differs from {@link #singleUse()} in
* that the read timestamp used may be inspected after the read has returned data or finished
* successfully.
*
* Example of single use read only transaction.
*
*
{@code
* long singerId = my_singer_id;
* String column = "FirstName";
* ReadOnlyTransaction txn = dbClient.singleUseReadOnlyTransaction();
* Struct row = txn.readRow("Singers", Key.of(singerId), Collections.singleton(column));
* row.getString(column);
* Timestamp timestamp = txn.getReadTimestamp();
* }
*/
ReadOnlyTransaction singleUseReadOnlyTransaction();
/**
* Returns a read-only transaction context in which a single read or query can be performed at
* given timestamp bound. This method differs from {@link #singleUse(TimestampBound)} in that the
* read timestamp used may be inspected after the read has returned data or finished successfully.
*
* Example of single use read only transaction with timestamp bound.
*
*
{@code
* long singerId = my_singer_id;
* String column = "FirstName";
* ReadOnlyTransaction txn =
* dbClient.singleUseReadOnlyTransaction(TimestampBound.ofMaxStaleness(10, TimeUnit.SECONDS));
* Struct row = txn.readRow("Singers", Key.of(singerId), Collections.singleton(column));
* row.getString(column);
* Timestamp timestamp = txn.getReadTimestamp();
* }
*
* @param bound the timestamp bound at which to perform the read
*/
ReadOnlyTransaction singleUseReadOnlyTransaction(TimestampBound bound);
/**
* Returns a read-only transaction context in which a multiple reads and/or queries can be
* performed using {@link TimestampBound#strong()} concurrency. All reads/queries will use the
* same timestamp, and the timestamp can be inspected after any read/query has returned data or
* finished successfully.
*
* Example of read only transaction.
*
*
{@code
* long singerId = my_singer_id;
* long albumId = my_album_id;
* String singerColumn = "FirstName";
* String albumColumn = "AlbumTitle";
* String albumTitle = null;
* // ReadOnlyTransaction should be closed to prevent resource leak.
* try (ReadOnlyTransaction txn = dbClient.readOnlyTransaction()) {
* Struct singerRow =
* txn.readRow("Singers", Key.of(singerId), Collections.singleton(singerColumn));
* Struct albumRow =
* txn.readRow("Albums", Key.of(singerId, albumId), Collections.singleton(albumColumn));
* singerRow.getString(singerColumn);
* albumTitle = albumRow.getString(albumColumn);
* }
* }
*/
ReadOnlyTransaction readOnlyTransaction();
/**
* Returns a read-only transaction context in which a multiple reads and/or queries can be
* performed at the given timestamp bound. All reads/queries will use the same timestamp, and the
* timestamp can be inspected after any read/query has returned data or finished successfully.
*
* Note that the bounded staleness modes, {@link TimestampBound.Mode#MIN_READ_TIMESTAMP} and
* {@link TimestampBound.Mode#MAX_STALENESS}, are not supported for multi-use read-only
* transactions.
*
*
Example of read only transaction with timestamp bound.
*
*
{@code
* long singerId = my_singer_id;
* long albumId = my_album_id;
* String singerColumn = "FirstName";
* String albumColumn = "AlbumTitle";
* String albumTitle = null;
* // ReadOnlyTransaction should be closed to prevent resource leak.
* try (ReadOnlyTransaction txn =
* dbClient.readOnlyTransaction(TimestampBound.ofExactStaleness(10, TimeUnit.SECONDS))) {
* Struct singerRow =
* txn.readRow("Singers", Key.of(singerId), Collections.singleton(singerColumn));
* Struct albumRow =
* txn.readRow("Albums", Key.of(singerId, albumId), Collections.singleton(albumColumn));
* singerRow.getString(singerColumn);
* albumTitle = albumRow.getString(albumColumn);
* }
* }
*
* @param bound the timestamp bound at which to perform the read
*/
ReadOnlyTransaction readOnlyTransaction(TimestampBound bound);
/**
* Returns a transaction runner for executing a single logical transaction with retries. The
* returned runner can only be used once.
*
* Example of a read write transaction.
*
*
* long singerId = my_singer_id;
* TransactionRunner runner = dbClient.readWriteTransaction();
* runner.run(
* new TransactionCallable<Void>() {
*
* {@literal @}Override
* public Void run(TransactionContext transaction) throws Exception {
* String column = "FirstName";
* Struct row =
* transaction.readRow("Singers", Key.of(singerId), Collections.singleton(column));
* String name = row.getString(column);
* transaction.buffer(
* Mutation.newUpdateBuilder("Singers").set(column).to(name.toUpperCase()).build());
* return null;
* }
* });
*
*
* Options for a transaction can include:
*
*
* - {@link Options#priority(com.google.cloud.spanner.Options.RpcPriority)}: The {@link
* RpcPriority} to use for the commit request of the transaction. The priority will not be
* applied to any other requests on the transaction.
*
- {@link Options#commitStats()}: Request that the server includes commit statistics in the
* {@link CommitResponse}.
*
*/
TransactionRunner readWriteTransaction(TransactionOption... options);
/**
* Returns a transaction manager which allows manual management of transaction lifecycle. This API
* is meant for advanced users. Most users should instead use the {@link
* #readWriteTransaction(TransactionOption...)} API instead.
*
* Example of using {@link TransactionManager}.
*
*
{@code
* long singerId = my_singer_id;
* try (TransactionManager manager = dbClient.transactionManager()) {
* TransactionContext transaction = manager.begin();
* while (true) {
* String column = "FirstName";
* Struct row = transaction.readRow("Singers", Key.of(singerId), Collections.singleton(column));
* String name = row.getString(column);
* transaction.buffer(
* Mutation.newUpdateBuilder("Singers").set(column).to(name.toUpperCase()).build());
* try {
* manager.commit();
* break;
* } catch (AbortedException e) {
* Thread.sleep(e.getRetryDelayInMillis());
* transaction = manager.resetForRetry();
* }
* }
* }
* }
*
* Options for a transaction can include:
*
*
* - {@link Options#priority(com.google.cloud.spanner.Options.RpcPriority)}: The {@link
* RpcPriority} to use for the commit request of the transaction. The priority will not be
* applied to any other requests on the transaction.
*
- {@link Options#commitStats()}: Request that the server includes commit statistics in the
* {@link CommitResponse}.
*
*/
TransactionManager transactionManager(TransactionOption... options);
/**
* Returns an asynchronous transaction runner for executing a single logical transaction with
* retries. The returned runner can only be used once.
*
* Example of a read write transaction.
*
*
{@code
* Executor executor = Executors.newSingleThreadExecutor();
* final long singerId = my_singer_id;
* AsyncRunner runner = client.runAsync();
* ApiFuture rowCount =
* runner.runAsync(
* () -> {
* String column = "FirstName";
* Struct row =
* txn.readRow("Singers", Key.of(singerId), Collections.singleton("Name"));
* String name = row.getString("Name");
* return txn.executeUpdateAsync(
* Statement.newBuilder("UPDATE Singers SET Name=@name WHERE SingerId=@id")
* .bind("id")
* .to(singerId)
* .bind("name")
* .to(name.toUpperCase())
* .build());
* },
* executor);
* }
*
* Options for a transaction can include:
*
*
* - {@link Options#priority(com.google.cloud.spanner.Options.RpcPriority)}: The {@link
* RpcPriority} to use for the commit request of the transaction. The priority will not be
* applied to any other requests on the transaction.
*
- {@link Options#commitStats()}: Request that the server includes commit statistics in the
* {@link CommitResponse}.
*
*/
AsyncRunner runAsync(TransactionOption... options);
/**
* Returns an asynchronous transaction manager which allows manual management of transaction
* lifecycle. This API is meant for advanced users. Most users should instead use the {@link
* #runAsync(TransactionOption...)} API instead.
*
* Example of using {@link AsyncTransactionManager}.
*
*
{@code
* long singerId = 1L;
* try (AsyncTransactionManager manager = client.transactionManagerAsync()) {
* TransactionContextFuture transactionFuture = manager.beginAsync();
* while (true) {
* String column = "FirstName";
* CommitTimestampFuture commitTimestamp =
* transactionFuture
* .then(
* (transaction, __) ->
* transaction.readRowAsync(
* "Singers", Key.of(singerId), Collections.singleton(column)))
* .then(
* (transaction, row) -> {
* String name = row.getString(column);
* return transaction.bufferAsync(
* Mutation.newUpdateBuilder("Singers")
* .set(column)
* .to(name.toUpperCase())
* .build());
* })
* .commitAsync();
* try {
* commitTimestamp.get();
* break;
* } catch (AbortedException e) {
* Thread.sleep(e.getRetryDelayInMillis());
* transactionFuture = manager.resetForRetryAsync();
* }
* }
* }
* }
*
* Options for a transaction can include:
*
* Options for a transaction can include:
*
*
* - {@link Options#priority(com.google.cloud.spanner.Options.RpcPriority)}: The {@link
* RpcPriority} to use for the commit request of the transaction. The priority will not be
* applied to any other requests on the transaction.
*
- {@link Options#commitStats()}: Request that the server includes commit statistics in the
* {@link CommitResponse}.
*
*/
AsyncTransactionManager transactionManagerAsync(TransactionOption... options);
/**
* Returns the lower bound of rows modified by this DML statement.
*
* The method will block until the update is complete. Running a DML statement with this method
* does not offer exactly once semantics, and therefore the DML statement should be idempotent.
* The DML statement must be fully-partitionable. Specifically, the statement must be expressible
* as the union of many statements which each access only a single row of the table. This is a
* Partitioned DML transaction in which a single Partitioned DML statement is executed.
* Partitioned DML partitions the key space and runs the DML statement over each partition in
* parallel using separate, internal transactions that commit independently. Partitioned DML
* transactions do not need to be committed.
*
*
Partitioned DML updates are used to execute a single DML statement with a different
* execution strategy that provides different, and often better, scalability properties for large,
* table-wide operations than DML in a {@link #readWriteTransaction(TransactionOption...)}
* transaction. Smaller scoped statements, such as an OLTP workload, should prefer using {@link
* TransactionContext#executeUpdate(Statement,UpdateOption...)} with {@link
* #readWriteTransaction(TransactionOption...)}.
*
*
That said, Partitioned DML is not a drop-in replacement for standard DML used in {@link
* #readWriteTransaction(TransactionOption...)}.
*
*
* - The DML statement must be fully-partitionable. Specifically, the statement must be
* expressible as the union of many statements which each access only a single row of the
* table.
*
- The statement is not applied atomically to all rows of the table. Rather, the statement
* is applied atomically to partitions of the table, in independent internal transactions.
* Secondary index rows are updated atomically with the base table rows.
*
- Partitioned DML does not guarantee exactly-once execution semantics against a partition.
* The statement will be applied at least once to each partition. It is strongly recommended
* that the DML statement should be idempotent to avoid unexpected results. For instance, it
* is potentially dangerous to run a statement such as `UPDATE table SET column = column +
* 1` as it could be run multiple times against some rows.
*
- The partitions are committed automatically - there is no support for Commit or Rollback.
* If the call returns an error, or if the client issuing the DML statement dies, it is
* possible that some rows had the statement executed on them successfully. It is also
* possible that statement was never executed against other rows.
*
- If any error is encountered during the execution of the partitioned DML operation (for
* instance, a UNIQUE INDEX violation, division by zero, or a value that cannot be stored
* due to schema constraints), then the operation is stopped at that point and an error is
* returned. It is possible that at this point, some partitions have been committed (or even
* committed multiple times), and other partitions have not been run at all.
*
*
* Given the above, Partitioned DML is good fit for large, database-wide, operations that are
* idempotent, such as deleting old rows from a very large table.
*/
long executePartitionedUpdate(Statement stmt, UpdateOption... options);
}