Skip to content

Commit 00cf1f2

Browse files
authored
fix: send UUID values as untyped strings to Spanner (GoogleCloudPlatform#3236)
* fix: send UUID values as untyped strings to Spanner Send UUID query parameter values as untyped string values to Spanner, so these can be used with both varchar and UUID columns. The latter is not yet supported on Spanner, so the tests currently only verify that they can be used with varchar. * fix: move table to test + add comment
1 parent d31eab4 commit 00cf1f2

3 files changed

Lines changed: 51 additions & 4 deletions

File tree

src/main/java/com/google/cloud/spanner/pgadapter/parsers/UuidParser.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import com.google.cloud.spanner.pgadapter.error.SQLState;
2323
import com.google.cloud.spanner.pgadapter.error.Severity;
2424
import com.google.common.collect.ImmutableMap;
25+
import com.google.protobuf.NullValue;
2526
import java.nio.charset.StandardCharsets;
2627
import java.util.UUID;
2728
import javax.annotation.Nonnull;
@@ -30,6 +31,9 @@
3031
/** Translate from wire protocol to UUID. */
3132
@InternalApi
3233
public class UuidParser extends Parser<UUID> {
34+
private static final Value NULL_VALUE =
35+
Value.untyped(
36+
com.google.protobuf.Value.newBuilder().setNullValue(NullValue.NULL_VALUE).build());
3337

3438
UuidParser(Object item) {
3539
this.item = (UUID) item;
@@ -131,6 +135,16 @@ public static byte[] convertToPG(ResultSet resultSet, int position, DataFormat f
131135

132136
@Override
133137
public void bind(ImmutableMap.Builder<String, Value> parametersBuilder, String name) {
134-
parametersBuilder.put(name, Value.uuid(this.item));
138+
// Send UUIDs to Spanner as untyped string values, so these can be used with both varchar and
139+
// UUID columns. This ensures backwards compatibility, as PGAdapter would send UUID values as
140+
// strings to Spanner before UUID type support was added to Spanner.
141+
parametersBuilder.put(
142+
name,
143+
this.item == null
144+
? NULL_VALUE
145+
: Value.untyped(
146+
com.google.protobuf.Value.newBuilder()
147+
.setStringValue(this.item.toString())
148+
.build()));
135149
}
136150
}

src/test/java/com/google/cloud/spanner/pgadapter/ITJdbcTest.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import static org.junit.Assert.assertNull;
2323
import static org.junit.Assert.assertThrows;
2424
import static org.junit.Assert.assertTrue;
25+
import static org.junit.Assume.assumeTrue;
2526

2627
import com.google.cloud.ByteArray;
2728
import com.google.cloud.Date;
@@ -51,6 +52,7 @@
5152
import java.util.Arrays;
5253
import java.util.Collections;
5354
import java.util.List;
55+
import java.util.UUID;
5456
import org.junit.AfterClass;
5557
import org.junit.Before;
5658
import org.junit.BeforeClass;
@@ -1224,6 +1226,33 @@ public void testSelectNamespaces() throws SQLException {
12241226
}
12251227
}
12261228

1229+
@Test
1230+
public void testUUID() throws SQLException {
1231+
// UUIDs are only supported in extended query mode.
1232+
// In simple mode, the PG driver will try to cast a literal to UUID.
1233+
assumeTrue(preferQueryMode.equals("extended"));
1234+
1235+
try (Connection connection = DriverManager.getConnection(getConnectionUrl())) {
1236+
// TODO: Change type to UUID once supported.
1237+
connection
1238+
.createStatement()
1239+
.execute(
1240+
"create table if not exists uuid_values (id bigint primary key, col_varchar varchar, col_uuid varchar)");
1241+
connection.createStatement().execute("truncate uuid_values");
1242+
// It should be possible to use UUID query parameters with both UUID and varchar columns.
1243+
try (PreparedStatement statement =
1244+
connection.prepareStatement(
1245+
"insert into uuid_values (id, col_varchar, col_uuid) values (?, ?, ?)")) {
1246+
UUID uuid = UUID.randomUUID();
1247+
// Verify that we can insert a UUID value into both a varchar and a UUID column.
1248+
statement.setLong(1, 1L);
1249+
statement.setObject(2, uuid);
1250+
statement.setObject(3, uuid);
1251+
assertEquals(1, statement.executeUpdate());
1252+
}
1253+
}
1254+
}
1255+
12271256
@Test
12281257
public void testSelectTypes() throws SQLException {
12291258
try (Connection connection = DriverManager.getConnection(getConnectionUrl())) {

src/test/java/com/google/cloud/spanner/pgadapter/JdbcMockServerTest.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4951,9 +4951,8 @@ public void testUUIDParameter() throws SQLException {
49514951
String jdbcSql = "SELECT * FROM all_types WHERE col_uuid=?";
49524952
String pgSql = "SELECT * FROM all_types WHERE col_uuid=$1";
49534953
UUID uuid = UUID.randomUUID();
4954-
mockSpanner.putStatementResult(
4955-
StatementResult.query(
4956-
Statement.newBuilder(pgSql).bind("p1").to(uuid).build(), ALL_TYPES_RESULTSET));
4954+
mockSpanner.putPartialStatementResult(
4955+
StatementResult.query(Statement.of(pgSql), ALL_TYPES_RESULTSET));
49574956

49584957
try (Connection connection = DriverManager.getConnection(createUrl())) {
49594958
try (PreparedStatement statement = connection.prepareStatement(jdbcSql)) {
@@ -4966,6 +4965,11 @@ public void testUUIDParameter() throws SQLException {
49664965
}
49674966

49684967
assertEquals(1, mockSpanner.countRequestsOfType(ExecuteSqlRequest.class));
4968+
ExecuteSqlRequest request = mockSpanner.getRequestsOfType(ExecuteSqlRequest.class).get(0);
4969+
assertEquals(1, request.getParams().getFieldsCount());
4970+
// UUIDs should be sent to Spanner as untyped values, so Spanner can infer the correct data
4971+
// type. This allows clients to use UUID query parameters with both STRING and UUID columns.
4972+
assertEquals(0, request.getParamTypesCount());
49694973
}
49704974

49714975
@Test

0 commit comments

Comments
 (0)