statements) throws SpannerException;
+ /**
+ * Creates a new database in a Cloud Spanner instance with the given {@link Dialect}.
+ *
+ * Example to create database.
+ *
+ *
{@code
+ * String instanceId = "my_instance_id";
+ * String createDatabaseStatement = "CREATE DATABASE \"my-database\"";
+ * Operation op = dbAdminClient
+ * .createDatabase(
+ * instanceId,
+ * createDatabaseStatement,
+ * Dialect.POSTGRESQL
+ * Collections.emptyList());
+ * Database db = op.waitFor().getResult();
+ * }
+ *
+ * @param instanceId the id of the instance in which to create the database.
+ * @param createDatabaseStatement the CREATE DATABASE statement for the database. This statement
+ * must use the dialect for the new database.
+ * @param dialect the dialect that the new database should use.
+ * @param statements DDL statements to run while creating the database, for example {@code CREATE
+ * TABLE MyTable ( ... )}. This should not include {@code CREATE DATABASE} statement.
+ */
+ default OperationFuture createDatabase(
+ String instanceId,
+ String createDatabaseStatement,
+ Dialect dialect,
+ Iterable statements)
+ throws SpannerException {
+ throw new UnsupportedOperationException("Unimplemented");
+ }
+
/**
* Creates a database in a Cloud Spanner instance. Any configuration options in the {@link
* Database} instance will be included in the {@link CreateDatabaseRequest}.
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseAdminClientImpl.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseAdminClientImpl.java
index 3285dde5f5e..86b0bdbb8de 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseAdminClientImpl.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/DatabaseAdminClientImpl.java
@@ -332,6 +332,26 @@ public OperationFuture createDatabase(
final Dialect dialect = Preconditions.checkNotNull(database.getDialect());
final String createStatement =
dialect.createDatabaseStatementFor(database.getId().getDatabase());
+
+ return createDatabase(createStatement, database, statements);
+ }
+
+ @Override
+ public OperationFuture createDatabase(
+ String instanceId,
+ String createDatabaseStatement,
+ Dialect dialect,
+ Iterable statements)
+ throws SpannerException {
+ Database database =
+ newDatabaseBuilder(DatabaseId.of(projectId, instanceId, "")).setDialect(dialect).build();
+
+ return createDatabase(createDatabaseStatement, database, statements);
+ }
+
+ private OperationFuture createDatabase(
+ String createStatement, Database database, Iterable statements)
+ throws SpannerException {
OperationFuture
rawOperationFuture =
rpc.createDatabase(
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/DdlBatch.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/DdlBatch.java
index 6d34c76fde8..0a34e6b5767 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/DdlBatch.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/DdlBatch.java
@@ -188,6 +188,9 @@ public ApiFuture executeDdlAsync(ParsedStatement ddl) {
"Only DDL statements are allowed. \""
+ ddl.getSqlWithoutComments()
+ "\" is not a DDL-statement.");
+ Preconditions.checkArgument(
+ !DdlClient.isCreateDatabaseStatement(ddl.getSqlWithoutComments()),
+ "CREATE DATABASE is not supported in DDL batches.");
statements.add(ddl.getSqlWithoutComments());
return ApiFutures.immediateFuture(null);
}
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/DdlClient.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/DdlClient.java
index f3c9cdba037..fedf60d7a91 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/DdlClient.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/DdlClient.java
@@ -17,9 +17,14 @@
package com.google.cloud.spanner.connection;
import com.google.api.gax.longrunning.OperationFuture;
+import com.google.cloud.spanner.Database;
import com.google.cloud.spanner.DatabaseAdminClient;
+import com.google.cloud.spanner.Dialect;
+import com.google.cloud.spanner.ErrorCode;
+import com.google.cloud.spanner.SpannerExceptionFactory;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
+import com.google.spanner.admin.database.v1.CreateDatabaseMetadata;
import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata;
import java.util.Collections;
import java.util.List;
@@ -79,6 +84,13 @@ private DdlClient(Builder builder) {
this.databaseName = builder.databaseName;
}
+ OperationFuture executeCreateDatabase(
+ String createStatement, Dialect dialect) {
+ Preconditions.checkArgument(isCreateDatabaseStatement(createStatement));
+ return dbAdminClient.createDatabase(
+ instanceId, createStatement, dialect, Collections.emptyList());
+ }
+
/** Execute a single DDL statement. */
OperationFuture executeDdl(String ddl) {
return executeDdl(Collections.singletonList(ddl));
@@ -86,6 +98,18 @@ OperationFuture executeDdl(String ddl) {
/** Execute a list of DDL statements as one operation. */
OperationFuture executeDdl(List statements) {
+ if (statements.stream().anyMatch(DdlClient::isCreateDatabaseStatement)) {
+ throw SpannerExceptionFactory.newSpannerException(
+ ErrorCode.INVALID_ARGUMENT, "CREATE DATABASE is not supported in a DDL batch");
+ }
return dbAdminClient.updateDatabaseDdl(instanceId, databaseName, statements, null);
}
+
+ /** Returns true if the statement is a `CREATE DATABASE ...` statement. */
+ static boolean isCreateDatabaseStatement(String statement) {
+ String[] tokens = statement.split("\\s+", 3);
+ return tokens.length >= 2
+ && tokens[0].equalsIgnoreCase("CREATE")
+ && tokens[1].equalsIgnoreCase("DATABASE");
+ }
}
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SingleUseTransaction.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SingleUseTransaction.java
index f34e6f72e71..2747b07778b 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SingleUseTransaction.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SingleUseTransaction.java
@@ -44,7 +44,6 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.spanner.admin.database.v1.DatabaseAdminGrpc;
-import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata;
import com.google.spanner.v1.SpannerGrpc;
import java.util.concurrent.Callable;
@@ -270,11 +269,17 @@ public ApiFuture executeDdlAsync(final ParsedStatement ddl) {
Callable callable =
() -> {
try {
- OperationFuture operation =
- ddlClient.executeDdl(ddl.getSqlWithoutComments());
- Void res = getWithStatementTimeout(operation, ddl);
+ OperationFuture, ?> operation;
+ if (DdlClient.isCreateDatabaseStatement(ddl.getSqlWithoutComments())) {
+ operation =
+ ddlClient.executeCreateDatabase(
+ ddl.getSqlWithoutComments(), dbClient.getDialect());
+ } else {
+ operation = ddlClient.executeDdl(ddl.getSqlWithoutComments());
+ }
+ getWithStatementTimeout(operation, ddl);
state = UnitOfWorkState.COMMITTED;
- return res;
+ return null;
} catch (Throwable t) {
state = UnitOfWorkState.COMMIT_FAILED;
throw t;
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/DdlBatchTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/DdlBatchTest.java
index 23792572d79..2496adb270b 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/DdlBatchTest.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/DdlBatchTest.java
@@ -22,6 +22,7 @@
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.anyList;
import static org.mockito.Mockito.anyString;
@@ -143,6 +144,17 @@ public void testExecuteQuery() {
}
}
+ @Test
+ public void testExecuteCreateDatabase() {
+ DdlBatch batch = createSubject();
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ batch.executeDdlAsync(
+ AbstractStatementParser.getInstance(Dialect.GOOGLE_STANDARD_SQL)
+ .parse(Statement.of("CREATE DATABASE foo"))));
+ }
+
@Test
public void testExecuteMetadataQuery() {
Statement statement = Statement.of("SELECT * FROM INFORMATION_SCHEMA.TABLES");
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/DdlClientTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/DdlClientTest.java
index 24b4ebf0964..a490949a31b 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/DdlClientTest.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/DdlClientTest.java
@@ -16,6 +16,8 @@
package com.google.cloud.spanner.connection;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.anyList;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.isNull;
@@ -66,4 +68,23 @@ public void testExecuteDdl() throws InterruptedException, ExecutionException {
subject.executeDdl(ddlList);
verify(client).updateDatabaseDdl(instanceId, databaseId, ddlList, null);
}
+
+ @Test
+ public void testIsCreateDatabase() {
+ assertTrue(DdlClient.isCreateDatabaseStatement("CREATE DATABASE foo"));
+ assertTrue(DdlClient.isCreateDatabaseStatement("CREATE DATABASE \"foo\""));
+ assertTrue(DdlClient.isCreateDatabaseStatement("CREATE DATABASE `foo`"));
+ assertTrue(DdlClient.isCreateDatabaseStatement("CREATE DATABASE\tfoo"));
+ assertTrue(DdlClient.isCreateDatabaseStatement("CREATE DATABASE\n foo"));
+ assertTrue(DdlClient.isCreateDatabaseStatement("CREATE DATABASE\t\n foo"));
+ assertTrue(DdlClient.isCreateDatabaseStatement("CREATE DATABASE"));
+ assertTrue(DdlClient.isCreateDatabaseStatement("CREATE\t \n DATABASE foo"));
+ assertTrue(DdlClient.isCreateDatabaseStatement("create\t \n DATABASE foo"));
+ assertTrue(DdlClient.isCreateDatabaseStatement("create database foo"));
+
+ assertFalse(DdlClient.isCreateDatabaseStatement("CREATE VIEW foo"));
+ assertFalse(DdlClient.isCreateDatabaseStatement("CREATE DATABAS foo"));
+ assertFalse(DdlClient.isCreateDatabaseStatement("CREATE DATABASEfoo"));
+ assertFalse(DdlClient.isCreateDatabaseStatement("CREATE foo"));
+ }
}
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/SingleUseTransactionTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/SingleUseTransactionTest.java
index 26a3952be28..67db5e2d8b4 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/SingleUseTransactionTest.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/SingleUseTransactionTest.java
@@ -34,6 +34,7 @@
import com.google.cloud.spanner.AsyncResultSet;
import com.google.cloud.spanner.CommitResponse;
import com.google.cloud.spanner.DatabaseClient;
+import com.google.cloud.spanner.Dialect;
import com.google.cloud.spanner.ErrorCode;
import com.google.cloud.spanner.Key;
import com.google.cloud.spanner.KeySet;
@@ -365,6 +366,7 @@ private SingleUseTransaction createSubject(
DatabaseClient dbClient = mock(DatabaseClient.class);
com.google.cloud.spanner.ReadOnlyTransaction singleUse =
new SimpleReadOnlyTransaction(staleness);
+ when(dbClient.getDialect()).thenReturn(Dialect.GOOGLE_STANDARD_SQL);
when(dbClient.singleUseReadOnlyTransaction(staleness)).thenReturn(singleUse);
final TransactionContext txContext = mock(TransactionContext.class);
@@ -537,6 +539,19 @@ public void testExecuteDdl() {
verify(ddlClient).executeDdl(sql);
}
+ @Test
+ public void testExecuteCreateDatabase() {
+ String sql = "CREATE DATABASE FOO";
+ ParsedStatement ddl = createParsedDdl(sql);
+ DdlClient ddlClient = createDefaultMockDdlClient();
+ when(ddlClient.executeCreateDatabase(sql, Dialect.GOOGLE_STANDARD_SQL))
+ .thenReturn(mock(OperationFuture.class));
+
+ SingleUseTransaction singleUseTransaction = createDdlSubject(ddlClient);
+ get(singleUseTransaction.executeDdlAsync(ddl));
+ verify(ddlClient).executeCreateDatabase(sql, Dialect.GOOGLE_STANDARD_SQL);
+ }
+
@Test
public void testExecuteQuery() {
for (TimestampBound staleness : getTestTimestampBounds()) {
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITDdlTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITDdlTest.java
index 74c072cd760..7a9c5aa9262 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITDdlTest.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITDdlTest.java
@@ -16,7 +16,14 @@
package com.google.cloud.spanner.connection.it;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThrows;
+
+import com.google.cloud.spanner.DatabaseAdminClient;
+import com.google.cloud.spanner.DatabaseNotFoundException;
import com.google.cloud.spanner.ParallelIntegrationTest;
+import com.google.cloud.spanner.Statement;
+import com.google.cloud.spanner.connection.Connection;
import com.google.cloud.spanner.connection.ITAbstractSpannerTest;
import com.google.cloud.spanner.connection.SqlScriptVerifier;
import org.junit.Test;
@@ -34,4 +41,20 @@ public void testSqlScript() throws Exception {
SqlScriptVerifier verifier = new SqlScriptVerifier(new ITConnectionProvider());
verifier.verifyStatementsInFile("ITDdlTest.sql", SqlScriptVerifier.class, false);
}
+
+ @Test
+ public void testCreateDatabase() {
+ DatabaseAdminClient client = getTestEnv().getTestHelper().getClient().getDatabaseAdminClient();
+ String instance = getTestEnv().getTestHelper().getInstanceId().getInstance();
+ String name = getTestEnv().getTestHelper().getUniqueDatabaseId();
+
+ assertThrows(DatabaseNotFoundException.class, () -> client.getDatabase(instance, name));
+
+ try (Connection connection = createConnection()) {
+ connection.execute(Statement.of(String.format("CREATE DATABASE `%s`", name)));
+ assertNotNull(client.getDatabase(instance, name));
+ } finally {
+ client.dropDatabase(instance, name);
+ }
+ }
}
From ca85276efa6a71d6e5e7951e0483d4e0889e40f0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?=
Date: Thu, 28 Apr 2022 15:14:12 +0200
Subject: [PATCH 15/24] chore: make DatabaseClient accessible in the Connection
API (#1847)
Makes the underlying `DatabaseClient` accessible in the Connection API so this can be used by drivers that need direct access to it.
See https://github.com/GoogleCloudPlatform/pgadapter/blob/8f0d47785e04dc9db6ee275074777849f006d797/src/main/java/com/google/cloud/spanner/pgadapter/utils/MutationWriter.java#L439
---
google-cloud-spanner/clirr-ignored-differences.xml | 6 +++++-
.../com/google/cloud/spanner/connection/Connection.java | 7 +++++++
.../google/cloud/spanner/connection/ConnectionImpl.java | 5 +++++
3 files changed, 17 insertions(+), 1 deletion(-)
diff --git a/google-cloud-spanner/clirr-ignored-differences.xml b/google-cloud-spanner/clirr-ignored-differences.xml
index 450eacde322..ef38a8cfa0f 100644
--- a/google-cloud-spanner/clirr-ignored-differences.xml
+++ b/google-cloud-spanner/clirr-ignored-differences.xml
@@ -60,10 +60,14 @@
com/google/cloud/spanner/spi/v1/SpannerRpc
com.google.api.gax.longrunning.OperationFuture copyBackup(com.google.cloud.spanner.BackupId, com.google.cloud.spanner.Backup)
+
+ 7012
+ com/google/cloud/spanner/connection/Connection
+ com.google.cloud.spanner.DatabaseClient getDatabaseClient()
+
7012
com/google/cloud/spanner/DatabaseAdminClient
com.google.api.gax.longrunning.OperationFuture createDatabase(java.lang.String, java.lang.String, com.google.cloud.spanner.Dialect, java.lang.Iterable)
-
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/Connection.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/Connection.java
index 9c74e5b9ca1..e3a5f7edbce 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/Connection.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/Connection.java
@@ -23,6 +23,7 @@
import com.google.cloud.spanner.AbortedException;
import com.google.cloud.spanner.AsyncResultSet;
import com.google.cloud.spanner.CommitResponse;
+import com.google.cloud.spanner.DatabaseClient;
import com.google.cloud.spanner.Dialect;
import com.google.cloud.spanner.ErrorCode;
import com.google.cloud.spanner.Mutation;
@@ -1104,6 +1105,12 @@ default Dialect getDialect() {
throw new UnsupportedOperationException("Not implemented");
}
+ /** The {@link DatabaseClient} that is used by this {@link Connection}. */
+ @InternalApi
+ default DatabaseClient getDatabaseClient() {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
/**
* 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
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionImpl.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionImpl.java
index 3f3bd218ef2..1a7d9b76682 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionImpl.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionImpl.java
@@ -332,6 +332,11 @@ public Dialect getDialect() {
return dbClient.getDialect();
}
+ @Override
+ public DatabaseClient getDatabaseClient() {
+ return dbClient;
+ }
+
@Override
public boolean isClosed() {
return closed;
From 182906f7a952991cf36c8401465c883fcc0f63e2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?=
Date: Thu, 28 Apr 2022 16:13:05 +0200
Subject: [PATCH 16/24] chore: support untyped values for statement parameters
(#1854)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* feat: support untyped values
* chore: support untyped values
Adds support for untyped values that can be used as statement
parameters. This is required for Spangres, as many native PG drivers
send (some) parameters without an explicit type, and expect the backend
to infer the type from the context. If we were to include a
(placeholder) type for such a parameter, the type inference on the
backend fails.
* chore: support untyped values
Adds support for untyped values that can be used as statement
parameters. This is required for Spangres, as many native PG drivers
send (some) parameters without an explicit type, and expect the backend
to infer the type from the context. If we were to include a
(placeholder) type for such a parameter, the type inference on the
backend fails.
* π¦ Updates from OwlBot post-processor
See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md
Co-authored-by: Owl Bot
---
.../cloud/spanner/AbstractReadContext.java | 4 +-
.../google/cloud/spanner/BatchClientImpl.java | 2 +-
.../spanner/PartitionedDmlTransaction.java | 2 +-
.../java/com/google/cloud/spanner/Value.java | 65 ++++++++++++++++++-
.../google/cloud/spanner/ValueBinderTest.java | 7 +-
.../com/google/cloud/spanner/ValueTest.java | 30 +++++++++
6 files changed, 103 insertions(+), 7 deletions(-)
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractReadContext.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractReadContext.java
index ea658a36eab..60324f8e83e 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractReadContext.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/AbstractReadContext.java
@@ -584,7 +584,7 @@ ExecuteSqlRequest.Builder getExecuteSqlRequestBuilder(
com.google.protobuf.Struct.Builder paramsBuilder = builder.getParamsBuilder();
for (Map.Entry param : stmtParameters.entrySet()) {
paramsBuilder.putFields(param.getKey(), Value.toProto(param.getValue()));
- if (param.getValue() != null) {
+ if (param.getValue() != null && param.getValue().getType() != null) {
builder.putParamTypes(param.getKey(), param.getValue().getType().toProto());
}
}
@@ -615,7 +615,7 @@ ExecuteBatchDmlRequest.Builder getExecuteBatchDmlRequestBuilder(
builder.getStatementsBuilder(idx).getParamsBuilder();
for (Map.Entry param : stmtParameters.entrySet()) {
paramsBuilder.putFields(param.getKey(), Value.toProto(param.getValue()));
- if (param.getValue() != null) {
+ if (param.getValue() != null && param.getValue().getType() != null) {
builder
.getStatementsBuilder(idx)
.putParamTypes(param.getKey(), param.getValue().getType().toProto());
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/BatchClientImpl.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/BatchClientImpl.java
index 222d754dac9..5a164d52490 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/BatchClientImpl.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/BatchClientImpl.java
@@ -170,7 +170,7 @@ public List partitionQuery(
Struct.Builder paramsBuilder = builder.getParamsBuilder();
for (Map.Entry param : stmtParameters.entrySet()) {
paramsBuilder.putFields(param.getKey(), Value.toProto(param.getValue()));
- if (param.getValue() != null) {
+ if (param.getValue() != null && param.getValue().getType() != null) {
builder.putParamTypes(param.getKey(), param.getValue().getType().toProto());
}
}
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/PartitionedDmlTransaction.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/PartitionedDmlTransaction.java
index 78c092f1792..cbe01c2d33d 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/PartitionedDmlTransaction.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/PartitionedDmlTransaction.java
@@ -218,7 +218,7 @@ private void setParameters(
com.google.protobuf.Struct.Builder paramsBuilder = requestBuilder.getParamsBuilder();
for (Map.Entry param : statementParameters.entrySet()) {
paramsBuilder.putFields(param.getKey(), Value.toProto(param.getValue()));
- if (param.getValue() != null) {
+ if (param.getValue() != null && param.getValue().getType() != null) {
requestBuilder.putParamTypes(param.getKey(), param.getValue().getType().toProto());
}
}
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Value.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Value.java
index 0bbeb36abf0..e3c53de9377 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Value.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/Value.java
@@ -87,6 +87,17 @@ public abstract class Value implements Serializable {
private static final char LIST_CLOSE = ']';
private static final long serialVersionUID = -5289864325087675338L;
+ /**
+ * Returns a {@link Value} that wraps the given proto value. This can be used to construct a value
+ * without a specific type, and let the backend infer the type based on the statement where it is
+ * used.
+ *
+ * @param value the non-null proto value (a {@link NullValue} is allowed)
+ */
+ public static Value untyped(com.google.protobuf.Value value) {
+ return new UntypedValueImpl(Preconditions.checkNotNull(value));
+ }
+
/**
* Returns a {@code BOOL} value.
*
@@ -914,7 +925,7 @@ public final boolean equals(Object o) {
}
AbstractValue that = (AbstractValue) o;
- if (!getType().equals(that.getType()) || isNull != that.isNull) {
+ if (!Objects.equals(getType(), that.getType()) || isNull != that.isNull) {
return false;
}
@@ -963,6 +974,58 @@ final void checkNotNull() {
}
}
+ private static class UntypedValueImpl extends AbstractValue {
+ private final com.google.protobuf.Value value;
+
+ private UntypedValueImpl(com.google.protobuf.Value value) {
+ super(value.hasNullValue(), null);
+ this.value = value;
+ }
+
+ @Override
+ public boolean getBool() {
+ checkNotNull();
+ Preconditions.checkState(value.hasBoolValue(), "This value does not contain a bool value");
+ return value.getBoolValue();
+ }
+
+ @Override
+ public String getString() {
+ checkNotNull();
+ Preconditions.checkState(
+ value.hasStringValue(), "This value does not contain a string value");
+ return value.getStringValue();
+ }
+
+ @Override
+ public double getFloat64() {
+ checkNotNull();
+ Preconditions.checkState(
+ value.hasNumberValue(), "This value does not contain a number value");
+ return value.getNumberValue();
+ }
+
+ @Override
+ void valueToString(StringBuilder b) {
+ b.append(value);
+ }
+
+ @Override
+ com.google.protobuf.Value valueToProto() {
+ return value;
+ }
+
+ @Override
+ boolean valueEquals(Value v) {
+ return ((UntypedValueImpl) v).value.equals(value);
+ }
+
+ @Override
+ int valueHash() {
+ return value.hashCode();
+ }
+ }
+
private static class BoolImpl extends AbstractValue {
private final boolean value;
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ValueBinderTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ValueBinderTest.java
index 122f5582736..ea26f09c2ed 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ValueBinderTest.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ValueBinderTest.java
@@ -56,10 +56,13 @@ Integer handle(Value value) {
public void reflection()
throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
// Test that every Value factory method has a counterpart in ValueBinder, and that invoking it
- // produces the expected Value.
+ // produces the expected Value. The only exception is for untyped values, which must be
+ // constructed manually as an untyped value and then assigned as a parameter.
for (Method method : Value.class.getMethods()) {
if (!Modifier.isStatic(method.getModifiers())
- || !method.getReturnType().equals(Value.class)) {
+ || !method.getReturnType().equals(Value.class)
+ || (method.getParameterTypes().length > 0
+ && method.getParameterTypes()[0].equals(com.google.protobuf.Value.class))) {
continue;
}
Method binderMethod = findBinderMethod(method);
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ValueTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ValueTest.java
index 08849274de5..54f6799e8c8 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ValueTest.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/ValueTest.java
@@ -22,7 +22,9 @@
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -63,6 +65,34 @@ private static Iterable plainIterable(T... values) {
return Lists.newArrayList(values);
}
+ @Test
+ public void untyped() {
+ com.google.protobuf.Value proto =
+ com.google.protobuf.Value.newBuilder().setStringValue("test").build();
+ Value v = Value.untyped(proto);
+ assertNull(v.getType());
+ assertFalse(v.isNull());
+ assertSame(proto, v.toProto());
+
+ assertEquals(
+ v, Value.untyped(com.google.protobuf.Value.newBuilder().setStringValue("test").build()));
+ assertEquals(
+ Value.untyped(com.google.protobuf.Value.newBuilder().setNumberValue(3.14d).build()),
+ Value.untyped(com.google.protobuf.Value.newBuilder().setNumberValue(3.14d).build()));
+ assertEquals(
+ Value.untyped(com.google.protobuf.Value.newBuilder().setBoolValue(true).build()),
+ Value.untyped(com.google.protobuf.Value.newBuilder().setBoolValue(true).build()));
+
+ assertNotEquals(
+ v, Value.untyped(com.google.protobuf.Value.newBuilder().setStringValue("foo").build()));
+ assertNotEquals(
+ Value.untyped(com.google.protobuf.Value.newBuilder().setNumberValue(3.14d).build()),
+ Value.untyped(com.google.protobuf.Value.newBuilder().setNumberValue(0.14d).build()));
+ assertNotEquals(
+ Value.untyped(com.google.protobuf.Value.newBuilder().setBoolValue(false).build()),
+ Value.untyped(com.google.protobuf.Value.newBuilder().setBoolValue(true).build()));
+ }
+
@Test
public void bool() {
Value v = Value.bool(true);
From 6e1aa8e755886d4f9c342d3d445be3e5bc603416 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?=
Date: Thu, 28 Apr 2022 16:20:36 +0200
Subject: [PATCH 17/24] chore: include client statement type in parsed
statement (#1849)
Include the ClientSideStatementType in ParsedStatement. This enables
clients to directly check what type of statement it is, which removes
the need for textual checks.
This will for example remove these types of checks: https://github.com/GoogleCloudPlatform/pgadapter/blob/2a3f2eb7cd16cc3c71ffc673544ca9e2baaba21c/src/main/java/com/google/cloud/spanner/pgadapter/statements/IntermediateStatement.java#L354
---
.../connection/AbstractStatementParser.java | 19 +++++++++-
.../connection/ClientSideStatement.java | 4 ++
.../connection/ClientSideStatementImpl.java | 8 ++++
.../connection/ClientSideStatements.json | 35 ++++++++++++++++++
.../connection/PG_ClientSideStatements.json | 37 +++++++++++++++++++
.../connection/ClientSideStatementsTest.java | 25 +++++++++++++
6 files changed, 127 insertions(+), 1 deletion(-)
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/AbstractStatementParser.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/AbstractStatementParser.java
index 25465990d2f..1465b8b4021 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/AbstractStatementParser.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/AbstractStatementParser.java
@@ -22,11 +22,13 @@
import com.google.cloud.spanner.SpannerException;
import com.google.cloud.spanner.SpannerExceptionFactory;
import com.google.cloud.spanner.Statement;
+import com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.spanner.v1.ExecuteSqlRequest.QueryOptions;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
@@ -270,6 +272,16 @@ public boolean isDdl() {
return false;
}
+ /**
+ * Returns the {@link ClientSideStatementType} of this statement. This method may only be called
+ * on statements of type {@link StatementType#CLIENT_SIDE}.
+ */
+ @InternalApi
+ public ClientSideStatementType getClientSideStatementType() {
+ Preconditions.checkState(type == StatementType.CLIENT_SIDE);
+ return clientSideStatement.getStatementType();
+ }
+
Statement getStatement() {
return statement;
}
@@ -314,7 +326,12 @@ ClientSideStatement getClientSideStatement() {
private final Set statements;
AbstractStatementParser(Set statements) {
- this.statements = statements;
+ this.statements = Collections.unmodifiableSet(statements);
+ }
+
+ @VisibleForTesting
+ Set getClientSideStatements() {
+ return statements;
}
/**
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ClientSideStatement.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ClientSideStatement.java
index f3c3691b96b..08f14b146a2 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ClientSideStatement.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ClientSideStatement.java
@@ -17,6 +17,7 @@
package com.google.cloud.spanner.connection;
import com.google.cloud.spanner.ResultSet;
+import com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType;
import java.util.List;
/**
@@ -47,6 +48,9 @@ interface ClientSideStatement {
/** @return true
if this {@link ClientSideStatement} will return an update count. */
boolean isUpdate();
+ /** @return the statement type */
+ ClientSideStatementType getStatementType();
+
/**
* Execute this {@link ClientSideStatement} on the given {@link ConnectionStatementExecutor}. The
* executor calls the appropriate method(s) on the {@link Connection}. The statement argument is
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ClientSideStatementImpl.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ClientSideStatementImpl.java
index 05dbc8975ce..2ff39131baa 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ClientSideStatementImpl.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ClientSideStatementImpl.java
@@ -17,6 +17,7 @@
package com.google.cloud.spanner.connection;
import com.google.cloud.spanner.SpannerException;
+import com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType;
import com.google.cloud.spanner.connection.StatementResult.ResultType;
import com.google.common.base.Preconditions;
import java.lang.reflect.Constructor;
@@ -106,6 +107,8 @@ public String getMessage() {
/** The result type of this statement. */
private ResultType resultType;
+ private ClientSideStatementType statementType;
+
/** The regular expression that should be used to recognize this class of statements. */
private String regex;
@@ -183,6 +186,11 @@ public boolean isUpdate() {
return resultType == ResultType.UPDATE_COUNT;
}
+ @Override
+ public ClientSideStatementType getStatementType() {
+ return statementType;
+ }
+
boolean matches(String statement) {
Preconditions.checkState(pattern != null, "This statement has not been compiled");
return pattern.matcher(statement).matches();
diff --git a/google-cloud-spanner/src/main/resources/com/google/cloud/spanner/connection/ClientSideStatements.json b/google-cloud-spanner/src/main/resources/com/google/cloud/spanner/connection/ClientSideStatements.json
index e06732bf4e8..860c0238e06 100644
--- a/google-cloud-spanner/src/main/resources/com/google/cloud/spanner/connection/ClientSideStatements.json
+++ b/google-cloud-spanner/src/main/resources/com/google/cloud/spanner/connection/ClientSideStatements.json
@@ -5,6 +5,7 @@
"name": "SHOW VARIABLE AUTOCOMMIT",
"executorName": "ClientSideStatementNoParamExecutor",
"resultType": "RESULT_SET",
+ "statementType": "SHOW_AUTOCOMMIT",
"regex": "(?is)\\A\\s*show\\s+variable\\s+autocommit\\s*\\z",
"method": "statementShowAutocommit",
"exampleStatements": ["show variable autocommit"]
@@ -13,6 +14,7 @@
"name": "SHOW VARIABLE READONLY",
"executorName": "ClientSideStatementNoParamExecutor",
"resultType": "RESULT_SET",
+ "statementType": "SHOW_READONLY",
"regex": "(?is)\\A\\s*show\\s+variable\\s+readonly\\s*\\z",
"method": "statementShowReadOnly",
"exampleStatements": ["show variable readonly"]
@@ -21,6 +23,7 @@
"name": "SHOW VARIABLE RETRY_ABORTS_INTERNALLY",
"executorName": "ClientSideStatementNoParamExecutor",
"resultType": "RESULT_SET",
+ "statementType": "SHOW_RETRY_ABORTS_INTERNALLY",
"regex": "(?is)\\A\\s*show\\s+variable\\s+retry_aborts_internally\\s*\\z",
"method": "statementShowRetryAbortsInternally",
"exampleStatements": ["show variable retry_aborts_internally"],
@@ -30,6 +33,7 @@
"name": "SHOW VARIABLE AUTOCOMMIT_DML_MODE",
"executorName": "ClientSideStatementNoParamExecutor",
"resultType": "RESULT_SET",
+ "statementType": "SHOW_AUTOCOMMIT_DML_MODE",
"regex": "(?is)\\A\\s*show\\s+variable\\s+autocommit_dml_mode\\s*\\z",
"method": "statementShowAutocommitDmlMode",
"exampleStatements": ["show variable autocommit_dml_mode"]
@@ -38,6 +42,7 @@
"name": "SHOW VARIABLE STATEMENT_TIMEOUT",
"executorName": "ClientSideStatementNoParamExecutor",
"resultType": "RESULT_SET",
+ "statementType": "SHOW_STATEMENT_TIMEOUT",
"regex": "(?is)\\A\\s*show\\s+variable\\s+statement_timeout\\s*\\z",
"method": "statementShowStatementTimeout",
"exampleStatements": ["show variable statement_timeout"]
@@ -46,6 +51,7 @@
"name": "SHOW VARIABLE READ_TIMESTAMP",
"executorName": "ClientSideStatementNoParamExecutor",
"resultType": "RESULT_SET",
+ "statementType": "SHOW_READ_TIMESTAMP",
"regex": "(?is)\\A\\s*show\\s+variable\\s+read_timestamp\\s*\\z",
"method": "statementShowReadTimestamp",
"exampleStatements": ["show variable read_timestamp"],
@@ -55,6 +61,7 @@
"name": "SHOW VARIABLE COMMIT_TIMESTAMP",
"executorName": "ClientSideStatementNoParamExecutor",
"resultType": "RESULT_SET",
+ "statementType": "SHOW_COMMIT_TIMESTAMP",
"regex": "(?is)\\A\\s*show\\s+variable\\s+commit_timestamp\\s*\\z",
"method": "statementShowCommitTimestamp",
"exampleStatements": ["show variable commit_timestamp"],
@@ -64,6 +71,7 @@
"name": "SHOW VARIABLE READ_ONLY_STALENESS",
"executorName": "ClientSideStatementNoParamExecutor",
"resultType": "RESULT_SET",
+ "statementType": "SHOW_READ_ONLY_STALENESS",
"regex": "(?is)\\A\\s*show\\s+variable\\s+read_only_staleness\\s*\\z",
"method": "statementShowReadOnlyStaleness",
"exampleStatements": ["show variable read_only_staleness"]
@@ -72,6 +80,7 @@
"name": "SHOW VARIABLE OPTIMIZER_VERSION",
"executorName": "ClientSideStatementNoParamExecutor",
"resultType": "RESULT_SET",
+ "statementType": "SHOW_OPTIMIZER_VERSION",
"regex": "(?is)\\A\\s*show\\s+variable\\s+optimizer_version\\s*\\z",
"method": "statementShowOptimizerVersion",
"exampleStatements": ["show variable optimizer_version"]
@@ -80,6 +89,7 @@
"name": "SHOW VARIABLE OPTIMIZER_STATISTICS_PACKAGE",
"executorName": "ClientSideStatementNoParamExecutor",
"resultType": "RESULT_SET",
+ "statementType": "SHOW_OPTIMIZER_STATISTICS_PACKAGE",
"regex": "(?is)\\A\\s*show\\s+variable\\s+optimizer_statistics_package\\s*\\z",
"method": "statementShowOptimizerStatisticsPackage",
"exampleStatements": ["show variable optimizer_statistics_package"]
@@ -88,6 +98,7 @@
"name": "SHOW VARIABLE RETURN_COMMIT_STATS",
"executorName": "ClientSideStatementNoParamExecutor",
"resultType": "RESULT_SET",
+ "statementType": "SHOW_RETURN_COMMIT_STATS",
"regex": "(?is)\\A\\s*show\\s+variable\\s+return_commit_stats\\s*\\z",
"method": "statementShowReturnCommitStats",
"exampleStatements": ["show variable return_commit_stats"]
@@ -96,6 +107,7 @@
"name": "SHOW VARIABLE COMMIT_RESPONSE",
"executorName": "ClientSideStatementNoParamExecutor",
"resultType": "RESULT_SET",
+ "statementType": "SHOW_COMMIT_RESPONSE",
"regex": "(?is)\\A\\s*show\\s+variable\\s+commit_response\\s*\\z",
"method": "statementShowCommitResponse",
"exampleStatements": ["show variable commit_response"],
@@ -105,6 +117,7 @@
"name": "SHOW VARIABLE STATEMENT_TAG",
"executorName": "ClientSideStatementNoParamExecutor",
"resultType": "RESULT_SET",
+ "statementType": "SHOW_STATEMENT_TAG",
"regex": "(?is)\\A\\s*show\\s+variable\\s+statement_tag\\s*\\z",
"method": "statementShowStatementTag",
"exampleStatements": ["show variable statement_tag"]
@@ -113,6 +126,7 @@
"name": "SHOW VARIABLE TRANSACTION_TAG",
"executorName": "ClientSideStatementNoParamExecutor",
"resultType": "RESULT_SET",
+ "statementType": "SHOW_TRANSACTION_TAG",
"regex": "(?is)\\A\\s*show\\s+variable\\s+transaction_tag\\s*\\z",
"method": "statementShowTransactionTag",
"exampleStatements": ["show variable transaction_tag"]
@@ -121,6 +135,7 @@
"name": "SHOW VARIABLE RPC_PRIORITY",
"executorName": "ClientSideStatementNoParamExecutor",
"resultType": "RESULT_SET",
+ "statementType": "SHOW_TRANSACTION_TAG",
"regex": "(?is)\\A\\s*show\\s+variable\\s+rpc_priority\\s*\\z",
"method": "statementShowRPCPriority",
"exampleStatements": ["show variable rpc_priority"]
@@ -129,6 +144,7 @@
"name": "BEGIN TRANSACTION",
"executorName": "ClientSideStatementNoParamExecutor",
"resultType": "NO_RESULT",
+ "statementType": "BEGIN",
"regex": "(?is)\\A\\s*(?:begin|start)(?:\\s+transaction)?\\s*\\z",
"method": "statementBeginTransaction",
"exampleStatements": ["begin", "start", "begin transaction", "start transaction"]
@@ -137,6 +153,7 @@
"name": "COMMIT TRANSACTION",
"executorName": "ClientSideStatementNoParamExecutor",
"resultType": "NO_RESULT",
+ "statementType": "COMMIT",
"regex": "(?is)\\A\\s*(?:commit)(?:\\s+transaction)?\\s*\\z",
"method": "statementCommit",
"exampleStatements": ["commit", "commit transaction"],
@@ -146,6 +163,7 @@
"name": "ROLLBACK TRANSACTION",
"executorName": "ClientSideStatementNoParamExecutor",
"resultType": "NO_RESULT",
+ "statementType": "ROLLBACK",
"regex": "(?is)\\A\\s*(?:rollback)(?:\\s+transaction)?\\s*\\z",
"method": "statementRollback",
"exampleStatements": ["rollback", "rollback transaction"],
@@ -155,6 +173,7 @@
"name": "START BATCH DDL",
"executorName": "ClientSideStatementNoParamExecutor",
"resultType": "NO_RESULT",
+ "statementType": "START_BATCH_DDL",
"regex": "(?is)\\A\\s*(?:start)(?:\\s+batch)(?:\\s+ddl)\\s*\\z",
"method": "statementStartBatchDdl",
"exampleStatements": ["start batch ddl"]
@@ -163,6 +182,7 @@
"name": "START BATCH DML",
"executorName": "ClientSideStatementNoParamExecutor",
"resultType": "NO_RESULT",
+ "statementType": "START_BATCH_DML",
"regex": "(?is)\\A\\s*(?:start)(?:\\s+batch)(?:\\s+dml)\\s*\\z",
"method": "statementStartBatchDml",
"exampleStatements": ["start batch dml"]
@@ -171,6 +191,7 @@
"name": "RUN BATCH",
"executorName": "ClientSideStatementNoParamExecutor",
"resultType": "NO_RESULT",
+ "statementType": "RUN_BATCH",
"regex": "(?is)\\A\\s*(?:run)(?:\\s+batch)\\s*\\z",
"method": "statementRunBatch",
"exampleStatements": ["run batch"],
@@ -180,6 +201,7 @@
"name": "ABORT BATCH",
"executorName": "ClientSideStatementNoParamExecutor",
"resultType": "NO_RESULT",
+ "statementType": "ABORT_BATCH",
"regex": "(?is)\\A\\s*(?:abort)(?:\\s+batch)\\s*\\z",
"method": "statementAbortBatch",
"exampleStatements": ["abort batch"],
@@ -189,6 +211,7 @@
"name": "SET AUTOCOMMIT = TRUE|FALSE",
"executorName": "ClientSideStatementSetExecutor",
"resultType": "NO_RESULT",
+ "statementType": "SET_AUTOCOMMIT",
"regex": "(?is)\\A\\s*set\\s+autocommit\\s*(?:=)\\s*(.*)\\z",
"method": "statementSetAutocommit",
"exampleStatements": ["set autocommit = true", "set autocommit = false"],
@@ -203,6 +226,7 @@
"name": "SET READONLY = TRUE|FALSE",
"executorName": "ClientSideStatementSetExecutor",
"resultType": "NO_RESULT",
+ "statementType": "SET_READONLY",
"regex": "(?is)\\A\\s*set\\s+readonly\\s*(?:=)\\s*(.*)\\z",
"method": "statementSetReadOnly",
"exampleStatements": ["set readonly = true", "set readonly = false"],
@@ -217,6 +241,7 @@
"name": "SET RETRY_ABORTS_INTERNALLY = TRUE|FALSE",
"executorName": "ClientSideStatementSetExecutor",
"resultType": "NO_RESULT",
+ "statementType": "SET_RETRY_ABORTS_INTERNALLY",
"regex": "(?is)\\A\\s*set\\s+retry_aborts_internally\\s*(?:=)\\s*(.*)\\z",
"method": "statementSetRetryAbortsInternally",
"exampleStatements": ["set retry_aborts_internally = true", "set retry_aborts_internally = false"],
@@ -232,6 +257,7 @@
"name": "SET AUTOCOMMIT_DML_MODE = 'PARTITIONED_NON_ATOMIC'|'TRANSACTIONAL'",
"executorName": "ClientSideStatementSetExecutor",
"resultType": "NO_RESULT",
+ "statementType": "SET_AUTOCOMMIT_DML_MODE",
"regex": "(?is)\\A\\s*set\\s+autocommit_dml_mode\\s*(?:=)\\s*(.*)\\z",
"method": "statementSetAutocommitDmlMode",
"exampleStatements": ["set autocommit_dml_mode='PARTITIONED_NON_ATOMIC'", "set autocommit_dml_mode='TRANSACTIONAL'"],
@@ -246,6 +272,7 @@
"name": "SET STATEMENT_TIMEOUT = ''|NULL",
"executorName": "ClientSideStatementSetExecutor",
"resultType": "NO_RESULT",
+ "statementType": "SET_STATEMENT_TIMEOUT",
"regex": "(?is)\\A\\s*set\\s+statement_timeout\\s*(?:=)\\s*(.*)\\z",
"method": "statementSetStatementTimeout",
"exampleStatements": ["set statement_timeout=null", "set statement_timeout='1s'", "set statement_timeout='100ms'", "set statement_timeout='10000us'", "set statement_timeout='9223372036854775807ns'"],
@@ -260,6 +287,7 @@
"name": "SET TRANSACTION READ ONLY|READ WRITE",
"executorName": "ClientSideStatementSetExecutor",
"resultType": "NO_RESULT",
+ "statementType": "SET_TRANSACTION_MODE",
"regex": "(?is)\\A\\s*set\\s+transaction\\s*(?:\\s+)\\s*(.*)\\z",
"method": "statementSetTransactionMode",
"exampleStatements": ["set transaction read only", "set transaction read write"],
@@ -275,6 +303,7 @@
"name": "SET READ_ONLY_STALENESS = 'STRONG' | 'MIN_READ_TIMESTAMP ' | 'READ_TIMESTAMP ' | 'MAX_STALENESS s|ms|us|ns' | 'EXACT_STALENESS (s|ms|us|ns)'",
"executorName": "ClientSideStatementSetExecutor",
"resultType": "NO_RESULT",
+ "statementType": "SET_READ_ONLY_STALENESS",
"regex": "(?is)\\A\\s*set\\s+read_only_staleness\\s*(?:=)\\s*(.*)\\z",
"method": "statementSetReadOnlyStaleness",
"exampleStatements": ["set read_only_staleness='STRONG'",
@@ -303,6 +332,7 @@
"name": "SET OPTIMIZER_VERSION = ''|'LATEST'|''",
"executorName": "ClientSideStatementSetExecutor",
"resultType": "NO_RESULT",
+ "statementType": "SET_OPTIMIZER_VERSION",
"regex": "(?is)\\A\\s*set\\s+optimizer_version\\s*(?:=)\\s*(.*)\\z",
"method": "statementSetOptimizerVersion",
"exampleStatements": ["set optimizer_version='1'", "set optimizer_version='200'", "set optimizer_version='LATEST'", "set optimizer_version=''"],
@@ -317,6 +347,7 @@
"name": "SET OPTIMIZER_STATISTICS_PACKAGE = ''|''",
"executorName": "ClientSideStatementSetExecutor",
"resultType": "NO_RESULT",
+ "statementType": "SET_OPTIMIZER_STATISTICS_PACKAGE",
"regex": "(?is)\\A\\s*set\\s+optimizer_statistics_package\\s*(?:=)\\s*(.*)\\z",
"method": "statementSetOptimizerStatisticsPackage",
"exampleStatements": ["set optimizer_statistics_package='auto_20191128_14_47_22UTC'", "set optimizer_statistics_package=''"],
@@ -331,6 +362,7 @@
"name": "SET RETURN_COMMIT_STATS = TRUE|FALSE",
"executorName": "ClientSideStatementSetExecutor",
"resultType": "NO_RESULT",
+ "statementType": "SET_RETURN_COMMIT_STATS",
"regex": "(?is)\\A\\s*set\\s+return_commit_stats\\s*(?:=)\\s*(.*)\\z",
"method": "statementSetReturnCommitStats",
"exampleStatements": ["set return_commit_stats = true", "set return_commit_stats = false"],
@@ -345,6 +377,7 @@
"name": "SET STATEMENT_TAG = ''",
"executorName": "ClientSideStatementSetExecutor",
"resultType": "NO_RESULT",
+ "statementType": "SET_STATEMENT_TAG",
"regex": "(?is)\\A\\s*set\\s+statement_tag\\s*(?:=)\\s*(.*)\\z",
"method": "statementSetStatementTag",
"exampleStatements": ["set statement_tag='tag1'", "set statement_tag='tag2'", "set statement_tag=''"],
@@ -359,6 +392,7 @@
"name": "SET TRANSACTION_TAG = ''",
"executorName": "ClientSideStatementSetExecutor",
"resultType": "NO_RESULT",
+ "statementType": "SET_TRANSACTION_TAG",
"regex": "(?is)\\A\\s*set\\s+transaction_tag\\s*(?:=)\\s*(.*)\\z",
"method": "statementSetTransactionTag",
"exampleStatements": ["set transaction_tag='tag1'", "set transaction_tag='tag2'", "set transaction_tag=''"],
@@ -374,6 +408,7 @@
"name": "SET RPC_PRIORITY = 'HIGH'|'MEDIUM'|'LOW'|'NULL'",
"executorName": "ClientSideStatementSetExecutor",
"resultType": "NO_RESULT",
+ "statementType": "SET_RPC_PRIORITY",
"regex": "(?is)\\A\\s*set\\s+rpc_priority\\s*(?:=)\\s*(.*)\\z",
"method": "statementSetRPCPriority",
"exampleStatements": [
diff --git a/google-cloud-spanner/src/main/resources/com/google/cloud/spanner/connection/PG_ClientSideStatements.json b/google-cloud-spanner/src/main/resources/com/google/cloud/spanner/connection/PG_ClientSideStatements.json
index 31e3a801005..281fc4792d4 100644
--- a/google-cloud-spanner/src/main/resources/com/google/cloud/spanner/connection/PG_ClientSideStatements.json
+++ b/google-cloud-spanner/src/main/resources/com/google/cloud/spanner/connection/PG_ClientSideStatements.json
@@ -5,6 +5,7 @@
"name": "SHOW [VARIABLE] AUTOCOMMIT",
"executorName": "ClientSideStatementNoParamExecutor",
"resultType": "RESULT_SET",
+ "statementType": "SHOW_AUTOCOMMIT",
"regex": "(?is)\\A\\s*show\\s+(?:variable\\s+)?autocommit\\s*\\z",
"method": "statementShowAutocommit",
"exampleStatements": ["show autocommit","show variable autocommit"]
@@ -13,6 +14,7 @@
"name": "SHOW [VARIABLE] SPANNER.READONLY",
"executorName": "ClientSideStatementNoParamExecutor",
"resultType": "RESULT_SET",
+ "statementType": "SHOW_READONLY",
"regex": "(?is)\\A\\s*show\\s+(?:variable\\s+)?spanner\\.readonly\\s*\\z",
"method": "statementShowReadOnly",
"exampleStatements": ["show spanner.readonly","show variable spanner.readonly"]
@@ -21,6 +23,7 @@
"name": "SHOW [VARIABLE] SPANNER.RETRY_ABORTS_INTERNALLY",
"executorName": "ClientSideStatementNoParamExecutor",
"resultType": "RESULT_SET",
+ "statementType": "SHOW_RETRY_ABORTS_INTERNALLY",
"regex": "(?is)\\A\\s*show\\s+(?:variable\\s+)?spanner\\.retry_aborts_internally\\s*\\z",
"method": "statementShowRetryAbortsInternally",
"exampleStatements": ["show spanner.retry_aborts_internally","show variable spanner.retry_aborts_internally"],
@@ -30,6 +33,7 @@
"name": "SHOW [VARIABLE] SPANNER.AUTOCOMMIT_DML_MODE",
"executorName": "ClientSideStatementNoParamExecutor",
"resultType": "RESULT_SET",
+ "statementType": "SHOW_AUTOCOMMIT_DML_MODE",
"regex": "(?is)\\A\\s*show\\s+(?:variable\\s+)?spanner\\.autocommit_dml_mode\\s*\\z",
"method": "statementShowAutocommitDmlMode",
"exampleStatements": ["show spanner.autocommit_dml_mode","show variable spanner.autocommit_dml_mode"]
@@ -38,6 +42,7 @@
"name": "SHOW [VARIABLE] STATEMENT_TIMEOUT",
"executorName": "ClientSideStatementNoParamExecutor",
"resultType": "RESULT_SET",
+ "statementType": "SHOW_STATEMENT_TIMEOUT",
"regex": "(?is)\\A\\s*show\\s+(?:variable\\s+)?statement_timeout\\s*\\z",
"method": "statementShowStatementTimeout",
"exampleStatements": ["show statement_timeout","show variable statement_timeout"]
@@ -46,6 +51,7 @@
"name": "SHOW [VARIABLE] SPANNER.READ_TIMESTAMP",
"executorName": "ClientSideStatementNoParamExecutor",
"resultType": "RESULT_SET",
+ "statementType": "SHOW_READ_TIMESTAMP",
"regex": "(?is)\\A\\s*show\\s+(?:variable\\s+)?spanner\\.read_timestamp\\s*\\z",
"method": "statementShowReadTimestamp",
"exampleStatements": ["show spanner.read_timestamp","show variable spanner.read_timestamp"],
@@ -55,6 +61,7 @@
"name": "SHOW [VARIABLE] SPANNER.COMMIT_TIMESTAMP",
"executorName": "ClientSideStatementNoParamExecutor",
"resultType": "RESULT_SET",
+ "statementType": "SHOW_COMMIT_TIMESTAMP",
"regex": "(?is)\\A\\s*show\\s+(?:variable\\s+)?spanner\\.commit_timestamp\\s*\\z",
"method": "statementShowCommitTimestamp",
"exampleStatements": ["show spanner.commit_timestamp","show variable spanner.commit_timestamp"],
@@ -64,6 +71,7 @@
"name": "SHOW [VARIABLE] SPANNER.READ_ONLY_STALENESS",
"executorName": "ClientSideStatementNoParamExecutor",
"resultType": "RESULT_SET",
+ "statementType": "SHOW_READ_ONLY_STALENESS",
"regex": "(?is)\\A\\s*show\\s+(?:variable\\s+)?spanner\\.read_only_staleness\\s*\\z",
"method": "statementShowReadOnlyStaleness",
"exampleStatements": ["show spanner.read_only_staleness","show variable spanner.read_only_staleness"]
@@ -72,6 +80,7 @@
"name": "SHOW [VARIABLE] SPANNER.OPTIMIZER_VERSION",
"executorName": "ClientSideStatementNoParamExecutor",
"resultType": "RESULT_SET",
+ "statementType": "SHOW_OPTIMIZER_VERSION",
"regex": "(?is)\\A\\s*show\\s+(?:variable\\s+)?spanner\\.optimizer_version\\s*\\z",
"method": "statementShowOptimizerVersion",
"exampleStatements": ["show spanner.optimizer_version","show variable spanner.optimizer_version"]
@@ -80,6 +89,7 @@
"name": "SHOW [VARIABLE] SPANNER.OPTIMIZER_STATISTICS_PACKAGE",
"executorName": "ClientSideStatementNoParamExecutor",
"resultType": "RESULT_SET",
+ "statementType": "SHOW_OPTIMIZER_STATISTICS_PACKAGE",
"regex": "(?is)\\A\\s*show\\s+(?:variable\\s+)?spanner\\.optimizer_statistics_package\\s*\\z",
"method": "statementShowOptimizerStatisticsPackage",
"exampleStatements": ["show spanner.optimizer_statistics_package","show variable spanner.optimizer_statistics_package"]
@@ -88,6 +98,7 @@
"name": "SHOW [VARIABLE] SPANNER.RETURN_COMMIT_STATS",
"executorName": "ClientSideStatementNoParamExecutor",
"resultType": "RESULT_SET",
+ "statementType": "SHOW_RETURN_COMMIT_STATS",
"regex": "(?is)\\A\\s*show\\s+(?:variable\\s+)?spanner\\.return_commit_stats\\s*\\z",
"method": "statementShowReturnCommitStats",
"exampleStatements": ["show spanner.return_commit_stats","show variable spanner.return_commit_stats"]
@@ -96,6 +107,7 @@
"name": "SHOW [VARIABLE] SPANNER.COMMIT_RESPONSE",
"executorName": "ClientSideStatementNoParamExecutor",
"resultType": "RESULT_SET",
+ "statementType": "SHOW_COMMIT_RESPONSE",
"regex": "(?is)\\A\\s*show\\s+(?:variable\\s+)?spanner\\.commit_response\\s*\\z",
"method": "statementShowCommitResponse",
"exampleStatements": ["show spanner.commit_response","show variable spanner.commit_response"],
@@ -105,6 +117,7 @@
"name": "SHOW [VARIABLE] SPANNER.STATEMENT_TAG",
"executorName": "ClientSideStatementNoParamExecutor",
"resultType": "RESULT_SET",
+ "statementType": "SHOW_STATEMENT_TAG",
"regex": "(?is)\\A\\s*show\\s+(?:variable\\s+)?spanner\\.statement_tag\\s*\\z",
"method": "statementShowStatementTag",
"exampleStatements": ["show spanner.statement_tag","show variable spanner.statement_tag"]
@@ -113,6 +126,7 @@
"name": "SHOW [VARIABLE] SPANNER.TRANSACTION_TAG",
"executorName": "ClientSideStatementNoParamExecutor",
"resultType": "RESULT_SET",
+ "statementType": "SHOW_TRANSACTION_TAG",
"regex": "(?is)\\A\\s*show\\s+(?:variable\\s+)?spanner\\.transaction_tag\\s*\\z",
"method": "statementShowTransactionTag",
"exampleStatements": ["show spanner.transaction_tag","show variable spanner.transaction_tag"]
@@ -121,6 +135,7 @@
"name": "SHOW [VARIABLE] SPANNER.RPC_PRIORITY",
"executorName": "ClientSideStatementNoParamExecutor",
"resultType": "RESULT_SET",
+ "statementType": "SHOW_RPC_PRIORITY",
"regex": "(?is)\\A\\s*show\\s+(?:variable\\s+)?spanner\\.rpc_priority\\s*\\z",
"method": "statementShowRPCPriority",
"exampleStatements": ["show spanner.rpc_priority","show variable spanner.rpc_priority"]
@@ -129,6 +144,7 @@
"name": "SHOW [VARIABLE] TRANSACTION ISOLATION LEVEL",
"executorName": "ClientSideStatementNoParamExecutor",
"resultType": "RESULT_SET",
+ "statementType": "SHOW_TRANSACTION_ISOLATION_LEVEL",
"regex": "(?is)\\A\\s*show\\s+(?:variable\\s+)?transaction\\s*isolation\\s*level\\s*\\z",
"method": "statementShowTransactionIsolationLevel",
"exampleStatements": ["show transaction isolation level","show variable transaction isolation level"]
@@ -137,6 +153,7 @@
"name": "{START | BEGIN} [TRANSACTION | WORK] [{ (READ ONLY|READ WRITE) | (ISOLATION LEVEL (DEFAULT|SERIALIZABLE)) }]",
"executorName": "ClientSideStatementPgBeginExecutor",
"resultType": "NO_RESULT",
+ "statementType": "BEGIN",
"regex": "(?is)\\A\\s*(?:begin|start)(?:\\s+transaction|\\s+work)?(\\s+read\\s+only|\\s+read\\s+write|\\s+isolation\\s+level\\s+default|\\s+isolation\\s+level\\s+serializable)?\\s*\\z",
"method": "statementBeginPgTransaction",
"exampleStatements": [
@@ -151,6 +168,7 @@
"name": "COMMIT [TRANSACTION | WORK] [AND NO CHAIN]",
"executorName": "ClientSideStatementNoParamExecutor",
"resultType": "NO_RESULT",
+ "statementType": "COMMIT",
"regex": "(?is)\\A\\s*(?:commit)(?:\\s+transaction|\\s+work)?(?:\\s+and\\s+no\\s+chain)?\\s*\\z",
"method": "statementCommit",
"exampleStatements": ["commit", "commit transaction", "commit work", "commit and no chain", "commit transaction and no chain", "commit work and no chain"],
@@ -160,6 +178,7 @@
"name": "ROLLBACK [TRANSACTION | WORK] [AND NO CHAIN]",
"executorName": "ClientSideStatementNoParamExecutor",
"resultType": "NO_RESULT",
+ "statementType": "ROLLBACK",
"regex": "(?is)\\A\\s*(?:rollback)(?:\\s+transaction|\\s+work)?(?:\\s+and\\s+no\\s+chain)?\\s*\\z",
"method": "statementRollback",
"exampleStatements": ["rollback", "rollback transaction", "rollback work", "rollback and no chain", "rollback transaction and no chain", "rollback work and no chain"],
@@ -169,6 +188,7 @@
"name": "START BATCH DDL",
"executorName": "ClientSideStatementNoParamExecutor",
"resultType": "NO_RESULT",
+ "statementType": "START_BATCH_DDL",
"regex": "(?is)\\A\\s*(?:start)(?:\\s+batch)(?:\\s+ddl)\\s*\\z",
"method": "statementStartBatchDdl",
"exampleStatements": ["start batch ddl"]
@@ -177,6 +197,7 @@
"name": "START BATCH DML",
"executorName": "ClientSideStatementNoParamExecutor",
"resultType": "NO_RESULT",
+ "statementType": "START_BATCH_DML",
"regex": "(?is)\\A\\s*(?:start)(?:\\s+batch)(?:\\s+dml)\\s*\\z",
"method": "statementStartBatchDml",
"exampleStatements": ["start batch dml"]
@@ -185,6 +206,7 @@
"name": "RUN BATCH",
"executorName": "ClientSideStatementNoParamExecutor",
"resultType": "NO_RESULT",
+ "statementType": "RUN_BATCH",
"regex": "(?is)\\A\\s*(?:run)(?:\\s+batch)\\s*\\z",
"method": "statementRunBatch",
"exampleStatements": ["run batch"],
@@ -194,6 +216,7 @@
"name": "ABORT BATCH",
"executorName": "ClientSideStatementNoParamExecutor",
"resultType": "NO_RESULT",
+ "statementType": "ABORT_BATCH",
"regex": "(?is)\\A\\s*(?:abort)(?:\\s+batch)\\s*\\z",
"method": "statementAbortBatch",
"exampleStatements": ["abort batch"],
@@ -203,6 +226,7 @@
"name": "SET AUTOCOMMIT =|TO TRUE|FALSE",
"executorName": "ClientSideStatementSetExecutor",
"resultType": "NO_RESULT",
+ "statementType": "SET_AUTOCOMMIT",
"regex": "(?is)\\A\\s*set\\s+autocommit(?:\\s*=\\s*|\\s+to\\s+)(.*)\\z",
"method": "statementSetAutocommit",
"exampleStatements": ["set autocommit = true", "set autocommit = false", "set autocommit to true", "set autocommit to false"],
@@ -217,6 +241,7 @@
"name": "SET SPANNER.READONLY =|TO TRUE|FALSE",
"executorName": "ClientSideStatementSetExecutor",
"resultType": "NO_RESULT",
+ "statementType": "SET_READONLY",
"regex": "(?is)\\A\\s*set\\s+spanner\\.readonly(?:\\s*=\\s*|\\s+to\\s+)(.*)\\z",
"method": "statementSetReadOnly",
"exampleStatements": ["set spanner.readonly = true", "set spanner.readonly = false", "set spanner.readonly to true", "set spanner.readonly to false"],
@@ -231,6 +256,7 @@
"name": "SET SPANNER.RETRY_ABORTS_INTERNALLY =|TO TRUE|FALSE",
"executorName": "ClientSideStatementSetExecutor",
"resultType": "NO_RESULT",
+ "statementType": "SET_RETRY_ABORTS_INTERNALLY",
"regex": "(?is)\\A\\s*set\\s+spanner\\.retry_aborts_internally(?:\\s*=\\s*|\\s+to\\s+)(.*)\\z",
"method": "statementSetRetryAbortsInternally",
"exampleStatements": ["set spanner.retry_aborts_internally = true", "set spanner.retry_aborts_internally = false", "set spanner.retry_aborts_internally to true", "set spanner.retry_aborts_internally to false"],
@@ -246,6 +272,7 @@
"name": "SET SPANNER.AUTOCOMMIT_DML_MODE =|TO 'PARTITIONED_NON_ATOMIC'|'TRANSACTIONAL'",
"executorName": "ClientSideStatementSetExecutor",
"resultType": "NO_RESULT",
+ "statementType": "SET_AUTOCOMMIT_DML_MODE",
"regex": "(?is)\\A\\s*set\\s+spanner\\.autocommit_dml_mode(?:\\s*=\\s*|\\s+to\\s+)(.*)\\z",
"method": "statementSetAutocommitDmlMode",
"exampleStatements": [
@@ -265,6 +292,7 @@
"name": "SET STATEMENT_TIMEOUT =|TO ''|INT8|DEFAULT",
"executorName": "ClientSideStatementSetExecutor",
"resultType": "NO_RESULT",
+ "statementType": "SET_STATEMENT_TIMEOUT",
"regex": "(?is)\\A\\s*set\\s+statement_timeout(?:\\s*=\\s*|\\s+to\\s+)(.*)\\z",
"method": "statementSetStatementTimeout",
"exampleStatements": [
@@ -292,6 +320,7 @@
"name": "SET TRANSACTION { (READ ONLY|READ WRITE) | (ISOLATION LEVEL (DEFAULT|SERIALIZABLE)) }",
"executorName": "ClientSideStatementSetExecutor",
"resultType": "NO_RESULT",
+ "statementType": "SET_TRANSACTION_MODE",
"regex": "(?is)\\A\\s*set\\s+transaction\\s*(?:\\s+)\\s*(.*)\\z",
"method": "statementSetPgTransactionMode",
"exampleStatements": ["set transaction read only", "set transaction read write", "set transaction isolation level default", "set transaction isolation level serializable"],
@@ -307,6 +336,7 @@
"name": "SET SESSION CHARACTERISTICS AS TRANSACTION { (READ ONLY|READ WRITE) | (ISOLATION LEVEL (DEFAULT|SERIALIZABLE)) }",
"executorName": "ClientSideStatementSetExecutor",
"resultType": "NO_RESULT",
+ "statementType": "SET_READONLY",
"regex": "(?is)\\A\\s*set\\s+session\\s+characteristics\\s+as\\s+transaction\\s*(?:\\s+)\\s*(.*)\\z",
"method": "statementSetPgSessionCharacteristicsTransactionMode",
"exampleStatements": ["set session characteristics as transaction read only", "set session characteristics as transaction read write", "set session characteristics as transaction isolation level default", "set session characteristics as transaction isolation level serializable"],
@@ -321,6 +351,7 @@
"name": "SET SPANNER.READ_ONLY_STALENESS =|TO 'STRONG' | 'MIN_READ_TIMESTAMP ' | 'READ_TIMESTAMP ' | 'MAX_STALENESS s|ms|us|ns' | 'EXACT_STALENESS (s|ms|us|ns)'",
"executorName": "ClientSideStatementSetExecutor",
"resultType": "NO_RESULT",
+ "statementType": "SET_READ_ONLY_STALENESS",
"regex": "(?is)\\A\\s*set\\s+spanner\\.read_only_staleness(?:\\s*=\\s*|\\s+to\\s+)(.*)\\z",
"method": "statementSetReadOnlyStaleness",
"exampleStatements": [
@@ -366,6 +397,7 @@
"name": "SET SPANNER.OPTIMIZER_VERSION =|TO ''|'LATEST'|''",
"executorName": "ClientSideStatementSetExecutor",
"resultType": "NO_RESULT",
+ "statementType": "SET_OPTIMIZER_VERSION",
"regex": "(?is)\\A\\s*set\\s+spanner\\.optimizer_version(?:\\s*=\\s*|\\s+to\\s+)(.*)\\z",
"method": "statementSetOptimizerVersion",
"exampleStatements": [
@@ -389,6 +421,7 @@
"name": "SET SPANNER.OPTIMIZER_STATISTICS_PACKAGE =|TO ''|''",
"executorName": "ClientSideStatementSetExecutor",
"resultType": "NO_RESULT",
+ "statementType": "SET_OPTIMIZER_STATISTICS_PACKAGE",
"regex": "(?is)\\A\\s*set\\s+spanner\\.optimizer_statistics_package(?:\\s*=\\s*|\\s+to\\s+)(.*)\\z",
"method": "statementSetOptimizerStatisticsPackage",
"exampleStatements": [
@@ -408,6 +441,7 @@
"name": "SET SPANNER.RETURN_COMMIT_STATS =|TO TRUE|FALSE",
"executorName": "ClientSideStatementSetExecutor",
"resultType": "NO_RESULT",
+ "statementType": "SET_RETURN_COMMIT_STATS",
"regex": "(?is)\\A\\s*set\\s+spanner\\.return_commit_stats(?:\\s*=\\s*|\\s+to\\s+)(.*)\\z",
"method": "statementSetReturnCommitStats",
"exampleStatements": ["set spanner.return_commit_stats = true", "set spanner.return_commit_stats = false", "set spanner.return_commit_stats to true", "set spanner.return_commit_stats to false"],
@@ -422,6 +456,7 @@
"name": "SET SPANNER.STATEMENT_TAG =|TO ''",
"executorName": "ClientSideStatementSetExecutor",
"resultType": "NO_RESULT",
+ "statementType": "SET_STATEMENT_TAG",
"regex": "(?is)\\A\\s*set\\s+spanner\\.statement_tag(?:\\s*=\\s*|\\s+to\\s+)(.*)\\z",
"method": "statementSetStatementTag",
"exampleStatements": [
@@ -443,6 +478,7 @@
"name": "SET SPANNER.TRANSACTION_TAG =|TO ''",
"executorName": "ClientSideStatementSetExecutor",
"resultType": "NO_RESULT",
+ "statementType": "SET_TRANSACTION_TAG",
"regex": "(?is)\\A\\s*set\\s+spanner\\.transaction_tag(?:\\s*=\\s*|\\s+to\\s+)(.*)\\z",
"method": "statementSetTransactionTag",
"exampleStatements": [
@@ -465,6 +501,7 @@
"name": "SET SPANNER.RPC_PRIORITY =|TO 'HIGH'|'MEDIUM'|'LOW'|'NULL'",
"executorName": "ClientSideStatementSetExecutor",
"resultType": "NO_RESULT",
+ "statementType": "SET_RPC_PRIORITY",
"regex": "(?is)\\A\\s*set\\s+spanner\\.rpc_priority(?:\\s*=\\s*|\\s+to\\s+)(.*)\\z",
"method": "statementSetRPCPriority",
"exampleStatements": [
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ClientSideStatementsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ClientSideStatementsTest.java
index caf73f02271..c0ca4f96c9c 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ClientSideStatementsTest.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ClientSideStatementsTest.java
@@ -16,9 +16,14 @@
package com.google.cloud.spanner.connection;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
import com.google.cloud.spanner.Dialect;
import com.google.cloud.spanner.ErrorCode;
import com.google.cloud.spanner.SpannerExceptionFactory;
+import com.google.cloud.spanner.Statement;
+import com.google.cloud.spanner.connection.StatementResult.ClientSideStatementType;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
@@ -60,6 +65,26 @@ public void testExecuteClientSideStatementsScript() throws Exception {
verifier.verifyStatementsInFile(getScriptFile(dialect), getClass(), true);
}
+ @Test
+ public void testClientSideStatementType() {
+ AbstractStatementParser parser = AbstractStatementParser.getInstance(dialect);
+
+ assertEquals(
+ ClientSideStatementType.BEGIN,
+ parser.parse(Statement.of("BEGIN TRANSACTION")).getClientSideStatementType());
+ assertEquals(
+ ClientSideStatementType.COMMIT,
+ parser.parse(Statement.of("COMMIT TRANSACTION")).getClientSideStatementType());
+ assertEquals(
+ ClientSideStatementType.ROLLBACK,
+ parser.parse(Statement.of("ROLLBACK TRANSACTION")).getClientSideStatementType());
+
+ for (ClientSideStatementImpl statement : parser.getClientSideStatements()) {
+ assertNotNull(
+ statement.toString() + " misses a statement type", statement.getStatementType());
+ }
+ }
+
private static PrintWriter writer;
/** Generates the test script file */
From 2471d9164ff1d7e0ba7987af321deba499938611 Mon Sep 17 00:00:00 2001
From: Rajat Bhatta <93644539+rajatbhatta@users.noreply.github.com>
Date: Fri, 29 Apr 2022 13:25:23 +0530
Subject: [PATCH 18/24] test: drop old databases from instance before running
integration tests. (#1861)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
This commit aims to resolve the RESOURCE_EXHAUSTED errors due to
number of databases in an instance reaching the maximum limit.
We drop only those DBs which are created through automated tests,
at least 24 hours ago.
Co-authored-by: Knut Olav LΓΈite
---
.../cloud/spanner/IntegrationTestEnv.java | 36 +++++++++++++++++++
1 file changed, 36 insertions(+)
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/IntegrationTestEnv.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/IntegrationTestEnv.java
index 3f4db8b9135..3de9f5358aa 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/IntegrationTestEnv.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/IntegrationTestEnv.java
@@ -19,12 +19,15 @@
import static com.google.common.base.Preconditions.checkState;
import com.google.api.gax.longrunning.OperationFuture;
+import com.google.api.gax.paging.Page;
+import com.google.cloud.Timestamp;
import com.google.cloud.spanner.testing.EmulatorSpannerHelper;
import com.google.cloud.spanner.testing.RemoteSpannerHelper;
import com.google.common.collect.Iterators;
import com.google.spanner.admin.instance.v1.CreateInstanceMetadata;
import io.grpc.Status;
import java.util.Random;
+import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.junit.rules.ExternalResource;
@@ -53,6 +56,7 @@ public class IntegrationTestEnv extends ExternalResource {
private TestEnvConfig config;
private InstanceAdminClient instanceAdminClient;
+ private DatabaseAdminClient databaseAdminClient;
private boolean isOwnedInstance;
private RemoteSpannerHelper testHelper;
@@ -93,9 +97,12 @@ protected void before() throws Throwable {
}
testHelper = createTestHelper(options, instanceId);
instanceAdminClient = testHelper.getClient().getInstanceAdminClient();
+ databaseAdminClient = testHelper.getClient().getDatabaseAdminClient();
logger.log(Level.FINE, "Test env endpoint is {0}", options.getHost());
if (isOwnedInstance) {
initializeInstance(instanceId);
+ } else {
+ cleanUpOldDatabases(instanceId);
}
}
@@ -162,6 +169,35 @@ private void initializeInstance(InstanceId instanceId) {
logger.log(Level.INFO, "Created test instance: {0}", createdInstance.getId());
}
+ private void cleanUpOldDatabases(InstanceId instanceId) {
+ long OLD_DB_THRESHOLD_SECS = TimeUnit.SECONDS.convert(24L, TimeUnit.HOURS);
+ Timestamp currentTimestamp = Timestamp.now();
+ int numDropped = 0;
+ Page page = databaseAdminClient.listDatabases(instanceId.getInstance());
+ String TEST_DB_REGEX = "(testdb_(.*)_(.*))|(mysample-(.*))";
+
+ logger.log(Level.INFO, "Dropping old test databases from {0}", instanceId.getName());
+ while (page != null) {
+ for (Database db : page.iterateAll()) {
+ try {
+ long timeDiff = db.getCreateTime().getSeconds() - currentTimestamp.getSeconds();
+ // Delete all databases which are more than OLD_DB_THRESHOLD_SECS seconds
+ // old.
+ if ((db.getId().getDatabase().matches(TEST_DB_REGEX))
+ && (timeDiff > OLD_DB_THRESHOLD_SECS)) {
+ logger.log(Level.INFO, "Dropping test database {0}", db.getId());
+ db.drop();
+ ++numDropped;
+ }
+ } catch (SpannerException e) {
+ logger.log(Level.SEVERE, "Failed to drop test database " + db.getId(), e);
+ }
+ }
+ page = page.getNextPage();
+ }
+ logger.log(Level.INFO, "Dropped {0} test database(s)", numDropped);
+ }
+
private void cleanUpInstance() {
try {
if (isOwnedInstance) {
From e8322986f158a86cdbb04332a9c49ead79fb2587 Mon Sep 17 00:00:00 2001
From: Sivakumar-Searce
Date: Fri, 29 Apr 2022 22:48:58 +0530
Subject: [PATCH 19/24] docs: add samples for PostgresSQL (#1781)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* Added samples for PG dialect databases
* docs: add samples for postgresql
* docs: samples tag fixes
* docs: samples tag fixes
* docs: samples tag violation fixes
* docs: samples imports and indent fixes
* docs: samples checkstyle fixes
* docs: add samples for postgresql
* docs: samples tag fixes
* docs: pg samples pr comments fixes
* docs: pg samples checkstyle fixes
* π¦ Updates from OwlBot post-processor
See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md
* π¦ Updates from OwlBot post-processor
See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md
Co-authored-by: Owl Bot
---
README.md | 5 +
.../spanner/AsyncQueryToListAsyncExample.java | 3 +
.../CustomTimeoutAndRetrySettingsExample.java | 2 +-
.../PgAsyncQueryToListAsyncExample.java | 90 +
.../example/spanner/PgAsyncRunnerExample.java | 124 ++
.../PgAsyncTransactionManagerExample.java | 140 ++
.../PgQueryWithNumericParameterSample.java | 60 +
.../com/example/spanner/PgSpannerSample.java | 1578 +++++++++++++++++
.../spanner/StatementTimeoutExample.java | 2 +-
.../example/spanner/PgAsyncExamplesIT.java | 251 +++
.../example/spanner/PgSpannerSampleIT.java | 304 ++++
.../PgSpannerStandaloneExamplesIT.java | 209 +++
12 files changed, 2766 insertions(+), 2 deletions(-)
create mode 100644 samples/snippets/src/main/java/com/example/spanner/PgAsyncQueryToListAsyncExample.java
create mode 100644 samples/snippets/src/main/java/com/example/spanner/PgAsyncRunnerExample.java
create mode 100644 samples/snippets/src/main/java/com/example/spanner/PgAsyncTransactionManagerExample.java
create mode 100644 samples/snippets/src/main/java/com/example/spanner/PgQueryWithNumericParameterSample.java
create mode 100644 samples/snippets/src/main/java/com/example/spanner/PgSpannerSample.java
create mode 100644 samples/snippets/src/test/java/com/example/spanner/PgAsyncExamplesIT.java
create mode 100644 samples/snippets/src/test/java/com/example/spanner/PgSpannerSampleIT.java
create mode 100644 samples/snippets/src/test/java/com/example/spanner/PgSpannerStandaloneExamplesIT.java
diff --git a/README.md b/README.md
index 8f78233506a..473c27dff26 100644
--- a/README.md
+++ b/README.md
@@ -265,10 +265,15 @@ Samples are in the [`samples/`](https://github.com/googleapis/java-spanner/tree/
| Get Instance Config Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/GetInstanceConfigSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/GetInstanceConfigSample.java) |
| List Databases Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/ListDatabasesSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/ListDatabasesSample.java) |
| List Instance Configs Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/ListInstanceConfigsSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/ListInstanceConfigsSample.java) |
+| Pg Async Query To List Async Example | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/PgAsyncQueryToListAsyncExample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/PgAsyncQueryToListAsyncExample.java) |
+| Pg Async Runner Example | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/PgAsyncRunnerExample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/PgAsyncRunnerExample.java) |
+| Pg Async Transaction Manager Example | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/PgAsyncTransactionManagerExample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/PgAsyncTransactionManagerExample.java) |
| Pg Batch Dml Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/PgBatchDmlSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/PgBatchDmlSample.java) |
| Pg Case Sensitivity Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/PgCaseSensitivitySample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/PgCaseSensitivitySample.java) |
| Pg Interleaved Table Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/PgInterleavedTableSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/PgInterleavedTableSample.java) |
| Pg Partitioned Dml Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/PgPartitionedDmlSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/PgPartitionedDmlSample.java) |
+| Pg Query With Numeric Parameter Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/PgQueryWithNumericParameterSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/PgQueryWithNumericParameterSample.java) |
+| Pg Spanner Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/PgSpannerSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/PgSpannerSample.java) |
| Query Information Schema Database Options Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/QueryInformationSchemaDatabaseOptionsSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/QueryInformationSchemaDatabaseOptionsSample.java) |
| Query With Json Parameter Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/QueryWithJsonParameterSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/QueryWithJsonParameterSample.java) |
| Query With Numeric Parameter Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/QueryWithNumericParameterSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/QueryWithNumericParameterSample.java) |
diff --git a/samples/snippets/src/main/java/com/example/spanner/AsyncQueryToListAsyncExample.java b/samples/snippets/src/main/java/com/example/spanner/AsyncQueryToListAsyncExample.java
index 57082ba3e05..11da6a13fdd 100644
--- a/samples/snippets/src/main/java/com/example/spanner/AsyncQueryToListAsyncExample.java
+++ b/samples/snippets/src/main/java/com/example/spanner/AsyncQueryToListAsyncExample.java
@@ -31,6 +31,9 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
+/**
+ * Example code for using Async query on Cloud Spanner and convert it to list.
+ */
class AsyncQueryToListAsyncExample {
static class Album {
final long singerId;
diff --git a/samples/snippets/src/main/java/com/example/spanner/CustomTimeoutAndRetrySettingsExample.java b/samples/snippets/src/main/java/com/example/spanner/CustomTimeoutAndRetrySettingsExample.java
index e3e51875141..7a4a806fdee 100644
--- a/samples/snippets/src/main/java/com/example/spanner/CustomTimeoutAndRetrySettingsExample.java
+++ b/samples/snippets/src/main/java/com/example/spanner/CustomTimeoutAndRetrySettingsExample.java
@@ -80,7 +80,7 @@ static void executeSqlWithCustomTimeoutAndRetrySettings(
.readWriteTransaction()
.run(transaction -> {
String sql =
- "INSERT Singers (SingerId, FirstName, LastName)\n"
+ "INSERT INTO Singers (SingerId, FirstName, LastName)\n"
+ "VALUES (20, 'George', 'Washington')";
long rowCount = transaction.executeUpdate(Statement.of(sql));
System.out.printf("%d record inserted.%n", rowCount);
diff --git a/samples/snippets/src/main/java/com/example/spanner/PgAsyncQueryToListAsyncExample.java b/samples/snippets/src/main/java/com/example/spanner/PgAsyncQueryToListAsyncExample.java
new file mode 100644
index 00000000000..63e7205563b
--- /dev/null
+++ b/samples/snippets/src/main/java/com/example/spanner/PgAsyncQueryToListAsyncExample.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2022 Google Inc.
+ *
+ * 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.example.spanner;
+
+// [START spanner_postgresql_async_query_to_list]
+import com.google.api.core.ApiFuture;
+import com.google.cloud.spanner.AsyncResultSet;
+import com.google.cloud.spanner.DatabaseClient;
+import com.google.cloud.spanner.DatabaseId;
+import com.google.cloud.spanner.Spanner;
+import com.google.cloud.spanner.SpannerOptions;
+import com.google.cloud.spanner.Statement;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+public class PgAsyncQueryToListAsyncExample {
+ static class Album {
+ final long singerId;
+ final long albumId;
+ final String albumTitle;
+
+ Album(long singerId, long albumId, String albumTitle) {
+ this.singerId = singerId;
+ this.albumId = albumId;
+ this.albumTitle = albumTitle;
+ }
+ }
+
+ static void asyncQueryToList() throws InterruptedException, ExecutionException, TimeoutException {
+ // TODO(developer): Replace these variables before running the sample.
+ String projectId = "my-project";
+ String instanceId = "my-instance";
+ String databaseId = "my-database";
+
+ try (Spanner spanner =
+ SpannerOptions.newBuilder().setProjectId(projectId).build().getService()) {
+ DatabaseClient client =
+ spanner.getDatabaseClient(DatabaseId.of(projectId, instanceId, databaseId));
+ asyncQueryToList(client);
+ }
+ }
+
+ // Execute a query asynchronously and transform the result to a list.
+ static void asyncQueryToList(DatabaseClient client)
+ throws InterruptedException, ExecutionException, TimeoutException {
+ ExecutorService executor = Executors.newSingleThreadExecutor();
+ ApiFuture extends List> albums;
+ try (AsyncResultSet resultSet =
+ client
+ .singleUse()
+ .executeQueryAsync(Statement.of("SELECT singerid as \"SingerId\", "
+ + "albumid as \"AlbumId\", albumtitle as \"AlbumTitle\" "
+ + "FROM Albums"))) {
+ // Convert the result set to a list of Albums asynchronously.
+ albums =
+ resultSet.toListAsync(
+ reader -> {
+ return new Album(
+ reader.getLong("SingerId"),
+ reader.getLong("AlbumId"),
+ reader.getString("AlbumTitle"));
+ },
+ executor);
+ }
+
+ for (Album album : albums.get(30L, TimeUnit.SECONDS)) {
+ System.out.printf("%d %d %s%n", album.singerId, album.albumId, album.albumTitle);
+ }
+ executor.shutdown();
+ }
+}
+//[END spanner_postgresql_async_query_to_list]
diff --git a/samples/snippets/src/main/java/com/example/spanner/PgAsyncRunnerExample.java b/samples/snippets/src/main/java/com/example/spanner/PgAsyncRunnerExample.java
new file mode 100644
index 00000000000..f05b509d4c8
--- /dev/null
+++ b/samples/snippets/src/main/java/com/example/spanner/PgAsyncRunnerExample.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2022 Google Inc.
+ *
+ * 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.example.spanner;
+
+//[START spanner_postgresql_async_read_write_transaction]
+import com.google.api.core.ApiFuture;
+import com.google.api.core.ApiFutures;
+import com.google.cloud.spanner.AsyncRunner;
+import com.google.cloud.spanner.DatabaseClient;
+import com.google.cloud.spanner.DatabaseId;
+import com.google.cloud.spanner.Key;
+import com.google.cloud.spanner.Spanner;
+import com.google.cloud.spanner.SpannerExceptionFactory;
+import com.google.cloud.spanner.SpannerOptions;
+import com.google.cloud.spanner.Statement;
+import com.google.cloud.spanner.Struct;
+import com.google.common.collect.ImmutableList;
+import com.google.common.util.concurrent.MoreExecutors;
+import java.util.Arrays;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+class PgAsyncRunnerExample {
+
+ static void asyncRunner() throws InterruptedException, ExecutionException, TimeoutException {
+ // TODO(developer): Replace these variables before running the sample.
+ String projectId = "my-project";
+ String instanceId = "my-instance";
+ String databaseId = "my-database";
+
+ try (Spanner spanner =
+ SpannerOptions.newBuilder().setProjectId(projectId).build().getService()) {
+ DatabaseClient client =
+ spanner.getDatabaseClient(DatabaseId.of(projectId, instanceId, databaseId));
+ asyncRunner(client);
+ }
+ }
+
+ // Execute a read/write transaction asynchronously.
+ static void asyncRunner(DatabaseClient client)
+ throws InterruptedException, ExecutionException, TimeoutException {
+ ExecutorService executor = Executors.newSingleThreadExecutor();
+
+ // Create an async transaction runner.
+ AsyncRunner runner = client.runAsync();
+ // The transaction returns the total number of rows that were updated as a future array of
+ // longs.
+ ApiFuture rowCounts =
+ runner.runAsync(
+ txn -> {
+ // Transfer marketing budget from one album to another. We do it in a
+ // transaction to ensure that the transfer is atomic.
+ ApiFuture album1BudgetFut =
+ txn.readRowAsync("Albums", Key.of(1, 1), ImmutableList.of("MarketingBudget"));
+ ApiFuture album2BudgetFut =
+ txn.readRowAsync("Albums", Key.of(2, 2), ImmutableList.of("MarketingBudget"));
+
+ try {
+ // Transaction will only be committed if this condition still holds at the
+ // time of commit. Otherwise it will be aborted and the AsyncWork will be
+ // rerun by the client library.
+ long transfer = 200_000;
+ if (album2BudgetFut.get().getLong(0) >= transfer) {
+ long album1Budget = album1BudgetFut.get().getLong(0);
+ long album2Budget = album2BudgetFut.get().getLong(0);
+
+ album1Budget += transfer;
+ album2Budget -= transfer;
+ Statement updateStatement1 =
+ Statement.newBuilder(
+ "UPDATE Albums "
+ + "SET MarketingBudget = $1 "
+ + "WHERE SingerId = 1 and AlbumId = 1")
+ .bind("p1")
+ .to(album1Budget)
+ .build();
+ Statement updateStatement2 =
+ Statement.newBuilder(
+ "UPDATE Albums "
+ + "SET MarketingBudget = $1 "
+ + "WHERE SingerId = 2 and AlbumId = 2")
+ .bind("p1")
+ .to(album2Budget)
+ .build();
+ return txn.batchUpdateAsync(
+ ImmutableList.of(updateStatement1, updateStatement2));
+ } else {
+ return ApiFutures.immediateFuture(new long[] {0L, 0L});
+ }
+ } catch (ExecutionException e) {
+ throw SpannerExceptionFactory.newSpannerException(e.getCause());
+ } catch (InterruptedException e) {
+ throw SpannerExceptionFactory.propagateInterrupt(e);
+ }
+ },
+ executor);
+
+ ApiFuture totalUpdateCount =
+ ApiFutures.transform(
+ rowCounts,
+ input -> Arrays.stream(input).sum(),
+ MoreExecutors.directExecutor());
+ System.out.printf("%d records updated.%n", totalUpdateCount.get(30L, TimeUnit.SECONDS));
+ executor.shutdown();
+ }
+}
+//[END spanner_postgresql_async_read_write_transaction]
diff --git a/samples/snippets/src/main/java/com/example/spanner/PgAsyncTransactionManagerExample.java b/samples/snippets/src/main/java/com/example/spanner/PgAsyncTransactionManagerExample.java
new file mode 100644
index 00000000000..d1b20decde6
--- /dev/null
+++ b/samples/snippets/src/main/java/com/example/spanner/PgAsyncTransactionManagerExample.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2022 Google Inc.
+ *
+ * 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.example.spanner;
+
+//[START spanner_postgresql_async_transaction_manager]
+import com.google.api.core.ApiFuture;
+import com.google.api.core.ApiFutures;
+import com.google.cloud.spanner.AbortedException;
+import com.google.cloud.spanner.AsyncTransactionManager;
+import com.google.cloud.spanner.AsyncTransactionManager.AsyncTransactionStep;
+import com.google.cloud.spanner.AsyncTransactionManager.CommitTimestampFuture;
+import com.google.cloud.spanner.AsyncTransactionManager.TransactionContextFuture;
+import com.google.cloud.spanner.DatabaseClient;
+import com.google.cloud.spanner.DatabaseId;
+import com.google.cloud.spanner.Key;
+import com.google.cloud.spanner.Spanner;
+import com.google.cloud.spanner.SpannerOptions;
+import com.google.cloud.spanner.Statement;
+import com.google.cloud.spanner.Struct;
+import com.google.common.collect.ImmutableList;
+import com.google.common.util.concurrent.MoreExecutors;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+class PgAsyncTransactionManagerExample {
+
+ static void asyncTransactionManager()
+ throws InterruptedException, ExecutionException, TimeoutException {
+ // TODO(developer): Replace these variables before running the sample.
+ String projectId = "my-project";
+ String instanceId = "my-instance";
+ String databaseId = "my-database";
+
+ try (Spanner spanner =
+ SpannerOptions.newBuilder().setProjectId(projectId).build().getService()) {
+ DatabaseClient client =
+ spanner.getDatabaseClient(DatabaseId.of(projectId, instanceId, databaseId));
+ asyncTransactionManager(client);
+ }
+ }
+
+ static void asyncTransactionManager(DatabaseClient client)
+ throws InterruptedException, ExecutionException, TimeoutException {
+ ExecutorService executor = Executors.newSingleThreadExecutor();
+
+ AsyncTransactionStep, long[]> updateCounts;
+ try (AsyncTransactionManager mgr = client.transactionManagerAsync()) {
+ TransactionContextFuture txn = mgr.beginAsync();
+ // Loop to retry aborted errors.
+ while (true) {
+ try {
+ updateCounts =
+ txn.then(
+ (transaction, v) -> {
+ // Execute two reads in parallel and return the result of these as the input
+ // for the next step of the transaction.
+ ApiFuture album1BudgetFut =
+ transaction.readRowAsync(
+ "Albums", Key.of(1, 1), ImmutableList.of("MarketingBudget"));
+ ApiFuture album2BudgetFut =
+ transaction.readRowAsync(
+ "Albums", Key.of(2, 2), ImmutableList.of("MarketingBudget"));
+ return ApiFutures.allAsList(
+ Arrays.asList(album1BudgetFut, album2BudgetFut));
+ },
+ executor)
+ // The input of the next step of the transaction is the return value of the
+ // previous step, i.e. a list containing the marketing budget of two Albums.
+ .then(
+ (transaction, budgets) -> {
+ long album1Budget = budgets.get(0).getLong(0);
+ long album2Budget = budgets.get(1).getLong(0);
+ long transfer = 200_000;
+ if (album2Budget >= transfer) {
+ album1Budget += transfer;
+ album2Budget -= transfer;
+ Statement updateStatement1 =
+ Statement.newBuilder(
+ "UPDATE Albums "
+ + "SET MarketingBudget = $1 "
+ + "WHERE SingerId = 1 and AlbumId = 1")
+ .bind("p1")
+ .to(album1Budget)
+ .build();
+ Statement updateStatement2 =
+ Statement.newBuilder(
+ "UPDATE Albums "
+ + "SET MarketingBudget = $1 "
+ + "WHERE SingerId = 2 and AlbumId = 2")
+ .bind("p1")
+ .to(album2Budget)
+ .build();
+ return transaction.batchUpdateAsync(
+ ImmutableList.of(updateStatement1, updateStatement2));
+ } else {
+ return ApiFutures.immediateFuture(new long[] {0L, 0L});
+ }
+ },
+ executor);
+ // Commit after the updates.
+ CommitTimestampFuture commitTsFut = updateCounts.commitAsync();
+ // Wait for the transaction to finish and execute a retry if necessary.
+ commitTsFut.get();
+ break;
+ } catch (AbortedException e) {
+ txn = mgr.resetForRetryAsync();
+ }
+ }
+ }
+
+ // Calculate the total update count.
+ ApiFuture totalUpdateCount =
+ ApiFutures.transform(
+ updateCounts,
+ input -> Arrays.stream(input).sum(),
+ MoreExecutors.directExecutor());
+ System.out.printf("%d records updated.%n", totalUpdateCount.get(30L, TimeUnit.SECONDS));
+ executor.shutdown();
+ }
+}
+//[END spanner_postgresql_async_transaction_manager]
diff --git a/samples/snippets/src/main/java/com/example/spanner/PgQueryWithNumericParameterSample.java b/samples/snippets/src/main/java/com/example/spanner/PgQueryWithNumericParameterSample.java
new file mode 100644
index 00000000000..144a26def38
--- /dev/null
+++ b/samples/snippets/src/main/java/com/example/spanner/PgQueryWithNumericParameterSample.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2022 Google Inc.
+ *
+ * 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.example.spanner;
+
+// [START spanner_postgresql_query_with_numeric_parameter]
+import com.google.cloud.spanner.DatabaseClient;
+import com.google.cloud.spanner.DatabaseId;
+import com.google.cloud.spanner.ResultSet;
+import com.google.cloud.spanner.Spanner;
+import com.google.cloud.spanner.SpannerOptions;
+import com.google.cloud.spanner.Statement;
+import com.google.cloud.spanner.Value;
+
+class PgQueryWithNumericParameterSample {
+
+ static void queryWithNumericParameter() {
+ // TODO(developer): Replace these variables before running the sample.
+ String projectId = "my-project";
+ String instanceId = "my-instance";
+ String databaseId = "my-database";
+
+ try (Spanner spanner =
+ SpannerOptions.newBuilder().setProjectId(projectId).build().getService()) {
+ DatabaseClient client =
+ spanner.getDatabaseClient(DatabaseId.of(projectId, instanceId, databaseId));
+ queryWithNumericParameter(client);
+ }
+ }
+
+ static void queryWithNumericParameter(DatabaseClient client) {
+ Statement statement =
+ Statement.newBuilder(
+ "SELECT venueid as \"VenueId\", revenue as \"Revenue\" FROM Venues WHERE Revenue "
+ + "< $1")
+ .bind("p1")
+ .to(Value.pgNumeric("100000"))
+ .build();
+ try (ResultSet resultSet = client.singleUse().executeQuery(statement)) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%d %s%n", resultSet.getLong("VenueId"), resultSet.getValue("Revenue"));
+ }
+ }
+ }
+}
+// [END spanner_postgresql_query_with_numeric_parameter]
diff --git a/samples/snippets/src/main/java/com/example/spanner/PgSpannerSample.java b/samples/snippets/src/main/java/com/example/spanner/PgSpannerSample.java
new file mode 100644
index 00000000000..9cc1d5d8497
--- /dev/null
+++ b/samples/snippets/src/main/java/com/example/spanner/PgSpannerSample.java
@@ -0,0 +1,1578 @@
+/*
+ * Copyright 2022 Google Inc.
+ *
+ * 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.example.spanner;
+
+import com.google.api.gax.longrunning.OperationFuture;
+import com.google.api.gax.paging.Page;
+import com.google.cloud.ByteArray;
+import com.google.cloud.Date;
+import com.google.cloud.Timestamp;
+import com.google.cloud.spanner.Database;
+import com.google.cloud.spanner.DatabaseAdminClient;
+import com.google.cloud.spanner.DatabaseClient;
+import com.google.cloud.spanner.DatabaseId;
+import com.google.cloud.spanner.Dialect;
+import com.google.cloud.spanner.Instance;
+import com.google.cloud.spanner.InstanceAdminClient;
+import com.google.cloud.spanner.InstanceId;
+import com.google.cloud.spanner.Key;
+import com.google.cloud.spanner.KeyRange;
+import com.google.cloud.spanner.KeySet;
+import com.google.cloud.spanner.Mutation;
+import com.google.cloud.spanner.Options;
+import com.google.cloud.spanner.ReadOnlyTransaction;
+import com.google.cloud.spanner.ResultSet;
+import com.google.cloud.spanner.Spanner;
+import com.google.cloud.spanner.SpannerBatchUpdateException;
+import com.google.cloud.spanner.SpannerException;
+import com.google.cloud.spanner.SpannerExceptionFactory;
+import com.google.cloud.spanner.SpannerOptions;
+import com.google.cloud.spanner.Statement;
+import com.google.cloud.spanner.Struct;
+import com.google.cloud.spanner.TimestampBound;
+import com.google.cloud.spanner.Value;
+import com.google.common.io.BaseEncoding;
+import com.google.longrunning.Operation;
+import com.google.protobuf.InvalidProtocolBufferException;
+import com.google.spanner.admin.database.v1.CreateBackupMetadata;
+import com.google.spanner.admin.database.v1.CreateDatabaseMetadata;
+import com.google.spanner.admin.database.v1.OptimizeRestoredDatabaseMetadata;
+import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata;
+import com.google.spanner.v1.ExecuteSqlRequest;
+import java.math.BigDecimal;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Example code for using the Cloud Spanner PostgreSQL interface.
+ */
+public class PgSpannerSample {
+ static final List SINGERS =
+ Arrays.asList(
+ new Singer(1, "Marc", "Richards"),
+ new Singer(2, "Catalina", "Smith"),
+ new Singer(3, "Alice", "Trentor"),
+ new Singer(4, "Lea", "Martin"),
+ new Singer(5, "David", "Lomond"));
+ static final List ALBUMS =
+ Arrays.asList(
+ new Album(1, 1, "Total Junk"),
+ new Album(1, 2, "Go, Go, Go"),
+ new Album(2, 1, "Green"),
+ new Album(2, 2, "Forever Hold Your Peace"),
+ new Album(2, 3, "Terrified"));
+
+ /** Class to contain performance sample data. */
+ static class Performance {
+
+ final long singerId;
+ final long venueId;
+ final String eventDate;
+ final long revenue;
+
+ Performance(long singerId, long venueId, String eventDate, long revenue) {
+ this.singerId = singerId;
+ this.venueId = venueId;
+ this.eventDate = eventDate;
+ this.revenue = revenue;
+ }
+ }
+
+ // [START spanner_postgresql_insert_data_with_timestamp_column]
+ static final List PERFORMANCES =
+ Arrays.asList(
+ new Performance(1, 4, "2017-10-05", 11000),
+ new Performance(1, 19, "2017-11-02", 15000),
+ new Performance(2, 42, "2017-12-23", 7000));
+ // [START spanner_postgresql_insert_datatypes_data]
+
+ static Value availableDates1 =
+ Value.dateArray(
+ Arrays.asList(
+ Date.parseDate("2020-12-01"),
+ Date.parseDate("2020-12-02"),
+ Date.parseDate("2020-12-03")));
+ static Value availableDates2 =
+ Value.dateArray(
+ Arrays.asList(
+ Date.parseDate("2020-11-01"),
+ Date.parseDate("2020-11-05"),
+ Date.parseDate("2020-11-15")));
+ static Value availableDates3 =
+ Value.dateArray(Arrays.asList(Date.parseDate("2020-10-01"), Date.parseDate("2020-10-07")));
+ // [END spanner_postgresql_insert_data_with_timestamp_column]
+ static String exampleBytes1 = BaseEncoding.base64().encode("Hello World 1".getBytes());
+ static String exampleBytes2 = BaseEncoding.base64().encode("Hello World 2".getBytes());
+ static String exampleBytes3 = BaseEncoding.base64().encode("Hello World 3".getBytes());
+ static final List VENUES =
+ Arrays.asList(
+ new Venue(
+ 4,
+ "Venue 4",
+ exampleBytes1,
+ 1800,
+ availableDates1,
+ "2018-09-02",
+ false,
+ 0.85543f,
+ new BigDecimal("215100.10")),
+ new Venue(
+ 19,
+ "Venue 19",
+ exampleBytes2,
+ 6300,
+ availableDates2,
+ "2019-01-15",
+ true,
+ 0.98716f,
+ new BigDecimal("1200100.00")),
+ new Venue(
+ 42,
+ "Venue 42",
+ exampleBytes3,
+ 3000,
+ availableDates3,
+ "2018-10-01",
+ false,
+ 0.72598f,
+ new BigDecimal("390650.99")));
+ // [END spanner_postgresql_insert_datatypes_data]
+
+ /** Class to contain venue sample data. */
+ static class Venue {
+
+ final long venueId;
+ final String venueName;
+ final String venueInfo;
+ final long capacity;
+ final Value availableDates;
+ final String lastContactDate;
+ final boolean outdoorVenue;
+ final float popularityScore;
+ final BigDecimal revenue;
+
+ Venue(
+ long venueId,
+ String venueName,
+ String venueInfo,
+ long capacity,
+ Value availableDates,
+ String lastContactDate,
+ boolean outdoorVenue,
+ float popularityScore,
+ BigDecimal revenue) {
+ this.venueId = venueId;
+ this.venueName = venueName;
+ this.venueInfo = venueInfo;
+ this.capacity = capacity;
+ this.availableDates = availableDates;
+ this.lastContactDate = lastContactDate;
+ this.outdoorVenue = outdoorVenue;
+ this.popularityScore = popularityScore;
+ this.revenue = revenue;
+ }
+ }
+
+ // [START spanner_postgresql_create_database]
+ static void createPostgreSqlDatabase(DatabaseAdminClient dbAdminClient, DatabaseId id) {
+ OperationFuture op = dbAdminClient.createDatabase(
+ dbAdminClient.newDatabaseBuilder(id).setDialect(Dialect.POSTGRESQL).build(),
+ Collections.emptyList());
+ try {
+ // Initiate the request which returns an OperationFuture.
+ Database db = op.get();
+ System.out.println("Created database [" + db.getId() + "]");
+ } catch (ExecutionException e) {
+ // If the operation failed during execution, expose the cause.
+ throw (SpannerException) e.getCause();
+ } catch (InterruptedException e) {
+ // Throw when a thread is waiting, sleeping, or otherwise occupied,
+ // and the thread is interrupted, either before or during the activity.
+ throw SpannerExceptionFactory.propagateInterrupt(e);
+ }
+ }
+ // [END spanner_postgresql_create_database]
+
+ // [START spanner_postgresql_insert_data]
+ static void writeExampleData(DatabaseClient dbClient) {
+ List mutations = new ArrayList<>();
+ for (Singer singer : SINGERS) {
+ mutations.add(
+ Mutation.newInsertBuilder("Singers")
+ .set("SingerId")
+ .to(singer.singerId)
+ .set("FirstName")
+ .to(singer.firstName)
+ .set("LastName")
+ .to(singer.lastName)
+ .build());
+ }
+ for (Album album : ALBUMS) {
+ mutations.add(
+ Mutation.newInsertBuilder("Albums")
+ .set("SingerId")
+ .to(album.singerId)
+ .set("AlbumId")
+ .to(album.albumId)
+ .set("AlbumTitle")
+ .to(album.albumTitle)
+ .build());
+ }
+ dbClient.write(mutations);
+ }
+ // [END spanner_postgresql_insert_data]
+
+ // [START spanner_postgresql_delete_data]
+ static void deleteExampleData(DatabaseClient dbClient) {
+ List mutations = new ArrayList<>();
+
+ // KeySet.Builder can be used to delete a specific set of rows.
+ // Delete the Albums with the key values (2,1) and (2,3).
+ mutations.add(
+ Mutation.delete(
+ "Albums", KeySet.newBuilder().addKey(Key.of(2, 1)).addKey(Key.of(2, 3)).build()));
+
+ // KeyRange can be used to delete rows with a key in a specific range.
+ // Delete a range of rows where the column key is >=3 and <5
+ mutations.add(
+ Mutation.delete("Singers", KeySet.range(KeyRange.closedOpen(Key.of(3), Key.of(5)))));
+
+ // KeySet.all() can be used to delete all the rows in a table.
+ // Delete remaining Singers rows, which will also delete the remaining Albums rows since it was
+ // defined with ON DELETE CASCADE.
+ mutations.add(Mutation.delete("Singers", KeySet.all()));
+
+ dbClient.write(mutations);
+ System.out.printf("Records deleted.\n");
+ }
+ // [END spanner_postgresql_delete_data]
+
+ // [START spanner_postgresql_query_data]
+ static void query(DatabaseClient dbClient) {
+ try (ResultSet resultSet =
+ dbClient
+ .singleUse() // Execute a single read or query against Cloud Spanner.
+ .executeQuery(Statement.of("SELECT SingerId, AlbumId, AlbumTitle FROM Albums"))) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%d %d %s\n", resultSet.getLong(0), resultSet.getLong(1),
+ resultSet.getString(2));
+ }
+ }
+ }
+ // [END spanner_postgresql_query_data]
+
+ // [START spanner_postgresql_read_data]
+ static void read(DatabaseClient dbClient) {
+ try (ResultSet resultSet =
+ dbClient
+ .singleUse()
+ .read(
+ "Albums",
+ KeySet.all(), // Read all rows in a table.
+ Arrays.asList("SingerId", "AlbumId", "AlbumTitle"))) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%d %d %s\n", resultSet.getLong(0), resultSet.getLong(1),
+ resultSet.getString(2));
+ }
+ }
+ }
+ // [END spanner_postgresql_read_data]
+
+ // [START spanner_postgresql_add_column]
+ static void addMarketingBudget(DatabaseAdminClient adminClient, DatabaseId dbId) {
+ OperationFuture op = adminClient.updateDatabaseDdl(
+ dbId.getInstanceId().getInstance(),
+ dbId.getDatabase(),
+ Arrays.asList("ALTER TABLE Albums ADD COLUMN MarketingBudget bigint"),
+ null);
+ try {
+ // Initiate the request which returns an OperationFuture.
+ op.get();
+ System.out.println("Added MarketingBudget column");
+ } catch (ExecutionException e) {
+ // If the operation failed during execution, expose the cause.
+ throw (SpannerException) e.getCause();
+ } catch (InterruptedException e) {
+ // Throw when a thread is waiting, sleeping, or otherwise occupied,
+ // and the thread is interrupted, either before or during the activity.
+ throw SpannerExceptionFactory.propagateInterrupt(e);
+ }
+ }
+ // [END spanner_postgresql_add_column]
+
+ // Before executing this method, a new column MarketingBudget has to be added to the Albums
+ // table by applying the DDL statement "ALTER TABLE Albums ADD COLUMN MarketingBudget INT64".
+ // [START spanner_postgresql_update_data]
+ static void update(DatabaseClient dbClient) {
+ // Mutation can be used to update/insert/delete a single row in a table. Here we use
+ // newUpdateBuilder to create update mutations.
+ List mutations =
+ Arrays.asList(
+ Mutation.newUpdateBuilder("Albums")
+ .set("SingerId")
+ .to(1)
+ .set("AlbumId")
+ .to(1)
+ .set("MarketingBudget")
+ .to(100000)
+ .build(),
+ Mutation.newUpdateBuilder("Albums")
+ .set("SingerId")
+ .to(2)
+ .set("AlbumId")
+ .to(2)
+ .set("MarketingBudget")
+ .to(500000)
+ .build());
+ // This writes all the mutations to Cloud Spanner atomically.
+ dbClient.write(mutations);
+ }
+ // [END spanner_postgresql_update_data]
+
+ // [START spanner_postgresql_read_write_transaction]
+ static void writeWithTransaction(DatabaseClient dbClient) {
+ dbClient
+ .readWriteTransaction()
+ .run(transaction -> {
+ // Transfer marketing budget from one album to another. We do it in a transaction to
+ // ensure that the transfer is atomic.
+ Struct row =
+ transaction.readRow("Albums", Key.of(2, 2), Arrays.asList("MarketingBudget"));
+ long album2Budget = row.getLong(0);
+ // Transaction will only be committed if this condition still holds at the time of
+ // commit. Otherwise it will be aborted and the callable will be rerun by the
+ // client library.
+ long transfer = 200000;
+ if (album2Budget >= transfer) {
+ long album1Budget =
+ transaction
+ .readRow("Albums", Key.of(1, 1), Arrays.asList("MarketingBudget"))
+ .getLong(0);
+ album1Budget += transfer;
+ album2Budget -= transfer;
+ transaction.buffer(
+ Mutation.newUpdateBuilder("Albums")
+ .set("SingerId")
+ .to(1)
+ .set("AlbumId")
+ .to(1)
+ .set("MarketingBudget")
+ .to(album1Budget)
+ .build());
+ transaction.buffer(
+ Mutation.newUpdateBuilder("Albums")
+ .set("SingerId")
+ .to(2)
+ .set("AlbumId")
+ .to(2)
+ .set("MarketingBudget")
+ .to(album2Budget)
+ .build());
+ }
+ return null;
+ });
+ }
+ // [END spanner_postgresql_read_write_transaction]
+
+ // [START spanner_postgresql_query_data_with_new_column]
+ static void queryMarketingBudget(DatabaseClient dbClient) {
+ // Rows without an explicit value for MarketingBudget will have a MarketingBudget equal to
+ // null. A try-with-resource block is used to automatically release resources held by
+ // ResultSet.
+ try (ResultSet resultSet =
+ dbClient
+ .singleUse()
+ .executeQuery(Statement.of("SELECT singerid as \"SingerId\", "
+ + "albumid as \"AlbumId\", marketingbudget as \"MarketingBudget\" "
+ + "FROM Albums"))) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%d %d %s\n",
+ resultSet.getLong("SingerId"),
+ resultSet.getLong("AlbumId"),
+ // We check that the value is non null. ResultSet getters can only be used to retrieve
+ // non null values.
+ resultSet.isNull("MarketingBudget") ? "NULL" :
+ resultSet.getLong("MarketingBudget"));
+ }
+ }
+ }
+ // [END spanner_postgresql_query_data_with_new_column]
+
+ // [START spanner_postgresql_create_index]
+ static void addIndex(DatabaseAdminClient adminClient, DatabaseId dbId) {
+ OperationFuture op =
+ adminClient.updateDatabaseDdl(
+ dbId.getInstanceId().getInstance(),
+ dbId.getDatabase(),
+ Arrays.asList("CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle)"),
+ null);
+ try {
+ // Initiate the request which returns an OperationFuture.
+ op.get();
+ System.out.println("Added AlbumsByAlbumTitle index");
+ } catch (ExecutionException e) {
+ // If the operation failed during execution, expose the cause.
+ throw (SpannerException) e.getCause();
+ } catch (InterruptedException e) {
+ // Throw when a thread is waiting, sleeping, or otherwise occupied,
+ // and the thread is interrupted, either before or during the activity.
+ throw SpannerExceptionFactory.propagateInterrupt(e);
+ }
+ }
+ // [END spanner_postgresql_create_index]
+
+ // [START spanner_postgresql_read_data_with_index]
+ static void readUsingIndex(DatabaseClient dbClient) {
+ try (ResultSet resultSet =
+ dbClient
+ .singleUse()
+ .readUsingIndex(
+ "Albums",
+ "AlbumsByAlbumTitle",
+ KeySet.all(),
+ Arrays.asList("AlbumId", "AlbumTitle"))) {
+ while (resultSet.next()) {
+ System.out.printf("%d %s\n", resultSet.getLong(0), resultSet.getString(1));
+ }
+ }
+ }
+ // [END spanner_postgresql_read_data_with_index]
+
+ // [START spanner_postgresql_create_storing_index]
+ static void addStoringIndex(DatabaseAdminClient adminClient, DatabaseId dbId) {
+ OperationFuture op = adminClient.updateDatabaseDdl(
+ dbId.getInstanceId().getInstance(),
+ dbId.getDatabase(),
+ Arrays.asList(
+ "CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) "
+ + "INCLUDE (MarketingBudget)"),
+ null);
+ try {
+ // Initiate the request which returns an OperationFuture.
+ op.get();
+ System.out.println("Added AlbumsByAlbumTitle2 index");
+ } catch (ExecutionException e) {
+ // If the operation failed during execution, expose the cause.
+ throw (SpannerException) e.getCause();
+ } catch (InterruptedException e) {
+ // Throw when a thread is waiting, sleeping, or otherwise occupied,
+ // and the thread is interrupted, either before or during the activity.
+ throw SpannerExceptionFactory.propagateInterrupt(e);
+ }
+ }
+ // [END spanner_postgresql_create_storing_index]
+
+ // Before running this example, create a storing index AlbumsByAlbumTitle2 by applying the DDL
+ // statement "CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) INCLUDE (MarketingBudget)".
+ // [START spanner_postgresql_read_data_with_storing_index]
+ static void readStoringIndex(DatabaseClient dbClient) {
+ // We can read MarketingBudget also from the index since it stores a copy of MarketingBudget.
+ try (ResultSet resultSet =
+ dbClient
+ .singleUse()
+ .readUsingIndex(
+ "Albums",
+ "AlbumsByAlbumTitle2",
+ KeySet.all(),
+ Arrays.asList("AlbumId", "AlbumTitle", "MarketingBudget"))) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%d %s %s\n",
+ resultSet.getLong(0),
+ resultSet.getString(1),
+ resultSet.isNull("marketingbudget") ? "NULL" : resultSet.getLong(2));
+ }
+ }
+ }
+ // [END spanner_postgresql_read_data_with_storing_index]
+
+ // [START spanner_postgresql_read_only_transaction]
+ static void readOnlyTransaction(DatabaseClient dbClient) {
+ // ReadOnlyTransaction must be closed by calling close() on it to release resources held by it.
+ // We use a try-with-resource block to automatically do so.
+ try (ReadOnlyTransaction transaction = dbClient.readOnlyTransaction()) {
+ ResultSet queryResultSet =
+ transaction.executeQuery(
+ Statement.of("SELECT SingerId, AlbumId, AlbumTitle FROM Albums"));
+ while (queryResultSet.next()) {
+ System.out.printf(
+ "%d %d %s\n",
+ queryResultSet.getLong(0), queryResultSet.getLong(1),
+ queryResultSet.getString(2));
+ }
+ try (ResultSet readResultSet =
+ transaction.read(
+ "Albums", KeySet.all(), Arrays.asList("SingerId", "AlbumId", "AlbumTitle"))) {
+ while (readResultSet.next()) {
+ System.out.printf(
+ "%d %d %s\n",
+ readResultSet.getLong(0), readResultSet.getLong(1),
+ readResultSet.getString(2));
+ }
+ }
+ }
+ }
+ // [END spanner_postgresql_read_only_transaction]
+
+ // [START spanner_postgresql_query_singers_table]
+ static void querySingersTable(DatabaseClient dbClient) {
+ try (ResultSet resultSet =
+ dbClient
+ .singleUse()
+ .executeQuery(Statement.of("SELECT singerid as \"SingerId\", "
+ + "firstname as \"FirstName\", lastname as \"LastName\" FROM Singers"))) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%s %s %s\n",
+ resultSet.getLong("SingerId"),
+ resultSet.getString("FirstName"),
+ resultSet.getString("LastName"));
+ }
+ }
+ }
+ // [END spanner_postgresql_query_singers_table]
+
+
+ // [START spanner_postgresql_dml_getting_started_insert]
+ static void writeUsingDml(DatabaseClient dbClient) {
+ // Insert 4 singer records
+ dbClient
+ .readWriteTransaction()
+ .run(transaction -> {
+ String sql =
+ "INSERT INTO Singers (SingerId, FirstName, LastName) VALUES "
+ + "(12, 'Melissa', 'Garcia'), "
+ + "(13, 'Russell', 'Morales'), "
+ + "(14, 'Jacqueline', 'Long'), "
+ + "(15, 'Dylan', 'Shaw')";
+ long rowCount = transaction.executeUpdate(Statement.of(sql));
+ System.out.printf("%d records inserted.\n", rowCount);
+ return null;
+ });
+ }
+ // [END spanner_postgresql_dml_getting_started_insert]
+
+ // [START spanner_postgresql_query_with_parameter]
+ static void queryWithParameter(DatabaseClient dbClient) {
+ Statement statement =
+ Statement.newBuilder(
+ "SELECT singerid AS \"SingerId\", "
+ + "firstname as \"FirstName\", lastname as \"LastName\" "
+ + "FROM Singers "
+ + "WHERE LastName = $1")
+ .bind("p1")
+ .to("Garcia")
+ .build();
+ try (ResultSet resultSet = dbClient.singleUse().executeQuery(statement)) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%d %s %s\n",
+ resultSet.getLong("SingerId"),
+ resultSet.getString("FirstName"),
+ resultSet.getString("LastName"));
+ }
+ }
+ }
+ // [END spanner_postgresql_query_with_parameter]
+
+ // [START spanner_postgresql_dml_getting_started_update]
+ static void writeWithTransactionUsingDml(DatabaseClient dbClient) {
+ dbClient
+ .readWriteTransaction()
+ .run(transaction -> {
+ // Transfer marketing budget from one album to another. We do it in a transaction to
+ // ensure that the transfer is atomic.
+ String sql1 =
+ "SELECT marketingbudget as \"MarketingBudget\" from Albums WHERE "
+ + "SingerId = 2 and AlbumId = 2";
+ ResultSet resultSet = transaction.executeQuery(Statement.of(sql1));
+ long album2Budget = 0;
+ while (resultSet.next()) {
+ album2Budget = resultSet.getLong("MarketingBudget");
+ }
+ // Transaction will only be committed if this condition still holds at the time of
+ // commit. Otherwise it will be aborted and the callable will be rerun by the
+ // client library.
+ long transfer = 200000;
+ if (album2Budget >= transfer) {
+ String sql2 =
+ "SELECT marketingbudget as \"MarketingBudget\" from Albums WHERE "
+ + "SingerId = 1 and AlbumId = 1";
+ ResultSet resultSet2 = transaction.executeQuery(Statement.of(sql2));
+ long album1Budget = 0;
+ while (resultSet2.next()) {
+ album1Budget = resultSet2.getLong("MarketingBudget");
+ }
+ album1Budget += transfer;
+ album2Budget -= transfer;
+ Statement updateStatement =
+ Statement.newBuilder(
+ "UPDATE Albums "
+ + "SET MarketingBudget = $1"
+ + "WHERE SingerId = 1 and AlbumId = 1")
+ .bind("p1")
+ .to(album1Budget)
+ .build();
+ transaction.executeUpdate(updateStatement);
+ Statement updateStatement2 =
+ Statement.newBuilder(
+ "UPDATE Albums "
+ + "SET MarketingBudget = $1"
+ + "WHERE SingerId = 2 and AlbumId = 2")
+ .bind("p1")
+ .to(album2Budget)
+ .build();
+ transaction.executeUpdate(updateStatement2);
+ }
+ return null;
+ });
+ }
+ // [END spanner_postgresql_dml_getting_started_update]
+
+ // [START spanner_postgresql_create_table_using_ddl]
+ static void createTableUsingDdl(DatabaseAdminClient dbAdminClient, DatabaseId id) {
+ OperationFuture op =
+ dbAdminClient.updateDatabaseDdl(
+ id.getInstanceId().getInstance(),
+ id.getDatabase(),
+ Arrays.asList(
+ "CREATE TABLE Singers ("
+ + " SingerId bigint NOT NULL,"
+ + " FirstName character varying(1024),"
+ + " LastName character varying(1024),"
+ + " SingerInfo bytea,"
+ + " PRIMARY KEY (SingerId)"
+ + ")",
+ "CREATE TABLE Albums ("
+ + " SingerId bigint NOT NULL,"
+ + " AlbumId bigint NOT NULL,"
+ + " AlbumTitle character varying(1024),"
+ + " PRIMARY KEY (SingerId, AlbumId)"
+ + ") INTERLEAVE IN PARENT Singers ON DELETE CASCADE"),
+ null);
+ try {
+ // Initiate the request which returns an OperationFuture.
+ op.get();
+ System.out.println("Created Singers & Albums tables in database: [" + id + "]");
+ } catch (ExecutionException e) {
+ // If the operation failed during execution, expose the cause.
+ throw (SpannerException) e.getCause();
+ } catch (InterruptedException e) {
+ // Throw when a thread is waiting, sleeping, or otherwise occupied,
+ // and the thread is interrupted, either before or during the activity.
+ throw SpannerExceptionFactory.propagateInterrupt(e);
+ }
+ }
+ // [END spanner_postgresql_create_table_using_ddl]
+
+ // [START spanner_postgresql_read_stale_data]
+ static void readStaleData(DatabaseClient dbClient) {
+ try (ResultSet resultSet =
+ dbClient
+ .singleUse(TimestampBound.ofExactStaleness(15, TimeUnit.SECONDS))
+ .read(
+ "Albums", KeySet.all(),
+ Arrays.asList("SingerId", "AlbumId", "MarketingBudget"))) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%d %d %s\n",
+ resultSet.getLong(0),
+ resultSet.getLong(1),
+ resultSet.isNull(2) ? "NULL" : resultSet.getLong(2));
+ }
+ }
+ }
+ // [END spanner_postgresql_read_stale_data]
+
+ // Before executing this method, a new column MarketingBudget has to be added to the Albums
+ // table by applying the DDL statement "ALTER TABLE Albums ADD COLUMN MarketingBudget BIGINT".
+ // In addition this update expects the LastUpdateTime column added by applying the DDL statement
+ // "ALTER TABLE Albums ADD COLUMN LastUpdateTime TIMESTAMPTZ"
+ // [START spanner_postgresql_update_data_with_timestamp_column]
+ static void updateWithTimestamp(DatabaseClient dbClient) {
+ // Mutation can be used to update/insert/delete a single row in a table. Here we use
+ // newUpdateBuilder to create update mutations.
+ List mutations =
+ Arrays.asList(
+ Mutation.newUpdateBuilder("Albums")
+ .set("SingerId")
+ .to(1)
+ .set("AlbumId")
+ .to(1)
+ .set("MarketingBudget")
+ .to(1000000)
+ .set("LastUpdateTime")
+ .to(Value.COMMIT_TIMESTAMP)
+ .build(),
+ Mutation.newUpdateBuilder("Albums")
+ .set("SingerId")
+ .to(2)
+ .set("AlbumId")
+ .to(2)
+ .set("MarketingBudget")
+ .to(750000)
+ .set("LastUpdateTime")
+ .to(Value.COMMIT_TIMESTAMP)
+ .build());
+ // This writes all the mutations to Cloud Spanner atomically.
+ dbClient.write(mutations);
+ }
+ // [END spanner_postgresql_update_data_with_timestamp_column]
+
+ // [START spanner_postgresql_add_timestamp_column]
+ static void addLastUpdateTimestampColumn(DatabaseAdminClient adminClient, DatabaseId dbId) {
+ OperationFuture op =
+ adminClient.updateDatabaseDdl(
+ dbId.getInstanceId().getInstance(),
+ dbId.getDatabase(),
+ Arrays.asList(
+ "ALTER TABLE Albums ADD COLUMN LastUpdateTime spanner.commit_timestamp"),
+ null);
+ try {
+ // Initiate the request which returns an OperationFuture.
+ op.get();
+ System.out.println("Added LastUpdateTime as a timestamp column in Albums table.");
+ } catch (ExecutionException e) {
+ // If the operation failed during execution, expose the cause.
+ throw (SpannerException) e.getCause();
+ } catch (InterruptedException e) {
+ // Throw when a thread is waiting, sleeping, or otherwise occupied,
+ // and the thread is interrupted, either before or during the activity.
+ throw SpannerExceptionFactory.propagateInterrupt(e);
+ }
+ }
+ // [END spanner_postgresql_add_timestamp_column]
+
+ // [START spanner_postgresql_query_data_with_timestamp_column]
+ static void queryMarketingBudgetWithTimestamp(DatabaseClient dbClient) {
+ // Rows without an explicit value for MarketingBudget will have a MarketingBudget equal to
+ // null. A try-with-resource block is used to automatically release resources held by
+ // ResultSet.
+ try (ResultSet resultSet =
+ dbClient
+ .singleUse()
+ .executeQuery(
+ Statement.of(
+ "SELECT singerid as \"SingerId\", albumid as \"AlbumId\", "
+ + "marketingbudget as \"MarketingBudget\","
+ + "lastupdatetime as \"LastUpdateTime\" FROM Albums"
+ + " ORDER BY LastUpdateTime DESC"))) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%d %d %s %s\n",
+ resultSet.getLong("SingerId"),
+ resultSet.getLong("AlbumId"),
+ // We check that the value is non null. ResultSet getters can only be used to retrieve
+ // non null values.
+ resultSet.isNull("MarketingBudget") ? "NULL" : resultSet.getLong("MarketingBudget"),
+ resultSet.isNull("LastUpdateTime") ? "NULL" : resultSet.getTimestamp("LastUpdateTime"));
+ }
+ }
+ }
+ // [END spanner_postgresql_query_data_with_timestamp_column]
+
+ // [START spanner_postgresql_create_table_with_timestamp_column]
+ static void createTableWithTimestamp(DatabaseAdminClient dbAdminClient, DatabaseId id) {
+ OperationFuture op =
+ dbAdminClient.updateDatabaseDdl(
+ id.getInstanceId().getInstance(),
+ id.getDatabase(),
+ Arrays.asList(
+ "CREATE TABLE Performances ("
+ + " SingerId BIGINT NOT NULL,"
+ + " VenueId BIGINT NOT NULL,"
+ + " Revenue BIGINT,"
+ + " LastUpdateTime SPANNER.COMMIT_TIMESTAMP NOT NULL,"
+ + " PRIMARY KEY (SingerId, VenueId))"
+ + " INTERLEAVE IN PARENT Singers ON DELETE CASCADE"),
+ null);
+ try {
+ // Initiate the request which returns an OperationFuture.
+ op.get();
+ System.out.println("Created Performances table in database: [" + id + "]");
+ } catch (ExecutionException e) {
+ // If the operation failed during execution, expose the cause.
+ throw (SpannerException) e.getCause();
+ } catch (InterruptedException e) {
+ // Throw when a thread is waiting, sleeping, or otherwise occupied,
+ // and the thread is interrupted, either before or during the activity.
+ throw SpannerExceptionFactory.propagateInterrupt(e);
+ }
+ }
+ // [END spanner_postgresql_create_table_with_timestamp_column]
+
+ // [START spanner_postgresql_insert_data_with_timestamp_column]
+ static void writeExampleDataWithTimestamp(DatabaseClient dbClient) {
+ List mutations = new ArrayList<>();
+ for (Performance performance : PERFORMANCES) {
+ mutations.add(
+ Mutation.newInsertBuilder("Performances")
+ .set("SingerId")
+ .to(performance.singerId)
+ .set("VenueId")
+ .to(performance.venueId)
+ .set("Revenue")
+ .to(performance.revenue)
+ .set("LastUpdateTime")
+ .to(Value.COMMIT_TIMESTAMP)
+ .build());
+ }
+ dbClient.write(mutations);
+ }
+ // [END spanner_postgresql_insert_data_with_timestamp_column]
+
+ static void queryPerformancesTable(DatabaseClient dbClient) {
+ // Rows without an explicit value for Revenue will have a Revenue equal to
+ // null. A try-with-resource block is used to automatically release resources held by
+ // ResultSet.
+ try (ResultSet resultSet =
+ dbClient
+ .singleUse()
+ .executeQuery(
+ Statement.of(
+ "SELECT singerid as \"SingerId\", venueid as \"VenueId\", "
+ + "revenue as \"Revenue\", lastupdatetime as \"LastUpdateTime\" "
+ + "FROM Performances ORDER BY LastUpdateTime DESC"))) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%d %d %s %s\n",
+ resultSet.getLong("SingerId"),
+ resultSet.getLong("VenueId"),
+ // We check that the value is non null. ResultSet getters can only be used to retrieve
+ // non null values.
+ resultSet.isNull("Revenue") ? "NULL" : resultSet.getLong("Revenue"),
+ resultSet.getTimestamp("LastUpdateTime"));
+ }
+ }
+ }
+
+ // [START spanner_postgresql_dml_standard_insert]
+ static void insertUsingDml(DatabaseClient dbClient) {
+ dbClient
+ .readWriteTransaction()
+ .run(transaction -> {
+ String sql =
+ "INSERT INTO Singers (SingerId, FirstName, LastName) "
+ + " VALUES (10, 'Virginia', 'Watson')";
+ long rowCount = transaction.executeUpdate(Statement.of(sql));
+ System.out.printf("%d record inserted.\n", rowCount);
+ return null;
+ });
+ }
+ // [END spanner_postgresql_dml_standard_insert]
+
+ // [START spanner_postgresql_dml_standard_update]
+ static void updateUsingDml(DatabaseClient dbClient) {
+ dbClient
+ .readWriteTransaction()
+ .run(transaction -> {
+ String sql =
+ "UPDATE Albums "
+ + "SET MarketingBudget = MarketingBudget * 2 "
+ + "WHERE SingerId = 1 and AlbumId = 1";
+ long rowCount = transaction.executeUpdate(Statement.of(sql));
+ System.out.printf("%d record updated.\n", rowCount);
+ return null;
+ });
+ }
+ // [END spanner_postgresql_dml_standard_update]
+
+ // [START spanner_postgresql_dml_standard_delete]
+ static void deleteUsingDml(DatabaseClient dbClient) {
+ dbClient
+ .readWriteTransaction()
+ .run(transaction -> {
+ String sql = "DELETE FROM Singers WHERE FirstName = 'Alice'";
+ long rowCount = transaction.executeUpdate(Statement.of(sql));
+ System.out.printf("%d record deleted.\n", rowCount);
+ return null;
+ });
+ }
+ // [END spanner_postgresql_dml_standard_delete]
+
+ // [START spanner_postgresql_dml_write_then_read]
+ static void writeAndReadUsingDml(DatabaseClient dbClient) {
+ dbClient
+ .readWriteTransaction()
+ .run(transaction -> {
+ // Insert record.
+ String sql =
+ "INSERT INTO Singers (SingerId, FirstName, LastName) "
+ + " VALUES (11, 'Timothy', 'Campbell')";
+ long rowCount = transaction.executeUpdate(Statement.of(sql));
+ System.out.printf("%d record inserted.\n", rowCount);
+ // Read newly inserted record.
+ sql = "SELECT firstname as \"FirstName\", lastname as \"LastName\" FROM Singers WHERE "
+ + "SingerId = 11";
+ // We use a try-with-resource block to automatically release resources held by
+ // ResultSet.
+ try (ResultSet resultSet = transaction.executeQuery(Statement.of(sql))) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%s %s\n",
+ resultSet.getString("FirstName"), resultSet.getString("LastName"));
+ }
+ }
+ return null;
+ });
+ }
+ // [END spanner_postgresql_dml_write_then_read]
+
+ // [START spanner_postgresql_dml_partitioned_update]
+ static void updateUsingPartitionedDml(DatabaseClient dbClient) {
+ String sql = "UPDATE Albums SET MarketingBudget = 100000 WHERE SingerId > 1";
+ long rowCount = dbClient.executePartitionedUpdate(Statement.of(sql));
+ System.out.printf("%d records updated.\n", rowCount);
+ }
+ // [END spanner_postgresql_dml_partitioned_update]
+
+ // [START spanner_postgresql_dml_partitioned_delete]
+ static void deleteUsingPartitionedDml(DatabaseClient dbClient) {
+ String sql = "DELETE FROM Singers WHERE SingerId > 10";
+ long rowCount = dbClient.executePartitionedUpdate(Statement.of(sql));
+ System.out.printf("%d records deleted.\n", rowCount);
+ }
+ // [END spanner_postgresql_dml_partitioned_delete]
+
+ // [START spanner_postgresql_dml_batch_update]
+ static void updateUsingBatchDml(DatabaseClient dbClient) {
+ dbClient
+ .readWriteTransaction()
+ .run(transaction -> {
+ List stmts = new ArrayList();
+ String sql =
+ "INSERT INTO Albums "
+ + "(SingerId, AlbumId, AlbumTitle, MarketingBudget) "
+ + "VALUES (1, 3, 'Test Album Title', 10000) ";
+ stmts.add(Statement.of(sql));
+ sql =
+ "UPDATE Albums "
+ + "SET MarketingBudget = MarketingBudget * 2 "
+ + "WHERE SingerId = 1 and AlbumId = 3";
+ stmts.add(Statement.of(sql));
+ long[] rowCounts;
+ try {
+ rowCounts = transaction.batchUpdate(stmts);
+ } catch (SpannerBatchUpdateException e) {
+ rowCounts = e.getUpdateCounts();
+ }
+ for (int i = 0; i < rowCounts.length; i++) {
+ System.out.printf("%d record updated by stmt %d.\n", rowCounts[i], i);
+ }
+ return null;
+ });
+ }
+ // [END spanner_postgresql_dml_batch_update]
+
+ // [START spanner_postgresql_create_table_with_datatypes]
+ static void createTableWithDatatypes(DatabaseAdminClient dbAdminClient, DatabaseId id) {
+ OperationFuture op =
+ dbAdminClient.updateDatabaseDdl(
+ id.getInstanceId().getInstance(),
+ id.getDatabase(),
+ Arrays.asList(
+ "CREATE TABLE Venues ("
+ + " VenueId BIGINT NOT NULL,"
+ + " VenueName character varying(100),"
+ + " VenueInfo bytea,"
+ + " Capacity BIGINT,"
+ + " OutdoorVenue BOOL, "
+ + " PopularityScore FLOAT8, "
+ + " Revenue NUMERIC, "
+ + " LastUpdateTime SPANNER.COMMIT_TIMESTAMP NOT NULL,"
+ + " PRIMARY KEY (VenueId))"),
+ null);
+ try {
+ // Initiate the request which returns an OperationFuture.
+ op.get();
+ System.out.println("Created Venues table in database: [" + id + "]");
+ } catch (ExecutionException e) {
+ // If the operation failed during execution, expose the cause.
+ throw (SpannerException) e.getCause();
+ } catch (InterruptedException e) {
+ // Throw when a thread is waiting, sleeping, or otherwise occupied,
+ // and the thread is interrupted, either before or during the activity.
+ throw SpannerExceptionFactory.propagateInterrupt(e);
+ }
+ }
+ // [END spanner_postgresql_create_table_with_datatypes]
+
+ // [START spanner_postgresql_insert_datatypes_data]
+ static void writeDatatypesData(DatabaseClient dbClient) {
+ List mutations = new ArrayList<>();
+ for (Venue venue : VENUES) {
+ mutations.add(
+ Mutation.newInsertBuilder("Venues")
+ .set("VenueId")
+ .to(venue.venueId)
+ .set("VenueName")
+ .to(venue.venueName)
+ .set("VenueInfo")
+ .to(venue.venueInfo)
+ .set("Capacity")
+ .to(venue.capacity)
+ .set("OutdoorVenue")
+ .to(venue.outdoorVenue)
+ .set("PopularityScore")
+ .to(venue.popularityScore)
+ .set("Revenue")
+ .to(venue.revenue)
+ .set("LastUpdateTime")
+ .to(Value.COMMIT_TIMESTAMP)
+ .build());
+ }
+ dbClient.write(mutations);
+ }
+ // [END spanner_postgresql_insert_datatypes_data]
+
+ // [START spanner_postgresql_query_with_bool_parameter]
+ static void queryWithBool(DatabaseClient dbClient) {
+ boolean exampleBool = true;
+ Statement statement =
+ Statement.newBuilder(
+ "SELECT venueid as \"VenueId\", venuename as \"VenueName\","
+ + " outdoorvenue as \"OutdoorVenue\" FROM Venues "
+ + "WHERE OutdoorVenue = $1")
+ .bind("p1")
+ .to(exampleBool)
+ .build();
+ try (ResultSet resultSet = dbClient.singleUse().executeQuery(statement)) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%d %s %b\n",
+ resultSet.getLong("VenueId"),
+ resultSet.getString("VenueName"),
+ resultSet.getBoolean("OutdoorVenue"));
+ }
+ }
+ }
+ // [END spanner_postgresql_query_with_bool_parameter]
+
+ // [START spanner_postgresql_query_with_bytes_parameter]
+ static void queryWithBytes(DatabaseClient dbClient) {
+ ByteArray exampleBytes =
+ ByteArray.fromBase64(BaseEncoding.base64().encode("Hello World 1".getBytes()));
+ Statement statement =
+ Statement.newBuilder(
+ "SELECT venueid as \"VenueId\", venuename as \"VenueName\" FROM Venues "
+ + "WHERE VenueInfo = $1")
+ .bind("p1")
+ .to(exampleBytes)
+ .build();
+ try (ResultSet resultSet = dbClient.singleUse().executeQuery(statement)) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%d %s\n", resultSet.getLong("VenueId"), resultSet.getString("VenueName"));
+ }
+ }
+ }
+ // [END spanner_postgresql_query_with_bytes_parameter]
+
+ // [START spanner_postgresql_query_with_float_parameter]
+ static void queryWithFloat(DatabaseClient dbClient) {
+ float exampleFloat = 0.8f;
+ Statement statement =
+ Statement.newBuilder(
+ "SELECT venueid as \"VenueId\", venuename as \"VenueName\", "
+ + "popularityscore as \"PopularityScore\" FROM Venues "
+ + "WHERE PopularityScore > $1")
+ .bind("p1")
+ .to(exampleFloat)
+ .build();
+ try (ResultSet resultSet = dbClient.singleUse().executeQuery(statement)) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%d %s %f\n",
+ resultSet.getLong("VenueId"),
+ resultSet.getString("VenueName"),
+ resultSet.getDouble("PopularityScore"));
+ }
+ }
+ }
+ // [END spanner_postgresql_query_with_float_parameter]
+
+ // [START spanner_postgresql_query_with_int_parameter]
+ static void queryWithInt(DatabaseClient dbClient) {
+ long exampleInt = 3000;
+ Statement statement =
+ Statement.newBuilder(
+ "SELECT venueid as \"VenueId\", venuename as \"VenueName\", "
+ + "capacity as \"Capacity\" "
+ + "FROM Venues " + "WHERE Capacity >= $1")
+ .bind("p1")
+ .to(exampleInt)
+ .build();
+ try (ResultSet resultSet = dbClient.singleUse().executeQuery(statement)) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%d %s %d\n",
+ resultSet.getLong("VenueId"),
+ resultSet.getString("VenueName"),
+ resultSet.getLong("Capacity"));
+ }
+ }
+ }
+ // [END spanner_postgresql_query_with_int_parameter]
+
+ // [START spanner_postgresql_query_with_string_parameter]
+ static void queryWithString(DatabaseClient dbClient) {
+ String exampleString = "Venue 42";
+ Statement statement =
+ Statement.newBuilder(
+ "SELECT venueid as \"VenueId\", venuename as \"VenueName\" FROM Venues WHERE"
+ + " VenueName = $1")
+ .bind("p1")
+ .to(exampleString)
+ .build();
+ try (ResultSet resultSet = dbClient.singleUse().executeQuery(statement)) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%d %s\n", resultSet.getLong("VenueId"), resultSet.getString("VenueName"));
+ }
+ }
+ }
+ // [END spanner_postgresql_query_with_string_parameter]
+
+ // [START spanner_postgresql_query_with_timestamp_parameter]
+ static void queryWithTimestampParameter(DatabaseClient dbClient) {
+ Statement statement =
+ Statement.newBuilder(
+ "SELECT venueid as \"VenueId\", venuename as \"VenueName\", "
+ + "lastupdatetime as \"LastUpdateTime\" FROM Venues "
+ + "WHERE LastUpdateTime < $1")
+ .bind("p1")
+ .to(Timestamp.now())
+ .build();
+ try (ResultSet resultSet = dbClient.singleUse().executeQuery(statement)) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%d %s %s\n",
+ resultSet.getLong("VenueId"),
+ resultSet.getString("VenueName"),
+ resultSet.getTimestamp("LastUpdateTime"));
+ }
+ }
+ }
+ // [END spanner_postgresql_query_with_timestamp_parameter]
+
+ // [START spanner_postgresql_query_with_numeric_parameter]
+ static void queryWithNumeric(DatabaseClient dbClient) {
+ Statement statement =
+ Statement.newBuilder(
+ "SELECT venueid as \"VenueId\", venuename as \"VenueName\", "
+ + "revenue as \"Revenue\" FROM Venues\n"
+ + "WHERE Revenue >= $1")
+ .bind("p1")
+ .to(Value.pgNumeric("300000"))
+ .build();
+ try (ResultSet resultSet = dbClient.singleUse().executeQuery(statement)) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%d %s %s%n",
+ resultSet.getLong("VenueId"),
+ resultSet.getString("VenueName"),
+ resultSet.getValue("Revenue"));
+ }
+ }
+ }
+ // [END spanner_postgresql_query_with_numeric_parameter]
+
+ // [START spanner_postgresql_create_client_with_query_options]
+ static void clientWithQueryOptions(DatabaseId db) {
+ SpannerOptions options =
+ SpannerOptions.newBuilder()
+ .setDefaultQueryOptions(
+ db, ExecuteSqlRequest.QueryOptions
+ .newBuilder()
+ .setOptimizerVersion("1")
+ // The list of available statistics packages can be found by querying the
+ // "INFORMATION_SCHEMA.spanner_postgresql_STATISTICS" table.
+ .setOptimizerStatisticsPackage("latest")
+ .build())
+ .build();
+ Spanner spanner = options.getService();
+ DatabaseClient dbClient = spanner.getDatabaseClient(db);
+ try (ResultSet resultSet =
+ dbClient
+ .singleUse()
+ .executeQuery(Statement.of("SELECT SingerId, AlbumId, AlbumTitle FROM Albums"))) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%d %d %s\n", resultSet.getLong(0), resultSet.getLong(1), resultSet.getString(2));
+ }
+ }
+ }
+ // [END spanner_postgresql_create_client_with_query_options]
+
+ // [START spanner_postgresql_query_with_query_options]
+ static void queryWithQueryOptions(DatabaseClient dbClient) {
+ try (ResultSet resultSet =
+ dbClient
+ .singleUse()
+ .executeQuery(
+ Statement
+ .newBuilder("SELECT SingerId, AlbumId, AlbumTitle FROM Albums")
+ .withQueryOptions(ExecuteSqlRequest.QueryOptions
+ .newBuilder()
+ .setOptimizerVersion("1")
+ // The list of available statistics packages can be found by querying
+ // the "INFORMATION_SCHEMA.spanner_postgresql_STATISTICS" table.
+ .setOptimizerStatisticsPackage("latest")
+ .build())
+ .build())) {
+ while (resultSet.next()) {
+ System.out.printf(
+ "%d %d %s\n", resultSet.getLong(0), resultSet.getLong(1), resultSet.getString(2));
+ }
+ }
+ }
+ // [END spanner_postgresql_query_with_query_options]
+
+ // [START spanner_postgresql_list_backup_operations]
+ static void listBackupOperations(InstanceAdminClient instanceAdminClient, DatabaseId databaseId) {
+ Instance instance = instanceAdminClient.getInstance(databaseId.getInstanceId().getInstance());
+ // Get create backup operations for the sample database.
+ Timestamp last24Hours = Timestamp.ofTimeSecondsAndNanos(TimeUnit.SECONDS.convert(
+ TimeUnit.HOURS.convert(Timestamp.now().getSeconds(), TimeUnit.SECONDS) - 24,
+ TimeUnit.HOURS), 0);
+ String filter =
+ String.format(
+ "(metadata.database:%s) AND "
+ + "(metadata.@type:type.googleapis.com/"
+ + "google.spanner.admin.database.v1.CreateBackupMetadata) AND "
+ + "(metadata.progress.start_time > \"%s\")",
+ databaseId.getName(), last24Hours);
+ Page operations = instance
+ .listBackupOperations(Options.filter(filter));
+ for (com.google.longrunning.Operation op : operations.iterateAll()) {
+ try {
+ CreateBackupMetadata metadata = op.getMetadata().unpack(CreateBackupMetadata.class);
+ System.out.println(
+ String.format(
+ "Backup %s on database %s pending: %d%% complete",
+ metadata.getName(),
+ metadata.getDatabase(),
+ metadata.getProgress().getProgressPercent()));
+ } catch (InvalidProtocolBufferException e) {
+ // The returned operation does not contain CreateBackupMetadata.
+ System.err.println(e.getMessage());
+ }
+ }
+ }
+ // [END spanner_postgresql_list_backup_operations]
+
+ // [START spanner_postgresql_list_database_operations]
+ static void listDatabaseOperations(
+ InstanceAdminClient instanceAdminClient,
+ DatabaseAdminClient dbAdminClient,
+ InstanceId instanceId) {
+ Instance instance = instanceAdminClient.getInstance(instanceId.getInstance());
+ // Get optimize restored database operations.
+ Timestamp last24Hours = Timestamp.ofTimeSecondsAndNanos(TimeUnit.SECONDS.convert(
+ TimeUnit.HOURS.convert(Timestamp.now().getSeconds(), TimeUnit.SECONDS) - 24,
+ TimeUnit.HOURS), 0);
+ String filter = String.format("(metadata.@type:type.googleapis.com/"
+ + "google.spanner.admin.database.v1.OptimizeRestoredDatabaseMetadata) AND "
+ + "(metadata.progress.start_time > \"%s\")", last24Hours);
+ for (Operation op : instance.listDatabaseOperations(Options.filter(filter)).iterateAll()) {
+ try {
+ OptimizeRestoredDatabaseMetadata metadata =
+ op.getMetadata().unpack(OptimizeRestoredDatabaseMetadata.class);
+ System.out.println(String.format(
+ "Database %s restored from backup is %d%% optimized",
+ metadata.getName(),
+ metadata.getProgress().getProgressPercent()));
+ } catch (InvalidProtocolBufferException e) {
+ // The returned operation does not contain OptimizeRestoredDatabaseMetadata.
+ System.err.println(e.getMessage());
+ }
+ }
+ }
+ // [END spanner_postgresql_list_database_operations]
+
+ static void run(
+ DatabaseClient dbClient,
+ DatabaseAdminClient dbAdminClient,
+ InstanceAdminClient instanceAdminClient,
+ String command,
+ DatabaseId database) {
+ switch (command) {
+ case "createpgdatabase":
+ createPostgreSqlDatabase(dbAdminClient, database);
+ break;
+ case "write":
+ writeExampleData(dbClient);
+ break;
+ case "delete":
+ deleteExampleData(dbClient);
+ break;
+ case "query":
+ query(dbClient);
+ break;
+ case "read":
+ read(dbClient);
+ break;
+ case "addmarketingbudget":
+ addMarketingBudget(dbAdminClient, database);
+ break;
+ case "update":
+ update(dbClient);
+ break;
+ case "writetransaction":
+ writeWithTransaction(dbClient);
+ break;
+ case "querymarketingbudget":
+ queryMarketingBudget(dbClient);
+ break;
+ case "addindex":
+ addIndex(dbAdminClient, database);
+ break;
+ case "readindex":
+ readUsingIndex(dbClient);
+ break;
+ case "addstoringindex":
+ addStoringIndex(dbAdminClient, database);
+ break;
+ case "readstoringindex":
+ readStoringIndex(dbClient);
+ break;
+ case "readonlytransaction":
+ readOnlyTransaction(dbClient);
+ break;
+ case "querysingerstable":
+ querySingersTable(dbClient);
+ break;
+ case "writeusingdml":
+ writeUsingDml(dbClient);
+ break;
+ case "querywithparameter":
+ queryWithParameter(dbClient);
+ break;
+ case "writewithtransactionusingdml":
+ writeWithTransactionUsingDml(dbClient);
+ break;
+ case "createtableusingddl":
+ createTableUsingDdl(dbAdminClient, database);
+ break;
+ case "readstaledata":
+ readStaleData(dbClient);
+ break;
+ case "addlastupdatetimestampcolumn":
+ addLastUpdateTimestampColumn(dbAdminClient, database);
+ break;
+ case "updatewithtimestamp":
+ updateWithTimestamp(dbClient);
+ break;
+ case "querywithtimestamp":
+ queryMarketingBudgetWithTimestamp(dbClient);
+ break;
+ case "createtablewithtimestamp":
+ createTableWithTimestamp(dbAdminClient, database);
+ break;
+ case "writewithtimestamp":
+ writeExampleDataWithTimestamp(dbClient);
+ break;
+ case "queryperformancestable":
+ queryPerformancesTable(dbClient);
+ break;
+ case "insertusingdml":
+ insertUsingDml(dbClient);
+ break;
+ case "updateusingdml":
+ updateUsingDml(dbClient);
+ break;
+ case "deleteusingdml":
+ deleteUsingDml(dbClient);
+ break;
+ case "writeandreadusingdml":
+ writeAndReadUsingDml(dbClient);
+ break;
+ case "updateusingpartitioneddml":
+ updateUsingPartitionedDml(dbClient);
+ break;
+ case "deleteusingpartitioneddml":
+ deleteUsingPartitionedDml(dbClient);
+ break;
+ case "updateusingbatchdml":
+ updateUsingBatchDml(dbClient);
+ break;
+ case "createtablewithdatatypes":
+ createTableWithDatatypes(dbAdminClient, database);
+ break;
+ case "writedatatypesdata":
+ writeDatatypesData(dbClient);
+ break;
+ case "querywithbool":
+ queryWithBool(dbClient);
+ break;
+ case "querywithbytes":
+ queryWithBytes(dbClient);
+ break;
+ case "querywithfloat":
+ queryWithFloat(dbClient);
+ break;
+ case "querywithint":
+ queryWithInt(dbClient);
+ break;
+ case "querywithstring":
+ queryWithString(dbClient);
+ break;
+ case "querywithtimestampparameter":
+ queryWithTimestampParameter(dbClient);
+ break;
+ case "querywithnumeric":
+ queryWithNumeric(dbClient);
+ break;
+ case "clientwithqueryoptions":
+ clientWithQueryOptions(database);
+ break;
+ case "querywithqueryoptions":
+ queryWithQueryOptions(dbClient);
+ break;
+ case "listbackupoperations":
+ listBackupOperations(instanceAdminClient, database);
+ break;
+ case "listdatabaseoperations":
+ listDatabaseOperations(instanceAdminClient, dbAdminClient, database.getInstanceId());
+ break;
+ default:
+ printUsageAndExit();
+ }
+ }
+
+ static void printUsageAndExit() {
+ System.err.println("Usage:");
+ System.err.println(" PgSpannerExample ");
+ System.err.println();
+ System.err.println("Examples:");
+ System.err.println(" PgSpannerExample createpgdatabase my-instance example-db");
+ System.err.println(" PgSpannerExample write my-instance example-db");
+ System.err.println(" PgSpannerExample delete my-instance example-db");
+ System.err.println(" PgSpannerExample query my-instance example-db");
+ System.err.println(" PgSpannerExample read my-instance example-db");
+ System.err.println(" PgSpannerExample addmarketingbudget my-instance example-db");
+ System.err.println(" PgSpannerExample update my-instance example-db");
+ System.err.println(" PgSpannerExample writetransaction my-instance example-db");
+ System.err.println(" PgSpannerExample querymarketingbudget my-instance example-db");
+ System.err.println(" PgSpannerExample addindex my-instance example-db");
+ System.err.println(" PgSpannerExample readindex my-instance example-db");
+ System.err.println(" PgSpannerExample addstoringindex my-instance example-db");
+ System.err.println(" PgSpannerExample readstoringindex my-instance example-db");
+ System.err.println(" PgSpannerExample readonlytransaction my-instance example-db");
+ System.err.println(" PgSpannerExample querysingerstable my-instance example-db");
+ System.err.println(" PgSpannerExample writeusingdml my-instance example-db");
+ System.err.println(" PgSpannerExample querywithparameter my-instance example-db");
+ System.err.println(" PgSpannerExample writewithtransactionusingdml my-instance example-db");
+ System.err.println(" PgSpannerExample createtableforsamples my-instance example-db");
+ System.err.println(" PgSpannerExample writewithtimestamp my-instance example-db");
+ System.err.println(" PgSpannerExample queryperformancestable my-instance example-db");
+ System.err.println(" PgSpannerExample writestructdata my-instance example-db");
+ System.err.println(" PgSpannerExample insertusingdml my-instance example-db");
+ System.err.println(" PgSpannerExample updateusingdml my-instance example-db");
+ System.err.println(" PgSpannerExample deleteusingdml my-instance example-db");
+ System.err.println(" PgSpannerExample writeandreadusingdml my-instance example-db");
+ System.err.println(" PgSpannerExample writeusingdml my-instance example-db");
+ System.err.println(" PgSpannerExample deleteusingpartitioneddml my-instance example-db");
+ System.err.println(" PgSpannerExample updateusingbatchdml my-instance example-db");
+ System.err.println(" PgSpannerExample createtablewithdatatypes my-instance example-db");
+ System.err.println(" PgSpannerExample writedatatypesdata my-instance example-db");
+ System.err.println(" PgSpannerExample querywithbool my-instance example-db");
+ System.err.println(" PgSpannerExample querywithbytes my-instance example-db");
+ System.err.println(" PgSpannerExample querywithfloat my-instance example-db");
+ System.err.println(" PgSpannerExample querywithint my-instance example-db");
+ System.err.println(" PgSpannerExample querywithstring my-instance example-db");
+ System.err.println(" PgSpannerExample querywithtimestampparameter my-instance example-db");
+ System.err.println(" PgSpannerExample clientwithqueryoptions my-instance example-db");
+ System.err.println(" PgSpannerExample querywithqueryoptions my-instance example-db");
+ System.err.println(" PgSpannerExample listbackupoperations my-instance example-db");
+ System.err.println(" PgSpannerExample listdatabaseoperations my-instance example-db");
+ System.exit(1);
+ }
+
+ public static void main(String[] args) {
+ if (args.length != 3) {
+ printUsageAndExit();
+ }
+ // [START spanner_init_client]
+ SpannerOptions options = SpannerOptions.newBuilder().build();
+ Spanner spanner = options.getService();
+ try {
+ // [END spanner_init_client]
+ String command = args[0];
+ DatabaseId db = DatabaseId.of(options.getProjectId(), args[1], args[2]);
+
+ // This will return the default project id based on the environment.
+ String clientProject = spanner.getOptions().getProjectId();
+ if (!db.getInstanceId().getProject().equals(clientProject)) {
+ System.err.println(
+ "Invalid project specified. Project in the database id should match the"
+ + "project name set in the environment variable GOOGLE_CLOUD_PROJECT. Expected: "
+ + clientProject);
+ printUsageAndExit();
+ }
+ // [START spanner_init_client]
+ DatabaseClient dbClient = spanner.getDatabaseClient(db);
+ DatabaseAdminClient dbAdminClient = spanner.getDatabaseAdminClient();
+ InstanceAdminClient instanceAdminClient = spanner.getInstanceAdminClient();
+ // [END spanner_init_client]
+
+ // Use client here...
+ run(dbClient, dbAdminClient, instanceAdminClient, command, db);
+ // [START spanner_init_client]
+ } finally {
+ spanner.close();
+ }
+ // [END spanner_init_client]
+ System.out.println("Closed client");
+ }
+
+ /** Class to contain singer sample data. */
+ static class Singer {
+
+ final long singerId;
+ final String firstName;
+ final String lastName;
+
+ Singer(long singerId, String firstName, String lastName) {
+ this.singerId = singerId;
+ this.firstName = firstName;
+ this.lastName = lastName;
+ }
+ }
+
+ /** Class to contain album sample data. */
+ static class Album {
+
+ final long singerId;
+ final long albumId;
+ final String albumTitle;
+
+ Album(long singerId, long albumId, String albumTitle) {
+ this.singerId = singerId;
+ this.albumId = albumId;
+ this.albumTitle = albumTitle;
+ }
+ }
+}
diff --git a/samples/snippets/src/main/java/com/example/spanner/StatementTimeoutExample.java b/samples/snippets/src/main/java/com/example/spanner/StatementTimeoutExample.java
index 6c45216b317..2a82467754f 100644
--- a/samples/snippets/src/main/java/com/example/spanner/StatementTimeoutExample.java
+++ b/samples/snippets/src/main/java/com/example/spanner/StatementTimeoutExample.java
@@ -65,7 +65,7 @@ public ApiCallContext configure(ApiCallContext context, ReqT reque
// Run the transaction in the custom context.
context.run(() ->
client.readWriteTransaction().run(transaction -> {
- String sql = "INSERT Singers (SingerId, FirstName, LastName)\n"
+ String sql = "INSERT INTO Singers (SingerId, FirstName, LastName)\n"
+ "VALUES (20, 'George', 'Washington')";
long rowCount = transaction.executeUpdate(Statement.of(sql));
System.out.printf("%d record inserted.%n", rowCount);
diff --git a/samples/snippets/src/test/java/com/example/spanner/PgAsyncExamplesIT.java b/samples/snippets/src/test/java/com/example/spanner/PgAsyncExamplesIT.java
new file mode 100644
index 00000000000..db5c7362cf8
--- /dev/null
+++ b/samples/snippets/src/test/java/com/example/spanner/PgAsyncExamplesIT.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright 2022 Google Inc.
+ *
+ * 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.example.spanner;
+
+import static com.example.spanner.SampleRunner.runSample;
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.cloud.spanner.DatabaseClient;
+import com.google.cloud.spanner.DatabaseId;
+import com.google.cloud.spanner.Dialect;
+import com.google.cloud.spanner.KeySet;
+import com.google.cloud.spanner.Mutation;
+import com.google.common.collect.ImmutableList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Integration tests for Cloud Spanner Async API examples for Postgresql.
+ */
+@RunWith(JUnit4.class)
+@SuppressWarnings("checkstyle:AbbreviationAsWordInName")
+public class PgAsyncExamplesIT extends SampleTestBase {
+
+ private static DatabaseId databaseId;
+
+ @BeforeClass
+ public static void createTestDatabase() throws Exception {
+ final String database = idGenerator.generateDatabaseId();
+ databaseId = DatabaseId.of(projectId, instanceId, database);
+ databaseAdminClient
+ .createDatabase(
+ databaseAdminClient
+ .newDatabaseBuilder(databaseId)
+ .setDialect(Dialect.POSTGRESQL).build(),
+ Collections.emptyList())
+ .get();
+ databaseAdminClient.updateDatabaseDdl(
+ instanceId,
+ database,
+ Arrays.asList(
+ "CREATE TABLE Singers ("
+ + " SingerId bigint NOT NULL,"
+ + " FirstName character varying(1024),"
+ + " LastName character varying(1024),"
+ + " SingerInfo bytea,"
+ + " PRIMARY KEY (SingerId)"
+ + ")",
+ "CREATE TABLE Albums ("
+ + " SingerId bigint NOT NULL,"
+ + " AlbumId bigint NOT NULL,"
+ + " AlbumTitle character varying(1024),"
+ + " MarketingBudget bigint,"
+ + " PRIMARY KEY (SingerId, AlbumId)"
+ + ") INTERLEAVE IN PARENT Singers ON DELETE CASCADE",
+ "CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle)"),
+ null).get();
+ }
+
+ static class Singer {
+
+ final long singerId;
+ final String firstName;
+ final String lastName;
+
+ Singer(long singerId, String firstName, String lastName) {
+ this.singerId = singerId;
+ this.firstName = firstName;
+ this.lastName = lastName;
+ }
+ }
+
+ static class Album {
+
+ final long singerId;
+ final long albumId;
+ final String albumTitle;
+ final Long marketingBudget;
+
+ Album(long singerId, long albumId, String albumTitle, Long marketingBudget) {
+ this.singerId = singerId;
+ this.albumId = albumId;
+ this.albumTitle = albumTitle;
+ this.marketingBudget = marketingBudget;
+ }
+ }
+
+ private static final List TEST_SINGERS =
+ Arrays.asList(
+ new Singer(1, "Marc", "Richards"),
+ new Singer(2, "Catalina", "Smith"),
+ new Singer(3, "Alice", "Trentor"),
+ new Singer(4, "Lea", "Martin"),
+ new Singer(5, "David", "Lomond"));
+ private static final List ALBUMS =
+ Arrays.asList(
+ new Album(1, 1, "Total Junk", 300_000L),
+ new Album(1, 2, "Go, Go, Go", 400_000L),
+ new Album(2, 1, "Green", 150_000L),
+ new Album(2, 2, "Forever Hold Your Peace", 350_000L),
+ new Album(2, 3, "Terrified", null));
+
+ @Before
+ public void insertTestData() {
+ DatabaseClient client = spanner.getDatabaseClient(databaseId);
+ ImmutableList.Builder mutations =
+ ImmutableList.builderWithExpectedSize(TEST_SINGERS.size());
+ for (Singer singer : TEST_SINGERS) {
+ mutations.add(
+ Mutation.newInsertBuilder("Singers")
+ .set("SingerId")
+ .to(singer.singerId)
+ .set("FirstName")
+ .to(singer.firstName)
+ .set("LastName")
+ .to(singer.lastName)
+ .build());
+ }
+ for (Album album : ALBUMS) {
+ mutations.add(
+ Mutation.newInsertBuilder("Albums")
+ .set("SingerId")
+ .to(album.singerId)
+ .set("AlbumId")
+ .to(album.albumId)
+ .set("AlbumTitle")
+ .to(album.albumTitle)
+ .set("MarketingBudget")
+ .to(album.marketingBudget)
+ .build());
+ }
+ client.write(mutations.build());
+ }
+
+ private void assertSingersOutput(String out) {
+ assertThat(out).contains("1 Marc Richard");
+ assertThat(out).contains("2 Catalina Smith");
+ assertThat(out).contains("3 Alice Trentor");
+ assertThat(out).contains("4 Lea Martin");
+ assertThat(out).contains("5 David Lomond");
+ }
+
+ private void assertAlbumsOutput(String out) {
+ assertThat(out).contains("1 1 Total Junk");
+ assertThat(out).contains("1 2 Go, Go, Go");
+ assertThat(out).contains("2 1 Green");
+ assertThat(out).contains("2 2 Forever Hold Your Peace");
+ assertThat(out).contains("2 3 Terrified");
+ }
+
+ @After
+ public void removeTestData() {
+ DatabaseClient client = spanner.getDatabaseClient(databaseId);
+ client.write(Arrays.asList(Mutation.delete("Singers", KeySet.all())));
+ }
+
+ @Test
+ public void asyncQuery_shouldReturnData() throws Exception {
+ String out = runSample(
+ () -> AsyncQueryExample.asyncQuery(spanner.getDatabaseClient(databaseId)));
+ assertAlbumsOutput(out);
+ }
+
+ @Test
+ public void asyncQueryToListAsync_shouldReturnData()
+ throws Exception {
+ String out = runSample(
+ () -> PgAsyncQueryToListAsyncExample
+ .asyncQueryToList(spanner.getDatabaseClient(databaseId)));
+ assertAlbumsOutput(out);
+ }
+
+ @Test
+ public void asyncRead_shouldReturnData()
+ throws Exception {
+ String out = runSample(() -> AsyncReadExample.asyncRead(spanner.getDatabaseClient(databaseId)));
+ assertAlbumsOutput(out);
+ }
+
+ @Test
+ public void asyncReadUsingIndex_shouldReturnDataInCorrectOrder() throws Exception {
+ String out = runSample(() -> AsyncReadUsingIndexExample
+ .asyncReadUsingIndex(spanner.getDatabaseClient(databaseId)));
+ assertThat(out)
+ .contains(
+ "2 Forever Hold Your Peace\n"
+ + "2 Go, Go, Go\n"
+ + "1 Green\n"
+ + "3 Terrified\n"
+ + "1 Total Junk");
+ }
+
+ @Test
+ public void asyncReadOnlyTransaction_shouldReturnData() throws Exception {
+ String out = runSample(() -> AsyncReadOnlyTransactionExample
+ .asyncReadOnlyTransaction(spanner.getDatabaseClient(databaseId)));
+ assertAlbumsOutput(out);
+ assertSingersOutput(out);
+ }
+
+ @Test
+ public void asyncDml_shouldInsertRows() throws Exception {
+ String out = runSample(() -> AsyncDmlExample.asyncDml(spanner.getDatabaseClient(databaseId)));
+ assertThat(out).contains("4 records inserted.");
+ }
+
+ @Test
+ public void asyncRunner_shouldUpdateRows() throws Exception {
+ String out = runSample(
+ () -> PgAsyncRunnerExample.asyncRunner(spanner.getDatabaseClient(databaseId)));
+ assertThat(out).contains("2 records updated.");
+ }
+
+ @Test
+ public void asyncTransactionManager_shouldUpdateRows() throws Exception {
+ String out = runSample(() -> PgAsyncTransactionManagerExample
+ .asyncTransactionManager(spanner.getDatabaseClient(databaseId)));
+ assertThat(out).contains("2 records updated.");
+ }
+
+ @Test
+ public void asyncReadRow_shouldPrintRow() throws Exception {
+ String out = runSample(
+ () -> AsyncReadRowExample.asyncReadRow(spanner.getDatabaseClient(databaseId)));
+ assertThat(out).contains("1 1 Total Junk");
+ assertThat(out).doesNotContain("1 2 Go, Go, Go");
+ assertThat(out).doesNotContain("2 1 Green");
+ assertThat(out).doesNotContain("2 2 Forever Hold Your Peace");
+ assertThat(out).doesNotContain("2 3 Terrified");
+ }
+}
diff --git a/samples/snippets/src/test/java/com/example/spanner/PgSpannerSampleIT.java b/samples/snippets/src/test/java/com/example/spanner/PgSpannerSampleIT.java
new file mode 100644
index 00000000000..796df092c80
--- /dev/null
+++ b/samples/snippets/src/test/java/com/example/spanner/PgSpannerSampleIT.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright 2022 Google Inc.
+ *
+ * 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.example.spanner;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.cloud.Timestamp;
+import com.google.cloud.spanner.Database;
+import com.google.cloud.spanner.DatabaseAdminClient;
+import com.google.cloud.spanner.DatabaseId;
+import com.google.cloud.spanner.Spanner;
+import com.google.cloud.spanner.SpannerOptions;
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Pattern;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Unit tests for {@code PgSpannerSample}
+ */
+@RunWith(JUnit4.class)
+@SuppressWarnings("checkstyle:abbreviationaswordinname")
+public class PgSpannerSampleIT {
+ private static final int DBID_LENGTH = 20;
+ // The instance needs to exist for tests to pass.
+ private static final String instanceId = System.getProperty("spanner.test.instance");
+ private static final String baseDbId = System.getProperty("spanner.sample.database");
+ private static final String databaseId = formatForTest(baseDbId);
+ private static final String encryptedDatabaseId = formatForTest(baseDbId);
+ private static final String encryptedBackupId = formatForTest(baseDbId);
+ private static final String encryptedRestoreId = formatForTest(baseDbId);
+ static Spanner spanner;
+ static DatabaseId dbId;
+ static DatabaseAdminClient dbClient;
+
+ @BeforeClass
+ public static void setUp() {
+ SpannerOptions options =
+ SpannerOptions.newBuilder().setAutoThrottleAdministrativeRequests().build();
+ spanner = options.getService();
+ dbClient = spanner.getDatabaseAdminClient();
+ dbId = DatabaseId.of(options.getProjectId(), instanceId, databaseId);
+ // Delete stale test databases that have been created earlier by this test, but not deleted.
+ deleteStaleTestDatabases();
+ }
+
+ static void deleteStaleTestDatabases() {
+ Timestamp now = Timestamp.now();
+ Pattern samplePattern = getTestDbIdPattern(PgSpannerSampleIT.baseDbId);
+ Pattern restoredPattern = getTestDbIdPattern("restored");
+ for (Database db : dbClient.listDatabases(PgSpannerSampleIT.instanceId).iterateAll()) {
+ if (TimeUnit.HOURS.convert(now.getSeconds() - db.getCreateTime().getSeconds(),
+ TimeUnit.SECONDS) > 24) {
+ if (db.getId().getDatabase().length() >= DBID_LENGTH) {
+ if (samplePattern.matcher(toComparableId(PgSpannerSampleIT.baseDbId,
+ db.getId().getDatabase())).matches()) {
+ db.drop();
+ }
+ if (restoredPattern.matcher(toComparableId("restored", db.getId().getDatabase()))
+ .matches()) {
+ db.drop();
+ }
+ }
+ }
+ }
+ }
+
+ @AfterClass
+ public static void tearDown() {
+ dbClient.dropDatabase(dbId.getInstanceId().getInstance(), dbId.getDatabase());
+ dbClient.dropDatabase(
+ dbId.getInstanceId().getInstance(), SpannerSample.createRestoredSampleDbId(dbId));
+ dbClient.dropDatabase(instanceId, encryptedDatabaseId);
+ dbClient.dropDatabase(instanceId, encryptedRestoreId);
+ dbClient.deleteBackup(instanceId, encryptedBackupId);
+ spanner.close();
+ }
+
+ private static String toComparableId(String baseId, String existingId) {
+ String zeroUuid = "00000000-0000-0000-0000-0000-00000000";
+ int shouldBeLength = (baseId + "-" + zeroUuid).length();
+ int missingLength = shouldBeLength - existingId.length();
+ return existingId + zeroUuid.substring(zeroUuid.length() - missingLength);
+ }
+
+ private static Pattern getTestDbIdPattern(String baseDbId) {
+ return Pattern.compile(
+ baseDbId + "-[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{8}",
+ Pattern.CASE_INSENSITIVE);
+ }
+
+ static String formatForTest(String name) {
+ return name + "-" + UUID.randomUUID().toString().substring(0, DBID_LENGTH);
+ }
+
+ private String runSample(String command) {
+ final PrintStream stdOut = System.out;
+ final ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ final PrintStream out = new PrintStream(bout);
+ System.setOut(out);
+ System.out.println(instanceId + ":" + databaseId);
+ PgSpannerSample.main(new String[]{command, instanceId, databaseId});
+ System.setOut(stdOut);
+ return bout.toString();
+ }
+
+ @Test
+ public void testSample() throws Exception {
+ assertThat(instanceId).isNotNull();
+ assertThat(databaseId).isNotNull();
+
+ System.out.println("Create Database ...");
+ String out = runSample("createpgdatabase");
+ assertThat(out).contains("Created database");
+ assertThat(out).contains(dbId.getName());
+
+ System.out.println("Create sample tables Singers and Albums ...");
+ runSample("createtableusingddl");
+
+ System.out.println("Write data to sample tables ...");
+ runSample("write");
+
+ System.out.println("Read data from sample tables ...");
+ out = runSample("read");
+ assertThat(out).contains("1 1 Total Junk");
+
+ System.out.println("Write data using DML to sample table ...");
+ runSample("writeusingdml");
+ System.out.println("Query Singers table ...");
+ out = runSample("querysingerstable");
+ assertThat(out).contains("Melissa Garcia");
+ out = runSample("query");
+ assertThat(out).contains("1 1 Total Junk");
+ out = runSample("querywithparameter");
+ assertThat(out).contains("12 Melissa Garcia");
+
+ System.out.println("Add column marketing budget ...");
+ runSample("addmarketingbudget");
+
+ // wait for 15 seconds to elapse and then run an update, and query for stale data
+ long lastUpdateDataTimeInMillis = System.currentTimeMillis();
+ while (System.currentTimeMillis() < lastUpdateDataTimeInMillis + 16000) {
+ Thread.sleep(1000);
+ }
+ System.out.println("Write data to marketing budget ...");
+ runSample("update");
+
+ System.out.println("Query marketing budget ...");
+ out = runSample("querymarketingbudget");
+ assertThat(out).contains("1 1 100000");
+ assertThat(out).contains("2 2 500000");
+
+ System.out.println("Write with transaction using dml...");
+ runSample("writewithtransactionusingdml");
+ out = runSample("querymarketingbudget");
+ assertThat(out).contains("1 1 300000");
+ assertThat(out).contains("1 1 300000");
+
+ System.out.println("Add index ...");
+ runSample("addindex");
+
+ System.out.println("Read index ...");
+ out = runSample("readindex");
+ assertThat(out).contains("Go, Go, Go");
+ assertThat(out).contains("Forever Hold Your Peace");
+ assertThat(out).contains("Green");
+
+ System.out.println("Add Storing index ...");
+ runSample("addstoringindex");
+
+ System.out.println("Read storing index ...");
+ out = runSample("readstoringindex");
+ assertThat(out).contains("300000");
+
+ System.out.println("Read only transaction ...");
+ out = runSample("readonlytransaction");
+ assertThat(out.replaceAll("[\r\n]+", " "))
+ .containsMatch("(Total Junk.*){2}");
+
+ System.out.println("Add Timestamp column ...");
+ out = runSample("addlastupdatetimestampcolumn");
+ assertThat(out).contains("Added LastUpdateTime as a timestamp column");
+
+ System.out.println("Update values in Timestamp column ...");
+ runSample("updatewithtimestamp");
+ out = runSample("querywithtimestamp");
+ assertThat(out).contains("1 1 1000000");
+ assertThat(out).contains("2 2 750000");
+
+ System.out.println("Create table with Timestamp column ...");
+ out = runSample("createtablewithtimestamp");
+ assertThat(out).contains("Created Performances table in database");
+
+ System.out.println("Write with Timestamp ...");
+ runSample("writewithtimestamp");
+ out = runSample("queryperformancestable");
+ assertThat(out).contains("1 4 11000");
+ assertThat(out).contains("1 19 15000");
+ assertThat(out).contains("2 42 7000");
+
+ System.out.println("Write using DML ...");
+ runSample("insertusingdml");
+ out = runSample("querysingerstable");
+ assertThat(out).contains("Virginia Watson");
+
+ System.out.println("Update using DML ...");
+ runSample("updateusingdml");
+ out = runSample("querymarketingbudget");
+ assertThat(out).contains("1 1 2000000");
+
+ System.out.println("Delete using DML ...");
+ runSample("deleteusingdml");
+ out = runSample("querysingerstable");
+ assertThat(out).doesNotContain("Alice Trentor");
+
+ System.out.println("Write and Read using DML ...");
+ out = runSample("writeandreadusingdml");
+ assertThat(out).contains("Timothy Campbell");
+
+ System.out.println("Update using partitioned DML ...");
+ runSample("updateusingpartitioneddml");
+ out = runSample("querymarketingbudget");
+ assertThat(out).contains("2 2 100000");
+ assertThat(out).contains("1 1 2000000");
+
+ System.out.println("Delete using Partitioned DML ...");
+ runSample("deleteusingpartitioneddml");
+ out = runSample("querysingerstable");
+ assertThat(out).doesNotContain("Timothy Grant");
+ assertThat(out).doesNotContain("Melissa Garcia");
+ assertThat(out).doesNotContain("Russell Morales");
+ assertThat(out).doesNotContain("Jacqueline Long");
+ assertThat(out).doesNotContain("Dylan Shaw");
+
+ System.out.println("Update in Batch using DML ...");
+ out = runSample("updateusingbatchdml");
+ assertThat(out).contains("1 record updated by stmt 0");
+ assertThat(out).contains("1 record updated by stmt 1");
+
+ System.out.println("Create table with data types ...");
+ out = runSample("createtablewithdatatypes");
+ assertThat(out).contains("Created Venues table in database");
+
+ System.out.println("Write into table and Query Boolean Type ...");
+ runSample("writedatatypesdata");
+ out = runSample("querywithbool");
+ assertThat(out).contains("19 Venue 19 true");
+
+ System.out.println("Query with Bytes ...");
+ out = runSample("querywithbytes");
+ assertThat(out).contains("4 Venue 4");
+
+ System.out.println("Query with Float ...");
+ out = runSample("querywithfloat");
+ assertThat(out).contains("4 Venue 4 0.8");
+ assertThat(out).contains("19 Venue 19 0.9");
+
+ System.out.println("Query with Int ...");
+ out = runSample("querywithint");
+ assertThat(out).contains("19 Venue 19 6300");
+ assertThat(out).contains("42 Venue 42 3000");
+
+ System.out.println("Query with String ...");
+ out = runSample("querywithstring");
+ assertThat(out).contains("42 Venue 42");
+
+ System.out.println("Query with Timestamp parameter ...");
+ out = runSample("querywithtimestampparameter");
+ assertThat(out).contains("4 Venue 4");
+ assertThat(out).contains("19 Venue 19");
+ assertThat(out).contains("42 Venue 42");
+
+ System.out.println("Query with Numeric Type ...");
+ out = runSample("querywithnumeric");
+ assertThat(out).contains("19 Venue 19 1200100");
+ assertThat(out).contains("42 Venue 42 390650.99");
+
+ System.out.println("Query options ...");
+ out = runSample("clientwithqueryoptions");
+ assertThat(out).contains("1 1 Total Junk");
+ out = runSample("querywithqueryoptions");
+ assertThat(out).contains("1 1 Total Junk");
+ }
+}
diff --git a/samples/snippets/src/test/java/com/example/spanner/PgSpannerStandaloneExamplesIT.java b/samples/snippets/src/test/java/com/example/spanner/PgSpannerStandaloneExamplesIT.java
new file mode 100644
index 00000000000..035b7cdc36b
--- /dev/null
+++ b/samples/snippets/src/test/java/com/example/spanner/PgSpannerStandaloneExamplesIT.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright 2022 Google Inc.
+ *
+ * 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.example.spanner;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.api.gax.longrunning.OperationFuture;
+import com.google.cloud.spanner.DatabaseAdminClient;
+import com.google.cloud.spanner.DatabaseClient;
+import com.google.cloud.spanner.DatabaseId;
+import com.google.cloud.spanner.Dialect;
+import com.google.cloud.spanner.Instance;
+import com.google.cloud.spanner.KeySet;
+import com.google.cloud.spanner.Mutation;
+import com.google.cloud.spanner.Spanner;
+import com.google.cloud.spanner.SpannerOptions;
+import com.google.common.collect.ImmutableList;
+import com.google.spanner.admin.database.v1.UpdateDatabaseDdlMetadata;
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.math.BigDecimal;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.concurrent.ExecutionException;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Integration tests for Cloud Spanner cloud client examples. */
+@RunWith(JUnit4.class)
+@SuppressWarnings("checkstyle:AbbreviationAsWordInName")
+public class PgSpannerStandaloneExamplesIT {
+ // The instance needs to exist for tests to pass.
+ private static String instanceId = System.getProperty("spanner.test.instance");
+ private static String baseDatabaseId = System.getProperty("spanner.sample.database", "mysample");
+ private static String databaseId = SpannerSampleIT.formatForTest(baseDatabaseId);
+ private static DatabaseId dbId;
+ private static DatabaseAdminClient dbClient;
+ private static Spanner spanner;
+
+ private String runExample(Runnable example) {
+ PrintStream stdOut = System.out;
+ ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ PrintStream out = new PrintStream(bout);
+ System.setOut(out);
+ example.run();
+ System.setOut(stdOut);
+ return bout.toString();
+ }
+
+ @BeforeClass
+ public static void createTestDatabase() throws Exception {
+ SpannerOptions options =
+ SpannerOptions.newBuilder().setAutoThrottleAdministrativeRequests().build();
+ spanner = options.getService();
+ dbClient = spanner.getDatabaseAdminClient();
+ if (instanceId == null) {
+ Iterator iterator =
+ spanner.getInstanceAdminClient().listInstances().iterateAll().iterator();
+ if (iterator.hasNext()) {
+ instanceId = iterator.next().getId().getInstance();
+ }
+ }
+ dbId = DatabaseId.of(options.getProjectId(), instanceId, databaseId);
+ dbClient
+ .createDatabase(
+ dbClient.newDatabaseBuilder(dbId).setDialect(Dialect.POSTGRESQL).build(),
+ Collections.emptyList())
+ .get();
+ dbClient.updateDatabaseDdl(
+ instanceId,
+ databaseId,
+ Arrays.asList(
+ "CREATE TABLE Singers ("
+ + " SingerId bigint NOT NULL,"
+ + " FirstName character varying(1024),"
+ + " LastName character varying(1024),"
+ + " SingerInfo bytea,"
+ + " PRIMARY KEY (SingerId)"
+ + ")",
+ "CREATE TABLE Venues ("
+ + "VenueId bigint NOT NULL,"
+ + "Revenue NUMERIC,"
+ + "PRIMARY KEY (VenueId))"),
+ null).get();
+ }
+
+ @AfterClass
+ public static void dropTestDatabase() throws Exception {
+ dbClient.dropDatabase(dbId.getInstanceId().getInstance(), dbId.getDatabase());
+ spanner.close();
+ }
+
+ @Before
+ public void deleteTestData() {
+ String projectId = spanner.getOptions().getProjectId();
+ DatabaseClient client =
+ spanner.getDatabaseClient(DatabaseId.of(projectId, instanceId, databaseId));
+ client.write(Collections.singleton(Mutation.delete("Singers", KeySet.all())));
+ client.write(Collections.singleton(Mutation.delete("Venues", KeySet.all())));
+ }
+
+ @Test
+ public void executeSqlWithCustomTimeoutAndRetrySettings_shouldWriteData() {
+ String projectId = spanner.getOptions().getProjectId();
+ String out =
+ runExample(
+ () ->
+ CustomTimeoutAndRetrySettingsExample.executeSqlWithCustomTimeoutAndRetrySettings(
+ projectId, instanceId, databaseId));
+ assertThat(out).contains("1 record inserted.");
+ }
+
+ @Test
+ public void executeSqlWithTimeout_shouldWriteData() {
+ String projectId = spanner.getOptions().getProjectId();
+ DatabaseClient client =
+ spanner.getDatabaseClient(DatabaseId.of(projectId, instanceId, databaseId));
+ String out = runExample(() -> StatementTimeoutExample.executeSqlWithTimeout(client));
+ assertThat(out).contains("1 record inserted.");
+ }
+
+ @Test
+ public void addNumericColumn_shouldSuccessfullyAddColumn()
+ throws InterruptedException, ExecutionException {
+ OperationFuture operation =
+ spanner
+ .getDatabaseAdminClient()
+ .updateDatabaseDdl(
+ instanceId,
+ databaseId,
+ ImmutableList.of("ALTER TABLE Venues DROP COLUMN Revenue"),
+ null);
+ operation.get();
+ String out =
+ runExample(
+ () -> {
+ try {
+ AddNumericColumnSample.addNumericColumn(
+ spanner.getDatabaseAdminClient(), instanceId, databaseId);
+ } catch (ExecutionException e) {
+ System.out.printf(
+ "Adding column `Revenue` failed: %s%n", e.getCause().getMessage());
+ } catch (InterruptedException e) {
+ System.out.printf("Adding column `Revenue` was interrupted%n");
+ }
+ });
+ assertThat(out).contains("Successfully added column `Revenue`");
+ }
+
+ @Test
+ public void updateNumericData_shouldWriteData() {
+ String projectId = spanner.getOptions().getProjectId();
+ String out =
+ runExample(
+ () ->
+ UpdateNumericDataSample.updateNumericData(
+ spanner.getDatabaseClient(DatabaseId.of(projectId, instanceId, databaseId))));
+ assertThat(out).contains("Venues successfully updated");
+ }
+
+ @Test
+ public void queryWithNumericParameter_shouldReturnResults() {
+ String projectId = spanner.getOptions().getProjectId();
+ DatabaseClient client =
+ spanner.getDatabaseClient(DatabaseId.of(projectId, instanceId, databaseId));
+ client.write(
+ ImmutableList.of(
+ Mutation.newInsertOrUpdateBuilder("Venues")
+ .set("VenueId")
+ .to(4L)
+ .set("Revenue")
+ .to(new BigDecimal("35000"))
+ .build(),
+ Mutation.newInsertOrUpdateBuilder("Venues")
+ .set("VenueId")
+ .to(19L)
+ .set("Revenue")
+ .to(new BigDecimal("104500"))
+ .build(),
+ Mutation.newInsertOrUpdateBuilder("Venues")
+ .set("VenueId")
+ .to(42L)
+ .set("Revenue")
+ .to(new BigDecimal("99999999999999999999999999999.99"))
+ .build()));
+ String out =
+ runExample(() -> PgQueryWithNumericParameterSample.queryWithNumericParameter(client));
+ assertThat(out).contains("4 35000");
+ }
+}
From 70838bc8c0401fa38b72b46a9303d426ea6578b1 Mon Sep 17 00:00:00 2001
From: WhiteSource Renovate
Date: Fri, 29 Apr 2022 22:20:35 +0200
Subject: [PATCH 20/24] build(deps): update dependency
com.google.cloud:google-cloud-shared-config to v1.4.0 (#1862)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
[](https://renovatebot.com)
This PR contains the following updates:
| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
| [com.google.cloud:google-cloud-shared-config](https://togithub.com/googleapis/java-shared-config) | `1.3.3` -> `1.4.0` | [](https://docs.renovatebot.com/merge-confidence/) | [](https://docs.renovatebot.com/merge-confidence/) | [](https://docs.renovatebot.com/merge-confidence/) | [](https://docs.renovatebot.com/merge-confidence/) |
---
### Release Notes
googleapis/java-shared-config
### [`v1.4.0`](https://togithub.com/googleapis/java-shared-config/blob/HEAD/CHANGELOG.md#140-httpsgithubcomgoogleapisjava-shared-configcomparev133v140-2022-04-28)
[Compare Source](https://togithub.com/googleapis/java-shared-config/compare/v1.3.3...v1.4.0)
##### Features
- **java:** remove native image module ([#471](https://togithub.com/googleapis/java-shared-config/issues/471)) ([7fcba01](https://togithub.com/googleapis/java-shared-config/commit/7fcba016b3138d7beaa4e962853f9bc80f59438c))
##### [1.3.3](https://togithub.com/googleapis/java-shared-config/compare/v1.3.2...v1.3.3) (2022-04-19)
##### Bug Fixes
- **java:** remove protobuf feature from native profile ([#461](https://togithub.com/googleapis/java-shared-config/issues/461)) ([ffd07cb](https://togithub.com/googleapis/java-shared-config/commit/ffd07cb18ee7d45d4daee1d9ea6f6d321fdca874))
##### Dependencies
- update dependency com.google.cloud:native-image-support to v0.12.11 ([#459](https://togithub.com/googleapis/java-shared-config/issues/459)) ([d20008d](https://togithub.com/googleapis/java-shared-config/commit/d20008df15209708fdf9d06828b567778190f4d0))
- update dependency com.google.cloud:native-image-support to v0.13.1 ([#465](https://togithub.com/googleapis/java-shared-config/issues/465)) ([b202064](https://togithub.com/googleapis/java-shared-config/commit/b2020648816feb4740ad70acedfed470d7da5bcf))
##### [1.3.2](https://togithub.com/googleapis/java-shared-config/compare/v1.3.1...v1.3.2) (2022-03-28)
##### Dependencies
- revert google-java-format to 1.7 ([#453](https://togithub.com/googleapis/java-shared-config/issues/453)) ([cbc777f](https://togithub.com/googleapis/java-shared-config/commit/cbc777f3e9ab75edb6fa2e0268a7446ae4111725))
##### [1.3.1](https://togithub.com/googleapis/java-shared-config/compare/v1.3.0...v1.3.1) (2022-03-25)
##### Dependencies
- update dependency com.google.cloud:native-image-support to v0.12.10 ([#443](https://togithub.com/googleapis/java-shared-config/issues/443)) ([5b39d5e](https://togithub.com/googleapis/java-shared-config/commit/5b39d5ee15121f052226ff873b6ab101e9c71de5))
- update dependency com.google.googlejavaformat:google-java-format to v1.15.0 ([#426](https://togithub.com/googleapis/java-shared-config/issues/426)) ([4c3c4b6](https://togithub.com/googleapis/java-shared-config/commit/4c3c4b66129632181e6bc363a0ecccf4f5aac914))
- update dependency org.graalvm.buildtools:junit-platform-native to v0.9.11 ([#448](https://togithub.com/googleapis/java-shared-config/issues/448)) ([f7f518e](https://togithub.com/googleapis/java-shared-config/commit/f7f518e87d1d9feb9ac54d7c099f97d8751ee3da))
- update dependency org.graalvm.buildtools:native-maven-plugin to v0.9.11 ([#449](https://togithub.com/googleapis/java-shared-config/issues/449)) ([3e1c0b5](https://togithub.com/googleapis/java-shared-config/commit/3e1c0b5a1d2f4a0db88c06a0d683ed90cbc745e2))
---
### Configuration
π
**Schedule**: At any time (no schedule defined).
π¦ **Automerge**: Disabled by config. Please merge this manually once you are satisfied.
β» **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.
π **Ignore**: Close this PR and you won't be reminded about this update again.
---
- [ ] If you want to rebase/retry this PR, click this checkbox.
---
This PR has been generated by [WhiteSource Renovate](https://renovate.whitesourcesoftware.com). View repository job log [here](https://app.renovatebot.com/dashboard#github/googleapis/java-spanner).
---
google-cloud-spanner-bom/pom.xml | 2 +-
pom.xml | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/google-cloud-spanner-bom/pom.xml b/google-cloud-spanner-bom/pom.xml
index d39ec3f6277..2ffb0bca97c 100644
--- a/google-cloud-spanner-bom/pom.xml
+++ b/google-cloud-spanner-bom/pom.xml
@@ -8,7 +8,7 @@
com.google.cloud
google-cloud-shared-config
- 1.3.3
+ 1.4.0
Google Cloud Spanner BOM
diff --git a/pom.xml b/pom.xml
index ed2c8d9c9e0..7ca0dd04fe9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -14,7 +14,7 @@
com.google.cloud
google-cloud-shared-config
- 1.3.3
+ 1.4.0
From 4f826c592082a8da23cb917a9352ae7b648ba6fe Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?=
Date: Wed, 4 May 2022 11:32:22 +0200
Subject: [PATCH 21/24] test: support PostgreSQL dialect for
RandomResultsGenerator (#1868)
Adds support for PostgreSQL dialect to RandomResultsGenerator
---
.../connection/RandomResultSetGenerator.java | 155 +++++++++++-------
1 file changed, 96 insertions(+), 59 deletions(-)
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/RandomResultSetGenerator.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/RandomResultSetGenerator.java
index a2870461cd0..024032ab765 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/RandomResultSetGenerator.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/RandomResultSetGenerator.java
@@ -16,9 +16,10 @@
package com.google.cloud.spanner.connection;
-import com.google.api.client.util.Base64;
import com.google.cloud.Date;
import com.google.cloud.Timestamp;
+import com.google.cloud.spanner.Dialect;
+import com.google.common.io.BaseEncoding;
import com.google.protobuf.ListValue;
import com.google.protobuf.NullValue;
import com.google.protobuf.Value;
@@ -28,6 +29,7 @@
import com.google.spanner.v1.StructType;
import com.google.spanner.v1.StructType.Field;
import com.google.spanner.v1.Type;
+import com.google.spanner.v1.TypeAnnotationCode;
import com.google.spanner.v1.TypeCode;
import java.math.BigDecimal;
import java.util.Random;
@@ -37,86 +39,109 @@
* of Cloud Spanner filled with random data.
*/
public class RandomResultSetGenerator {
- private static final Type[] TYPES =
- new Type[] {
- Type.newBuilder().setCode(TypeCode.BOOL).build(),
- Type.newBuilder().setCode(TypeCode.INT64).build(),
- Type.newBuilder().setCode(TypeCode.FLOAT64).build(),
- Type.newBuilder().setCode(TypeCode.NUMERIC).build(),
- Type.newBuilder().setCode(TypeCode.STRING).build(),
- Type.newBuilder().setCode(TypeCode.JSON).build(),
- Type.newBuilder().setCode(TypeCode.BYTES).build(),
- Type.newBuilder().setCode(TypeCode.DATE).build(),
- Type.newBuilder().setCode(TypeCode.TIMESTAMP).build(),
- Type.newBuilder()
- .setCode(TypeCode.ARRAY)
- .setArrayElementType(Type.newBuilder().setCode(TypeCode.BOOL))
- .build(),
- Type.newBuilder()
- .setCode(TypeCode.ARRAY)
- .setArrayElementType(Type.newBuilder().setCode(TypeCode.INT64))
- .build(),
- Type.newBuilder()
- .setCode(TypeCode.ARRAY)
- .setArrayElementType(Type.newBuilder().setCode(TypeCode.FLOAT64))
- .build(),
- Type.newBuilder()
- .setCode(TypeCode.ARRAY)
- .setArrayElementType(Type.newBuilder().setCode(TypeCode.NUMERIC))
- .build(),
- Type.newBuilder()
- .setCode(TypeCode.ARRAY)
- .setArrayElementType(Type.newBuilder().setCode(TypeCode.STRING))
- .build(),
- Type.newBuilder()
- .setCode(TypeCode.ARRAY)
- .setArrayElementType(Type.newBuilder().setCode(TypeCode.JSON))
- .build(),
- Type.newBuilder()
- .setCode(TypeCode.ARRAY)
- .setArrayElementType(Type.newBuilder().setCode(TypeCode.BYTES))
- .build(),
- Type.newBuilder()
- .setCode(TypeCode.ARRAY)
- .setArrayElementType(Type.newBuilder().setCode(TypeCode.DATE))
- .build(),
- Type.newBuilder()
- .setCode(TypeCode.ARRAY)
- .setArrayElementType(Type.newBuilder().setCode(TypeCode.TIMESTAMP))
- .build(),
- };
+ private static Type[] generateTypes(Dialect dialect) {
+ return new Type[] {
+ Type.newBuilder().setCode(TypeCode.BOOL).build(),
+ Type.newBuilder().setCode(TypeCode.INT64).build(),
+ Type.newBuilder().setCode(TypeCode.FLOAT64).build(),
+ dialect == Dialect.POSTGRESQL
+ ? Type.newBuilder()
+ .setCode(TypeCode.NUMERIC)
+ .setTypeAnnotation(TypeAnnotationCode.PG_NUMERIC)
+ .build()
+ : Type.newBuilder().setCode(TypeCode.NUMERIC).build(),
+ Type.newBuilder().setCode(TypeCode.STRING).build(),
+ Type.newBuilder()
+ .setCode(dialect == Dialect.POSTGRESQL ? TypeCode.STRING : TypeCode.JSON)
+ .build(),
+ Type.newBuilder().setCode(TypeCode.BYTES).build(),
+ Type.newBuilder().setCode(TypeCode.DATE).build(),
+ Type.newBuilder().setCode(TypeCode.TIMESTAMP).build(),
+ Type.newBuilder()
+ .setCode(TypeCode.ARRAY)
+ .setArrayElementType(Type.newBuilder().setCode(TypeCode.BOOL))
+ .build(),
+ Type.newBuilder()
+ .setCode(TypeCode.ARRAY)
+ .setArrayElementType(Type.newBuilder().setCode(TypeCode.INT64))
+ .build(),
+ Type.newBuilder()
+ .setCode(TypeCode.ARRAY)
+ .setArrayElementType(Type.newBuilder().setCode(TypeCode.FLOAT64))
+ .build(),
+ Type.newBuilder()
+ .setCode(TypeCode.ARRAY)
+ .setArrayElementType(
+ dialect == Dialect.POSTGRESQL
+ ? Type.newBuilder()
+ .setCode(TypeCode.NUMERIC)
+ .setTypeAnnotation(TypeAnnotationCode.PG_NUMERIC)
+ : Type.newBuilder().setCode(TypeCode.NUMERIC))
+ .build(),
+ Type.newBuilder()
+ .setCode(TypeCode.ARRAY)
+ .setArrayElementType(Type.newBuilder().setCode(TypeCode.STRING))
+ .build(),
+ Type.newBuilder()
+ .setCode(TypeCode.ARRAY)
+ .setArrayElementType(
+ Type.newBuilder()
+ .setCode(dialect == Dialect.POSTGRESQL ? TypeCode.STRING : TypeCode.JSON))
+ .build(),
+ Type.newBuilder()
+ .setCode(TypeCode.ARRAY)
+ .setArrayElementType(Type.newBuilder().setCode(TypeCode.BYTES))
+ .build(),
+ Type.newBuilder()
+ .setCode(TypeCode.ARRAY)
+ .setArrayElementType(Type.newBuilder().setCode(TypeCode.DATE))
+ .build(),
+ Type.newBuilder()
+ .setCode(TypeCode.ARRAY)
+ .setArrayElementType(Type.newBuilder().setCode(TypeCode.TIMESTAMP))
+ .build(),
+ };
+ }
- private static ResultSetMetadata generateMetadata() {
+ private static ResultSetMetadata generateMetadata(Type[] types) {
StructType.Builder rowTypeBuilder = StructType.newBuilder();
- for (int col = 0; col < TYPES.length; col++) {
- rowTypeBuilder.addFields(Field.newBuilder().setName("COL" + col).setType(TYPES[col])).build();
+ for (int col = 0; col < types.length; col++) {
+ rowTypeBuilder.addFields(Field.newBuilder().setName("COL" + col).setType(types[col])).build();
}
ResultSetMetadata.Builder builder = ResultSetMetadata.newBuilder();
builder.setRowType(rowTypeBuilder.build());
return builder.build();
}
- private static final ResultSetMetadata METADATA = generateMetadata();
-
+ private final ResultSetMetadata metadata;
+ private final Dialect dialect;
+ private final Type[] types;
private final int rowCount;
private final Random random = new Random();
public RandomResultSetGenerator(int rowCount) {
+ this(rowCount, Dialect.GOOGLE_STANDARD_SQL);
+ }
+
+ public RandomResultSetGenerator(int rowCount, Dialect dialect) {
this.rowCount = rowCount;
+ this.dialect = dialect;
+ this.types = generateTypes(dialect);
+ this.metadata = generateMetadata(types);
}
public ResultSet generate() {
ResultSet.Builder builder = ResultSet.newBuilder();
for (int row = 0; row < rowCount; row++) {
ListValue.Builder rowBuilder = ListValue.newBuilder();
- for (Type type : TYPES) {
+ for (Type type : types) {
Value.Builder valueBuilder = Value.newBuilder();
setRandomValue(valueBuilder, type);
rowBuilder.addValues(valueBuilder.build());
}
builder.addRows(rowBuilder.build());
}
- builder.setMetadata(METADATA);
+ builder.setMetadata(metadata);
return builder.build();
}
@@ -142,7 +167,7 @@ private void setRandomValue(Value.Builder builder, Type type) {
case BYTES:
byte[] bytes = new byte[random.nextInt(200)];
random.nextBytes(bytes);
- builder.setStringValue(Base64.encodeBase64String(bytes));
+ builder.setStringValue(BaseEncoding.base64().encode(bytes));
break;
case JSON:
builder.setStringValue("\"" + random.nextInt(200) + "\":\"" + random.nextInt(200) + "\"");
@@ -154,10 +179,18 @@ private void setRandomValue(Value.Builder builder, Type type) {
builder.setStringValue(date.toString());
break;
case FLOAT64:
- builder.setNumberValue(random.nextDouble());
+ if (randomNaN()) {
+ builder.setNumberValue(Double.NaN);
+ } else {
+ builder.setNumberValue(random.nextDouble());
+ }
break;
case NUMERIC:
- builder.setStringValue(BigDecimal.valueOf(random.nextDouble()).toString());
+ if (dialect == Dialect.POSTGRESQL && randomNaN()) {
+ builder.setStringValue("NaN");
+ } else {
+ builder.setStringValue(BigDecimal.valueOf(random.nextDouble()).toString());
+ }
break;
case INT64:
builder.setStringValue(String.valueOf(random.nextLong()));
@@ -184,4 +217,8 @@ private void setRandomValue(Value.Builder builder, Type type) {
private boolean randomNull() {
return random.nextInt(10) == 0;
}
+
+ private boolean randomNaN() {
+ return random.nextInt(10) == 0;
+ }
}
From f74c77d3786d37173914dbddbaca12ba1209adc5 Mon Sep 17 00:00:00 2001
From: WhiteSource Renovate
Date: Thu, 5 May 2022 02:02:11 +0200
Subject: [PATCH 22/24] build(deps): update dependency
org.apache.maven.plugins:maven-project-info-reports-plugin to v3.3.0 (#1859)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
[](https://renovatebot.com)
This PR contains the following updates:
| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
| [org.apache.maven.plugins:maven-project-info-reports-plugin](https://maven.apache.org/plugins/) | `3.2.2` -> `3.3.0` | [](https://docs.renovatebot.com/merge-confidence/) | [](https://docs.renovatebot.com/merge-confidence/) | [](https://docs.renovatebot.com/merge-confidence/) | [](https://docs.renovatebot.com/merge-confidence/) |
---
### Configuration
π
**Schedule**: At any time (no schedule defined).
π¦ **Automerge**: Disabled by config. Please merge this manually once you are satisfied.
β» **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.
π **Ignore**: Close this PR and you won't be reminded about this update again.
---
- [ ] If you want to rebase/retry this PR, click this checkbox.
---
This PR has been generated by [WhiteSource Renovate](https://renovate.whitesourcesoftware.com). View repository job log [here](https://app.renovatebot.com/dashboard#github/googleapis/java-spanner).
---
pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pom.xml b/pom.xml
index 7ca0dd04fe9..6f81a5acc31 100644
--- a/pom.xml
+++ b/pom.xml
@@ -159,7 +159,7 @@
org.apache.maven.plugins
maven-project-info-reports-plugin
- 3.2.2
+ 3.3.0
From f1d2d3ef1dbd30c153616c2efcc362c1330705e1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?=
Date: Thu, 5 May 2022 06:31:50 +0200
Subject: [PATCH 23/24] feat: support CredentialsProvider in Connection API
(#1869)
* feat: support CredentialsProvider in Connection API
Adds suppport for setting a CredentialsProvider instead of a
credentialsUrl in a connection string. The CredentialsProvider reference
must be a class name to a public class with a public no-arg constructor.
This option is available in the Connection API, which means that any
client that uses that API can directly benefit from it (this effectively
means the JDBC driver).
Fixes b/231174409
---
.../spanner/connection/ConnectionImpl.java | 5 +
.../spanner/connection/ConnectionOptions.java | 97 +++++++++--
.../cloud/spanner/connection/SpannerPool.java | 28 +--
.../connection/AbstractMockServerTest.java | 2 +-
.../connection/ConnectionOptionsTest.java | 162 +++++++++++-------
.../connection/CredentialsProviderTest.java | 126 ++++++++++++++
6 files changed, 332 insertions(+), 88 deletions(-)
create mode 100644 google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/CredentialsProviderTest.java
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionImpl.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionImpl.java
index 1a7d9b76682..6770cf42d90 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionImpl.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionImpl.java
@@ -259,6 +259,11 @@ static UnitOfWorkType of(TransactionMode transactionMode) {
setDefaultTransactionOptions();
}
+ @VisibleForTesting
+ Spanner getSpanner() {
+ return this.spanner;
+ }
+
private DdlClient createDdlClient() {
return DdlClient.newBuilder()
.setDatabaseAdminClient(spanner.getDatabaseAdminClient())
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionOptions.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionOptions.java
index 0dba8931573..7923156da1c 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionOptions.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionOptions.java
@@ -17,6 +17,7 @@
package com.google.cloud.spanner.connection;
import com.google.api.core.InternalApi;
+import com.google.api.gax.core.CredentialsProvider;
import com.google.api.gax.rpc.TransportChannelProvider;
import com.google.auth.Credentials;
import com.google.auth.oauth2.AccessToken;
@@ -36,15 +37,20 @@
import com.google.common.base.Preconditions;
import com.google.common.collect.Sets;
import com.google.spanner.v1.ExecuteSqlRequest.QueryOptions;
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import java.util.stream.Stream;
import javax.annotation.Nullable;
/**
@@ -182,6 +188,8 @@ public String[] getValidValues() {
public static final String CREDENTIALS_PROPERTY_NAME = "credentials";
/** Name of the 'encodedCredentials' connection property. */
public static final String ENCODED_CREDENTIALS_PROPERTY_NAME = "encodedCredentials";
+ /** Name of the 'credentialsProvider' connection property. */
+ public static final String CREDENTIALS_PROVIDER_PROPERTY_NAME = "credentialsProvider";
/**
* OAuth token to use for authentication. Cannot be used in combination with a credentials file.
*/
@@ -231,6 +239,9 @@ public String[] getValidValues() {
ConnectionProperty.createStringProperty(
ENCODED_CREDENTIALS_PROPERTY_NAME,
"Base64-encoded credentials to use for this connection. If neither this property or a credentials location are set, the connection will use the default Google Cloud credentials for the runtime environment."),
+ ConnectionProperty.createStringProperty(
+ CREDENTIALS_PROVIDER_PROPERTY_NAME,
+ "The class name of the com.google.api.gax.core.CredentialsProvider implementation that should be used to obtain credentials for connections."),
ConnectionProperty.createStringProperty(
OAUTH_TOKEN_PROPERTY_NAME,
"A valid pre-existing OAuth token to use for authentication for this connection. Setting this property will take precedence over any value set for a credentials file."),
@@ -386,6 +397,12 @@ private boolean isValidUri(String uri) {
* encodedCredentials (String): A Base64 encoded string containing the Google credentials
* to use. You should only set either this property or the `credentials` (file location)
* property, but not both at the same time.
+ * credentialsProvider (String): Class name of the {@link
+ * com.google.api.gax.core.CredentialsProvider} that should be used to get credentials for
+ * a connection that is created by this {@link ConnectionOptions}. The credentials will be
+ * retrieved from the {@link com.google.api.gax.core.CredentialsProvider} when a new
+ * connection is created. A connection will use the credentials that were obtained at
+ * creation during its lifetime.
* 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
@@ -501,6 +518,7 @@ public static Builder newBuilder() {
private final String warnings;
private final String credentialsUrl;
private final String encodedCredentials;
+ private final CredentialsProvider credentialsProvider;
private final String oauthToken;
private final Credentials fixedCredentials;
@@ -537,22 +555,22 @@ private ConnectionOptions(Builder builder) {
this.credentialsUrl =
builder.credentialsUrl != null ? builder.credentialsUrl : parseCredentials(builder.uri);
this.encodedCredentials = parseEncodedCredentials(builder.uri);
- // Check that not both a credentials location and encoded credentials have been specified in the
- // connection URI.
- Preconditions.checkArgument(
- this.credentialsUrl == null || this.encodedCredentials == null,
- "Cannot specify both a credentials URL and encoded credentials. Only set one of the properties.");
-
+ this.credentialsProvider = parseCredentialsProvider(builder.uri);
this.oauthToken =
builder.oauthToken != null ? builder.oauthToken : parseOAuthToken(builder.uri);
- this.fixedCredentials = builder.credentials;
- // Check that not both credentials and an OAuth token have been specified.
+ // Check that at most one of credentials location, encoded credentials, credentials provider and
+ // OUAuth token has been specified in the connection URI.
Preconditions.checkArgument(
- (builder.credentials == null
- && this.credentialsUrl == null
- && this.encodedCredentials == null)
- || this.oauthToken == null,
- "Cannot specify both credentials and an OAuth token.");
+ Stream.of(
+ this.credentialsUrl,
+ this.encodedCredentials,
+ this.credentialsProvider,
+ this.oauthToken)
+ .filter(Objects::nonNull)
+ .count()
+ <= 1,
+ "Specify only one of credentialsUrl, encodedCredentials, credentialsProvider and OAuth token");
+ this.fixedCredentials = builder.credentials;
this.userAgent = parseUserAgent(this.uri);
QueryOptions.Builder queryOptionsBuilder = QueryOptions.newBuilder();
@@ -570,14 +588,24 @@ private ConnectionOptions(Builder builder) {
// 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
+ if (this.fixedCredentials == null
&& this.credentialsUrl == null
&& this.encodedCredentials == null
+ && this.credentialsProvider == null
&& this.oauthToken == null
&& this.usePlainText) {
this.credentials = NoCredentials.getInstance();
} else if (this.oauthToken != null) {
this.credentials = new GoogleCredentials(new AccessToken(oauthToken, null));
+ } else if (this.credentialsProvider != null) {
+ try {
+ this.credentials = this.credentialsProvider.getCredentials();
+ } catch (IOException exception) {
+ throw SpannerExceptionFactory.newSpannerException(
+ ErrorCode.INVALID_ARGUMENT,
+ "Failed to get credentials from CredentialsProvider: " + exception.getMessage(),
+ exception);
+ }
} else if (this.fixedCredentials != null) {
this.credentials = fixedCredentials;
} else if (this.encodedCredentials != null) {
@@ -691,18 +719,49 @@ static boolean parseRetryAbortsInternally(String uri) {
}
@VisibleForTesting
- static String parseCredentials(String uri) {
+ static @Nullable String parseCredentials(String uri) {
String value = parseUriProperty(uri, CREDENTIALS_PROPERTY_NAME);
return value != null ? value : DEFAULT_CREDENTIALS;
}
@VisibleForTesting
- static String parseEncodedCredentials(String uri) {
+ static @Nullable String parseEncodedCredentials(String uri) {
return parseUriProperty(uri, ENCODED_CREDENTIALS_PROPERTY_NAME);
}
@VisibleForTesting
- static String parseOAuthToken(String uri) {
+ static @Nullable CredentialsProvider parseCredentialsProvider(String uri) {
+ String name = parseUriProperty(uri, CREDENTIALS_PROVIDER_PROPERTY_NAME);
+ if (name != null) {
+ try {
+ Class extends CredentialsProvider> clazz =
+ (Class extends CredentialsProvider>) Class.forName(name);
+ Constructor extends CredentialsProvider> constructor = clazz.getDeclaredConstructor();
+ return constructor.newInstance();
+ } catch (ClassNotFoundException classNotFoundException) {
+ throw SpannerExceptionFactory.newSpannerException(
+ ErrorCode.INVALID_ARGUMENT,
+ "Unknown or invalid CredentialsProvider class name: " + name,
+ classNotFoundException);
+ } catch (NoSuchMethodException noSuchMethodException) {
+ throw SpannerExceptionFactory.newSpannerException(
+ ErrorCode.INVALID_ARGUMENT,
+ "Credentials provider " + name + " does not have a public no-arg constructor.",
+ noSuchMethodException);
+ } catch (InvocationTargetException
+ | InstantiationException
+ | IllegalAccessException exception) {
+ throw SpannerExceptionFactory.newSpannerException(
+ ErrorCode.INVALID_ARGUMENT,
+ "Failed to create an instance of " + name + ": " + exception.getMessage(),
+ exception);
+ }
+ }
+ return null;
+ }
+
+ @VisibleForTesting
+ static @Nullable String parseOAuthToken(String uri) {
String value = parseUriProperty(uri, OAUTH_TOKEN_PROPERTY_NAME);
return value != null ? value : DEFAULT_OAUTH_TOKEN;
}
@@ -849,6 +908,10 @@ Credentials getFixedCredentials() {
return this.fixedCredentials;
}
+ CredentialsProvider getCredentialsProvider() {
+ return this.credentialsProvider;
+ }
+
/** The {@link SessionPoolOptions} of this {@link ConnectionOptions}. */
public SessionPoolOptions getSessionPoolOptions() {
return sessionPoolOptions;
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SpannerPool.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SpannerPool.java
index 2712143da7a..f94e27d963d 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SpannerPool.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SpannerPool.java
@@ -27,12 +27,10 @@
import com.google.common.base.Function;
import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
-import com.google.common.base.Predicates;
import com.google.common.base.Ticker;
-import com.google.common.collect.Iterables;
import io.grpc.ManagedChannelBuilder;
+import java.io.IOException;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -43,6 +41,7 @@
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
+import java.util.stream.Stream;
import javax.annotation.concurrent.GuardedBy;
/**
@@ -120,15 +119,17 @@ static class CredentialsKey {
static final Object DEFAULT_CREDENTIALS_KEY = new Object();
final Object key;
- static CredentialsKey create(ConnectionOptions options) {
+ static CredentialsKey create(ConnectionOptions options) throws IOException {
return new CredentialsKey(
- Iterables.find(
- Arrays.asList(
+ Stream.of(
options.getOAuthToken(),
+ options.getCredentialsProvider() == null ? null : options.getCredentials(),
options.getFixedCredentials(),
options.getCredentialsUrl(),
- DEFAULT_CREDENTIALS_KEY),
- Predicates.notNull()));
+ DEFAULT_CREDENTIALS_KEY)
+ .filter(Objects::nonNull)
+ .findFirst()
+ .get());
}
private CredentialsKey(Object key) {
@@ -155,10 +156,17 @@ static class SpannerPoolKey {
@VisibleForTesting
static SpannerPoolKey of(ConnectionOptions options) {
- return new SpannerPoolKey(options);
+ try {
+ return new SpannerPoolKey(options);
+ } catch (IOException ioException) {
+ throw SpannerExceptionFactory.newSpannerException(
+ ErrorCode.INVALID_ARGUMENT,
+ "Failed to get credentials: " + ioException.getMessage(),
+ ioException);
+ }
}
- private SpannerPoolKey(ConnectionOptions options) {
+ private SpannerPoolKey(ConnectionOptions options) throws IOException {
this.host = options.getHost();
this.projectId = options.getProjectId();
this.credentialsKey = CredentialsKey.create(options);
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/AbstractMockServerTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/AbstractMockServerTest.java
index 6cf838845b5..7f9163e8e46 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/AbstractMockServerTest.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/AbstractMockServerTest.java
@@ -173,7 +173,7 @@ public static void stopServer() {
try {
SpannerPool.INSTANCE.checkAndCloseSpanners(
CheckAndCloseSpannersMode.ERROR,
- new ForceCloseSpannerFunction(100L, TimeUnit.MILLISECONDS));
+ new ForceCloseSpannerFunction(500L, TimeUnit.MILLISECONDS));
} finally {
Logger.getLogger(AbstractFuture.class.getName()).setUseParentHandlers(futureParentHandlers);
Logger.getLogger(LogExceptionRunnable.class.getName())
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionOptionsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionOptionsTest.java
index 837783ee18b..cb862ca3061 100644
--- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionOptionsTest.java
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ConnectionOptionsTest.java
@@ -20,10 +20,12 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
+import com.google.api.gax.core.CredentialsProvider;
+import com.google.api.gax.core.NoCredentialsProvider;
import com.google.api.gax.grpc.InstantiatingGrpcChannelProvider;
import com.google.api.gax.rpc.TransportChannelProvider;
+import com.google.auth.Credentials;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.auth.oauth2.ServiceAccountCredentials;
import com.google.cloud.NoCredentials;
@@ -33,6 +35,7 @@
import com.google.common.io.BaseEncoding;
import com.google.common.io.Files;
import java.io.File;
+import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.Collections;
@@ -254,22 +257,14 @@ public void testBuilderSetUri() {
}
private void setInvalidUri(ConnectionOptions.Builder builder, String uri) {
- try {
- builder.setUri(uri);
- fail(uri + " should be considered an invalid uri");
- } catch (IllegalArgumentException e) {
- // Expected exception
- }
+ assertThrows(IllegalArgumentException.class, () -> builder.setUri(uri));
}
private void setInvalidProperty(
ConnectionOptions.Builder builder, String uri, String expectedInvalidProperties) {
- try {
- builder.setUri(uri);
- fail("missing expected exception");
- } catch (IllegalArgumentException e) {
- assertThat(e.getMessage()).contains(expectedInvalidProperties);
- }
+ IllegalArgumentException exception =
+ assertThrows(IllegalArgumentException.class, () -> builder.setUri(uri));
+ assertTrue(exception.getMessage(), exception.getMessage().contains(expectedInvalidProperties));
}
@Test
@@ -381,12 +376,14 @@ public void testParseOAuthToken() {
.setUri(
"cloudspanner:/projects/test-project-123/instances/test-instance/databases/test-database"
+ "?OAuthToken=RsT5OjbzRn430zqMLgV3Ia;credentials=/path/to/credentials.json");
- try {
- builder.build();
- fail("missing expected exception");
- } catch (IllegalArgumentException e) {
- assertThat(e.getMessage()).contains("Cannot specify both credentials and an OAuth token");
- }
+ IllegalArgumentException exception =
+ assertThrows(IllegalArgumentException.class, builder::build);
+ assertTrue(
+ exception.getMessage(),
+ exception
+ .getMessage()
+ .contains(
+ "Specify only one of credentialsUrl, encodedCredentials, credentialsProvider and OAuth token"));
// Now try to use only an OAuth token.
builder =
@@ -416,17 +413,22 @@ public void testSetOAuthToken() {
@Test
public void testSetOAuthTokenAndCredentials() {
- try {
- ConnectionOptions.newBuilder()
- .setUri(
- "cloudspanner:/projects/test-project-123/instances/test-instance/databases/test-database")
- .setOAuthToken("RsT5OjbzRn430zqMLgV3Ia")
- .setCredentialsUrl(FILE_TEST_PATH)
- .build();
- fail("missing expected exception");
- } catch (IllegalArgumentException e) {
- assertThat(e.getMessage()).contains("Cannot specify both credentials and an OAuth token");
- }
+ IllegalArgumentException exception =
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ ConnectionOptions.newBuilder()
+ .setUri(
+ "cloudspanner:/projects/test-project-123/instances/test-instance/databases/test-database")
+ .setOAuthToken("RsT5OjbzRn430zqMLgV3Ia")
+ .setCredentialsUrl(FILE_TEST_PATH)
+ .build());
+ assertTrue(
+ exception.getMessage(),
+ exception
+ .getMessage()
+ .contains(
+ "Specify only one of credentialsUrl, encodedCredentials, credentialsProvider and OAuth token"));
}
@Test
@@ -451,16 +453,16 @@ public void testLenient() {
assertThat(options.getWarnings()).contains("bar");
assertThat(options.getWarnings()).doesNotContain("lenient");
- try {
- ConnectionOptions.newBuilder()
- .setUri(
- "cloudspanner:/projects/test-project-123/instances/test-instance/databases/test-database?bar=foo")
- .setCredentialsUrl(FILE_TEST_PATH)
- .build();
- fail("missing expected exception");
- } catch (IllegalArgumentException e) {
- assertThat(e.getMessage()).contains("bar");
- }
+ IllegalArgumentException exception =
+ assertThrows(
+ IllegalArgumentException.class,
+ () ->
+ ConnectionOptions.newBuilder()
+ .setUri(
+ "cloudspanner:/projects/test-project-123/instances/test-instance/databases/test-database?bar=foo")
+ .setCredentialsUrl(FILE_TEST_PATH)
+ .build());
+ assertTrue(exception.getMessage(), exception.getMessage().contains("bar"));
}
@Test
@@ -492,29 +494,31 @@ public void testLocalConnectionError() {
String uri =
"cloudspanner://localhost:1/projects/test-project/instances/test-instance/databases/test-database?usePlainText=true";
ConnectionOptions options = ConnectionOptions.newBuilder().setUri(uri).build();
- try (Connection connection = options.getConnection()) {
- fail("Missing expected exception");
- } catch (SpannerException e) {
- assertEquals(ErrorCode.UNAVAILABLE, e.getErrorCode());
- assertThat(e.getMessage())
- .contains(
- String.format(
- "The connection string '%s' contains host 'localhost:1', but no running", uri));
- }
+ SpannerException exception = assertThrows(SpannerException.class, options::getConnection);
+ assertEquals(ErrorCode.UNAVAILABLE, exception.getErrorCode());
+ assertTrue(
+ exception.getMessage(),
+ exception
+ .getMessage()
+ .contains(
+ String.format(
+ "The connection string '%s' contains host 'localhost:1', but no running",
+ uri)));
}
@Test
public void testInvalidCredentials() {
String uri =
"cloudspanner:/projects/test-project/instances/test-instance/databases/test-database?credentials=/some/non/existing/path";
- try {
- ConnectionOptions.newBuilder().setUri(uri).build();
- fail("Missing expected exception");
- } catch (SpannerException e) {
- assertEquals(ErrorCode.INVALID_ARGUMENT, e.getErrorCode());
- assertThat(e.getMessage())
- .contains("Invalid credentials path specified: /some/non/existing/path");
- }
+ SpannerException exception =
+ assertThrows(
+ SpannerException.class, () -> ConnectionOptions.newBuilder().setUri(uri).build());
+ assertEquals(ErrorCode.INVALID_ARGUMENT, exception.getErrorCode());
+ assertTrue(
+ exception.getMessage(),
+ exception
+ .getMessage()
+ .contains("Invalid credentials path specified: /some/non/existing/path"));
}
@Test
@@ -571,9 +575,47 @@ public void testSetCredentialsAndEncodedCredentials() throws Exception {
assertThrows(
IllegalArgumentException.class,
() -> ConnectionOptions.newBuilder().setUri(uri).build());
- assertThat(e.getMessage())
- .contains(
- "Cannot specify both a credentials URL and encoded credentials. Only set one of the properties.");
+ assertTrue(
+ e.getMessage(),
+ e.getMessage()
+ .contains(
+ "Specify only one of credentialsUrl, encodedCredentials, credentialsProvider and OAuth token"));
+ }
+
+ public static class TestCredentialsProvider implements CredentialsProvider {
+ @Override
+ public Credentials getCredentials() throws IOException {
+ return NoCredentials.getInstance();
+ }
+ }
+
+ @Test
+ public void testValidCredentialsProvider() {
+ String uri =
+ String.format(
+ "cloudspanner:/projects/test-project/instances/test-instance/databases/test-database?credentialsProvider=%s",
+ TestCredentialsProvider.class.getName());
+
+ ConnectionOptions options = ConnectionOptions.newBuilder().setUri(uri).build();
+ assertEquals(NoCredentials.getInstance(), options.getCredentials());
+ }
+
+ @Test
+ public void testSetCredentialsAndCredentialsProvider() {
+ String uri =
+ String.format(
+ "cloudspanner:/projects/test-project/instances/test-instance/databases/test-database?credentials=%s;credentialsProvider=%s",
+ FILE_TEST_PATH, NoCredentialsProvider.class.getName());
+
+ IllegalArgumentException e =
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> ConnectionOptions.newBuilder().setUri(uri).build());
+ assertTrue(
+ e.getMessage(),
+ e.getMessage()
+ .contains(
+ "Specify only one of credentialsUrl, encodedCredentials, credentialsProvider and OAuth token"));
}
@Test
diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/CredentialsProviderTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/CredentialsProviderTest.java
new file mode 100644
index 00000000000..da7751f3889
--- /dev/null
+++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/CredentialsProviderTest.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2022 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.connection;
+
+import static org.junit.Assert.assertEquals;
+
+import com.google.api.gax.core.CredentialsProvider;
+import com.google.auth.Credentials;
+import com.google.auth.oauth2.OAuth2Credentials;
+import io.grpc.ManagedChannelBuilder;
+import java.io.IOException;
+import java.io.ObjectStreamException;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class CredentialsProviderTest extends AbstractMockServerTest {
+ private static final AtomicInteger COUNTER = new AtomicInteger();
+
+ @BeforeClass
+ public static void resetCounter() {
+ COUNTER.set(0);
+ }
+
+ private static final class TestCredentials extends OAuth2Credentials {
+ private final int id;
+
+ private TestCredentials(int id) {
+ this.id = id;
+ }
+
+ private Object readResolve() throws ObjectStreamException {
+ return this;
+ }
+
+ public boolean equals(Object obj) {
+ if (!(obj instanceof TestCredentials)) {
+ return false;
+ }
+ return this.id == ((TestCredentials) obj).id;
+ }
+
+ public int hashCode() {
+ return System.identityHashCode(this.id);
+ }
+ }
+
+ static final class TestCredentialsProvider implements CredentialsProvider {
+ @Override
+ public Credentials getCredentials() throws IOException {
+ return new TestCredentials(COUNTER.incrementAndGet());
+ }
+ }
+
+ @Test
+ public void testCredentialsProvider() {
+ ConnectionOptions options =
+ ConnectionOptions.newBuilder()
+ .setUri(
+ String.format(
+ "cloudspanner://localhost:%d/projects/proj/instances/inst/databases/db?credentialsProvider=%s",
+ getPort(), TestCredentialsProvider.class.getName()))
+ .setConfigurator(
+ spannerOptions ->
+ spannerOptions.setChannelConfigurator(ManagedChannelBuilder::usePlaintext))
+ .build();
+
+ try (Connection connection = options.getConnection()) {
+ assertEquals(
+ TestCredentials.class,
+ ((ConnectionImpl) connection).getSpanner().getOptions().getCredentials().getClass());
+ TestCredentials credentials =
+ (TestCredentials)
+ ((ConnectionImpl) connection).getSpanner().getOptions().getCredentials();
+ assertEquals(1, credentials.id);
+ }
+ // The second connection should get the same credentials from the provider.
+ try (Connection connection = options.getConnection()) {
+ assertEquals(
+ TestCredentials.class,
+ ((ConnectionImpl) connection).getSpanner().getOptions().getCredentials().getClass());
+ TestCredentials credentials =
+ (TestCredentials)
+ ((ConnectionImpl) connection).getSpanner().getOptions().getCredentials();
+ assertEquals(1, credentials.id);
+ }
+
+ // Creating new ConnectionOptions should refresh the credentials.
+ options =
+ ConnectionOptions.newBuilder()
+ .setUri(
+ String.format(
+ "cloudspanner://localhost:%d/projects/proj/instances/inst/databases/db?credentialsProvider=%s",
+ getPort(), TestCredentialsProvider.class.getName()))
+ .setConfigurator(
+ spannerOptions ->
+ spannerOptions.setChannelConfigurator(ManagedChannelBuilder::usePlaintext))
+ .build();
+ try (Connection connection = options.getConnection()) {
+ assertEquals(
+ TestCredentials.class,
+ ((ConnectionImpl) connection).getSpanner().getOptions().getCredentials().getClass());
+ TestCredentials credentials =
+ (TestCredentials)
+ ((ConnectionImpl) connection).getSpanner().getOptions().getCredentials();
+ assertEquals(2, credentials.id);
+ }
+ }
+}
From c0de54d483592e49c54f232bc24a3631fac5d66e Mon Sep 17 00:00:00 2001
From: "release-please[bot]"
<55107282+release-please[bot]@users.noreply.github.com>
Date: Tue, 10 May 2022 11:32:56 +0530
Subject: [PATCH 24/24] chore(main): release 6.24.0 (#1846)
Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com>
---
CHANGELOG.md | 21 +++++++++++++++++++
google-cloud-spanner-bom/pom.xml | 18 ++++++++--------
google-cloud-spanner/pom.xml | 4 ++--
.../pom.xml | 4 ++--
.../pom.xml | 4 ++--
grpc-google-cloud-spanner-v1/pom.xml | 4 ++--
pom.xml | 16 +++++++-------
.../pom.xml | 4 ++--
.../pom.xml | 4 ++--
proto-google-cloud-spanner-v1/pom.xml | 4 ++--
samples/snapshot/pom.xml | 2 +-
versions.txt | 14 ++++++-------
12 files changed, 60 insertions(+), 39 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fd510305ad3..4d3609ba587 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,26 @@
# Changelog
+## [6.24.0](https://github.com/googleapis/java-spanner/compare/v6.23.3...v6.24.0) (2022-05-05)
+
+
+### Features
+
+* Copy backup samples ([#1802](https://github.com/googleapis/java-spanner/issues/1802)) ([787ccad](https://github.com/googleapis/java-spanner/commit/787ccadcba01193d541bfd1b80b055fb5d4c2bb3))
+* support CREATE DATABASE in Connection API ([#1845](https://github.com/googleapis/java-spanner/issues/1845)) ([40110fe](https://github.com/googleapis/java-spanner/commit/40110feb22986c6b5dac6885eae7f0b331aede61))
+* support CredentialsProvider in Connection API ([#1869](https://github.com/googleapis/java-spanner/issues/1869)) ([f1d2d3e](https://github.com/googleapis/java-spanner/commit/f1d2d3ef1dbd30c153616c2efcc362c1330705e1))
+
+
+### Dependencies
+
+* update dependency com.google.cloud:google-cloud-monitoring to v3.2.8 ([#1831](https://github.com/googleapis/java-spanner/issues/1831)) ([088fb50](https://github.com/googleapis/java-spanner/commit/088fb50a673a99e6921503be0f84b8291173240e))
+* update dependency com.google.cloud:google-cloud-monitoring to v3.2.9 ([#1851](https://github.com/googleapis/java-spanner/issues/1851)) ([4d6bb2d](https://github.com/googleapis/java-spanner/commit/4d6bb2dd233fba60d213d36f15aead67dff57dec))
+* update dependency com.google.cloud:google-cloud-trace to v2.1.11 ([#1799](https://github.com/googleapis/java-spanner/issues/1799)) ([049635d](https://github.com/googleapis/java-spanner/commit/049635d4bc3210bd9ce41444f17c8b9d67af969a))
+
+
+### Documentation
+
+* add samples for PostgresSQL ([#1781](https://github.com/googleapis/java-spanner/issues/1781)) ([e832298](https://github.com/googleapis/java-spanner/commit/e8322986f158a86cdbb04332a9c49ead79fb2587))
+
### [6.23.3](https://github.com/googleapis/java-spanner/compare/v6.23.2...v6.23.3) (2022-04-21)
diff --git a/google-cloud-spanner-bom/pom.xml b/google-cloud-spanner-bom/pom.xml
index 2ffb0bca97c..3c4da8c7caa 100644
--- a/google-cloud-spanner-bom/pom.xml
+++ b/google-cloud-spanner-bom/pom.xml
@@ -3,7 +3,7 @@
4.0.0
com.google.cloud
google-cloud-spanner-bom
- 6.23.4-SNAPSHOT
+ 6.24.0
pom
com.google.cloud
@@ -53,43 +53,43 @@
com.google.cloud
google-cloud-spanner
- 6.23.4-SNAPSHOT
+ 6.24.0
com.google.cloud
google-cloud-spanner
test-jar
- 6.23.4-SNAPSHOT
+ 6.24.0
com.google.api.grpc
grpc-google-cloud-spanner-v1
- 6.23.4-SNAPSHOT
+ 6.24.0
com.google.api.grpc
grpc-google-cloud-spanner-admin-instance-v1
- 6.23.4-SNAPSHOT
+ 6.24.0
com.google.api.grpc
grpc-google-cloud-spanner-admin-database-v1
- 6.23.4-SNAPSHOT
+ 6.24.0
com.google.api.grpc
proto-google-cloud-spanner-admin-instance-v1
- 6.23.4-SNAPSHOT
+ 6.24.0
com.google.api.grpc
proto-google-cloud-spanner-v1
- 6.23.4-SNAPSHOT
+ 6.24.0
com.google.api.grpc
proto-google-cloud-spanner-admin-database-v1
- 6.23.4-SNAPSHOT
+ 6.24.0