Skip to content

Commit 70f4b5e

Browse files
feat(spanner): Add support for UUID data type.
1 parent 4d2a380 commit 70f4b5e

File tree

15 files changed

+245
-47
lines changed

15 files changed

+245
-47
lines changed

apis/Google.Cloud.Spanner.Data/Google.Cloud.Spanner.Data.IntegrationTests/AllTypesTableFixture.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ public string CreateInsertCommand(bool skipProtobufValue = false) =>
5252
ProtobufRectangleValue,
5353
ProtobufPersonValue,
5454
ProtobufValueWrapperValue,
55+
{EmptyOnEmulator("UuidValue,")}
5556
BoolArrayValue,
5657
Int64ArrayValue,
5758
Float32ArrayValue,
@@ -67,6 +68,7 @@ public string CreateInsertCommand(bool skipProtobufValue = false) =>
6768
ProtobufDurationArrayValue,
6869
ProtobufRectangleArrayValue,
6970
ProtobufPersonArrayValue,
71+
{EmptyOnEmulator("UuidArrayValue,")}
7072
ProtobufValueWrapperArrayValue) VALUES(
7173
@K,
7274
@BoolValue,
@@ -84,6 +86,7 @@ public string CreateInsertCommand(bool skipProtobufValue = false) =>
8486
@ProtobufRectangleValue,
8587
@ProtobufPersonValue,
8688
@ProtobufValueWrapperValue,
89+
{EmptyOnEmulator("@UuidValue,")}
8790
@BoolArrayValue,
8891
@Int64ArrayValue,
8992
@Float32ArrayValue,
@@ -99,6 +102,7 @@ public string CreateInsertCommand(bool skipProtobufValue = false) =>
99102
@ProtobufDurationArrayValue,
100103
@ProtobufRectangleArrayValue,
101104
@ProtobufPersonArrayValue,
105+
{EmptyOnEmulator("@UuidArrayValue,")}
102106
@ProtobufValueWrapperArrayValue
103107
)";
104108

@@ -120,6 +124,7 @@ BytesValue BYTES(MAX),
120124
ProtobufRectangleValue {Rectangle.Descriptor.FullName},
121125
ProtobufPersonValue {Person.Descriptor.FullName},
122126
ProtobufValueWrapperValue {ValueWrapper.Descriptor.FullName},
127+
{EmptyOnEmulator("UuidValue UUID,")}
123128
BoolArrayValue ARRAY<BOOL>,
124129
Int64ArrayValue ARRAY<INT64>,
125130
Float32ArrayValue ARRAY<FLOAT32>,
@@ -135,9 +140,12 @@ BytesValue BYTES(MAX),
135140
ProtobufDurationArrayValue ARRAY<{Duration.Descriptor.FullName}>,
136141
ProtobufRectangleArrayValue ARRAY<{Rectangle.Descriptor.FullName}>,
137142
ProtobufPersonArrayValue ARRAY<{Person.Descriptor.FullName}>,
138-
ProtobufValueWrapperArrayValue ARRAY<{ValueWrapper.Descriptor.FullName}>
143+
{EmptyOnEmulator("UuidArrayValue ARRAY<UUID>,")}
144+
ProtobufValueWrapperArrayValue ARRAY<{ValueWrapper.Descriptor.FullName}>,
139145
) PRIMARY KEY(K)");
140146

141147
private string MaybeEmptyOnProduction(string text, bool skip) => skip && !RunningOnEmulator ? "" : text;
148+
149+
private string EmptyOnEmulator(string text) => RunningOnEmulator ? "" : text;
142150
}
143151
}

apis/Google.Cloud.Spanner.Data/Google.Cloud.Spanner.Data.IntegrationTests/BindingTests.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ public BindingTests(SpannerDatabaseFixture fixture) =>
6161
SpannerDbType.Float32,
6262
SpannerDbType.Json,
6363
SpannerDbType.Interval,
64+
SpannerDbType.Uuid,
6465
SpannerDbType.FromClrType(typeof(Duration)),
6566
SpannerDbType.FromClrType(typeof(Rectangle)),
6667
SpannerDbType.FromClrType(typeof(Person)),
@@ -76,6 +77,7 @@ public BindingTests(SpannerDatabaseFixture fixture) =>
7677
SpannerDbType.ArrayOf(SpannerDbType.Float32),
7778
SpannerDbType.ArrayOf(SpannerDbType.Json),
7879
SpannerDbType.ArrayOf(SpannerDbType.Interval),
80+
SpannerDbType.ArrayOf(SpannerDbType.Uuid),
7981
SpannerDbType.ArrayOf(SpannerDbType.FromClrType(typeof(Duration))),
8082
SpannerDbType.ArrayOf(SpannerDbType.FromClrType(typeof(Rectangle))),
8183
SpannerDbType.ArrayOf(SpannerDbType.FromClrType(typeof(Person))),
@@ -393,5 +395,21 @@ public async Task BindProtobufValueWrapperEmptyArray() => await TestBindNonNull(
393395
private void MaybeSkipIfOnProduction(SpannerDbType spannerDbType) =>
394396
Skip.If(!_fixture.RunningOnEmulator && BindProductionUnsupportedNullData.Any<SpannerDbType>(spannerDbType.Equals),
395397
$"Production does not support {spannerDbType}.");
398+
399+
[Fact]
400+
public Task BindUuid() => TestBindNonNull(
401+
SpannerDbType.Uuid,
402+
Guid.NewGuid(),
403+
r => r.GetGuid(0));
404+
405+
[Fact]
406+
public Task BindUuidArray() => TestBindNonNull(
407+
SpannerDbType.ArrayOf(SpannerDbType.Uuid),
408+
new Guid?[] { Guid.NewGuid(), null, Guid.NewGuid() });
409+
410+
[Fact]
411+
public Task BindUuidEmptyArray() => TestBindNonNull(
412+
SpannerDbType.ArrayOf(SpannerDbType.Uuid),
413+
new Guid[] { });
396414
}
397415
}

apis/Google.Cloud.Spanner.Data/Google.Cloud.Spanner.Data.IntegrationTests/GetSchemaTableTests.cs

Lines changed: 52 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -57,44 +57,59 @@ public async Task GetSchemaTable_WithFlagEnabled_ReturnsSchema(string columnName
5757
}
5858

5959
// These SpannerDbTypes are supported on emulator.
60-
public static TheoryData<string, System.Type, SpannerDbType> SchemaTestData { get; } =
61-
new TheoryData<string, System.Type, SpannerDbType>
60+
public static TheoryData<string, System.Type, SpannerDbType> SchemaTestData
61+
{
62+
get
6263
{
63-
// Base types.
64-
{ "BoolValue", typeof(bool), SpannerDbType.Bool },
65-
{ "Int64Value", typeof(long), SpannerDbType.Int64 },
66-
{ "Float32Value", typeof(float), SpannerDbType.Float32 },
67-
{ "Float64Value", typeof(double), SpannerDbType.Float64 },
68-
{ "NumericValue", typeof(SpannerNumeric), SpannerDbType.Numeric },
69-
{ "StringValue", typeof(string), SpannerDbType.String },
70-
{ "BytesValue", typeof(byte[]), SpannerDbType.Bytes },
71-
{ "TimestampValue", typeof(DateTime), SpannerDbType.Timestamp },
72-
{ "DateValue", typeof(DateTime), SpannerDbType.Date },
73-
{ "JsonValue", typeof(string), SpannerDbType.Json },
74-
{ "ProtobufDurationValue", typeof(Value), SpannerDbType.FromClrType(typeof(Duration)) },
75-
{ "ProtobufRectangleValue", typeof(Value), SpannerDbType.FromClrType(typeof(Rectangle)) },
76-
{ "ProtobufValueValue", typeof(Value), SpannerDbType.FromClrType(typeof(Value)) },
77-
{ "ProtobufPersonValue", typeof(Value), SpannerDbType.FromClrType(typeof(Person)) },
78-
{ "ProtobufValueWrapperValue", typeof(Value), SpannerDbType.FromClrType(typeof(ValueWrapper)) },
79-
80-
// Array types.
81-
{ "BoolArrayValue", typeof(List<bool>), SpannerDbType.ArrayOf(SpannerDbType.Bool) },
82-
{ "Int64ArrayValue", typeof(List<long>), SpannerDbType.ArrayOf(SpannerDbType.Int64) },
83-
{ "Float32ArrayValue", typeof(List<float>), SpannerDbType.ArrayOf(SpannerDbType.Float32) },
84-
{ "Float64ArrayValue", typeof(List<double>), SpannerDbType.ArrayOf(SpannerDbType.Float64) },
85-
{ "NumericArrayValue", typeof(List<SpannerNumeric>), SpannerDbType.ArrayOf(SpannerDbType.Numeric) },
86-
{ "StringArrayValue", typeof(List<string>), SpannerDbType.ArrayOf(SpannerDbType.String) },
87-
{ "Base64ArrayValue", typeof(List<byte[]>), SpannerDbType.ArrayOf(SpannerDbType.Bytes) },
88-
{ "BytesArrayValue", typeof(List<byte[]>), SpannerDbType.ArrayOf(SpannerDbType.Bytes) },
89-
{ "TimestampArrayValue", typeof(List<DateTime>), SpannerDbType.ArrayOf(SpannerDbType.Timestamp) },
90-
{ "DateArrayValue", typeof(List<DateTime>), SpannerDbType.ArrayOf(SpannerDbType.Date) },
91-
{ "JsonArrayValue", typeof(List<string>), SpannerDbType.ArrayOf(SpannerDbType.Json) },
92-
{ "ProtobufDurationArrayValue", typeof(List<Value>), SpannerDbType.ArrayOf(SpannerDbType.FromClrType(typeof(Duration))) },
93-
{ "ProtobufRectangleArrayValue", typeof(List<Value>), SpannerDbType.ArrayOf(SpannerDbType.FromClrType(typeof(Rectangle))) },
94-
{ "ProtobufValueArrayValue", typeof(List<Value>), SpannerDbType.ArrayOf(SpannerDbType.FromClrType(typeof(Value))) },
95-
{ "ProtobufPersonArrayValue", typeof(List<Value>), SpannerDbType.ArrayOf(SpannerDbType.FromClrType(typeof(Person))) },
96-
{ "ProtobufValueWrapperArrayValue", typeof(List<Value>), SpannerDbType.ArrayOf(SpannerDbType.FromClrType(typeof(ValueWrapper))) },
97-
};
64+
var data = new TheoryData<string, System.Type, SpannerDbType>
65+
{
66+
// Base types.
67+
{ "BoolValue", typeof(bool), SpannerDbType.Bool },
68+
{ "Int64Value", typeof(long), SpannerDbType.Int64 },
69+
{ "Float32Value", typeof(float), SpannerDbType.Float32 },
70+
{ "Float64Value", typeof(double), SpannerDbType.Float64 },
71+
{ "NumericValue", typeof(SpannerNumeric), SpannerDbType.Numeric },
72+
{ "StringValue", typeof(string), SpannerDbType.String },
73+
{ "BytesValue", typeof(byte[]), SpannerDbType.Bytes },
74+
{ "TimestampValue", typeof(DateTime), SpannerDbType.Timestamp },
75+
{ "DateValue", typeof(DateTime), SpannerDbType.Date },
76+
{ "JsonValue", typeof(string), SpannerDbType.Json },
77+
{ "ProtobufDurationValue", typeof(Value), SpannerDbType.FromClrType(typeof(Duration)) },
78+
{ "ProtobufRectangleValue", typeof(Value), SpannerDbType.FromClrType(typeof(Rectangle)) },
79+
{ "ProtobufValueValue", typeof(Value), SpannerDbType.FromClrType(typeof(Value)) },
80+
{ "ProtobufPersonValue", typeof(Value), SpannerDbType.FromClrType(typeof(Person)) },
81+
{ "ProtobufValueWrapperValue", typeof(Value), SpannerDbType.FromClrType(typeof(ValueWrapper)) },
82+
83+
// Array types.
84+
{ "BoolArrayValue", typeof(List<bool>), SpannerDbType.ArrayOf(SpannerDbType.Bool) },
85+
{ "Int64ArrayValue", typeof(List<long>), SpannerDbType.ArrayOf(SpannerDbType.Int64) },
86+
{ "Float32ArrayValue", typeof(List<float>), SpannerDbType.ArrayOf(SpannerDbType.Float32) },
87+
{ "Float64ArrayValue", typeof(List<double>), SpannerDbType.ArrayOf(SpannerDbType.Float64) },
88+
{ "NumericArrayValue", typeof(List<SpannerNumeric>), SpannerDbType.ArrayOf(SpannerDbType.Numeric) },
89+
{ "StringArrayValue", typeof(List<string>), SpannerDbType.ArrayOf(SpannerDbType.String) },
90+
{ "Base64ArrayValue", typeof(List<byte[]>), SpannerDbType.ArrayOf(SpannerDbType.Bytes) },
91+
{ "BytesArrayValue", typeof(List<byte[]>), SpannerDbType.ArrayOf(SpannerDbType.Bytes) },
92+
{ "TimestampArrayValue", typeof(List<DateTime>), SpannerDbType.ArrayOf(SpannerDbType.Timestamp) },
93+
{ "DateArrayValue", typeof(List<DateTime>), SpannerDbType.ArrayOf(SpannerDbType.Date) },
94+
{ "JsonArrayValue", typeof(List<string>), SpannerDbType.ArrayOf(SpannerDbType.Json) },
95+
{ "ProtobufDurationArrayValue", typeof(List<Value>), SpannerDbType.ArrayOf(SpannerDbType.FromClrType(typeof(Duration))) },
96+
{ "ProtobufRectangleArrayValue", typeof(List<Value>), SpannerDbType.ArrayOf(SpannerDbType.FromClrType(typeof(Rectangle))) },
97+
{ "ProtobufValueArrayValue", typeof(List<Value>), SpannerDbType.ArrayOf(SpannerDbType.FromClrType(typeof(Value))) },
98+
{ "ProtobufPersonArrayValue", typeof(List<Value>), SpannerDbType.ArrayOf(SpannerDbType.FromClrType(typeof(Person))) },
99+
{ "ProtobufValueWrapperArrayValue", typeof(List<Value>), SpannerDbType.ArrayOf(SpannerDbType.FromClrType(typeof(ValueWrapper))) },
100+
};
101+
102+
// Uuid is not supported on emulator.
103+
// We don't have a fixture here, but we can check the environment variable.
104+
if (Environment.GetEnvironmentVariable("SPANNER_EMULATOR_HOST") == null)
105+
{
106+
data.Add("UuidValue", typeof(Guid), SpannerDbType.Uuid);
107+
data.Add("UuidArrayValue", typeof(List<Guid>), SpannerDbType.ArrayOf(SpannerDbType.Uuid));
108+
}
109+
110+
return data;
111+
}
112+
}
98113

99114
internal static async Task GetSchemaTable_WithFlagEnabled_ReturnsSchema_Impl(string columnName, System.Type type, SpannerDbType spannerDbType, string connectionString, string selectQuery)
100115
{

apis/Google.Cloud.Spanner.Data/Google.Cloud.Spanner.Data.IntegrationTests/WriteTests.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,11 @@ private async Task ExecuteWriteNullsTest(Func<SpannerParameterCollection, Task<i
117117
// b/348711708
118118
parameters.Add("ProtobufValueValue", SpannerDbType.FromClrType(typeof(Value)), null);
119119
}
120+
if (!_fixture.RunningOnEmulator)
121+
{
122+
parameters.Add("UuidValue", SpannerDbType.Uuid, null);
123+
parameters.Add("UuidArrayValue", SpannerDbType.ArrayOf(SpannerDbType.Uuid), null);
124+
}
120125

121126
Assert.Equal(1, await insertCommand(parameters));
122127
await WithLastRowAsync(reader =>
@@ -157,6 +162,11 @@ await WithLastRowAsync(reader =>
157162
// b/348711708
158163
Assert.True(reader.IsDBNull(reader.GetOrdinal("ProtobufValueValue")));
159164
}
165+
if (!_fixture.RunningOnEmulator)
166+
{
167+
Assert.True(reader.IsDBNull(reader.GetOrdinal("UuidValue")));
168+
Assert.True(reader.IsDBNull(reader.GetOrdinal("UuidArrayValue")));
169+
}
160170
}, GetConnection(), GetWriteTestReader);
161171
}
162172

@@ -243,11 +253,19 @@ private async Task ExecuteWriteValuesTest(Func<SpannerParameterCollection, Task<
243253
{ "ProtobufValueArrayValue", SpannerDbType.ArrayOf(SpannerDbType.FromClrType(typeof(Value))), pvArray },
244254
};
245255

256+
var testUuid = Guid.NewGuid();
257+
Guid?[] uuidArray = { Guid.NewGuid(), null, Guid.NewGuid() };
258+
246259
if (_fixture.RunningOnEmulator || !isDml)
247260
{
248261
// b/348711708
249262
parameters.Add("ProtobufValueValue", SpannerDbType.FromClrType(typeof(Value)), Value.ForString("Hello"));
250263
}
264+
if (!_fixture.RunningOnEmulator)
265+
{
266+
parameters.Add("UuidValue", SpannerDbType.Uuid, testUuid);
267+
parameters.Add("UuidArrayValue", SpannerDbType.ArrayOf(SpannerDbType.Uuid), uuidArray);
268+
}
251269

252270
Assert.Equal(1, await insertCommand(parameters));
253271
await WithLastRowAsync(reader =>
@@ -291,6 +309,11 @@ await WithLastRowAsync(reader =>
291309
// b/348711708
292310
Assert.Equal(Value.ForString("Hello"), reader.GetFieldValue<Value>(reader.GetOrdinal("ProtobufValueValue")));
293311
}
312+
if (!_fixture.RunningOnEmulator)
313+
{
314+
Assert.Equal(testUuid, reader.GetFieldValue<Guid>(reader.GetOrdinal("UuidValue")));
315+
Assert.Equal(uuidArray, reader.GetFieldValue<Guid?[]>(reader.GetOrdinal("UuidArrayValue")));
316+
}
294317
}, GetConnection(), GetWriteTestReader);
295318
}
296319

apis/Google.Cloud.Spanner.Data/Google.Cloud.Spanner.Data.Tests/KeysTests.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ public void CreateKeyFromParameterCollection()
4040
{ "", SpannerDbType.Interval, Interval.Parse("P1Y2M3D") },
4141
{ "", SpannerDbType.PgOid, 2 },
4242
{ "", SpannerDbType.String, "test" },
43-
{ "", SpannerDbType.Timestamp, new DateTime(2021, 9, 10, 9, 37, 10, DateTimeKind.Utc) }
43+
{ "", SpannerDbType.Timestamp, new DateTime(2021, 9, 10, 9, 37, 10, DateTimeKind.Utc) },
44+
{ "", SpannerDbType.Uuid, Guid.Parse("8f8c4746-17b1-4d9f-a634-58e11942095f") }
4445
});
4546

4647
var actual = key.ToProtobuf(SpannerConversionOptions.Default);
@@ -61,7 +62,8 @@ public void CreateKeyFromParameterCollection()
6162
Value.ForString("P1Y2M3D"),
6263
Value.ForString("2"),
6364
Value.ForString("test"),
64-
Value.ForString("2021-09-10T09:37:10Z")
65+
Value.ForString("2021-09-10T09:37:10Z"),
66+
Value.ForString("8f8c4746-17b1-4d9f-a634-58e11942095f")
6567
}
6668
};
6769
Assert.Equal(expected, actual);

apis/Google.Cloud.Spanner.Data/Google.Cloud.Spanner.Data.Tests/SpannerCommandTests.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1344,6 +1344,7 @@ public async Task CanExecuteReadCommand()
13441344
{"timestamp", SpannerDbType.Timestamp, new DateTime(2021, 9, 8, 15, 22, 59, DateTimeKind.Utc)},
13451345
{"bool", SpannerDbType.Bool, true},
13461346
{"interval", SpannerDbType.Interval, Interval.Parse("P1Y2M3D")},
1347+
{"uuid", SpannerDbType.Uuid, new Guid("8f8c4746-17b1-4d9f-a634-58e11942095f")},
13471348
}));
13481349
using var reader = await command.ExecuteReaderAsync();
13491350
Assert.True(reader.HasRows);
@@ -1365,6 +1366,7 @@ public async Task CanExecuteReadCommand()
13651366
new Value { StringValue = "2021-09-08T15:22:59Z" },
13661367
new Value { BoolValue = true },
13671368
new Value { StringValue = "P1Y2M3D" },
1369+
new Value { StringValue = "8f8c4746-17b1-4d9f-a634-58e11942095f"},
13681370
} } } })),
13691371
Arg.Any<CallSettings>());
13701372
}

0 commit comments

Comments
 (0)