diff --git a/ClickHouse.Driver.Tests/SQL/SqlParameterizedSelectTests.cs b/ClickHouse.Driver.Tests/SQL/SqlParameterizedSelectTests.cs index 348a548..9ff806a 100644 --- a/ClickHouse.Driver.Tests/SQL/SqlParameterizedSelectTests.cs +++ b/ClickHouse.Driver.Tests/SQL/SqlParameterizedSelectTests.cs @@ -100,7 +100,7 @@ SELECT 1 FROM (SELECT tuple(1, 'a', NULL) AS res) WHERE res.1 = tupleElement({var:Tuple(Int32, String, Nullable(Int32))}, 1) AND res.2 = tupleElement({var:Tuple(Int32, String, Nullable(Int32))}, 2) - AND res.3 is NULL + AND res.3 is NULL AND tupleElement({var:Tuple(Int32, String, Nullable(Int32))}, 3) is NULL"; using var command = connection.CreateCommand(); command.CommandText = sql; @@ -130,5 +130,43 @@ SELECT 1 result.GetEnsureSingleRow(); } + [Test] + public async Task ShouldExecuteSelectWithValueTupleParameter() + { + var sql = @" + SELECT 1 + FROM (SELECT tuple(1, 'a', NULL) AS res) + WHERE res.1 = tupleElement({var:Tuple(Int32, String, Nullable(Int32))}, 1) + AND res.2 = tupleElement({var:Tuple(Int32, String, Nullable(Int32))}, 2) + AND res.3 is NULL + AND tupleElement({var:Tuple(Int32, String, Nullable(Int32))}, 3) is NULL"; + using var command = connection.CreateCommand(); + command.CommandText = sql; + + command.AddParameter("var", ValueTuple.Create(1, "a", null)); + + var result = await command.ExecuteReaderAsync(); + result.GetEnsureSingleRow(); + } + + [Test] + public async Task ShouldExecuteSelectWithUnderlyingValueTupleParameter() + { + var sql = @" + SELECT 1 + FROM (SELECT tuple(123, tuple(5, 'a', 7)) AS res) + WHERE res.1 = tupleElement({var:Tuple(Int32, Tuple(UInt8, String, Nullable(Int32)))}, 1) + AND res.2.1 = tupleElement(tupleElement({var:Tuple(Int32, Tuple(UInt8, String, Nullable(Int32)))}, 2), 1) + AND res.2.2 = tupleElement(tupleElement({var:Tuple(Int32, Tuple(UInt8, String, Nullable(Int32)))}, 2), 2) + AND res.2.3 = tupleElement(tupleElement({var:Tuple(Int32, Tuple(UInt8, String, Nullable(Int32)))}, 2), 3)"; + using var command = connection.CreateCommand(); + command.CommandText = sql; + + command.AddParameter("var", ValueTuple.Create(123, ValueTuple.Create((byte)5, "a", 7))); + + var result = await command.ExecuteReaderAsync(); + result.GetEnsureSingleRow(); + } + public void Dispose() => connection?.Dispose(); } diff --git a/ClickHouse.Driver.Tests/Types/TypeMappingTests.cs b/ClickHouse.Driver.Tests/Types/TypeMappingTests.cs index 88653ee..ec99d47 100644 --- a/ClickHouse.Driver.Tests/Types/TypeMappingTests.cs +++ b/ClickHouse.Driver.Tests/Types/TypeMappingTests.cs @@ -91,6 +91,7 @@ public class TypeMappingTests [TestCase(typeof(DateOnly), ExpectedResult = "Date")] #endif [TestCase(typeof(Tuple), ExpectedResult = "Tuple(Int32,UInt8,Nullable(Float32),Array(String))")] + [TestCase(typeof(ValueTuple), ExpectedResult = "Tuple(Int32,UInt8,Nullable(Float32),Array(String))")] public string ShouldConvertToClickHouseType(Type type) => TypeConverter.ToClickHouseType(type).ToString(); [Test, Explicit] diff --git a/ClickHouse.Driver/Types/TypeConverter.cs b/ClickHouse.Driver/Types/TypeConverter.cs index 5b2ccc9..4904af8 100644 --- a/ClickHouse.Driver/Types/TypeConverter.cs +++ b/ClickHouse.Driver/Types/TypeConverter.cs @@ -291,6 +291,11 @@ public static ClickHouseType ToClickHouseType(Type type) return new TupleType { UnderlyingTypes = type.GetGenericArguments().Select(ToClickHouseType).ToArray() }; } + if (type.IsGenericType && type.GetGenericTypeDefinition().FullName.StartsWith("System.ValueTuple", StringComparison.InvariantCulture)) + { + return new TupleType { UnderlyingTypes = type.GetGenericArguments().Select(ToClickHouseType).ToArray() }; + } + if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>)) { var types = type.GetGenericArguments().Select(ToClickHouseType).ToArray();