From 5474d40b6d1679f31adf8b8f6ea2d14b0a53c8d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Knut=20Olav=20L=C3=B8ite?= Date: Fri, 21 Apr 2023 11:47:30 +0200 Subject: [PATCH] test: untyped nulls as statement parameters (#2388) Add tests to verify that untyped null statement parameters work with both GoogleSQL, PostgreSQL and the emulator. Verifies that https://github.com/GoogleCloudPlatform/cloud-spanner-emulator/issues/31 has been fixed. --- .../google/cloud/spanner/it/ITWriteTest.java | 214 ++++++++++++++---- 1 file changed, 167 insertions(+), 47 deletions(-) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITWriteTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITWriteTest.java index 3179093139e..020beb68ef9 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITWriteTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITWriteTest.java @@ -27,6 +27,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.fail; import static org.junit.Assume.assumeFalse; +import static org.junit.Assume.assumeTrue; import com.google.cloud.ByteArray; import com.google.cloud.Date; @@ -51,6 +52,7 @@ import com.google.cloud.spanner.connection.ConnectionOptions; import com.google.cloud.spanner.testing.EmulatorSpannerHelper; import com.google.common.collect.ImmutableList; +import com.google.protobuf.NullValue; import io.grpc.Context; import java.math.BigDecimal; import java.util.ArrayList; @@ -98,8 +100,7 @@ public static List data() { private static DatabaseClient googleStandardSQLClient; private static DatabaseClient postgreSQLClient; - // TODO: Remove when the emulator supports NUMERIC and JSON - private static final String GOOGLE_STANDARD_SQL_SCHEMA_WITH_NUMERIC_AND_JSON = + private static final String GOOGLE_STANDARD_SQL_SCHEMA = "CREATE TABLE T (" + " K STRING(MAX) NOT NULL," + " BoolValue BOOL," @@ -122,13 +123,14 @@ public static List data() { + " NumericArrayValue ARRAY," + ") PRIMARY KEY (K)"; - private static final String POSTGRESQL_SCHEMA_WITH_NUMERIC = + private static final String POSTGRESQL_SCHEMA = "CREATE TABLE T (" + " K VARCHAR PRIMARY KEY," + " BoolValue BOOL," + " Int64Value BIGINT," + " Float64Value DOUBLE PRECISION," + " StringValue VARCHAR," + + " JsonValue JSONB," + " BytesValue BYTEA," + " TimestampValue TIMESTAMPTZ," + " DateValue DATE," @@ -137,31 +139,13 @@ public static List data() { + " Int64ArrayValue BIGINT[]," + " Float64ArrayValue DOUBLE PRECISION[]," + " StringArrayValue VARCHAR[]," + + " JsonArrayValue JSONB[]," + " BytesArrayValue BYTEA[]," + " TimestampArrayValue TIMESTAMPTZ[]," + " DateArrayValue DATE[]," + " NumericArrayValue NUMERIC[]" + ")"; - private static final String GOOGLE_STANDARD_SQL_SCHEMA_WITHOUT_NUMERIC_AND_JSON = - "CREATE TABLE T (" - + " K STRING(MAX) NOT NULL," - + " BoolValue BOOL," - + " Int64Value INT64," - + " Float64Value FLOAT64," - + " StringValue STRING(MAX)," - + " BytesValue BYTES(MAX)," - + " TimestampValue TIMESTAMP OPTIONS (allow_commit_timestamp = true)," - + " DateValue DATE," - + " BoolArrayValue ARRAY," - + " Int64ArrayValue ARRAY," - + " Float64ArrayValue ARRAY," - + " StringArrayValue ARRAY," - + " BytesArrayValue ARRAY," - + " TimestampArrayValue ARRAY," - + " DateArrayValue ARRAY," - + ") PRIMARY KEY (K)"; - /** Sequence used to generate unique keys. */ private static int seq; @@ -170,21 +154,14 @@ public static List data() { @BeforeClass public static void setUpDatabase() throws ExecutionException, InterruptedException, TimeoutException { - if (EmulatorSpannerHelper.isUsingEmulator()) { - Database googleStandardSQLDatabase = - env.getTestHelper() - .createTestDatabase(GOOGLE_STANDARD_SQL_SCHEMA_WITHOUT_NUMERIC_AND_JSON); - - googleStandardSQLClient = env.getTestHelper().getDatabaseClient(googleStandardSQLDatabase); - } else { - Database googleStandardSQLDatabase = - env.getTestHelper().createTestDatabase(GOOGLE_STANDARD_SQL_SCHEMA_WITH_NUMERIC_AND_JSON); + Database googleStandardSQLDatabase = + env.getTestHelper().createTestDatabase(GOOGLE_STANDARD_SQL_SCHEMA); - googleStandardSQLClient = env.getTestHelper().getDatabaseClient(googleStandardSQLDatabase); + googleStandardSQLClient = env.getTestHelper().getDatabaseClient(googleStandardSQLDatabase); + if (!EmulatorSpannerHelper.isUsingEmulator()) { Database postgreSQLDatabase = env.getTestHelper() - .createTestDatabase( - Dialect.POSTGRESQL, Collections.singletonList(POSTGRESQL_SCHEMA_WITH_NUMERIC)); + .createTestDatabase(Dialect.POSTGRESQL, Collections.singletonList(POSTGRESQL_SCHEMA)); postgreSQLClient = env.getTestHelper().getDatabaseClient(postgreSQLDatabase); } } @@ -396,7 +373,6 @@ public void writeStringNull() { @Test public void writeJson() { - assumeFalse("Emulator does not yet support JSON", EmulatorSpannerHelper.isUsingEmulator()); assumeFalse("PostgreSQL does not yet support JSON", dialect.dialect == Dialect.POSTGRESQL); write(baseInsert().set("JsonValue").to(Value.json("{\"rating\":9,\"open\":true}")).build()); Struct row = readLastRow("JsonValue"); @@ -407,7 +383,6 @@ public void writeJson() { @Test public void writeJsonEmpty() { - assumeFalse("Emulator does not yet support JSON", EmulatorSpannerHelper.isUsingEmulator()); assumeFalse("PostgreSQL does not yet support JSON", dialect.dialect == Dialect.POSTGRESQL); write(baseInsert().set("JsonValue").to(Value.json("{}")).build()); Struct row = readLastRow("JsonValue"); @@ -418,7 +393,6 @@ public void writeJsonEmpty() { @Test public void writeJsonNull() { - assumeFalse("Emulator does not yet support JSON", EmulatorSpannerHelper.isUsingEmulator()); assumeFalse("PostgreSQL does not yet support JSON", dialect.dialect == Dialect.POSTGRESQL); write(baseInsert().set("JsonValue").to(Value.json(null)).build()); Struct row = readLastRow("JsonValue"); @@ -588,7 +562,6 @@ public void writeDateNull() { @Test public void writeNumeric() { - assumeFalse("Emulator does not yet support NUMERIC", EmulatorSpannerHelper.isUsingEmulator()); write(baseInsert().set("NumericValue").to("3.141592").build()); Struct row = readLastRow("NumericValue"); assertThat(row.isNull(0)).isFalse(); @@ -601,7 +574,6 @@ public void writeNumeric() { @Test public void writeNumericNull() { - assumeFalse("Emulator does not yet support NUMERIC", EmulatorSpannerHelper.isUsingEmulator()); write(baseInsert().set("NumericValue").to((String) null).build()); Struct row = readLastRow("NumericValue"); assertThat(row.isNull(0)).isTrue(); @@ -751,7 +723,6 @@ public void writeStringArray() { @Test public void writeJsonArrayNull() { assumeFalse("PostgreSQL does not yet support Array", dialect.dialect == Dialect.POSTGRESQL); - assumeFalse("Emulator does not yet support JSON", EmulatorSpannerHelper.isUsingEmulator()); write(baseInsert().set("JsonArrayValue").toJsonArray(null).build()); Struct row = readLastRow("JsonArrayValue"); assertThat(row.isNull(0)).isTrue(); @@ -761,7 +732,6 @@ public void writeJsonArrayNull() { @Test public void writeJsonArrayEmpty() { assumeFalse("PostgreSQL does not yet support Array", dialect.dialect == Dialect.POSTGRESQL); - assumeFalse("Emulator does not yet support JSON", EmulatorSpannerHelper.isUsingEmulator()); write(baseInsert().set("JsonArrayValue").toJsonArray(Collections.emptyList()).build()); Struct row = readLastRow("JsonArrayValue"); assertThat(row.isNull(0)).isFalse(); @@ -772,7 +742,6 @@ public void writeJsonArrayEmpty() { @Test public void writeJsonArray() { assumeFalse("PostgreSQL does not yet support Array", dialect.dialect == Dialect.POSTGRESQL); - assumeFalse("Emulator does not yet support JSON", EmulatorSpannerHelper.isUsingEmulator()); write(baseInsert().set("JsonArrayValue").toJsonArray(Arrays.asList("[]", null, "{}")).build()); Struct row = readLastRow("JsonArrayValue"); assertThat(row.isNull(0)).isFalse(); @@ -783,7 +752,6 @@ public void writeJsonArray() { @Test public void writeJsonArrayNoNulls() { assumeFalse("PostgreSQL does not yet support Array", dialect.dialect == Dialect.POSTGRESQL); - assumeFalse("Emulator does not yet support JSON", EmulatorSpannerHelper.isUsingEmulator()); write( baseInsert() .set("JsonArrayValue") @@ -878,7 +846,6 @@ public void writeDateArray() { @Test public void writeNumericArrayNull() { - assumeFalse("Emulator does not yet support NUMERIC", EmulatorSpannerHelper.isUsingEmulator()); write(baseInsert().set("NumericArrayValue").toNumericArray(null).build()); Struct row = readLastRow("NumericArrayValue"); assertThat(row.isNull(0)).isTrue(); @@ -886,7 +853,6 @@ public void writeNumericArrayNull() { @Test public void writeNumericArrayEmpty() { - assumeFalse("Emulator does not yet support NUMERIC", EmulatorSpannerHelper.isUsingEmulator()); write(baseInsert().set("NumericArrayValue").toNumericArray(ImmutableList.of()).build()); Struct row = readLastRow("NumericArrayValue"); assertThat(row.isNull(0)).isFalse(); @@ -899,7 +865,6 @@ public void writeNumericArrayEmpty() { @Test public void writeNumericArray() { - assumeFalse("Emulator does not yet support NUMERIC", EmulatorSpannerHelper.isUsingEmulator()); write( baseInsert() .set("NumericArrayValue") @@ -918,7 +883,6 @@ public void writeNumericArray() { @Test public void writeNumericArrayNoNulls() { - assumeFalse("Emulator does not yet support NUMERIC", EmulatorSpannerHelper.isUsingEmulator()); write( baseInsert() .set("NumericArrayValue") @@ -1013,4 +977,160 @@ public void deadline() { executor.shutdown(); } } + + @Test + public void testWriteUntypedNullValuesGoogleSQL() { + assumeFalse( + "PostgreSQL uses a different parameter format", dialect.dialect == Dialect.POSTGRESQL); + Value untypedNull = + Value.untyped( + com.google.protobuf.Value.newBuilder().setNullValue(NullValue.NULL_VALUE).build()); + assertEquals( + Long.valueOf(1L), + client + .readWriteTransaction() + .run( + transaction -> + transaction.executeUpdate( + Statement.newBuilder( + "insert into T (" + + "K," + + "BoolValue," + + "Int64Value," + + "Float64Value," + + "StringValue," + + "JsonValue," + + "BytesValue," + + "TimestampValue," + + "DateValue," + + "NumericValue," + + "BoolArrayValue," + + "Int64ArrayValue," + + "Float64ArrayValue," + + "StringArrayValue," + + "JsonArrayValue," + + "BytesArrayValue," + + "TimestampArrayValue," + + "DateArrayValue," + + "NumericArrayValue" + + ") values (@k, @bool, @int64, @float64, @string, @json, @bytes, @timestamp, @date, @numeric, " + + "@boolArray, @int64Array, @float64Array, @stringArray, @jsonArray, @bytesArray, @timestampArray, @dateArray, @numericArray)") + .bind("k") + .to(uniqueString()) + .bind("bool") + .to(untypedNull) + .bind("int64") + .to(untypedNull) + .bind("float64") + .to(untypedNull) + .bind("string") + .to(untypedNull) + .bind("json") + .to(untypedNull) + .bind("bytes") + .to(untypedNull) + .bind("timestamp") + .to(untypedNull) + .bind("date") + .to(untypedNull) + .bind("numeric") + .to(untypedNull) + .bind("boolArray") + .to(untypedNull) + .bind("int64Array") + .to(untypedNull) + .bind("float64Array") + .to(untypedNull) + .bind("stringArray") + .to(untypedNull) + .bind("jsonArray") + .to(untypedNull) + .bind("bytesArray") + .to(untypedNull) + .bind("timestampArray") + .to(untypedNull) + .bind("dateArray") + .to(untypedNull) + .bind("numericArray") + .to(untypedNull) + .build()))); + } + + @Test + public void testWriteUntypedNullValuesPostgreSQL() { + assumeTrue( + "PostgreSQL uses a different parameter format", dialect.dialect == Dialect.POSTGRESQL); + Value untypedNull = + Value.untyped( + com.google.protobuf.Value.newBuilder().setNullValue(NullValue.NULL_VALUE).build()); + assertEquals( + Long.valueOf(1L), + client + .readWriteTransaction() + .run( + transaction -> + transaction.executeUpdate( + Statement.newBuilder( + "insert into T (" + + "K," + + "BoolValue," + + "Int64Value," + + "Float64Value," + + "StringValue," + + "JsonValue," + + "BytesValue," + + "TimestampValue," + + "DateValue," + + "NumericValue," + + "BoolArrayValue," + + "Int64ArrayValue," + + "Float64ArrayValue," + + "StringArrayValue," + + "JsonArrayValue," + + "BytesArrayValue," + + "TimestampArrayValue," + + "DateArrayValue," + + "NumericArrayValue" + + ") values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, " + + "$11, $12, $13, $14, $15, $16, $17, $18, $19)") + .bind("p1") + .to(uniqueString()) + .bind("p2") + .to(untypedNull) + .bind("p3") + .to(untypedNull) + .bind("p4") + .to(untypedNull) + .bind("p5") + .to(untypedNull) + .bind("p6") + .to(untypedNull) + .bind("p7") + .to(untypedNull) + .bind("p8") + .to(untypedNull) + .bind("p9") + .to(untypedNull) + .bind("p10") + .to(untypedNull) + .bind("p11") + .to(untypedNull) + .bind("p12") + .to(untypedNull) + .bind("p13") + .to(untypedNull) + .bind("p14") + .to(untypedNull) + .bind("p15") + .to(untypedNull) + .bind("p16") + .to(untypedNull) + .bind("p17") + .to(untypedNull) + .bind("p18") + .to(untypedNull) + .bind("p19") + .to(untypedNull) + .build()))); + } }