Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Support UUID data type #14301

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public string CreateInsertCommand(bool skipProtobufValue = false) =>
ProtobufRectangleValue,
{EmptyOnEmulator("ProtobufPersonValue,")/* b/348716298 */}
{EmptyOnEmulator("ProtobufValueWrapperValue,")/* b/348716298 */}
{EmptyOnEmulator("UuidValue,")}
BoolArrayValue,
Int64ArrayValue,
{EmptyOnEmulator("Float32ArrayValue,")}
Expand All @@ -67,7 +68,8 @@ public string CreateInsertCommand(bool skipProtobufValue = false) =>
ProtobufDurationArrayValue,
ProtobufRectangleArrayValue
{EmptyOnEmulator(", ProtobufPersonArrayValue")/* b/348716298 */}
{EmptyOnEmulator(", ProtobufValueWrapperArrayValue")/* b/348716298 */}) VALUES(
{EmptyOnEmulator(", ProtobufValueWrapperArrayValue")/* b/348716298 */}
{EmptyOnEmulator(", UuidArrayValue")} VALUES(
@K,
@BoolValue,
@Int64Value,
Expand All @@ -84,6 +86,7 @@ public string CreateInsertCommand(bool skipProtobufValue = false) =>
@ProtobufRectangleValue,
{EmptyOnEmulator("@ProtobufPersonValue,")/* b/348716298 */}
{EmptyOnEmulator("@ProtobufValueWrapperValue,")/* b/348716298 */}
{EmptyOnEmulator("@UuidValue,")}
@BoolArrayValue,
@Int64ArrayValue,
{EmptyOnEmulator("@Float32ArrayValue,")}
Expand All @@ -100,6 +103,7 @@ public string CreateInsertCommand(bool skipProtobufValue = false) =>
@ProtobufRectangleArrayValue
{EmptyOnEmulator(", @ProtobufPersonArrayValue")/* b/348716298 */}
{EmptyOnEmulator(", @ProtobufValueWrapperArrayValue")/* b/348716298 */}
{EmptyOnEmulator(", @UuidArrayValue")}
)";

// Note: the emulator doesn't yet support the JSON type.
Expand All @@ -121,6 +125,7 @@ BytesValue BYTES(MAX),
ProtobufRectangleValue {Rectangle.Descriptor.FullName},
{EmptyOnEmulator($"ProtobufPersonValue {Person.Descriptor.FullName},")/* b/348716298 */}
{EmptyOnEmulator($"ProtobufValueWrapperValue {ValueWrapper.Descriptor.FullName},")/* b/348716298 */}
{EmptyOnEmulator("UuidValue UUID,")}
BoolArrayValue ARRAY<BOOL>,
Int64ArrayValue ARRAY<INT64>,
{EmptyOnEmulator("Float32ArrayValue ARRAY<FLOAT32>,")}
Expand All @@ -137,6 +142,7 @@ BytesValue BYTES(MAX),
ProtobufRectangleArrayValue ARRAY<{Rectangle.Descriptor.FullName}>
{EmptyOnEmulator($", ProtobufPersonArrayValue ARRAY<{Person.Descriptor.FullName}>")/* b/348716298 */}
{EmptyOnEmulator($", ProtobufValueWrapperArrayValue ARRAY<{ValueWrapper.Descriptor.FullName}>")/* b/348716298 */}
{EmptyOnEmulator($", UuidArrayValue ARRAY<UUID>")}
) PRIMARY KEY(K)");

private string EmptyOnEmulator(string text) => RunningOnEmulator ? "" : text;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public BindingTests(SpannerDatabaseFixture fixture) =>
SpannerDbType.Bytes,
SpannerDbType.FromClrType(typeof(Duration)),
SpannerDbType.FromClrType(typeof(Rectangle)),
SpannerDbType.Uuid,
SpannerDbType.ArrayOf(SpannerDbType.Bool),
SpannerDbType.ArrayOf(SpannerDbType.String),
SpannerDbType.ArrayOf(SpannerDbType.Int64),
Expand All @@ -69,7 +70,7 @@ public BindingTests(SpannerDatabaseFixture fixture) =>
SpannerDbType.ArrayOf(SpannerDbType.Date),
SpannerDbType.ArrayOf(SpannerDbType.Bytes),
SpannerDbType.ArrayOf(SpannerDbType.FromClrType(typeof(Duration))),
SpannerDbType.ArrayOf(SpannerDbType.FromClrType(typeof(Rectangle)))
SpannerDbType.ArrayOf(SpannerDbType.FromClrType(typeof(Rectangle))),
};

// These SpannerDbTypes are unsupported on emulator.
Expand Down Expand Up @@ -346,7 +347,7 @@ public async Task BindProtobufRectangle() => await TestBindNonNull(
SpannerDbType.FromClrType(typeof(Rectangle)), testRectangle, r => r.GetFieldValue<Rectangle>(0));

[Fact]
public async Task BindProtobufRectanlgeArray() => await TestBindNonNull(
public async Task BindProtobufRectangleArray() => await TestBindNonNull(
SpannerDbType.ArrayOf(SpannerDbType.FromClrType(typeof(Rectangle))),
new Rectangle[] { testRectangle, null, new Rectangle() });

Expand Down Expand Up @@ -389,6 +390,23 @@ public async Task BindProtobufValueWrapperEmptyArray() => await TestBindNonNull(
SpannerDbType.ArrayOf(SpannerDbType.FromClrType(typeof(ValueWrapper))),
new ValueWrapper[] { });

[Fact]
public Task BindUuid() => TestBindNonNull(
SpannerDbType.Uuid,
Guid.NewGuid(),
r => r.GetString(0));

[Fact]
public Task BindUuidArray() => TestBindNonNull(
SpannerDbType.ArrayOf(SpannerDbType.Uuid),
new[] { "d587bc27-90fe-4c23-bb25-2404279e152a", null, "350636fe-9c72-4b3d-ab70-167904974288" });

[Fact]
public Task BindUuidEmptyArray() => TestBindNonNull(
SpannerDbType.ArrayOf(SpannerDbType.Uuid),
new string[] { });


private void MaybeSkipIfOnEmulator(SpannerDbType spannerDbType) =>
Skip.If(_fixture.RunningOnEmulator && BindUnsupportedNullData.Any<SpannerDbType>(spannerDbType.Equals),
$"The emulator does not support {spannerDbType}.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ private async Task ExecuteWriteNullsTest(Func<SpannerParameterCollection, Task<i
{ "NumericValue", SpannerDbType.Numeric, null },
{ "ProtobufDurationValue", SpannerDbType.FromClrType(typeof(Duration)), null },
{ "ProtobufRectangleValue", SpannerDbType.FromClrType(typeof(Rectangle)), null },

{ "BoolArrayValue", SpannerDbType.ArrayOf(SpannerDbType.Bool), null },
{ "Int64ArrayValue", SpannerDbType.ArrayOf(SpannerDbType.Int64), null },
{ "Float64ArrayValue", SpannerDbType.ArrayOf(SpannerDbType.Float64), null },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public void CreateKeyFromParameterCollection()
{ "", SpannerDbType.PgOid, 2 },
{ "", SpannerDbType.String, "test" },
{ "", SpannerDbType.Timestamp, new DateTime(2021, 9, 10, 9, 37, 10, DateTimeKind.Utc) }
{ "", SpannerDbType.Uuid, new Guid("8f8c4746-17b1-4d9f-a634-58e11942095f") }
});

var actual = key.ToProtobuf(SpannerConversionOptions.Default);
Expand Down Expand Up @@ -311,6 +312,8 @@ public void ToString_()
new Key(new SpannerParameterCollection { new SpannerParameter { ParameterName = "k2", Value = 1.71m } }));
var range2 = KeyRange.ClosedClosed(new Key(new SpannerParameterCollection { new SpannerParameter { ParameterName = "k3", Value = new DateTime(2022, 06, 08) } }),
new Key(new SpannerParameterCollection { new SpannerParameter { ParameterName = "k4", Value = new DateTime(2024, 06, 08) } }));
var range3 = KeyRange.ClosedClosed(new Key(new SpannerParameterCollection { new SpannerParameter { ParameterName = "k5", Value = new Guid("8f8c4746-17b1-4d9f-a634-58e11942095f") } }),
new Key(new SpannerParameterCollection { new SpannerParameter { ParameterName = "k6", Value = new Guid("8f8c4746-17b1-4d9f-a634-58e11942095f") } }));

Assert.Equal("[[ \"3.14\" ], [ \"1.71\" ])", range1.ToString(builder));
Assert.Equal("[[ \"2022-06-08T00:00:00Z\" ], [ \"2024-06-08T00:00:00Z\" ]]", range2.ToString(builder));
Expand All @@ -324,6 +327,7 @@ public void ToString_()
builder.ClrToSpannerTypeDefaultMappings = "";
Assert.Equal("[[ \"3.14\" ], [ \"1.71\" ])", range1.ToString(builder));
Assert.Equal("[[ \"2022-06-08T00:00:00Z\" ], [ \"2024-06-08T00:00:00Z\" ]]", range2.ToString(builder));
Assert.Equal("8f8c4746-17b1-4d9f-a634-58e11942095f", "8f8c4746-17b1-4d9f-a634-58e11942095f", range3.toString(builder));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1328,6 +1328,7 @@ public async Task CanExecuteReadCommand()
{"date", SpannerDbType.Date, new DateTime(2021, 9, 8, 0, 0, 0, DateTimeKind.Utc)},
{"timestamp", SpannerDbType.Timestamp, new DateTime(2021, 9, 8, 15, 22, 59, DateTimeKind.Utc)},
{"bool", SpannerDbType.Bool, true},
{"uuid", SpannerDbType.Uuid, new Guid("8f8c4746-17b1-4d9f-a634-58e11942095f")},
}));
using var reader = await command.ExecuteReaderAsync();
Assert.True(reader.HasRows);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public static IEnumerable<object[]> GetDbTypeConversions()
yield return new object[] { SpannerDbType.PgOid, DbType.Int64, false };
yield return new object[] { SpannerDbType.FromClrType(typeof(Duration)), DbType.Object, false };
yield return new object[] { SpannerDbType.FromClrType(typeof(Rectangle)), DbType.Object, false };
yield return new object[] { SpannerDbType.Uuid, DbType }
}

[Theory]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ public string Host
// TODO: Now that ServiceEndpoint has been removed, we don't have separate host/port for the default endpoint.
// This is currently hardcoded for convenience; it's unlikely to ever change, but ideally we'd parse it from the
// SpannerClient.DefaultEndpoint;
get => GetValueOrDefault(nameof(Host), "spanner.googleapis.com");
get => GetValueOrDefault(nameof(Host), "staging-wrenchworks.sandbox.googleapis.com"); // DO NOT SUBMIT -- TESTING ONLY
set => this[nameof(Host)] = value;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ internal class SpannerConversionOptions
DecimalToNumeric,
DecimalToPgNumeric,
DateTimeToDate,
DateTimeToTimestamp
DateTimeToTimestamp,
GuidToUuid
};

/// <summary>
Expand All @@ -55,10 +56,12 @@ internal class SpannerConversionOptions
internal const string DecimalToPgNumeric = nameof(DecimalToPgNumeric);
internal const string DateTimeToDate = nameof(DateTimeToDate);
internal const string DateTimeToTimestamp = nameof(DateTimeToTimestamp);
internal const string GuidToUuid = nameof(GuidToUuid);

// Constants for Spanner To CLR type mappings.
internal const string DateToSpannerDate = nameof(DateToSpannerDate);
internal const string DateToDateTime = nameof(DateToDateTime);
internal const string UuidToGuid = nameof(UuidToGuid);

// Predefined instances; these will change as the class grows, but hopefully
// for most cases we can avoid creating new instances.
Expand All @@ -83,11 +86,21 @@ internal class SpannerConversionOptions
/// </summary>
internal SpannerDbType DateTimeToConfiguredSpannerType { get; private set; }

/// <summary>
/// The configured SpannerDbType for the Guid CLR type.
/// </summary>
internal SpannerDbType GuidToConfiguredSpannerType { get; private set; }

/// <summary>
/// The configured CLR type for the Date SpannerDbType.
/// </summary>
internal System.Type DateToConfiguredClrType { get; private set; }

/// <summary>
/// The configured CLR type for the Uuid SpannerDbType.
/// </summary>
internal System.Type GuidToConfiguredClrType { get; private set; }

private SpannerConversionOptions(bool useDBNull)
{
UseDBNull = useDBNull;
Expand All @@ -112,7 +125,8 @@ internal static SpannerConversionOptions ForConnectionStringBuilder(SpannerConne
{
DateTimeToConfiguredSpannerType = DateTimeToConfiguredSpannerType,
DecimalToConfiguredSpannerType = DecimalToConfiguredSpannerType,
DateToConfiguredClrType = DateToConfiguredClrType
DateToConfiguredClrType = DateToConfiguredClrType,
GuidToConfiguredClrType = GuidToConfiguredClrType
};

internal SpannerConversionOptions WithClrDefaultForNullSetting(bool useClrDefaultToNull)
Expand Down Expand Up @@ -141,11 +155,13 @@ internal void SetClrToSpannerTypeDefaults()
SingleToConfiguredSpannerType = SpannerDbType.Float32;
DecimalToConfiguredSpannerType = SpannerDbType.Numeric;
DateTimeToConfiguredSpannerType = SpannerDbType.Timestamp;
GuidToConfiguredSpannerType = SpannerDbType.Uuid;
}

internal void SetSpannerToClrTypeDefaults()
{
DateToConfiguredClrType = typeof(DateTime);
GuidToConfiguredClrType = typeof(Guid);
}

private void ValidateAndParseSpannerToClrTypeMappings(string input, SpannerConversionOptions options)
Expand Down Expand Up @@ -177,6 +193,10 @@ private void ValidateAndParseSpannerToClrTypeMappings(string input, SpannerConve
{
options.DateToConfiguredClrType = typeof(SpannerDate);
}
else if (string.Equals(mapping, GuidToUuid, StringComparison.OrdinalIgnoreCase))
{
options.GuidToConfiguredClrType = typeof(Guid);
}
}
}

Expand Down Expand Up @@ -231,6 +251,10 @@ private void ValidateAndParseClrToSpannerTypeMappings(string input, SpannerConve
{
options.DateTimeToConfiguredSpannerType = SpannerDbType.Timestamp;
}
else if (string.Equals(mapping, GuidToUuid, StringComparison.OrdinalIgnoreCase))
{
options.GuidToConfiguredSpannerType = SpannerDbType.Uuid;
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
//
// 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.
Expand Down Expand Up @@ -50,9 +50,10 @@ public static bool TryParse(string spannerType, out SpannerDbType spannerDbType)
case TypeCode.Bytes:
case TypeCode.Json:
case TypeCode.Numeric:
case TypeCode.Uuid:
if (!string.IsNullOrEmpty(remainder))
{
//unexepected inner remainder on simple type
// Unexepected inner remainder on simple type.
return false;
}
// If there's no size, we can use cached values.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,11 @@ internal Value ToProtobufValue(object value)
StringValue = StripTimePart(
XmlConvert.ToString(Convert.ToDateTime(value, InvariantCulture), XmlDateTimeSerializationMode.Utc))
};
case TypeCode.Uuid:
return new Value
{
StringValue = Convert.ToString(value, InvariantCulture)
};
case TypeCode.Array:
if (value is IEnumerable enumerable)
{
Expand Down Expand Up @@ -516,6 +521,20 @@ private object ConvertToClrTypeImpl(Value wireValue, System.Type targetClrType,
}
}

if (targetClrType == typeof(Guid))
{
switch (wireValue.KindCase)
{
case Value.KindOneofCase.NullValue:
return null;
case Value.KindOneofCase.StringValue:
return Guid.Parse(wireValue.StringValue);
default:
throw new InvalidOperationException(
$"Invalid Type conversion from {wireValue.KindCase} to {targetClrType.FullName}");
}
}

if (targetClrType == typeof(string))
{
switch (wireValue.KindCase)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,11 @@
/// </summary>
public static SpannerDbType PgOid { get; } = new SpannerDbType(TypeCode.Int64, TypeAnnotationCode.PgOid);

/// <summary>
/// Representation of Spanner UUID type.
/// </summary>
public static SpannerDbType Uuid { get; } = new SpannerDbType(TypeCode.Uuid);

private static readonly Dictionary<V1.Type, SpannerDbType> s_simpleTypes
= new Dictionary<V1.Type, SpannerDbType>
{
Expand All @@ -119,7 +124,8 @@
{ new V1.Type { Code = TypeCode.Json, TypeAnnotation = TypeAnnotationCode.PgJsonb }, PgJsonb },
{ new V1.Type { Code = TypeCode.Numeric }, Numeric },
{ new V1.Type { Code = TypeCode.Numeric, TypeAnnotation = TypeAnnotationCode.PgNumeric }, PgNumeric },
{ new V1.Type { Code = TypeCode.Int64, TypeAnnotation = TypeAnnotationCode.PgOid }, PgOid }
{ new V1.Type { Code = TypeCode.Int64, TypeAnnotation = TypeAnnotationCode.PgOid }, PgOid },
{ new V1.Type { Code = TypeCode.Uuid }, Uuid }
};

internal static SpannerDbType FromType(V1.Type type) =>
Expand Down Expand Up @@ -212,6 +218,8 @@
return DbType.Binary;
case TypeCode.Json:
return DbType.String;
case TypeCode.Uuid:
return DbType.Uuid;

Check failure on line 222 in apis/Google.Cloud.Spanner.Data/Google.Cloud.Spanner.Data/SpannerDbType.cs

View workflow job for this annotation

GitHub Actions / test

'DbType' does not contain a definition for 'Uuid' and no accessible extension method 'Uuid' accepting a first argument of type 'DbType' could be found (are you missing a using directive or an assembly reference?)

Check failure on line 222 in apis/Google.Cloud.Spanner.Data/Google.Cloud.Spanner.Data/SpannerDbType.cs

View workflow job for this annotation

GitHub Actions / build ('Google\.Cloud\.[M-Z].*')

'DbType' does not contain a definition for 'Uuid' and no accessible extension method 'Uuid' accepting a first argument of type 'DbType' could be found (are you missing a using directive or an assembly reference?)

Check failure on line 222 in apis/Google.Cloud.Spanner.Data/Google.Cloud.Spanner.Data/SpannerDbType.cs

View workflow job for this annotation

GitHub Actions / build ('Google\.Cloud\.[M-Z].*')

'DbType' does not contain a definition for 'Uuid' and no accessible extension method 'Uuid' accepting a first argument of type 'DbType' could be found (are you missing a using directive or an assembly reference?)
default:
return DbType.Object;
}
Expand Down Expand Up @@ -256,6 +264,8 @@
return typeof(SpannerNumeric);
case TypeCode.Json:
return typeof(string);
case TypeCode.Uuid:
return typeof(Guid);
default:
// If we don't recognize it, we use the protobuf Value well-known type.
// But since, as of June 2024, we support protobuf, we need to handle Value
Expand Down Expand Up @@ -286,6 +296,7 @@
DbType.VarNumeric => Numeric,
DbType.Object => Unspecified,
DbType.String => String,
DbType.Uuid => Uuid,

Check failure on line 299 in apis/Google.Cloud.Spanner.Data/Google.Cloud.Spanner.Data/SpannerDbType.cs

View workflow job for this annotation

GitHub Actions / diff

'DbType' does not contain a definition for 'Uuid' and no accessible extension method 'Uuid' accepting a first argument of type 'DbType' could be found (are you missing a using directive or an assembly reference?)

Check failure on line 299 in apis/Google.Cloud.Spanner.Data/Google.Cloud.Spanner.Data/SpannerDbType.cs

View workflow job for this annotation

GitHub Actions / test

'DbType' does not contain a definition for 'Uuid' and no accessible extension method 'Uuid' accepting a first argument of type 'DbType' could be found (are you missing a using directive or an assembly reference?)

Check failure on line 299 in apis/Google.Cloud.Spanner.Data/Google.Cloud.Spanner.Data/SpannerDbType.cs

View workflow job for this annotation

GitHub Actions / build ('Google\.Cloud\.[M-Z].*')

'DbType' does not contain a definition for 'Uuid' and no accessible extension method 'Uuid' accepting a first argument of type 'DbType' could be found (are you missing a using directive or an assembly reference?)

Check failure on line 299 in apis/Google.Cloud.Spanner.Data/Google.Cloud.Spanner.Data/SpannerDbType.cs

View workflow job for this annotation

GitHub Actions / build ('Google\.Cloud\.[M-Z].*')

'DbType' does not contain a definition for 'Uuid' and no accessible extension method 'Uuid' accepting a first argument of type 'DbType' could be found (are you missing a using directive or an assembly reference?)
_ => throw new ArgumentOutOfRangeException(nameof(DbType), dbType, null),
};

Expand Down Expand Up @@ -411,6 +422,10 @@
{
return new SpannerDbType(descriptor.FullName);
}
if (type == typeof(Guid))
{
return Uuid;
}
return Unspecified;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,11 @@ internal SpannerDbType GetConfiguredSpannerDbType(SpannerConversionOptions optio
{
return options.DateTimeToConfiguredSpannerType;
}

if (Value is Guid)
{
return options.GuidToConfiguredSpannerType;
}
}

// If we are here, use defaults.
Expand Down
Loading