示例#1
0
        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.");
    }
示例#3
0
        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);
        }
示例#4
0
        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);
    }
示例#6
0
        public void UnaryNegation(string text)
        {
            var positive = SpannerNumeric.Parse(text);
            var negative = SpannerNumeric.Parse("-" + text);

            Assert.Equal(negative, -positive);
            Assert.Equal(positive, -negative);
        }
示例#7
0
        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);
        }
示例#8
0
        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);
        }
示例#9
0
        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);
        }
示例#10
0
        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));
        }
示例#11
0
        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));
        }
示例#12
0
        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));
        }
示例#13
0
        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));
        }
示例#14
0
        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));
        }
示例#15
0
        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);
        }
示例#16
0
        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());
        }
示例#17
0
        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;
            }
        }
示例#18
0
        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);
        }
示例#19
0
        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);
        }
示例#20
0
        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);
        }
示例#21
0
        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);
        }
示例#22
0
        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}");
        }
示例#23
0
        // 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);
            }
        }
示例#24
0
        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));
        }
示例#26
0
 public void TryParse_Invalid(string text)
 {
     Assert.False(SpannerNumeric.TryParse(text, out var value));
     Assert.Equal(SpannerNumeric.Zero, value);
 }
示例#27
0
        public void UnaryPlus(string text)
        {
            var value = SpannerNumeric.Parse(text);

            Assert.Equal(value, +value);
        }