Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ public class PageView
| **Variant** | `Variant(T1, T2, ...)` | `object` |
| **Dynamic** | `Dynamic` | `object` |
| **JSON** | `Json` | `JsonNode` or `string` |
| **Geographic** | `Point`, `Ring`, `LineString`, `Polygon`, `MultiLineString`, `MultiPolygon`, `Geometry` | `Tuple<double,double>` and arrays thereof; `object` for Geometry |
| **Wrappers** | `Nullable(T)`, `LowCardinality(T)` | Unwrapped automatically |

## Current Status
Expand Down Expand Up @@ -167,7 +168,7 @@ var ev = await ctx.Events.Where(e => e.Id == 1).SingleAsync();
string action = ev.Payload!["action"]!.GetValue<string>(); // "click"
```

If you prefer working with raw JSON strings, map the property as `string` with a `Json` column type — the provider applies a `ValueConverter` automatically:
If you prefer working with raw JSON strings, map the property as `string` with a `Json` column type — the provider will store and retrieve the raw JSON string as-is:

```csharp
public class Event
Expand All @@ -192,7 +193,6 @@ entity.Property(e => e.Payload).HasColumnType("Json");
- UPDATE / DELETE (ClickHouse mutations are async, not OLTP-compatible)
- Migrations
- JOINs, subqueries, set operations
- Geo types

## Building

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,32 @@ public class ClickHouseTypeMappingSource : RelationalTypeMappingSource
private static readonly RelationalTypeMapping TimeMapping = new ClickHouseTimeSpanTypeMapping();
private static readonly RelationalTypeMapping JsonMapping = new ClickHouseJsonTypeMapping();

// Geo types — structural aliases composed from existing Tuple/Array/Variant mappings.
// Driver returns Tuple<double, double> (reference tuple) for Point; array-based geo types
// require reference tuples because Expression.Convert cannot convert Tuple<>[] to ValueTuple<>[].
private static readonly RelationalTypeMapping GeoPointMapping =
new ClickHouseTupleTypeMapping([Float64Mapping, Float64Mapping], useValueTuple: false);
private static readonly RelationalTypeMapping GeoRingMapping =
new ClickHouseArrayTypeMapping(GeoPointMapping);
private static readonly RelationalTypeMapping GeoLineStringMapping =
new ClickHouseArrayTypeMapping(GeoPointMapping);
private static readonly RelationalTypeMapping GeoPolygonMapping =
new ClickHouseArrayTypeMapping(GeoRingMapping);
private static readonly RelationalTypeMapping GeoMultiLineStringMapping =
new ClickHouseArrayTypeMapping(GeoLineStringMapping);
private static readonly RelationalTypeMapping GeoMultiPolygonMapping =
new ClickHouseArrayTypeMapping(GeoPolygonMapping);
// Alphabetical order matches driver's GeometryType discriminator indices
private static readonly RelationalTypeMapping GeoGeometryMapping =
new ClickHouseVariantTypeMapping([
GeoLineStringMapping, // 0
GeoMultiLineStringMapping, // 1
GeoMultiPolygonMapping, // 2
GeoPointMapping, // 3
GeoPolygonMapping, // 4
GeoRingMapping, // 5
]);

private static readonly Dictionary<Type, RelationalTypeMapping> ClrTypeMappings = new()
{
{ typeof(string), StringMapping },
Expand Down Expand Up @@ -103,6 +129,15 @@ public class ClickHouseTypeMappingSource : RelationalTypeMappingSource
["IPv6"] = IPv6Mapping,

["Json"] = JsonMapping,

// Geo types (structural aliases for Tuple/Array/Variant)
["Point"] = GeoPointMapping,
["Ring"] = GeoRingMapping,
["LineString"] = GeoLineStringMapping,
["Polygon"] = GeoPolygonMapping,
["MultiLineString"] = GeoMultiLineStringMapping,
["MultiPolygon"] = GeoMultiPolygonMapping,
["Geometry"] = GeoGeometryMapping,
};

// Matches a single-quoted string like 'UTC' or 'Asia/Tokyo'
Expand Down Expand Up @@ -457,13 +492,16 @@ public ClickHouseTypeMappingSource(
if (!string.Equals(mappingInfo.StoreTypeNameBase, "Json", StringComparison.OrdinalIgnoreCase))
return null;

// string CLR type + Json store type → ValueConverter-backed mapping
// string CLR type + Json store type → specific string Json mapping
if (mappingInfo.ClrType == typeof(string))
return new ClickHouseJsonTypeMapping(typeof(string));
return StringJsonMapping;

return JsonMapping;
}

// Cached string-specific Json mapping to avoid repeated allocations
private static readonly RelationalTypeMapping StringJsonMapping = new ClickHouseJsonTypeMapping(typeof(string));

private static bool IsReferenceTuple(Type? type)
=> type is not null && type.IsGenericType && type.FullName?.StartsWith("System.Tuple`") == true;

Expand Down
Loading
Loading