ClickHouse / ClickHouse/clickhouse-cs
Types: Object('json') column is parsed as SimpleAggregateFunction, so GetSchema("Columns") reports "SimpleAggregateFunction(, Json)"
- Dominant language
- C#
- Stars
- 94
- Forks
- 22
- Avg merge
- 11h 26m
- Merged PRs (30d)
- 22
Description
### Describe the bug
`ObjectType.Parse` builds and returns a `SimpleAggregateFunctionType` instead of an `ObjectType`
(`ClickHouse.Driver/Types/ObjectType.cs:16-22`):
```csharp
public override ParameterizedType Parse(SyntaxTreeNode node, Func parseClickHouseTypeFunc, TypeSettings settings)
{
return new SimpleAggregateFunctionType
{
UnderlyingType = parseClickHouseTypeFunc(node.ChildNodes[0]),
};
}
```
Every other `ParameterizedType.Parse` returns its own type. As a result a column whose server type is
`Object('json')` resolves to a `SimpleAggregateFunctionType` whose `AggregateFunction` is `null`, so
its rendered name is `SimpleAggregateFunction(, Json)`.
This is user-visible through `GetSchema("Columns")`, which reads `system.columns.type` and runs it
through `TypeConverter.ParseClickHouseType`, then reports `clickHouseType.ToString()` as
`ProviderType` (`ClickHouse.Driver/Utility/SchemaDescriber.cs:128-133`).
Two side effects of the same defect:
* `ObjectType`'s own members (`Name`, `ToString`, `Read`, `Write`) are unreachable — no code path can
ever produce an `ObjectType` instance.
* The alias `{ "OBJECT('JSON')", "Json" }` (`TypeConverter.cs:95`) is dead. `ExtractTypeName` looks up
the alias table with the parsed node value, which for `Object('json')` is just `Object`, so the
parenthesised alias key never matches and the intended mapping to `Json` never happens.
Data reads are not corrupted: `SimpleAggregateFunctionType.Read/Write` delegate to `UnderlyingType`,
which is the same type `ObjectType` would have delegated to. The defect is in the reported type
identity, not in the values.
### Steps to reproduce
1. Start a ClickHouse 25.8 server with `allow_experimental_object_type=1`.
2. `CREATE TABLE zz_probe (id Int32, o Object('json')) ENGINE=Memory`
3. Call `connection.GetSchema("Columns", new[] { "default", "zz_probe" })` and read `ProviderType`.
### Expected behaviour
The reported type should identify the column as an `Object`/`Json` column, not as a
`SimpleAggregateFunction`. The server reports it as `Object('json')`:
```
$ curl -s 'http://server:8123/' --data-binary "SELECT name, type FROM system.columns WHERE table='zz_probe' FORMAT TSV"
id Int32
o Object(\'json\')
```
The alias table already states the intent for this type (`OBJECT('JSON')` -> `Json`), so resolving it
to `SimpleAggregateFunction` is clearly unintended.
### Code example
```csharp
using var conn = new ClickHouseConnection("Host=server;Port=8123;Username=default;Compression=false");
conn.CustomSettings.Add("allow_experimental_object_type", 1);
await conn.ExecuteStatementAsync("CREATE TABLE zz_probe (id Int32, o Object('json')) ENGINE=Memory");
var schema = conn.GetSchema("Columns", new[] { "default", "zz_probe" });
foreach (DataRow r in schema.Rows)
Console.WriteLine($"{r["Name"]} => ProviderType='{r["ProviderType"]}'");
```
Actual output:
```
id => ProviderType='Int32'
o => ProviderType='SimpleAggregateFunction(, Json)'
```
Parsing the type string directly shows the same result for every `Object(...)` shape:
```
INPUT=Object('json') -> CLR=SimpleAggregateFunctionType ToString=SimpleAggregateFunction(, Json)
INPUT=Object(String) -> CLR=SimpleAggregateFunctionType ToString=SimpleAggregateFunction(, String)
INPUT=Object(Nullable(String)) -> CLR=SimpleAggregateFunctionType ToString=SimpleAggregateFunction(, Nullable(String))
```
Contrast case that must keep its current behaviour:
```
INPUT=SimpleAggregateFunction(sum, Int64) -> ToString=SimpleAggregateFunction(sum, Int64) (correct)
```
### Error log
No exception. The type is silently reported under the wrong name.
### Root cause
`ClickHouse.Driver/Types/ObjectType.cs:16-22` — `Parse` returns a `SimpleAggregateFunctionType`
rather than an `ObjectType`. Because `SimpleAggregateFunctionType.Parse` expects two child nodes
(`AggregateFunction`, `UnderlyingType`) while `Object(...)` has one, the produced instance also has a
`null` `AggregateFunction`, which is what renders as the empty first argument.
### Suggested fix
Return the type the class represents:
```csharp
return new ObjectType
{
UnderlyingType = parseClickHouseTypeFunc(node.ChildNodes[0]),
};
```
If, instead, `Object('json')` is meant to be an alias of `Json` (which the `OBJECT('JSON')` alias
entry suggests), then the alias lookup should be made to actually fire and the intent documented —
but either way it should not resolve to a third, unrelated type. `SimpleAggregateFunction(...)`
parsing must keep its current behaviour.
### Configuration
#### Environment
* Client version: `main` (commit at the time of testing), built for `net10.0`
* .NET version: .NET 10.0 SDK
* OS: Linux (Debian container)
#### ClickHouse server
* ClickHouse Server version: 25.8.28.1 (the repo's supported floor; the type still exists there).
Note: on 26.7 the server has removed `Object(...)` entirely (`Unknown data type family: Object`),
so this only affects servers that still accept the deprecated type.
* ClickHouse Server non-default settings: `allow_experimental_object_type=1`
* `CREATE TABLE`: `CREATE TABLE zz_probe (id Int32, o Object('json')) ENGINE=Memory`
---
Found by automated analysis of this client while working on #542 / PR #544, and verified against a
live 25.8 server rather than by inspection.
Contributor guide
Research direction
Start in ClickHouse.Driver/Types/ObjectType.cs:16-22 and inspect how ObjectType.Parse constructs the parsed type. Reproduce the examples through TypeConverter.ParseClickHouseType or GetSchema("Columns"), then verify Object(...) reports its intended type while SimpleAggregateFunction(sum, Int64) remains unchanged; SchemaDescriber.cs:128-133 shows the reported ProviderType path.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp, sql
- Domain
- databases
- Issue type
- Bug
- Difficulty
- 1/5
- Estimated time
- Under an hour
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 85/100