Skip to content

Types: Object('json') column is parsed as SimpleAggregateFunction, so GetSchema("Columns") reports "SimpleAggregateFunction(, Json)" #545

Description

@polyglotAI-bot

Describe the bug

ObjectType.Parse builds and returns a SimpleAggregateFunctionType instead of an ObjectType
(ClickHouse.Driver/Types/ObjectType.cs:16-22):

public override ParameterizedType Parse(SyntaxTreeNode node, Func<SyntaxTreeNode, ClickHouseType> 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

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-22Parse 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:

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions