ClickHouse / ClickHouse/ClickHouse

A `Dynamic` constant holding an `Enum` is re-emitted to a shard as its bare number: `remote()` reads return the wrong row and diverge from the local plan

Open
#119,745 1 comment 0 reactions 0 assignees View on GitHub
comp-distributed comp-query-analyzer
Dominant language
C++
Stars
49.9k
Forks
9k
Avg merge
21h 32m
Merged PRs (30d)
515

Description

**TL;DR:** A `Dynamic` constant holding an `Enum` value is re-emitted to a shard as its bare underlying number (`_CAST(3, 'Dynamic')`), so a `remote()`/`Distributed` read filters on `'3'` instead of `'7'` and returns different rows than the identical local query. `=` against the same constant succeeds locally but throws `NO_COMMON_TYPE` remotely. The same constant wrapped in `Variant` is serialized correctly, because the `Variant` path names the member type and the `Dynamic` path does not.

**Company or project name:** N/A

**Describe what's wrong**

The enum is `Enum8('7' = 3)`: the name `'7'` and the number `3` are both valid strings and both stored, so a cell distinguishes "returned nothing" from "returned the WRONG row".

```sql
CREATE TABLE tdist (v String) ENGINE = MergeTree ORDER BY v;
INSERT INTO tdist VALUES ('7'), ('3'), ('zz');
```

1. The same query returns different rows depending on whether the read is local or remote (master at the head of #117998, which fixed the local side):

```sql
SELECT arraySort(groupArray(v)) FROM tdist
WHERE v IN (CAST(CAST('7', 'Enum8(\'7\' = 3)') AS Dynamic));
-- ['7'] (correct)

SELECT arraySort(groupArray(v)) FROM remote('127.0.0.1', currentDatabase(), tdist)
WHERE v IN (CAST(CAST('7', 'Enum8(\'7\' = 3)') AS Dynamic));
-- ['3'] (wrong row; the control WHERE v IN ('7') returns ['7'])

SELECT arraySort(groupArray(v)) FROM remote('127.0.0.1', currentDatabase(), tdist)
WHERE v NOT IN (CAST(CAST('7', 'Enum8(\'7\' = 3)') AS Dynamic));
-- ['7','zz']: the matching row is dropped, the row that should be excluded is returned
```

2. `=` throws remotely while working locally, and the error text shows the re-emitted literal that lost the `Enum`:

```sql
SELECT arraySort(groupArray(v)) FROM remote('127.0.0.1', currentDatabase(), tdist)
WHERE v = CAST(CAST('7', 'Enum8(\'7\' = 3)') AS Dynamic);
-- Code: 386. DB::Exception: There is no supertype for types String, Int8 ...
-- while executing 'FUNCTION equals(v, _CAST(Int8_3_Dynamic, 'Dynamic'))'
-- The local variant of the same query returns ['7'].
```

3. The `Variant` carrier is correct through the same route: `WHERE v IN (CAST(CAST('7', 'Enum8(\'7\' = 3)') AS Variant(Array(UInt8), Enum8('7' = 3))))` returns `['7']` both locally and remotely.

**Which ClickHouse versions reproduce the issue?**

Reproduced on 26.9.1.1 master including the head of #117998. The shard-side wrong filter is pre-existing (before that fix the local side returned the same wrong row for the same reason, so the divergence was invisible); #117998 listed this face as sharing "a cause one layer up" and left it unaddressed.

**Expected behavior**

A `Dynamic` constant reaches the shard with its stored subtype intact: the remote `IN` matches `'7'` and only `'7'`, agreeing with the local plan, and the remote `=` succeeds like the local one.

**Additional context**

When the initiator re-emits the constant for the shard, the `Dynamic` branch of `columnConstantToExactLiteralASTImpl` (`src/Analyzer/Utils.cpp`) recurses into the active member without naming its type, on the grounds that `Dynamic` accepts any type. For members whose literal is inferred back to the same type that is lossless, but an `Enum` member's literal is its bare number, so the shard re-infers `Int8` and the value's identity changes from `'7'` to `3`. The `Variant` branch directly above it wraps the member literal in a cast to the exact member type for exactly this reason, which is why the `Variant` carrier round-trips correctly. `ColumnDynamic::getTypeAt` exposes the stored subtype, so the `Dynamic` branch has the type it would need to name.

Contributor guide

Open the contributing guide

Research direction

Start in src/Analyzer/Utils.cpp at columnConstantToExactLiteralASTImpl, comparing the Dynamic branch with the Variant branch above it, and inspect ColumnDynamic::getTypeAt. Reproduce the supplied remote IN and = queries, then confirm that the shard preserves the Enum subtype and returns the same rows and comparison result as the local query.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp, sql
Domain
databases, distributed-systems
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
72/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.