Skip to content

Runtime filter crash on Tuple(Variant(...), ...) join key — incomplete fix for nested Variant #101415

@clickgapai

Description

@clickgapai

Found via ClickGap automated review. Please close or comment if this is incorrect or needs adjustment.

Retrospective finding from a historical scan of PR #100182 (merged 2026-03-20). Confirmed on current codebase — close with a note if already fixed.

Describe what's wrong

Joining on a Tuple containing a Variant element with exactly 1 row on the right side crashes with LOGICAL_ERROR: 'Unexpected return type from __applyFilter. Expected UInt8. Got Nullable(UInt8)'

Root cause: RuntimeFilterLookup.h:109-111 — isVariant()/isDynamic() only checks top-level type, while hasNullable() traverses nested types (Array, Tuple, Map) but doesn't recognize Variant/Dynamic as null-containing. Nested Variant inside compound types is missed.

Affected locations:

  • src/Processors/QueryPlan/RuntimeFilterLookup.h:109 — argument_can_have_nulls initialization misses nested Variant in compound types
  • src/DataTypes/hasNullable.cpp:9 — hasNullable() doesn't check for Variant/Dynamic types during recursive traversal

Impact: Server crashes (LOGICAL_ERROR) on valid SQL when joining on Tuple or other compound types containing Variant elements, with exactly 1 distinct value on the right side of the join.

Does it reproduce on most recent release?

Yes — confirmed on current master (commit 3b93559b0d1c).

How to reproduce

-- Bug: Tuple(Variant(...), ...) as join key with 1 row on right triggers ValuesCount::ONE path
-- which uses equals function producing Nullable(UInt8) instead of UInt8, causing LOGICAL_ERROR.
-- hasNullable() doesn't detect Variant inside Tuple, and isVariant() only checks top-level type.
SELECT *
FROM
    (SELECT tuple(v, 1) as k FROM format(TSV, 'v Variant(String, Bool)', 'true\nfalse')) AS t1
    JOIN
    (SELECT tuple(v, 1) as k FROM format(TSV, 'v Variant(String, Bool)', 'true')) AS t2
    USING (k);

Try it on ClickHouse Fiddle

Expected behavior

(true,1)

Error message and/or stacktrace

Code: 49. DB::Exception: Unexpected return type from __applyFilter. Expected UInt8. Got Nullable(UInt8). (LOGICAL_ERROR)

Additional context

Open risks:

  • Map(String, Variant(...)) as join key may also be affected, though Map join keys are uncommon

Suggested fix: Either extend hasNullable() in hasNullable.cpp to also return true for Variant/Dynamic types (fixing it for all callers), or change the runtime filter check to recursively inspect nested types for Variant/Dynamic. The hasNullable.cpp fix is more robust: add if (WhichDataType(type).isVariant() || WhichDataType(type).isDynamic()) return true; before the Array/Tuple/Map checks.

Analysis details: Confidence HIGH | Severity P0 | Testability: STATELESS_SQL

Found during automated review of PR #100182.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugConfirmed user-visible misbehaviour in official release

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions