Skip to content
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
@@ -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
Expand Up @@ -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") }

Copy link
Copy Markdown
Contributor

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

{ "", SpannerDbType.Uuid, Guid.Parse("8f8c4746-17b1-4d9f-a634-58e11942095f") },
{ "", SpannerDbType.Enum, Fantasy.CharacterClass.Warrior },
});

var actual = key.ToProtobuf(SpannerConversionOptions.Default);
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,14 @@ public override long GetBytes(int ordinal, long fieldOffset, byte[] buffer, int
/// <returns>The value converted to a <see cref="SpannerDate"/>.</returns>
public SpannerDate GetSpannerDate(int i) => GetFieldValue<SpannerDate>(i);

/// <summary>
/// Gets the value of the specified column as a System.Enum.
/// </summary>
/// <typeparam name="T">Enum type</typeparam>
/// <param name="i">The index of the column to retrieve.</param>
/// <returns>The value converted to an enum, if defined.</returns>
public T GetEnum<T>(int i) where T : System.Enum => GetFieldValue<T>(i);

/// <inheritdoc />
public override object GetValue(int i) => this[i];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You will need to add some logic in SpannerDbType.StringParsing to handle enum member names.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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?

I understood this as being a conversion method for reading the response from Spanner from a SELECT query and from the type.proto definition it should come formatted as a string containing the decimal Enums representation:

  // Encoded as `string`, in decimal format.
  ENUM = 14;

Are there other uses for this?

You will need to add some logic in SpannerDbType.StringParsing to handle enum member names.

Good callout, will address this part after a discussion about Enums FQNs

}
Comment thread
efevans marked this conversation as resolved.

return ConvertToClrTypeImpl(protobufValue, targetClrType, options);
}

Expand Down Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>
{
Expand Down Expand Up @@ -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,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
Loading