// Note: the options can *currently* be null because we're not using them, but // every call site should check that it could provide options if they become required. internal Value ToProtobufValue(object value, SpannerConversionOptions options) { if (value == null || value is DBNull) { return(Value.ForNull()); } // If we add any other special values, we can create an interface to delegate to instead. if (value is CommitTimestamp ts) { return(ts.ToProtobufValue(this)); } switch (TypeCode) { case TypeCode.Bytes: if (value is string s) { return(new Value { StringValue = s }); } if (value is byte[] bArray) { return(new Value { StringValue = Convert.ToBase64String(bArray) }); } throw new ArgumentException("TypeCode.Bytes only supports string and byte[]", nameof(value)); case TypeCode.Bool: return(new Value { BoolValue = Convert.ToBoolean(value) }); case TypeCode.String: if (value is DateTime dateTime) { // If the value is a DateTime, we always convert using XmlConvert. // This allows us to convert back to a datetime reliably from the // resulting string (so roundtrip works properly if the developer uses // a string as a backing field for a datetime for whatever reason). return(new Value { StringValue = XmlConvert.ToString(dateTime, XmlDateTimeSerializationMode.Utc) }); } // All the other conversions will fail naturally, but let's make sure we don't convert structs // to strings. if (value is SpannerStruct) { throw new ArgumentException("SpannerStruct cannot be used for string parameters", nameof(value)); } return(new Value { StringValue = Convert.ToString(value, InvariantCulture) }); case TypeCode.Int64: return(new Value { StringValue = Convert.ToInt64(value, InvariantCulture) .ToString(InvariantCulture) }); case TypeCode.Float64: return(new Value { NumberValue = Convert.ToDouble(value, InvariantCulture) }); case TypeCode.Timestamp: return(new Value { StringValue = XmlConvert.ToString(Convert.ToDateTime(value, InvariantCulture), XmlDateTimeSerializationMode.Utc) }); case TypeCode.Date: return(new Value { StringValue = StripTimePart( XmlConvert.ToString(Convert.ToDateTime(value, InvariantCulture), XmlDateTimeSerializationMode.Utc)) }); case TypeCode.Array: if (value is IEnumerable enumerable) { return(Value.ForList( enumerable.Cast <object>() .Select(x => ArrayElementType.ToProtobufValue(x, options)).ToArray())); } throw new ArgumentException("The given array instance needs to implement IEnumerable."); case TypeCode.Struct: if (value is SpannerStruct spannerStruct) { return(new Value { ListValue = new ListValue { Values = { spannerStruct.Select(f => f.Type.ToProtobufValue(f.Value, options)) } } }); } throw new ArgumentException("Struct parameters must be of type SpannerStruct"); case TypeCode.Numeric: if (value is SpannerNumeric spannerNumeric) { return(Value.ForString(spannerNumeric.ToString())); } if (value is string str) { return(Value.ForString(SpannerNumeric.Parse(str).ToString())); } if (value is float || value is double || value is decimal) { // We throw if there's a loss of precision. We could use // LossOfPrecisionHandling.Truncate but GoogleSQL documentation requests to // use half-away-from-zero rounding but the SpannerNumeric implementation // truncates instead. return(Value.ForString(SpannerNumeric.FromDecimal( Convert.ToDecimal(value, InvariantCulture), LossOfPrecisionHandling.Throw).ToString())); } if (value is sbyte || value is short || value is int || value is long) { SpannerNumeric numericValue = Convert.ToInt64(value, InvariantCulture); return(Value.ForString(numericValue.ToString())); } if (value is byte || value is ushort || value is uint || value is ulong) { SpannerNumeric numericValue = Convert.ToUInt64(value, InvariantCulture); return(Value.ForString(numericValue.ToString())); } throw new ArgumentException("Numeric parameters must be of type SpannerNumeric or string"); default: throw new ArgumentOutOfRangeException(nameof(TypeCode), TypeCode, null); } }
internal Value ToProtobufValue(object value) { if (value == null || value is DBNull) { return(Value.ForNull()); } switch (TypeCode) { case TypeCode.Bytes: if (value is string s) { return(new Value { StringValue = s }); } if (value is byte[] bArray) { return(new Value { StringValue = Convert.ToBase64String(bArray) }); } throw new ArgumentException("TypeCode.Bytes only supports string and byte[]", nameof(value)); case TypeCode.Bool: return(new Value { BoolValue = Convert.ToBoolean(value) }); case TypeCode.String: if (value is DateTime dateTime) { // If the value is a DateTime, we always convert using XmlConvert. // This allows us to convert back to a datetime reliably from the // resulting string (so roundtrip works properly if the developer uses // a string as a backing field for a datetime for whatever reason). return(new Value { StringValue = XmlConvert.ToString(dateTime, XmlDateTimeSerializationMode.Utc) }); } return(new Value { StringValue = Convert.ToString(value, InvariantCulture) }); case TypeCode.Int64: return(new Value { StringValue = Convert.ToInt64(value, InvariantCulture) .ToString(InvariantCulture) }); case TypeCode.Float64: return(new Value { NumberValue = Convert.ToDouble(value, InvariantCulture) }); case TypeCode.Timestamp: return(new Value { StringValue = XmlConvert.ToString(Convert.ToDateTime(value, InvariantCulture), XmlDateTimeSerializationMode.Utc) }); case TypeCode.Date: return(new Value { StringValue = StripTimePart( XmlConvert.ToString(Convert.ToDateTime(value, InvariantCulture), XmlDateTimeSerializationMode.Utc)) }); case TypeCode.Array: if (value is IEnumerable enumerable) { return(Value.ForList( enumerable.Cast <object>() .Select(x => ArrayElementType.ToProtobufValue(x)).ToArray())); } throw new ArgumentException("The given array instance needs to implement IEnumerable."); case TypeCode.Struct: if (value is IDictionary dictionary) { var structValue = new Struct(); foreach (var key in dictionary.Keys) { string keyString = Convert.ToString(key, InvariantCulture); if (!StructMembers.ContainsKey(keyString)) { throw new ArgumentException("The given struct instance has members not defined in the Struct.", nameof(value)); } structValue.Fields[keyString] = StructMembers[keyString].ToProtobufValue( dictionary[key]); } return(Value.ForStruct(structValue)); } throw new ArgumentException("The given struct instance needs to implement IDictionary."); default: throw new ArgumentOutOfRangeException(nameof(TypeCode), TypeCode, null); } }