diff --git a/apis/Google.Cloud.Spanner.Data/Google.Cloud.Spanner.Data.CommonTesting/TestProtos/Fantasy.cs b/apis/Google.Cloud.Spanner.Data/Google.Cloud.Spanner.Data.CommonTesting/TestProtos/Fantasy.cs new file mode 100644 index 000000000000..6df8a801164e --- /dev/null +++ b/apis/Google.Cloud.Spanner.Data/Google.Cloud.Spanner.Data.CommonTesting/TestProtos/Fantasy.cs @@ -0,0 +1,51 @@ +// +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: fantasy.proto +// +#pragma warning disable 1591, 0612, 3021, 8981 +#region Designer generated code + +using pb = global::Google.Protobuf; +using pbc = global::Google.Protobuf.Collections; +using pbr = global::Google.Protobuf.Reflection; +using scg = global::System.Collections.Generic; +namespace Fantasy { + + /// Holder for reflection information generated from fantasy.proto + public static partial class FantasyReflection { + + #region Descriptor + /// File descriptor for fantasy.proto + public static pbr::FileDescriptor Descriptor { + get { return descriptor; } + } + private static pbr::FileDescriptor descriptor; + + static FantasyReflection() { + byte[] descriptorData = global::System.Convert.FromBase64String( + string.Concat( + "Cg1mYW50YXN5LnByb3RvEgdmYW50YXN5Kl8KDkNoYXJhY3RlckNsYXNzEh8K", + "G0NIQVJBQ1RFUl9DTEFTU19VTlNQRUNJRklFRBAAEgsKB1dBUlJJT1IQARII", + "CgRNQUdFEAISCQoFUk9HVUUQAxIKCgZDTEVSSUMQBEIKqgIHRmFudGFzeWIG", + "cHJvdG8z")); + descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData, + new pbr::FileDescriptor[] { }, + new pbr::GeneratedClrTypeInfo(new[] {typeof(global::Fantasy.CharacterClass), }, null, null)); + } + #endregion + + } + #region Enums + public enum CharacterClass { + [pbr::OriginalName("CHARACTER_CLASS_UNSPECIFIED")] Unspecified = 0, + [pbr::OriginalName("WARRIOR")] Warrior = 1, + [pbr::OriginalName("MAGE")] Mage = 2, + [pbr::OriginalName("ROGUE")] Rogue = 3, + [pbr::OriginalName("CLERIC")] Cleric = 4, + } + + #endregion + +} + +#endregion Designer generated code diff --git a/apis/Google.Cloud.Spanner.Data/Google.Cloud.Spanner.Data.CommonTesting/TestProtos/fantasy.proto b/apis/Google.Cloud.Spanner.Data/Google.Cloud.Spanner.Data.CommonTesting/TestProtos/fantasy.proto new file mode 100644 index 000000000000..ba77127ca511 --- /dev/null +++ b/apis/Google.Cloud.Spanner.Data/Google.Cloud.Spanner.Data.CommonTesting/TestProtos/fantasy.proto @@ -0,0 +1,13 @@ +syntax = "proto3"; + +package fantasy; + +option csharp_namespace = "Fantasy"; + +enum CharacterClass { + CHARACTER_CLASS_UNSPECIFIED = 0; + WARRIOR = 1; + MAGE = 2; + ROGUE = 3; + CLERIC = 4; +} diff --git a/apis/Google.Cloud.Spanner.Data/Google.Cloud.Spanner.Data.Tests/KeysTests.cs b/apis/Google.Cloud.Spanner.Data/Google.Cloud.Spanner.Data.Tests/KeysTests.cs index 275a90e807ee..ee18b328ff93 100644 --- a/apis/Google.Cloud.Spanner.Data/Google.Cloud.Spanner.Data.Tests/KeysTests.cs +++ b/apis/Google.Cloud.Spanner.Data/Google.Cloud.Spanner.Data.Tests/KeysTests.cs @@ -41,7 +41,8 @@ public void CreateKeyFromParameterCollection() { "", SpannerDbType.PgOid, 2 }, { "", SpannerDbType.String, "test" }, { "", SpannerDbType.Timestamp, new DateTime(2021, 9, 10, 9, 37, 10, DateTimeKind.Utc) }, - { "", SpannerDbType.Uuid, Guid.Parse("8f8c4746-17b1-4d9f-a634-58e11942095f") } + { "", SpannerDbType.Uuid, Guid.Parse("8f8c4746-17b1-4d9f-a634-58e11942095f") }, + { "", SpannerDbType.Enum, Fantasy.CharacterClass.Warrior }, }); var actual = key.ToProtobuf(SpannerConversionOptions.Default); @@ -63,7 +64,8 @@ public void CreateKeyFromParameterCollection() Value.ForString("2"), Value.ForString("test"), Value.ForString("2021-09-10T09:37:10Z"), - Value.ForString("8f8c4746-17b1-4d9f-a634-58e11942095f") + Value.ForString("8f8c4746-17b1-4d9f-a634-58e11942095f"), + Value.ForString("1") } }; Assert.Equal(expected, actual); diff --git a/apis/Google.Cloud.Spanner.Data/Google.Cloud.Spanner.Data/SpannerDataReader.cs b/apis/Google.Cloud.Spanner.Data/Google.Cloud.Spanner.Data/SpannerDataReader.cs index c117d6f868fe..a85f351544a3 100644 --- a/apis/Google.Cloud.Spanner.Data/Google.Cloud.Spanner.Data/SpannerDataReader.cs +++ b/apis/Google.Cloud.Spanner.Data/Google.Cloud.Spanner.Data/SpannerDataReader.cs @@ -250,6 +250,14 @@ public override long GetBytes(int ordinal, long fieldOffset, byte[] buffer, int /// The value converted to a . public SpannerDate GetSpannerDate(int i) => GetFieldValue(i); + /// + /// Gets the value of the specified column as a System.Enum. + /// + /// Enum type + /// The index of the column to retrieve. + /// The value converted to an enum, if defined. + public T GetEnum(int i) where T : System.Enum => GetFieldValue(i); + /// public override object GetValue(int i) => this[i]; diff --git a/apis/Google.Cloud.Spanner.Data/Google.Cloud.Spanner.Data/SpannerDbType.ValueConversion.cs b/apis/Google.Cloud.Spanner.Data/Google.Cloud.Spanner.Data/SpannerDbType.ValueConversion.cs index 7ab24c9a2514..368b05323759 100644 --- a/apis/Google.Cloud.Spanner.Data/Google.Cloud.Spanner.Data/SpannerDbType.ValueConversion.cs +++ b/apis/Google.Cloud.Spanner.Data/Google.Cloud.Spanner.Data/SpannerDbType.ValueConversion.cs @@ -110,6 +110,11 @@ internal object ConvertToClrType(Value protobufValue, System.Type targetClrType, return Guid.Parse(ConvertToClrTypeImpl(protobufValue, options)); } + if (targetClrType.IsEnum) + { + return System.Enum.ToObject(targetClrType, ConvertToClrTypeImpl(protobufValue, options)); + } + return ConvertToClrTypeImpl(protobufValue, targetClrType, options); } @@ -306,6 +311,20 @@ internal Value ToProtobufValue(object value) return Value.ForString(V1.Interval.Parse(stringValue).ToString()); } throw new ArgumentException($"Interval parameters must be of type {typeof(Interval).FullName} or string"); + case TypeCode.Enum: + if (value is string enumStr) + { + return Value.ForString(enumStr); + } + if (value is System.Enum or sbyte or short or int or long) + { + return Value.ForString(Convert.ToInt64(value, InvariantCulture).ToString(InvariantCulture)); + } + if (value is byte or ushort or uint or ulong) + { + return Value.ForString(Convert.ToUInt64(value, InvariantCulture).ToString(InvariantCulture)); + } + throw new ArgumentException($"Enum parameters must be of one of the following types: System.Enum, string, sbyte, short, int, long, byte, ushort, uint, ulong"); default: throw new ArgumentOutOfRangeException(nameof(TypeCode), TypeCode, null); } diff --git a/apis/Google.Cloud.Spanner.Data/Google.Cloud.Spanner.Data/SpannerDbType.cs b/apis/Google.Cloud.Spanner.Data/Google.Cloud.Spanner.Data/SpannerDbType.cs index 824a0e80c738..5f3658a16ed6 100644 --- a/apis/Google.Cloud.Spanner.Data/Google.Cloud.Spanner.Data/SpannerDbType.cs +++ b/apis/Google.Cloud.Spanner.Data/Google.Cloud.Spanner.Data/SpannerDbType.cs @@ -111,6 +111,11 @@ public sealed partial class SpannerDbType /// public static SpannerDbType Uuid { get; } = new SpannerDbType(TypeCode.Uuid); + /// + /// Representation of Spanner Protobuf Enum type. + /// + public static SpannerDbType Enum { get; } = new SpannerDbType(TypeCode.Enum); + private static readonly Dictionary s_simpleTypes = new Dictionary { @@ -315,6 +320,10 @@ internal static SpannerDbType FromProtobufType(V1.Type type) return new SpannerDbType(type.StructType.Fields.Select(f => new StructField(f.Name, FromProtobufType(f.Type))).ToList()); case TypeCode.Proto: return new SpannerDbType(type.ProtoTypeFqn); + case TypeCode.Enum: + // This is handled here because equality logic prevent enum type working in s_simpleTypes, + // but we also don't want to carry arround the proto FQN since we dont use it + return Enum; default: return FromType(type); } diff --git a/apis/Google.Cloud.Spanner.Data/Google.Cloud.Spanner.Data/SpannerParameter.cs b/apis/Google.Cloud.Spanner.Data/Google.Cloud.Spanner.Data/SpannerParameter.cs index 1cd188077355..9ef368b676c5 100644 --- a/apis/Google.Cloud.Spanner.Data/Google.Cloud.Spanner.Data/SpannerParameter.cs +++ b/apis/Google.Cloud.Spanner.Data/Google.Cloud.Spanner.Data/SpannerParameter.cs @@ -150,7 +150,8 @@ internal object GetValidatedValue() + $"({nameof(SpannerDbType.Bool)}, {nameof(SpannerDbType.Int64)}, {nameof(SpannerDbType.Float32)} ,{nameof(SpannerDbType.Float64)}," + $" {nameof(SpannerDbType.Timestamp)}, {nameof(SpannerDbType.Date)}, {nameof(SpannerDbType.String)}," + $" {nameof(SpannerDbType.Bytes)}, {nameof(SpannerDbType.Json)}, {nameof(SpannerDbType.PgJsonb)}, {nameof(SpannerDbType.Numeric)}," - + $" {nameof(SpannerDbType.PgNumeric)}, {nameof(SpannerDbType.PgOid)}), {nameof(SpannerDbType.Interval)}, {nameof(SpannerDbType.Uuid)}"); + + $" {nameof(SpannerDbType.PgNumeric)}, {nameof(SpannerDbType.PgOid)}), {nameof(SpannerDbType.Interval)}, {nameof(SpannerDbType.Uuid)}," + + $" {nameof(SpannerDbType.Enum)}"); } return Value; }