public void CreateKeyFromParameterCollection() { var key = new Key( new SpannerParameterCollection { { "", SpannerDbType.Bool, true }, { "", SpannerDbType.Bytes, new byte[] { 1, 2, 3 } }, { "", SpannerDbType.Date, new DateTime(2021, 9, 10, 0, 0, 0, DateTimeKind.Utc) }, { "", SpannerDbType.Float64, 3.14 }, { "", SpannerDbType.Int64, 1 }, { "", SpannerDbType.Json, "{\"key\": \"value\"}" }, { "", SpannerDbType.Numeric, SpannerNumeric.Parse("10.1") }, { "", SpannerDbType.String, "test" }, { "", SpannerDbType.Timestamp, new DateTime(2021, 9, 10, 9, 37, 10, DateTimeKind.Utc) } }); var index = 0; Assert.True(key.KeyParts.Values[index++].BoolValue); Assert.Equal(Convert.ToBase64String(new byte[] { 1, 2, 3 }), key.KeyParts.Values[index++].StringValue); Assert.Equal("2021-09-10", key.KeyParts.Values[index++].StringValue); Assert.Equal(3.14, key.KeyParts.Values[index++].NumberValue); Assert.Equal("1", key.KeyParts.Values[index++].StringValue); Assert.Equal("{\"key\": \"value\"}", key.KeyParts.Values[index++].StringValue); Assert.Equal("10.1", key.KeyParts.Values[index++].StringValue); Assert.Equal("test", key.KeyParts.Values[index++].StringValue); Assert.Equal("2021-09-10T09:37:10Z", key.KeyParts.Values[index++].StringValue); }
public async Task UpdateDataWithNumericAsync(string projectId, string instanceId, string databaseId) { string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId}"; List <Venue> venues = new List <Venue> { new Venue { VenueId = 4, Revenue = SpannerNumeric.Parse("35000") }, new Venue { VenueId = 19, Revenue = SpannerNumeric.Parse("104500") }, new Venue { VenueId = 42, Revenue = SpannerNumeric.Parse("99999999999999999999999999999.99") }, }; // Create connection to Cloud Spanner. using var connection = new SpannerConnection(connectionString); await connection.OpenAsync(); await Task.WhenAll(venues.Select(venue => { // Update rows in the Venues table. using var cmd = connection.CreateUpdateCommand("Venues", new SpannerParameterCollection { { "VenueId", SpannerDbType.Int64, venue.VenueId }, { "Revenue", SpannerDbType.Numeric, venue.Revenue } }); return(cmd.ExecuteNonQueryAsync()); })); Console.WriteLine("Data updated."); }
public void ParseToStringRoundTrip( [CombinatorialValues( "123456789012345678901234567.890123456", "79228162514264337593543950335.000000001", MaxText, EpsilonText, "5", "50", "50.01", "0.1", "0.123", "0.00123" )] string text, bool negate) { if (negate) { text = "-" + text; } SpannerNumeric numeric = SpannerNumeric.Parse(text); Assert.Equal(text, numeric.ToString()); // Check that TryParse works too Assert.True(SpannerNumeric.TryParse(text, out var numeric2)); Assert.Equal(numeric, numeric2); }
public void Subtraction_Overflow(string leftText, string rightText) { var left = SpannerNumeric.Parse(leftText); var right = SpannerNumeric.Parse(rightText); Assert.Throws <OverflowException>(() => left - right); }
public async Task <List <Venue> > QueryDataWithNumericParameterAsync(string projectId, string instanceId, string databaseId) { string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId}"; using var connection = new SpannerConnection(connectionString); using var cmd = connection.CreateSelectCommand( "SELECT VenueId, Revenue FROM Venues WHERE Revenue < @maxRevenue", new SpannerParameterCollection { { "maxRevenue", SpannerDbType.Numeric, SpannerNumeric.Parse("100000") } }); var venues = new List <Venue>(); using var reader = await cmd.ExecuteReaderAsync(); while (await reader.ReadAsync()) { venues.Add(new Venue { VenueId = reader.GetFieldValue <int>("VenueId"), Revenue = reader.GetFieldValue <SpannerNumeric>("Revenue") }); } return(venues); }
public void UnaryNegation(string text) { var positive = SpannerNumeric.Parse(text); var negative = SpannerNumeric.Parse("-" + text); Assert.Equal(negative, -positive); Assert.Equal(positive, -negative); }
public void Subtraction_Valid(string leftText, string rightText, string expectedText) { var left = SpannerNumeric.Parse(leftText); var right = SpannerNumeric.Parse(rightText); var expected = SpannerNumeric.Parse(expectedText); Assert.Equal(expected, left - right); }
public void Equality(string controlText, string[] equalText, string[] unequalText) { SpannerNumeric control = SpannerNumeric.Parse(controlText); SpannerNumeric[] equal = equalText.Select(SpannerNumeric.Parse).ToArray(); SpannerNumeric[] unequal = unequalText.Select(SpannerNumeric.Parse).ToArray(); EqualityTester.AssertEqual(control, equal, unequal); EqualityTester.AssertEqualityOperators(control, equal, unequal); }
public void CompareTo_Unequal(string smallerText, string biggerText) { var smaller = SpannerNumeric.Parse(smallerText); var bigger = SpannerNumeric.Parse(biggerText); Assert.InRange(smaller.CompareTo(bigger), int.MinValue, -1); Assert.InRange(bigger.CompareTo(smaller), 1, int.MaxValue); Assert.InRange(((IComparable)smaller).CompareTo(bigger), int.MinValue, -1); Assert.InRange(((IComparable)bigger).CompareTo(smaller), 1, int.MaxValue); }
public void CompareTo_Equal(string text) { var value = SpannerNumeric.Parse(text); // To compare with a different instance. var value1 = SpannerNumeric.Parse(text); Assert.Equal(0, value.CompareTo(value)); Assert.Equal(0, (IComparable)value.CompareTo(value)); Assert.Equal(0, value1.CompareTo(value)); Assert.Equal(0, (IComparable)value1.CompareTo(value)); }
public void ToDecimal_Overflow(LossOfPrecisionHandling handling, bool negate) { string text = "79228162514264337593543950336"; // decimal.MaxValue + 1 SpannerNumeric numeric = SpannerNumeric.Parse(text); if (negate) { numeric = -numeric; } Assert.Throws <OverflowException>(() => numeric.ToDecimal(handling)); }
public void CompareTo_NegativeZero() { // These really are the same value... we don't differentiate. var regularZero = SpannerNumeric.Zero; var negativeZero = SpannerNumeric.Parse("-0"); Assert.Equal(0, regularZero.CompareTo(negativeZero)); Assert.Equal(0, (IComparable)regularZero.CompareTo(negativeZero)); Assert.Equal(0, negativeZero.CompareTo(regularZero)); Assert.Equal(0, (IComparable)negativeZero.CompareTo(regularZero)); }
public void ConversionFromDecimal_Lossy(string decimalText, string expectedText) { decimal input = decimal.Parse(decimalText, CultureInfo.InvariantCulture); SpannerNumeric expected = SpannerNumeric.Parse(expectedText); SpannerNumeric conversionOutput = (SpannerNumeric)input; SpannerNumeric fromDecimalOutput = SpannerNumeric.FromDecimal(input, LossOfPrecisionHandling.Truncate); Assert.Equal(expected, conversionOutput); Assert.Equal(expected, fromDecimalOutput); Assert.Throws <ArgumentException>(() => SpannerNumeric.FromDecimal(input, LossOfPrecisionHandling.Throw)); }
public void ToDecimal_LossOfPrecision_Throw( [CombinatorialValues( "79228162514264337593543950335.000000001", // decimal.MaxValue + epsilon; doesn't count as overflow "123456789012345678900.123456789" // Simpler example of more significant digits than decimal can handle )] string text, bool negate) { if (negate) { text = "-" + text; } SpannerNumeric numeric = SpannerNumeric.Parse(text); Assert.Throws <InvalidOperationException>(() => numeric.ToDecimal(LossOfPrecisionHandling.Throw)); }
public void ToDecimal_Precise( [CombinatorialValues( "79228162514264337593543950335", // decimal.MaxValue "0.000000001", // Epsilon for numeric "12345678901234567890.123456789" // Maximum significant digits with 9dps )] string text, LossOfPrecisionHandling handling, bool negate) { if (negate) { text = "-" + text; } SpannerNumeric numeric = SpannerNumeric.Parse(text); decimal expected = decimal.Parse(text, CultureInfo.InvariantCulture); decimal actual = numeric.ToDecimal(handling); Assert.Equal(expected, actual); }
public void ConversionFromDecimal( [CombinatorialValues( "1", "10", "1.123456789", "79228162514264337593543950335", // decimal.MaxValue "1234567890123456789012345678" )] string text, bool negate) { if (negate) { text = "-" + text; } decimal parsed = decimal.Parse(text, CultureInfo.InvariantCulture); SpannerNumeric numeric = (SpannerNumeric)parsed; Assert.Equal(text, numeric.ToString()); }
public void TryParse_UnsupportedCultures(string text, string culture) { CultureInfo oldCulture = CultureInfo.CurrentCulture; try { CultureInfo.CurrentCulture = CultureInfo.CreateSpecificCulture(culture); // Just to demonstrate that these numbers are properly parsed with the given culture. // We don't parse them because we don't support the culture. Assert.True(Decimal.TryParse(text, out decimal decimalValue)); Assert.False(SpannerNumeric.TryParse(text, out var value)); Assert.Equal(SpannerNumeric.Zero, value); } finally { CultureInfo.CurrentCulture = oldCulture; } }
public void CreateKeyRangeOpenOpen() { var openOpen = KeyRange.OpenOpen(new Key(SpannerNumeric.Parse("0.1")), new Key(SpannerNumeric.Parse("100.1"))); Assert.False(openOpen.BeginInclusive); Assert.False(openOpen.EndInclusive); Assert.Equal(new Key(SpannerNumeric.Parse("0.1")), openOpen.BeginAt); Assert.Equal(new Key(SpannerNumeric.Parse("100.1")), openOpen.End); Assert.Equal(new V1.KeyRange { StartOpen = new ListValue { Values = { new [] { new Value { StringValue = "0.1" } } } }, EndOpen = new ListValue { Values = { new [] { new Value { StringValue = "100.1" } } } } }, openOpen.Protobuf); }
public void ToDecimal_LossOfPrecision_Truncate( [CombinatorialValues( "79228162514264337593543950335.0000000001", // decimal.MaxValue + epsilon; doesn't count as overflow "123456789012345678900.123456789" // Simpler example of more significant digits than decimal can handle )] string text, bool negate) { if (negate) { text = "-" + text; } SpannerNumeric numeric = SpannerNumeric.Parse(text); // Decimal.Parse will silently lose precision decimal expected = decimal.Parse(text, CultureInfo.InvariantCulture); decimal actual = numeric.ToDecimal(LossOfPrecisionHandling.Truncate); Assert.Equal(expected, actual); // Conversion via the explicit conversion should do the same thing decimal actual2 = (decimal)numeric; Assert.Equal(expected, actual2); }
public void CreateKeyFromValues() { var key = new Key( true, new byte[] { 1, 2, 3 }, // DATE cannot be created directly from a Clr type. 3.14, 1L, // JSON cannot be created directly from a Clr type. SpannerNumeric.Parse("10.1"), "test", new DateTime(2021, 9, 10, 9, 37, 10, DateTimeKind.Utc) ); var index = 0; Assert.True(key.KeyParts.Values[index++].BoolValue); Assert.Equal(Convert.ToBase64String(new byte[] { 1, 2, 3 }), key.KeyParts.Values[index++].StringValue); Assert.Equal(3.14, key.KeyParts.Values[index++].NumberValue); Assert.Equal("1", key.KeyParts.Values[index++].StringValue); Assert.Equal("10.1", key.KeyParts.Values[index++].StringValue); Assert.Equal("test", key.KeyParts.Values[index++].StringValue); Assert.Equal("2021-09-10T09:37:10Z", key.KeyParts.Values[index++].StringValue); }
public async Task TestEntity_ConvertValuesWithoutPrecisionLossOrOverflow_Succeeds() { var sql = $"SELECT `t`.`Id`, `t`.`ByteCol`, `t`.`DecimalCol`, `t`.`FloatCol`" + $"{Environment.NewLine}FROM `TestEntities` AS `t`{Environment.NewLine}" + $"WHERE `t`.`Id` = @__p_0{Environment.NewLine}LIMIT 1"; _fixture.SpannerMock.AddOrUpdateStatementResult(sql, StatementResult.CreateResultSet( new List <Tuple <V1.Type, string> > { new Tuple <V1.Type, string>(new V1.Type { Code = V1.TypeCode.Int64 }, "Id"), new Tuple <V1.Type, string>(new V1.Type { Code = V1.TypeCode.Int64 }, "ByteCol"), new Tuple <V1.Type, string>(new V1.Type { Code = V1.TypeCode.Numeric }, "DecimalCol"), new Tuple <V1.Type, string>(new V1.Type { Code = V1.TypeCode.Float64 }, "FloatCol"), }, new List <object[]> { new object[] { 1L, 1L, "3.14", 1.0d }, } )); using var db = new TypeConversionDbContext(ConnectionString); var row = await db.TestEntities.FindAsync(1L); Assert.Equal(1L, row.Id); Assert.Equal((byte)1, row.ByteCol); Assert.Equal(SpannerNumeric.Parse("3.14"), SpannerNumeric.FromDecimal(row.DecimalCol, LossOfPrecisionHandling.Truncate)); Assert.Equal(1.0d, row.FloatCol); }
private object ConvertToClrTypeImpl(Value wireValue, System.Type targetClrType, SpannerConversionOptions options) { //If the wireValue itself is assignable to the target type, just return it //This covers both typeof(Value) and typeof(object). if (wireValue == null || targetClrType == null || targetClrType == typeof(Value)) { return(wireValue); } if (wireValue.KindCase == Value.KindOneofCase.StructValue) { throw new InvalidOperationException($"google.protobuf.Struct values are invalid in Spanner"); } // targetClrType should be one of the values returned by DefaultClrType if (targetClrType == typeof(bool)) { switch (wireValue.KindCase) { case Value.KindOneofCase.NullValue: return(default(bool)); case Value.KindOneofCase.StringValue: if (TypeCode == TypeCode.Int64) { return(Convert.ToBoolean(Convert.ToInt64(wireValue.StringValue, InvariantCulture))); } return(Convert.ToBoolean(wireValue.StringValue)); case Value.KindOneofCase.BoolValue: return(wireValue.BoolValue); case Value.KindOneofCase.NumberValue: return(Convert.ToBoolean(wireValue.NumberValue)); default: throw new InvalidOperationException( $"Invalid Type conversion from {wireValue.KindCase} to {targetClrType.FullName}"); } } if (targetClrType == typeof(char)) { switch (wireValue.KindCase) { case Value.KindOneofCase.BoolValue: return(Convert.ToChar(wireValue.BoolValue)); case Value.KindOneofCase.NumberValue: return(Convert.ToChar(wireValue.NumberValue)); case Value.KindOneofCase.NullValue: return(default(char)); case Value.KindOneofCase.StringValue: if (TypeCode == TypeCode.Int64) { return(Convert.ToChar(Convert.ToInt64(wireValue.StringValue, InvariantCulture))); } return(Convert.ToChar(wireValue.StringValue)); default: throw new InvalidOperationException( $"Invalid Type conversion from {wireValue.KindCase} to {targetClrType.FullName}"); } } if (targetClrType == typeof(long)) { switch (wireValue.KindCase) { case Value.KindOneofCase.BoolValue: return(Convert.ToInt64(wireValue.BoolValue)); case Value.KindOneofCase.NumberValue: return(Convert.ToInt64(wireValue.NumberValue)); case Value.KindOneofCase.NullValue: return(default(long)); case Value.KindOneofCase.StringValue: return(Convert.ToInt64(wireValue.StringValue, InvariantCulture)); default: throw new InvalidOperationException( $"Invalid Type conversion from {wireValue.KindCase} to {targetClrType.FullName}"); } } if (targetClrType == typeof(ulong)) { switch (wireValue.KindCase) { case Value.KindOneofCase.BoolValue: return(Convert.ToUInt64(wireValue.BoolValue)); case Value.KindOneofCase.NumberValue: return(Convert.ToUInt64(wireValue.NumberValue)); case Value.KindOneofCase.NullValue: return(default(ulong)); case Value.KindOneofCase.StringValue: return(Convert.ToUInt64(wireValue.StringValue, InvariantCulture)); default: throw new InvalidOperationException( $"Invalid Type conversion from {wireValue.KindCase} to {targetClrType.FullName}"); } } if (targetClrType == typeof(decimal)) { switch (wireValue.KindCase) { case Value.KindOneofCase.BoolValue: return(Convert.ToDecimal(wireValue.BoolValue)); case Value.KindOneofCase.NumberValue: return(Convert.ToDecimal(wireValue.NumberValue)); case Value.KindOneofCase.NullValue: return(default(decimal)); case Value.KindOneofCase.StringValue: return(Convert.ToDecimal(wireValue.StringValue, InvariantCulture)); default: throw new InvalidOperationException( $"Invalid Type conversion from {wireValue.KindCase} to {targetClrType.FullName}"); } } if (targetClrType == typeof(double)) { switch (wireValue.KindCase) { case Value.KindOneofCase.BoolValue: return(Convert.ToDouble(wireValue.BoolValue)); case Value.KindOneofCase.NullValue: return(default(double)); case Value.KindOneofCase.NumberValue: return(wireValue.NumberValue); case Value.KindOneofCase.StringValue: if (string.Compare(wireValue.StringValue, "NaN", StringComparison.OrdinalIgnoreCase) == 0) { return(double.NaN); } if (string.Compare(wireValue.StringValue, "Infinity", StringComparison.OrdinalIgnoreCase) == 0) { return(double.PositiveInfinity); } if (string.Compare(wireValue.StringValue, "-Infinity", StringComparison.OrdinalIgnoreCase) == 0) { return(double.NegativeInfinity); } return(Convert.ToDouble(wireValue.StringValue, InvariantCulture)); default: throw new InvalidOperationException( $"Invalid Type conversion from {wireValue.KindCase} to {targetClrType.FullName}"); } } if (targetClrType == typeof(DateTime)) { switch (wireValue.KindCase) { case Value.KindOneofCase.NullValue: return(null); case Value.KindOneofCase.StringValue: return(XmlConvert.ToDateTime(wireValue.StringValue, XmlDateTimeSerializationMode.Utc)); default: throw new InvalidOperationException( $"Invalid Type conversion from {wireValue.KindCase} to {targetClrType.FullName}"); } } if (targetClrType == typeof(Timestamp)) { switch (wireValue.KindCase) { case Value.KindOneofCase.NullValue: return(null); case Value.KindOneofCase.StringValue: return(Protobuf.WellKnownTypes.Timestamp.Parser.ParseJson(wireValue.StringValue)); default: throw new InvalidOperationException( $"Invalid Type conversion from {wireValue.KindCase} to {targetClrType.FullName}"); } } if (targetClrType == typeof(string)) { switch (wireValue.KindCase) { case Value.KindOneofCase.NullValue: return(null); case Value.KindOneofCase.NumberValue: return(wireValue.NumberValue.ToString(InvariantCulture)); case Value.KindOneofCase.StringValue: return(wireValue.StringValue); case Value.KindOneofCase.BoolValue: return(wireValue.BoolValue.ToString()); default: return(wireValue.ToString()); } } if (targetClrType == typeof(byte[])) { switch (wireValue.KindCase) { case Value.KindOneofCase.NullValue: return(null); case Value.KindOneofCase.StringValue: return(Convert.FromBase64String(wireValue.StringValue)); default: throw new ArgumentOutOfRangeException(); } } if (targetClrType == typeof(SpannerStruct)) { if (TypeCode != TypeCode.Struct) { throw new ArgumentException( $"{targetClrType.FullName} can only be used for struct results"); } if (wireValue.KindCase != Value.KindOneofCase.ListValue) { throw new ArgumentException( $"Invalid conversion from {wireValue.KindCase} to {targetClrType.FullName}"); } var values = wireValue.ListValue.Values; // StructFields will definitely be non-null, as we can only construct SpannerDbTypes of structs // by passing them fields. var ret = new SpannerStruct(); if (StructFields.Count != values.Count) { throw new InvalidOperationException( $"Incorrect number of struct fields. SpannerDbType has {StructFields.Count}; list has {values.Count}"); } // Could use Zip, but this is probably simpler. for (int i = 0; i < values.Count; i++) { var field = StructFields[i]; ret.Add(field.Name, field.Type, field.Type.ConvertToClrType(values[i], typeof(object), options, topLevel: false)); } return(ret); } // It's questionable as to whether we want to support this, but it does no harm to do so. if (typeof(IDictionary).IsAssignableFrom(targetClrType)) { if (targetClrType == typeof(IDictionary)) { // Default type depends on whether it's a struct or not. targetClrType = TypeCode == TypeCode.Struct ? typeof(Dictionary <string, object>) : typeof(Dictionary <int, object>); } //a bit of recursion here... IDictionary dictionary = (IDictionary)Activator.CreateInstance(targetClrType); var itemType = targetClrType.GetGenericArguments().Skip(1).FirstOrDefault() ?? typeof(object); switch (wireValue.KindCase) { case Value.KindOneofCase.ListValue: if (TypeCode == TypeCode.Struct) { for (int i = 0; i < StructFields.Count; i++) { var elementValue = wireValue.ListValue.Values[i]; var field = StructFields[i]; dictionary[field.Name] = field.Type.ConvertToClrType(elementValue, itemType, options, topLevel: false); } } else { var i = 0; foreach (var listItemValue in wireValue.ListValue.Values) { dictionary[i] = ArrayElementType.ConvertToClrType(listItemValue, itemType, options, topLevel: false); i++; } } return(dictionary); default: throw new ArgumentException( $"Invalid Type conversion from {wireValue.KindCase} to {targetClrType.FullName}"); } } if (targetClrType.IsArray) { switch (wireValue.KindCase) { case Value.KindOneofCase.NullValue: return(null); case Value.KindOneofCase.ListValue: var newArray = Array.CreateInstance( targetClrType.GetElementType(), wireValue.ListValue.Values.Count); var i = 0; foreach (var obj in wireValue.ListValue.Values.Select( x => ArrayElementType.ConvertToClrType(x, targetClrType.GetElementType(), options, topLevel: false))) { newArray.SetValue(obj, i); i++; } return(newArray); default: throw new InvalidOperationException( $"Invalid Type conversion from {wireValue.KindCase} to {targetClrType.FullName}"); } } if (targetClrType == typeof(SpannerNumeric)) { if (TypeCode != TypeCode.Numeric) { throw new ArgumentException($"{targetClrType.FullName} can only be used for numeric results"); } switch (wireValue.KindCase) { case Value.KindOneofCase.NullValue: return(null); case Value.KindOneofCase.StringValue: return(SpannerNumeric.Parse(wireValue.StringValue)); default: throw new InvalidOperationException( $"Invalid Type conversion from {wireValue.KindCase} to {targetClrType.FullName}"); } } if (typeof(IList).IsAssignableFrom(targetClrType)) { if (targetClrType == typeof(IList)) { targetClrType = typeof(List <object>); } switch (wireValue.KindCase) { case Value.KindOneofCase.NullValue: return(null); case Value.KindOneofCase.ListValue: var newList = (IList)Activator.CreateInstance(targetClrType); var itemType = targetClrType.GetGenericArguments().FirstOrDefault() ?? typeof(object); foreach (var obj in wireValue.ListValue.Values.Select( x => ArrayElementType.ConvertToClrType(x, itemType, options, topLevel: false))) { newList.Add(obj); } return(newList); default: throw new InvalidOperationException( $"Invalid Type conversion from {wireValue.KindCase} to {targetClrType.FullName}"); } } throw new ArgumentException( $"Invalid Type conversion from {wireValue.KindCase} to {targetClrType.FullName}"); }
// 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); } }
public void ConversionFromUInt64(ulong input) { SpannerNumeric value = input; Assert.Equal(input.ToString(CultureInfo.InvariantCulture), value.ToString()); }
public async Task ReadWriteTransaction() { decimal initialBudget1 = 1225250.00m; decimal initialBudget2 = 2250198.28m; _fixture.SpannerMock.AddOrUpdateStatementResult( "SELECT MarketingBudget FROM Albums WHERE SingerId = 1 AND AlbumId = 1", StatementResult.CreateSingleColumnResultSet(new V1.Type { Code = TypeCode.Numeric }, "MarketingBudget", initialBudget1)); _fixture.SpannerMock.AddOrUpdateStatementResult( "SELECT MarketingBudget FROM Albums WHERE SingerId = 2 AND AlbumId = 2", StatementResult.CreateSingleColumnResultSet(new V1.Type { Code = TypeCode.Numeric }, "MarketingBudget", initialBudget2)); string connectionString = $"Data Source=projects/p1/instances/i1/databases/d1;Host={_fixture.Host};Port={_fixture.Port}"; decimal transferAmount = 200000; decimal secondBudget = 0; decimal firstBudget = 0; using var connection = new SpannerConnection(connectionString, ChannelCredentials.Insecure); await connection.OpenAsync(); using (var transaction = await connection.BeginTransactionAsync()) { // Create statement to select the second album's data. var cmdLookup = connection.CreateSelectCommand( "SELECT MarketingBudget FROM Albums WHERE SingerId = 2 AND AlbumId = 2"); cmdLookup.Transaction = transaction; // Excecute the select query. using (var reader = await cmdLookup.ExecuteReaderAsync()) { while (await reader.ReadAsync()) { secondBudget = reader.GetNumeric(reader.GetOrdinal("MarketingBudget")).ToDecimal(LossOfPrecisionHandling.Throw); } } // Read the first album's budget. cmdLookup = connection.CreateSelectCommand( "SELECT MarketingBudget FROM Albums WHERE SingerId = 1 AND AlbumId = 1"); cmdLookup.Transaction = transaction; using (var reader = await cmdLookup.ExecuteReaderAsync()) { while (await reader.ReadAsync()) { firstBudget = reader.GetNumeric(reader.GetOrdinal("MarketingBudget")).ToDecimal(LossOfPrecisionHandling.Throw); } } // Specify update command parameters. var cmd = connection.CreateUpdateCommand("Albums", new SpannerParameterCollection { { "SingerId", SpannerDbType.Int64 }, { "AlbumId", SpannerDbType.Int64 }, { "MarketingBudget", SpannerDbType.Numeric }, }); cmd.Transaction = transaction; // Update second album to remove the transfer amount. secondBudget -= transferAmount; cmd.Parameters["SingerId"].Value = 2; cmd.Parameters["AlbumId"].Value = 2; cmd.Parameters["MarketingBudget"].Value = secondBudget; await cmd.ExecuteNonQueryAsync(); // Update first album to add the transfer amount. firstBudget += transferAmount; cmd.Parameters["SingerId"].Value = 1; cmd.Parameters["AlbumId"].Value = 1; cmd.Parameters["MarketingBudget"].Value = firstBudget; await cmd.ExecuteNonQueryAsync(); await transaction.CommitAsync(); } // Assert that the correct updates were sent. Stack <IMessage> requests = new Stack <IMessage>(_fixture.SpannerMock.Requests); Assert.Equal(typeof(CommitRequest), requests.Peek().GetType()); CommitRequest commit = (CommitRequest)requests.Pop(); Assert.Equal(2, commit.Mutations.Count); Mutation update1 = commit.Mutations.Last(); Assert.Equal(Mutation.OperationOneofCase.Update, update1.OperationCase); Assert.Equal("Albums", update1.Update.Table); Assert.Equal("1", update1.Update.Values.ElementAt(0).Values.ElementAt(0).StringValue); Assert.Equal( SpannerNumeric.FromDecimal(initialBudget1 + transferAmount, LossOfPrecisionHandling.Throw), SpannerNumeric.Parse(update1.Update.Values.ElementAt(0).Values.ElementAt(2).StringValue)); Mutation update2 = commit.Mutations.First(); Assert.Equal(Mutation.OperationOneofCase.Update, update2.OperationCase); Assert.Equal("Albums", update2.Update.Table); Assert.Equal("2", update2.Update.Values.ElementAt(0).Values.ElementAt(0).StringValue); Assert.Equal( SpannerNumeric.FromDecimal(initialBudget2 - transferAmount, LossOfPrecisionHandling.Throw), SpannerNumeric.Parse(update2.Update.Values.ElementAt(0).Values.ElementAt(2).StringValue)); }
public void TryParse_Invalid(string text) { Assert.False(SpannerNumeric.TryParse(text, out var value)); Assert.Equal(SpannerNumeric.Zero, value); }
public void UnaryPlus(string text) { var value = SpannerNumeric.Parse(text); Assert.Equal(value, +value); }