-
Notifications
You must be signed in to change notification settings - Fork 408
feat(Spanner): Add support for Protobuf Enums #15699
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
base: main
Are you sure you want to change the base?
Changes from all commits
954c623
53468c5
0da2a91
f752fd3
fb83b37
49965ac
5cbc2b0
0a073d6
032daff
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| // <auto-generated> | ||
| // Generated by the protocol buffer compiler. DO NOT EDIT! | ||
| // source: fantasy.proto | ||
| // </auto-generated> | ||
| #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 { | ||
|
|
||
| /// <summary>Holder for reflection information generated from fantasy.proto</summary> | ||
| public static partial class FantasyReflection { | ||
|
|
||
| #region Descriptor | ||
| /// <summary>File descriptor for fantasy.proto</summary> | ||
| 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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -110,6 +110,11 @@ internal object ConvertToClrType(Value protobufValue, System.Type targetClrType, | |
| return Guid.Parse(ConvertToClrTypeImpl<string>(protobufValue, options)); | ||
| } | ||
|
|
||
| if (targetClrType.IsEnum) | ||
| { | ||
| return System.Enum.ToObject(targetClrType, ConvertToClrTypeImpl<long>(protobufValue, options)); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the case of a string are we sure it cannot be the enum member name (e.g. "Colors.Red") rather than the enum member value that is used? docs.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You will need to add some logic in
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I understood this as being a conversion method for reading the response from Spanner from a Are there other uses for this?
Good callout, will address this part after a discussion about Enums FQNs |
||
| } | ||
|
efevans marked this conversation as resolved.
|
||
|
|
||
| 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); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -111,6 +111,11 @@ public sealed partial class SpannerDbType | |
| /// </summary> | ||
| public static SpannerDbType Uuid { get; } = new SpannerDbType(TypeCode.Uuid); | ||
|
|
||
| /// <summary> | ||
| /// Representation of Spanner Protobuf Enum type. | ||
| /// </summary> | ||
| public static SpannerDbType Enum { get; } = new SpannerDbType(TypeCode.Enum); | ||
|
|
||
| private static readonly Dictionary<V1.Type, SpannerDbType> s_simpleTypes | ||
| = new Dictionary<V1.Type, SpannerDbType> | ||
| { | ||
|
|
@@ -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, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think this is true, Spanner will need the fully qualified name to figure out what type to cast to on it's end.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In my testing so far I haven't had a FQN to easily grab from unless I hardcoded it. Unlike Proto messages that expose the FQN as part of the C# compiled proto, Enums do not have this and as you point out our Spanner will need the FQN if we send a parameter as type Enum. In my testing, if the FQN isn't hard coded then there's no trivial way to get it so I've been adding parameters as Int64: cmdInsert.Parameters.Add("class", SpannerDbType.Int64, character.CharacterClass);Will address this more after a discussion on how we will handle Enum FQNs |
||
| // but we also don't want to carry arround the proto FQN since we dont use it | ||
| return Enum; | ||
| default: | ||
| return FromType(type); | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's add some integration tests to validate against a real spanner instance