11using System ;
22using System . Collections . Generic ;
33using System . Linq ;
4+ using System . Net ;
5+ using System . Numerics ;
6+ using System . Text . Json ;
7+ using System . Text . Json . Nodes ;
48using System . Threading . Tasks ;
59using ClickHouse . Driver . ADO ;
610using ClickHouse . Driver . ADO . Readers ;
11+ using ClickHouse . Driver . Numerics ;
712using ClickHouse . Driver . Tests . Attributes ;
13+ using ClickHouse . Driver . Types ;
814using ClickHouse . Driver . Utility ;
915
1016namespace ClickHouse . Driver . Tests . Types ;
1117
1218public class DynamicTests : AbstractConnectionTestFixture
1319{
20+ public static IEnumerable < TestCaseData > DirectDynamicCastQueries
21+ {
22+ get
23+ {
24+ foreach ( var sample in TestUtilities . GetDataTypeSamples ( ) . Where ( s => ShouldBeSupportedInDynamic ( s . ClickHouseType ) ) )
25+ {
26+ yield return new TestCaseData ( sample . ExampleExpression , sample . ClickHouseType , sample . ExampleValue )
27+ . SetName ( $ "Direct_{ sample . ClickHouseType } _{ sample . ExampleValue } ") ;
28+ }
29+
30+ // Some additional test cases for dynamic specifically
31+ // JSON with complex type hints
32+ yield return new TestCaseData (
33+ "'{\" a\" : 1}'" ,
34+ "Json(max_dynamic_paths=10, max_dynamic_types=3, a Int64, SKIP path.to.skip, SKIP REGEXP 'regex.path.*')" ,
35+ new JsonObject { [ "a" ] = 1L }
36+ ) . SetName ( "Direct_Json_Complex" ) ;
37+
38+ yield return new TestCaseData (
39+ "1::Int32" ,
40+ "Dynamic" ,
41+ 1
42+ ) . SetName ( "Nested_Dynamic" ) ;
43+ }
44+ }
45+
46+ [ Test ]
47+ [ RequiredFeature ( Feature . Dynamic ) ]
48+ [ TestCaseSource ( typeof ( DynamicTests ) , nameof ( DirectDynamicCastQueries ) ) ]
49+ public async Task ShouldParseDirectDynamicCast ( string valueSql , string clickHouseType , object expectedValue )
50+ {
51+ // Direct cast to Dynamic without going through JSON
52+ using var reader =
53+ ( ClickHouseDataReader ) await connection . ExecuteReaderAsync (
54+ $ "SELECT ({ valueSql } ::{ clickHouseType } )::Dynamic") ;
55+
56+ ClassicAssert . IsTrue ( reader . Read ( ) ) ;
57+ var result = reader . GetValue ( 0 ) ;
58+ TestUtilities . AssertEqual ( expectedValue , result ) ;
59+ ClassicAssert . IsFalse ( reader . Read ( ) ) ;
60+ }
61+
62+ private static bool ShouldBeSupportedInDynamic ( string clickHouseType )
63+ {
64+ // Geo types not supported
65+ if ( clickHouseType is "Point" or "Ring" or "Polygon" or "MultiPolygon" or "Nothing" )
66+ {
67+ return false ;
68+ }
69+
70+ return true ;
71+ }
72+
1473 public static IEnumerable < TestCaseData > SimpleSelectQueries => TestUtilities . GetDataTypeSamples ( )
1574 . Where ( s => ShouldBeSupportedInJson ( s . ClickHouseType ) )
16- . Select ( sample => GetTestCaseData ( sample . ExampleExpression , sample . ClickHouseType , sample . ExampleValue ) ) ;
75+ . Select ( sample => GetTestCaseData ( sample . ExampleExpression , sample . ClickHouseType , sample . ExampleValue ) )
76+ . Where ( x => x != null ) ;
1777
1878 [ Test ]
1979 [ RequiredFeature ( Feature . Dynamic ) ]
2080 [ TestCaseSource ( typeof ( DynamicTests ) , nameof ( SimpleSelectQueries ) ) ]
21- public async Task ShouldMatchFrameworkType ( string valueSql , Type frameworkType )
81+ public async Task ShouldMatchFrameworkTypeViaJson ( string valueSql , Type frameworkType )
2282 {
83+ // This query returns the value as Dynamic type via JSON. The dynamicType may or may not match the actual type provided.
84+ // eg IPv4 will be a String.
2385 using var reader =
2486 ( ClickHouseDataReader ) await connection . ExecuteReaderAsync (
2587 $ "select json.value from (select map('value', { valueSql } )::JSON as json)") ;
@@ -37,6 +99,11 @@ private static TestCaseData GetTestCaseData(string exampleExpression, string cli
3799 return new TestCaseData ( exampleExpression , typeof ( DateTime ) ) ;
38100 }
39101
102+ if ( clickHouseType . StartsWith ( "Time" ) )
103+ {
104+ return new TestCaseData ( exampleExpression , typeof ( string ) ) ;
105+ }
106+
40107 if ( clickHouseType . StartsWith ( "Int" ) || clickHouseType . StartsWith ( "UInt" ) )
41108 {
42109 return new TestCaseData ( exampleExpression , typeof ( long ) ) ;
@@ -46,11 +113,6 @@ private static TestCaseData GetTestCaseData(string exampleExpression, string cli
46113 {
47114 return new TestCaseData ( exampleExpression , typeof ( string ) ) ;
48115 }
49-
50- if ( clickHouseType . StartsWith ( "Time" ) )
51- {
52- return new TestCaseData ( exampleExpression , typeof ( TimeSpan ) ) ;
53- }
54116
55117 if ( clickHouseType . StartsWith ( "Float" ) )
56118 {
@@ -72,16 +134,28 @@ floatRemainder is 0
72134 {
73135 case "Array(Int32)" or "Array(Nullable(Int32))" :
74136 return new TestCaseData ( exampleExpression , typeof ( long ? [ ] ) ) ;
137+ case "Array(Float32)" or "Array(Nullable(Float32))" :
138+ return new TestCaseData ( exampleExpression , typeof ( double ? [ ] ) ) ;
75139 case "Array(String)" :
76140 return new TestCaseData ( exampleExpression , typeof ( string [ ] ) ) ;
77- case "IPv4" or "IPv6" or "String" or "UUID" :
141+ case "Array(Bool)" :
142+ return new TestCaseData ( exampleExpression , typeof ( bool ? [ ] ) ) ;
143+ case "String" or "UUID" :
78144 return new TestCaseData ( exampleExpression , typeof ( string ) ) ;
79145 case "Nothing" :
80146 return new TestCaseData ( exampleExpression , typeof ( DBNull ) ) ;
81147 case "Bool" :
82148 return new TestCaseData ( exampleExpression , typeof ( bool ) ) ;
149+ case "IPv4" or "IPv6" :
150+ return new TestCaseData ( exampleExpression , typeof ( string ) ) ;
83151 }
84152
153+ if ( clickHouseType . StartsWith ( "Array" ) )
154+ {
155+ // Array handling is already covered above, we don't need to re-do it for every element type
156+ return null ;
157+ }
158+
85159 throw new ArgumentException ( $ "{ clickHouseType } not supported") ;
86160 }
87161
@@ -100,12 +174,6 @@ private static bool ShouldBeSupportedInJson(string clickHouseType)
100174 return false ;
101175 }
102176
103- // Time and Time64 are not currently supported
104- if ( clickHouseType . StartsWith ( "Time" ) )
105- {
106- return false ;
107- }
108-
109177 switch ( clickHouseType )
110178 {
111179 case "Int128" :
0 commit comments