public async Task <List <int> > QueryDataWithStructAsync(string projectId, string instanceId, string databaseId)
    {
        // [START spanner_create_struct_with_data]
        var nameStruct = new SpannerStruct
        {
            { "FirstName", SpannerDbType.String, "Elena" },
            { "LastName", SpannerDbType.String, "Campbell" },
        };
        // [END spanner_create_struct_with_data]

        string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId}";

        var singerIds = new List <int>();

        using var connection = new SpannerConnection(connectionString);
        using var cmd        = connection.CreateSelectCommand(
                  "SELECT SingerId FROM Singers "
                  + "WHERE STRUCT<FirstName STRING, LastName STRING>"
                  + "(FirstName, LastName) = @name");

        cmd.Parameters.Add("name", nameStruct.GetSpannerDbType(), nameStruct);
        using var reader = await cmd.ExecuteReaderAsync();

        while (await reader.ReadAsync())
        {
            singerIds.Add(reader.GetFieldValue <int>("SingerId"));
        }
        return(singerIds);
    }
        public void NullProtection()
        {
            Assert.Throws <ArgumentNullException>(() => new SpannerStruct.Field("name", null, "value"));
            var value = new SpannerStruct();

            Assert.Throws <ArgumentNullException>(() => value.Add("name", null, "value"));
            Assert.Throws <ArgumentNullException>(() => value.Add(null));
        }
        public void NullNameCoalescing()
        {
            var value = new SpannerStruct
            {
                { null, SpannerDbType.String, "foo" },
                new SpannerStruct.Field(null, SpannerDbType.String, "bar")
            };

            Assert.Equal("", value[0].Name);
            Assert.Equal("", value[1].Name);
        }
Example #4
0
 private static void AssertSampleStruct(SpannerStruct actual)
 {
     Assert.Equal(s_sampleStruct.Count, actual.Count);
     for (int i = 0; i < s_sampleStruct.Count; i++)
     {
         var expectedField = s_sampleStruct[i];
         var actualField   = actual[i];
         Assert.Equal(expectedField.Name, actualField.Name);
         Assert.Equal(expectedField.Type, actualField.Type);
         Assert.Equal(expectedField.Value, actualField.Value);
     }
 }
        public void SimpleProperties()
        {
            var value = new SpannerStruct
            {
                { "f", SpannerDbType.String, "value" }
            };
            var field = value[0];

            Assert.Equal("f", field.Name);
            Assert.Equal(SpannerDbType.String, field.Type);
            Assert.Equal("value", field.Value);
        }
        public void InterfaceAccess()
        {
            var value = new SpannerStruct
            {
                { "f", SpannerDbType.String, null },
                { "g", SpannerDbType.Int64, 100L },
            };
            var fields = value.ToList();

            Assert.Same(fields[0], value[0]);
            Assert.Same(fields[1], value[1]);
            Assert.Equal(2, fields.Count);
            Assert.Throws <ArgumentOutOfRangeException>(() => fields[2]);
        }
Example #7
0
    public async Task <int> UpdateUsingDmlWithStructCoreAsync(string projectId, string instanceId, string databaseId)
    {
        var nameStruct = new SpannerStruct
        {
            { "FirstName", SpannerDbType.String, "Timothy" },
            { "LastName", SpannerDbType.String, "Campbell" }
        };
        string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId}";

        using var connection = new SpannerConnection(connectionString);
        await connection.OpenAsync();

        using var cmd = connection.CreateDmlCommand("UPDATE Singers SET LastName = 'Grant' WHERE STRUCT<FirstName STRING, LastName STRING>(FirstName, LastName) = @name");
        cmd.Parameters.Add("name", nameStruct.GetSpannerDbType(), nameStruct);
        int rowCount = await cmd.ExecuteNonQueryAsync();

        Console.WriteLine($"{rowCount} row(s) updated...");
        return(rowCount);
    }
Example #8
0
        public void DeserializeComplexStruct()
        {
            var complexStruct = new SpannerStruct
            {
                { "StructField", s_sampleStruct.GetSpannerDbType(), s_sampleStruct },
                { "ArrayField", SpannerDbType.ArrayOf(SpannerDbType.Int64), GetIntsForArray().Select(x => (long)x).ToList() }
            };
            var wireValue = JsonParser.Default.Parse <Value>($"[ {s_sampleStructSerialized}, [ \"4\", \"5\", \"6\" ] ]");
            var actual    = complexStruct.GetSpannerDbType().ConvertToClrType <SpannerStruct>(wireValue, SpannerConversionOptions.Default);

            Assert.Equal(2, actual.Count);

            Assert.Equal("StructField", actual[0].Name);
            Assert.Equal(s_sampleStruct.GetSpannerDbType(), actual[0].Type);
            AssertSampleStruct((SpannerStruct)actual[0].Value);

            Assert.Equal("ArrayField", actual[1].Name);
            Assert.Equal(SpannerDbType.ArrayOf(SpannerDbType.Int64), actual[1].Type);
            Assert.Equal(new[] { 4L, 5L, 6L }, actual[1].Value);
        }
Example #9
0
    public async Task <List <int> > QueryDataWithNestedStructFieldAsync(string projectId, string instanceId, string databaseId)
    {
        string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId}";

        SpannerStruct name1 = new SpannerStruct
        {
            { "FirstName", SpannerDbType.String, "Elena" },
            { "LastName", SpannerDbType.String, "Campbell" }
        };
        SpannerStruct name2 = new SpannerStruct
        {
            { "FirstName", SpannerDbType.String, "Hannah" },
            { "LastName", SpannerDbType.String, "Harris" }
        };
        SpannerStruct songInfo = new SpannerStruct
        {
            { "song_name", SpannerDbType.String, "Imagination" },
            { "artistNames", SpannerDbType.ArrayOf(name1.GetSpannerDbType()), new[] { name1, name2 } }
        };

        var singerIds = new List <int>();

        using var connection = new SpannerConnection(connectionString);
        using var cmd        = connection.CreateSelectCommand(
                  "SELECT SingerId, @song_info.song_name "
                  + "FROM Singers WHERE STRUCT<FirstName STRING, LastName STRING>(FirstName, LastName) "
                  + "IN UNNEST(@song_info.artistNames)");

        cmd.Parameters.Add("song_info", songInfo.GetSpannerDbType(), songInfo);

        using var reader = await cmd.ExecuteReaderAsync();

        while (await reader.ReadAsync())
        {
            var singerId = reader.GetFieldValue <int>("SingerId");
            singerIds.Add(singerId);
            Console.WriteLine($"SingerId: {singerId}");
            Console.WriteLine($"Song Name: {reader.GetFieldValue<string>(1)}");
        }
        return(singerIds);
    }
    public async Task <List <int> > QueryDataWithArrayOfStructAsync(string projectId, string instanceId, string databaseId)
    {
        // [START spanner_create_user_defined_struct]
        var nameType = new SpannerStruct
        {
            { "FirstName", SpannerDbType.String, null },
            { "LastName", SpannerDbType.String, null }
        };
        // [END spanner_create_user_defined_struct]

        // [START spanner_create_array_of_struct_with_data]
        var bandMembers = new List <SpannerStruct>
        {
            new SpannerStruct {
                { "FirstName", SpannerDbType.String, "Elena" }, { "LastName", SpannerDbType.String, "Campbell" }
            },
            new SpannerStruct {
                { "FirstName", SpannerDbType.String, "Gabriel" }, { "LastName", SpannerDbType.String, "Wright" }
            },
            new SpannerStruct {
                { "FirstName", SpannerDbType.String, "Benjamin" }, { "LastName", SpannerDbType.String, "Martinez" }
            },
        };
        // [END spanner_create_array_of_struct_with_data]

        var    singerIds        = new List <int>();
        string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId}";

        using var connection = new SpannerConnection(connectionString);
        using var cmd        = connection.CreateSelectCommand(
                  "SELECT SingerId FROM Singers WHERE STRUCT<FirstName STRING, LastName STRING> "
                  + "(FirstName, LastName) IN UNNEST(@names)");
        cmd.Parameters.Add("names", SpannerDbType.ArrayOf(nameType.GetSpannerDbType()), bandMembers);
        using var reader = await cmd.ExecuteReaderAsync();

        while (await reader.ReadAsync())
        {
            singerIds.Add(reader.GetFieldValue <int>("SingerId"));
        }
        return(singerIds);
    }
    public async Task <List <int> > QueryDataWithStructFieldAsync(string projectId, string instanceId, string databaseId)
    {
        string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId}";

        var structParam = new SpannerStruct
        {
            { "FirstName", SpannerDbType.String, "Elena" },
            { "LastName", SpannerDbType.String, "Campbell" },
        };

        var singerIds = new List <int>();

        using var connection = new SpannerConnection(connectionString);
        using var cmd        = connection.CreateSelectCommand("SELECT SingerId FROM Singers WHERE FirstName = @name.FirstName");

        cmd.Parameters.Add("name", structParam.GetSpannerDbType(), structParam);
        using var reader = await cmd.ExecuteReaderAsync();

        while (await reader.ReadAsync())
        {
            singerIds.Add(reader.GetFieldValue <int>("SingerId"));
        }
        return(singerIds);
    }
        public static IEnumerable <object[]> GetSpannerStringConversions()
        {
            yield return(new object[] { "STRING", SpannerDbType.String });

            yield return(new object[] { "STRING(MAX)", SpannerDbType.String });

            yield return(new object[] { "BOOL", SpannerDbType.Bool });

            yield return(new object[] { "BYTES", SpannerDbType.Bytes });

            yield return(new object[] { "DATE", SpannerDbType.Date });

            yield return(new object[] { "FLOAT64", SpannerDbType.Float64 });

            yield return(new object[] { "INT64", SpannerDbType.Int64 });

            yield return(new object[] { "TIMESTAMP", SpannerDbType.Timestamp });

            yield return(new object[] { " STRING  ", SpannerDbType.String });

            yield return(new object[] { "  BOOL  ", SpannerDbType.Bool });

            yield return(new object[] { "  BYTES  ", SpannerDbType.Bytes });

            yield return(new object[] { "  DATE  ", SpannerDbType.Date });

            yield return(new object[] { "  FLOAT64  ", SpannerDbType.Float64 });

            yield return(new object[] { "  INT64  ", SpannerDbType.Int64 });

            yield return(new object[] { "  TIMESTAMP  ", SpannerDbType.Timestamp });

            yield return(new object[] { "STRING(2)", SpannerDbType.String.WithSize(2) });

            yield return(new object[] { "STRING(100)", SpannerDbType.String.WithSize(100) });

            yield return(new object[] { " STRING ( 100  )  ", SpannerDbType.String.WithSize(100) });

            yield return(new object[] { "STRING (  MAX )", SpannerDbType.String });

            yield return(new object[] { "STRING(100)", SpannerDbType.String, false });

            yield return(new object[] { "BYTES(100)", SpannerDbType.Bytes.WithSize(100) });

            yield return(new object[] { "BYTES(100)", SpannerDbType.Bytes, false });

            yield return(new object[] { " BYTES ( 100  )  ", SpannerDbType.Bytes.WithSize(100) });

            //some common array types.
            yield return(new object[] { "ARRAY<STRING>", SpannerDbType.ArrayOf(SpannerDbType.String) });

            yield return(new object[] { "ARRAY<STRING(MAX)>", SpannerDbType.ArrayOf(SpannerDbType.String) });

            yield return(new object[] { "ARRAY<STRING(5)>", SpannerDbType.ArrayOf(SpannerDbType.String.WithSize(5)) });

            yield return(new object[] { "ARRAY<STRING>", SpannerDbType.ArrayOf(SpannerDbType.String) });

            yield return(new object[] { "ARRAY<BOOL>", SpannerDbType.ArrayOf(SpannerDbType.Bool) });

            yield return(new object[] { "ARRAY<BYTES>", SpannerDbType.ArrayOf(SpannerDbType.Bytes) });

            yield return(new object[] { "ARRAY<BYTES(100)>", SpannerDbType.ArrayOf(SpannerDbType.Bytes.WithSize(100)) });

            yield return(new object[] { "ARRAY<DATE>", SpannerDbType.ArrayOf(SpannerDbType.Date) });

            yield return(new object[] { "ARRAY<FLOAT64>", SpannerDbType.ArrayOf(SpannerDbType.Float64) });

            yield return(new object[] { "ARRAY<INT64>", SpannerDbType.ArrayOf(SpannerDbType.Int64) });

            yield return(new object[] { "ARRAY<TIMESTAMP>", SpannerDbType.ArrayOf(SpannerDbType.Timestamp) });

            yield return(new object[] { "ARRAY<STRING(5)>", SpannerDbType.ArrayOf(SpannerDbType.String), false });

            yield return(new object[] { "ARRAY<BYTES(5)>", SpannerDbType.ArrayOf(SpannerDbType.Bytes), false });

            yield return(new object[] { "ARRAY <  STRING (   5 )>", SpannerDbType.ArrayOf(SpannerDbType.String.WithSize(5)) });

            yield return(new object[] { "ARRAY<  STRING (   5 )  > ", SpannerDbType.ArrayOf(SpannerDbType.String.WithSize(5)) });


            // Structs

            // Empty struct
            yield return(new object[] { "STRUCT<>", new SpannerStruct().GetSpannerDbType() });

            // Non-unique fields
            var sampleStruct = new SpannerStruct
            {
                { "F", SpannerDbType.String, null },
                { "F", SpannerDbType.Int64, null },
                { "G", SpannerDbType.String, null },
            };

            yield return(new object[] { "STRUCT<F:STRING,F:INT64,G:STRING>", sampleStruct.GetSpannerDbType() });

            // Empty fields
            sampleStruct = new SpannerStruct
            {
                { "", SpannerDbType.String, null },
                { "F", SpannerDbType.Int64, null },
                { "", SpannerDbType.String, null },
            };
            yield return(new object[] { "STRUCT<STRING,F:INT64,STRING>", sampleStruct.GetSpannerDbType() });

            sampleStruct = new SpannerStruct
            {
                { "F1", SpannerDbType.String, null },
                { "F2", SpannerDbType.Int64, null },
            };
            yield return(new object[] { "STRUCT<F1:STRING,F2:INT64>", sampleStruct.GetSpannerDbType() });

            yield return(new object[] { "STRUCT< F1 : STRING , F2  : INT64 >", sampleStruct.GetSpannerDbType() });

            sampleStruct = new SpannerStruct
            {
                { "F1", SpannerDbType.String, null },
                { "F2", SpannerDbType.Int64, null },
                { "F3", SpannerDbType.Bool, null },
                { "F4", SpannerDbType.Bytes, null },
                { "F5", SpannerDbType.Date, null },
                { "F6", SpannerDbType.Float64, null },
                { "F7", SpannerDbType.Timestamp, null },
            };
            yield return(new object[] { "STRUCT<F1:STRING,F2:INT64,F3:BOOL,F4:BYTES,F5:DATE,F6:FLOAT64,F7:TIMESTAMP>", sampleStruct.GetSpannerDbType() });

            sampleStruct = new SpannerStruct
            {
                { "F1", SpannerDbType.String.WithSize(100), null },
                { "F2", SpannerDbType.Bytes.WithSize(200), null },
            };
            yield return(new object[] { "STRUCT<F1: STRING(100), F2: BYTES(200)>", sampleStruct.GetSpannerDbType() });

            // Struct of struct
            var nestedStruct = new SpannerStruct {
                { "F2", SpannerDbType.Int64, null }
            };

            sampleStruct = new SpannerStruct
            {
                { "F1", nestedStruct.GetSpannerDbType(), null }
            };
            yield return(new object[] { "STRUCT<F1:STRUCT<F2:INT64>>", sampleStruct.GetSpannerDbType() });

            // Struct of array
            sampleStruct = new SpannerStruct
            {
                { "F1", SpannerDbType.ArrayOf(SpannerDbType.Int64), null }
            };
            yield return(new object[] { "STRUCT<F1:ARRAY<INT64>>", sampleStruct.GetSpannerDbType() });

            // Array of struct
            sampleStruct = new SpannerStruct
            {
                { "F1", SpannerDbType.Int64, null }
            };
            yield return(new object[] { "ARRAY<STRUCT<F1:INT64>>", SpannerDbType.ArrayOf(sampleStruct.GetSpannerDbType()) });
        }
Example #13
0
        // This data serves as inputs to converting from CLR types that a developer
        // would use in his app to a given json serialized format using the spanner type
        // to determine how to serialize.
        // The inputs are used to test both ways (also deseralizing the generated json
        // to a requested clr type).  However some cases are specified as only one direction
        // usually because the conversion is by definition lossy.
        public static IEnumerable <object[]> GetValidValueConversions()
        {
            // Format is:  LocalClrInstance,  SpannerType,  SerializedJsonFromProto, [test one or both ways]
            // Testing can be one way if there is loss of information in the conversion.

            // Spanner type = Float64 tests.
            yield return(new object[] { true, SpannerDbType.Float64, "1" });

            yield return(new object[] { false, SpannerDbType.Float64, "0" });

            yield return(new object[] { (byte)1, SpannerDbType.Float64, "1" });

            yield return(new object[] { (sbyte)1, SpannerDbType.Float64, "1" });

            yield return(new object[] { 1.5M, SpannerDbType.Float64, "1.5" });

            yield return(new object[] { 1.5D, SpannerDbType.Float64, "1.5" });

            yield return(new object[] { 1.5F, SpannerDbType.Float64, "1.5" });

            yield return(new object[] { double.NegativeInfinity, SpannerDbType.Float64, Quote("-Infinity") });

            yield return(new object[] { double.PositiveInfinity, SpannerDbType.Float64, Quote("Infinity") });

            yield return(new object[] { double.NaN, SpannerDbType.Float64, Quote("NaN") });

            yield return(new object[] { 1, SpannerDbType.Float64, "1" });

            yield return(new object[] { 1U, SpannerDbType.Float64, "1" });

            yield return(new object[] { 1L, SpannerDbType.Float64, "1" });

            yield return(new object[] { (ulong)1, SpannerDbType.Float64, "1" });

            yield return(new object[] { (short)1, SpannerDbType.Float64, "1" });

            yield return(new object[] { (ushort)1, SpannerDbType.Float64, "1" });

            yield return(new object[] { "1", SpannerDbType.Float64, "1" });

            yield return(new object[] { "1.5", SpannerDbType.Float64, "1.5" });

            yield return(new object[] { DBNull.Value, SpannerDbType.Float64, "null" });

            // Spanner type = Int64 tests.
            yield return(new object[] { true, SpannerDbType.Int64, Quote("1") });

            yield return(new object[] { false, SpannerDbType.Int64, Quote("0") });

            yield return(new object[] { (char)1, SpannerDbType.Int64, Quote("1") });

            yield return(new object[] { (byte)1, SpannerDbType.Int64, Quote("1") });

            yield return(new object[] { (sbyte)1, SpannerDbType.Int64, Quote("1") });

            yield return(new object[] { 2M, SpannerDbType.Int64, Quote("2") });

            yield return(new object[] { 1.5M, SpannerDbType.Int64, Quote("2"), TestType.ClrToValue });

            yield return(new object[] { 2D, SpannerDbType.Int64, Quote("2") });

            yield return(new object[] { 1.5D, SpannerDbType.Int64, Quote("2"), TestType.ClrToValue });

            yield return(new object[] { 2F, SpannerDbType.Int64, Quote("2") });

            yield return(new object[] { 1.5F, SpannerDbType.Int64, Quote("2"), TestType.ClrToValue });

            yield return(new object[] { 1, SpannerDbType.Int64, Quote("1") });

            yield return(new object[] { 1U, SpannerDbType.Int64, Quote("1") });

            yield return(new object[] { 1L, SpannerDbType.Int64, Quote("1") });

            yield return(new object[] { (ulong)1, SpannerDbType.Int64, Quote("1") });

            yield return(new object[] { (short)1, SpannerDbType.Int64, Quote("1") });

            yield return(new object[] { (ushort)1, SpannerDbType.Int64, Quote("1") });

            yield return(new object[] { "1", SpannerDbType.Int64, Quote("1") });

            // Spanner type = Bool tests.
            yield return(new object[] { true, SpannerDbType.Bool, "true" });

            yield return(new object[] { false, SpannerDbType.Bool, "false" });

            yield return(new object[] { (byte)1, SpannerDbType.Bool, "true" });

            yield return(new object[] { (byte)0, SpannerDbType.Bool, "false" });

            yield return(new object[] { (sbyte)1, SpannerDbType.Bool, "true" });

            yield return(new object[] { (sbyte)0, SpannerDbType.Bool, "false" });

            yield return(new object[] { 1M, SpannerDbType.Bool, "true" });

            yield return(new object[] { 6.5M, SpannerDbType.Bool, "true", TestType.ClrToValue });

            yield return(new object[] { 0M, SpannerDbType.Bool, "false" });

            yield return(new object[] { 1D, SpannerDbType.Bool, "true" });

            yield return(new object[] { 6.5D, SpannerDbType.Bool, "true", TestType.ClrToValue });

            yield return(new object[] { 0D, SpannerDbType.Bool, "false" });

            yield return(new object[] { 1F, SpannerDbType.Bool, "true" });

            yield return(new object[] { 6.5F, SpannerDbType.Bool, "true", TestType.ClrToValue });

            yield return(new object[] { 0F, SpannerDbType.Bool, "false" });

            yield return(new object[] { 1, SpannerDbType.Bool, "true" });

            yield return(new object[] { 11, SpannerDbType.Bool, "true", TestType.ClrToValue });

            yield return(new object[] { 0, SpannerDbType.Bool, "false" });

            yield return(new object[] { 1U, SpannerDbType.Bool, "true" });

            yield return(new object[] { 100U, SpannerDbType.Bool, "true", TestType.ClrToValue });

            yield return(new object[] { 0U, SpannerDbType.Bool, "false" });

            yield return(new object[] { 1L, SpannerDbType.Bool, "true" });

            yield return(new object[] { -1L, SpannerDbType.Bool, "true", TestType.ClrToValue });

            yield return(new object[] { 0L, SpannerDbType.Bool, "false" });

            yield return(new object[] { (ulong)1, SpannerDbType.Bool, "true" });

            yield return(new object[] { (ulong)21, SpannerDbType.Bool, "true", TestType.ClrToValue });

            yield return(new object[] { (ulong)0, SpannerDbType.Bool, "false" });

            yield return(new object[] { (short)1, SpannerDbType.Bool, "true" });

            yield return(new object[] { (short)-1, SpannerDbType.Bool, "true", TestType.ClrToValue });

            yield return(new object[] { (short)0, SpannerDbType.Bool, "false" });

            yield return(new object[] { (ushort)1, SpannerDbType.Bool, "true" });

            yield return(new object[] { (ushort)11, SpannerDbType.Bool, "true", TestType.ClrToValue });

            yield return(new object[] { (ushort)0, SpannerDbType.Bool, "false" });

            // Spanner type = String tests.
            // Note the casing on bool->string follows c# bool conversion semantics (by design).
            yield return(new object[] { true, SpannerDbType.String, Quote("True") });

            yield return(new object[] { false, SpannerDbType.String, Quote("False") });

            yield return(new object[] { (char)26, SpannerDbType.String, Quote("\\u001a") });

            yield return(new object[] { (byte)1, SpannerDbType.String, Quote("1") });

            yield return(new object[] { (sbyte)1, SpannerDbType.String, Quote("1") });

            yield return(new object[] { 1.5M, SpannerDbType.String, Quote("1.5") });

            yield return(new object[] { 1.5D, SpannerDbType.String, Quote("1.5") });

            yield return(new object[] { 1.5F, SpannerDbType.String, Quote("1.5") });

            yield return(new object[] { 1, SpannerDbType.String, Quote("1") });

            yield return(new object[] { 1U, SpannerDbType.String, Quote("1") });

            yield return(new object[] { 1L, SpannerDbType.String, Quote("1") });

            yield return(new object[] { (ulong)1, SpannerDbType.String, Quote("1") });

            yield return(new object[] { (short)1, SpannerDbType.String, Quote("1") });

            yield return(new object[] { (ushort)1, SpannerDbType.String, Quote("1") });

            yield return(new object[] { s_testDate, SpannerDbType.String, Quote("2017-01-31T03:15:30.5Z") });

            // Note the difference in C# conversions from special doubles.
            yield return(new object[] { double.NegativeInfinity, SpannerDbType.String, Quote("-Infinity") });

            yield return(new object[] { double.PositiveInfinity, SpannerDbType.String, Quote("Infinity") });

            yield return(new object[] { double.NaN, SpannerDbType.String, Quote("NaN") });

            yield return(new object[] { "1.5", SpannerDbType.String, Quote("1.5") });

            yield return(new object[]
                         { new ToStringClass("hello"), SpannerDbType.String, Quote("hello"), TestType.ClrToValue });

            // Spanner type = Date+Timestamp tests.  Some of these are one way due to either a lossy conversion (date loses time)
            // or a string formatting difference.
            yield return(new object[] { s_testDate, SpannerDbType.Date, Quote("2017-01-31"), TestType.ClrToValue });

            yield return(new object[] { "1/31/2017", SpannerDbType.Date, Quote("2017-01-31"), TestType.ClrToValue });

            yield return(new object[]
                         { "1/31/2017 3:15:30 AM", SpannerDbType.Date, Quote("2017-01-31"), TestType.ClrToValue });

            yield return(new object[] { s_testDate, SpannerDbType.Timestamp, Quote("2017-01-31T03:15:30.5Z") });

            yield return(new object[] { s_testDate.AddTicks(5), SpannerDbType.Timestamp,
                                        Quote("2017-01-31T03:15:30.5000005Z") });

            yield return(new object[]
                         { "1/31/2017", SpannerDbType.Timestamp, Quote("2017-01-31T00:00:00Z"), TestType.ClrToValue });

            yield return(new object[]
                         { "1/31/2017 3:15:30 AM", SpannerDbType.Timestamp, Quote("2017-01-31T03:15:30Z"), TestType.ClrToValue });

            yield return(new object[]
                         { "1/31/2017 3:15:30 AM", SpannerDbType.Timestamp, Quote("2017-01-31T03:15:30Z"), TestType.ClrToValue });

            // Spanner type = Bytes tests.
            yield return(new object[] { s_base64Encoded, SpannerDbType.Bytes, Quote(s_base64Encoded) });

            yield return(new object[] { s_bytesToEncode, SpannerDbType.Bytes, Quote(s_base64Encoded) });

            yield return(new object[] { "passthrubadbytes", SpannerDbType.Bytes, Quote("passthrubadbytes") });

            // List test cases (list of type X).
            yield return(new object[]
            {
                new List <string>(GetStringsForArray()), SpannerDbType.ArrayOf(SpannerDbType.String),
                "[ \"abc\", \"123\", \"def\" ]"
            });

            yield return(new object[]
            {
                new List <double>(GetFloatsForArray()), SpannerDbType.ArrayOf(SpannerDbType.Float64),
                "[ 1, 2, 3 ]"
            });

            yield return(new object[]
            {
                new List <int>(GetIntsForArray()), SpannerDbType.ArrayOf(SpannerDbType.Int64),
                "[ \"4\", \"5\", \"6\" ]"
            });

            yield return(new object[]
            {
                new List <bool>(GetBoolsForArray()), SpannerDbType.ArrayOf(SpannerDbType.Bool),
                "[ true, false, true ]"
            });

            yield return(new object[]
            {
                new List <DateTime>(GetDatesForArray()), SpannerDbType.ArrayOf(SpannerDbType.Date),
                "[ \"2017-01-31\", \"2016-02-15\", \"2015-03-31\" ]"
            });

            yield return(new object[]
            {
                new List <DateTime>(GetTimestampsForArray()), SpannerDbType.ArrayOf(SpannerDbType.Timestamp),
                "[ \"2017-01-31T03:15:30.5Z\", \"2016-02-15T13:15:30Z\", \"2015-03-31T03:15:30.25Z\" ]"
            });

            // List test cases (various source/target list types)
            yield return(new object[]
            {
                GetStringsForArray(), SpannerDbType.ArrayOf(SpannerDbType.String),
                "[ \"abc\", \"123\", \"def\" ]", TestType.ClrToValue
            });

#pragma warning disable DE0006 // ArrayList deprecation
            yield return(new object[]
            {
                new ArrayList(GetStringsForArray().ToList()), SpannerDbType.ArrayOf(SpannerDbType.String),
                "[ \"abc\", \"123\", \"def\" ]"
            });

#pragma warning restore DE0006
            yield return(new object[]
            {
                new List <object>(GetStringsForArray()), SpannerDbType.ArrayOf(SpannerDbType.String),
                "[ \"abc\", \"123\", \"def\" ]"
            });

            yield return(new object[]
            {
                new CustomList(GetStringsForArray()), SpannerDbType.ArrayOf(SpannerDbType.String),
                "[ \"abc\", \"123\", \"def\" ]"
            });

            // List test cases with null entries
            yield return(new object[]
            {
                new List <string> {
                    "x", null, "y"
                }, SpannerDbType.ArrayOf(SpannerDbType.String),
                "[ \"x\", null, \"y\" ]"
            });

            yield return(new object[]
            {
                new double?[] { 5.5, null, 10.5 }, SpannerDbType.ArrayOf(SpannerDbType.Float64),
                "[ 5.5, null, 10.5 ]"
            });

            // Struct test case includes nested complex conversions.
            // These are one-way only, because SpannerStruct doesn't (and can't generally) implement
            // IEquatable<T>. (By the time the nested value can be an array etc, it ends up being infeasible.)
            // Deserialization is therefore handled separately in hand-written tests.
            yield return(new object[]
            {
                s_sampleStruct,
                s_sampleStruct.GetSpannerDbType(),
                s_sampleStructSerialized,
                TestType.ClrToValue
            });

            // Array of structs.
            yield return(new object[]
            {
                new List <object>(new[] { s_sampleStruct }),
                SpannerDbType.ArrayOf(s_sampleStruct.GetSpannerDbType()), $"[ {s_sampleStructSerialized} ]",
                TestType.ClrToValue
            });

            // Struct of struct and array.
            var complexStruct = new SpannerStruct
            {
                { "StructField", s_sampleStruct.GetSpannerDbType(), s_sampleStruct },
                { "ArrayField", SpannerDbType.ArrayOf(SpannerDbType.Int64), GetIntsForArray().Select(x => (long)x).ToList() }
            };
            yield return(new object[]
            {
                complexStruct, complexStruct.GetSpannerDbType(),
                $"[ {s_sampleStructSerialized}, [ \"4\", \"5\", \"6\" ] ]",
                TestType.ClrToValue
            });
        }