internal static StatementResult CreateSingleColumnResultSet(V1.Type type, string col, params object[] values) { ResultSet rs = new ResultSet { Metadata = new ResultSetMetadata { RowType = new StructType() }, }; rs.Metadata.RowType.Fields.Add(new StructType.Types.Field { Name = col, Type = type, }); foreach (object val in values) { ListValue row = new ListValue(); row.Values.Add(SpannerConverter.ToProtobufValue(type, val)); rs.Rows.Add(row); } return(CreateQuery(rs)); }
internal static Value ToProtobufValue(V1.Type type, object value) { if (value == null || value is DBNull) { return(Value.ForNull()); } switch (type.Code) { 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 => ToProtobufValue(type.ArrayElementType, x)).ToArray())); } throw new ArgumentException("The given array instance needs to implement IEnumerable."); case TypeCode.Struct: throw new ArgumentException("Struct values are not supported"); 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(type.Code), type.Code, null); } }