Skip to content

Commit 2dcd8f0

Browse files
committed
Add paste-ready upstream (apache/datafusion) issue drafts
Session GitHub access is scoped to xqlsystems/xarray-sql, so the issues can't be filed on apache/datafusion from here. Add the two ready-to-file drafts (AI-usage disclosure per the DataFusion AI policy, inline + Rust repros, links to the downstream tracking issues #220/#221) so they can be filed upstream directly. Co-Authored-By: Claude Opus 4.8 <[email protected]> Claude-Session: https://claude.ai/code/session_019VuSeCio99NcME5eubcN3N
1 parent 113d213 commit 2dcd8f0

2 files changed

Lines changed: 158 additions & 0 deletions

File tree

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<!-- Draft to file at apache/datafusion. Title below; body is everything under it. -->
2+
3+
# Title
4+
5+
Aggregating a dictionary column across partitions overflows the key type ("Dictionary key bigger than the key type")
6+
7+
# Body
8+
9+
> Filed with AI assistance, per the project's AI policy. The reproduction was written and verified by running it against `datafusion 54.0.0` (`pyarrow 23.0.0`, arrow-rs 58.3.0); a human reviewed it before filing.
10+
11+
## Describe the bug
12+
13+
`GROUP BY` over a dictionary-typed column fails when the *combined* dictionary across partitions exceeds the per-batch key width:
14+
15+
```
16+
DataFusion error: Arrow error: Dictionary key bigger than the key type
17+
```
18+
19+
Each input partition is a **valid** `Dictionary(Int8, Int64)` — its dictionary has ≤128 distinct values, so an `Int8` key is legal *per batch*. But the partitions carry **disjoint** values, so when the aggregate combines them the combined dictionary exceeds 128 entries and the `Int8` key overflows.
20+
21+
The disjointness matters: when every partition shares the *same* dictionary values, they are unified and there is no overflow. That is why this is intermittent in the wild.
22+
23+
## To Reproduce
24+
25+
Pure `datafusion` + `pyarrow`, no other deps, no network:
26+
27+
```python
28+
import numpy as np, pyarrow as pa
29+
from datafusion import SessionContext
30+
31+
PER_PARTITION, N_PARTITIONS = 100, 50 # combined cardinality 5000 >> Int8 max
32+
partitions = []
33+
for p in range(N_PARTITIONS):
34+
values = pa.array(np.arange(p * PER_PARTITION, p * PER_PARTITION + PER_PARTITION, dtype=np.int64))
35+
keys = pa.array(np.arange(PER_PARTITION, dtype=np.int8))
36+
k = pa.DictionaryArray.from_arrays(keys, values) # valid Dictionary(Int8, Int64) per batch
37+
partitions.append(pa.record_batch({"k": k, "v": pa.array(np.ones(PER_PARTITION))}))
38+
39+
ctx = SessionContext()
40+
ctx.register_record_batches("t", [[b] for b in partitions]) # one partition per batch
41+
ctx.sql("SELECT k, SUM(v) FROM t GROUP BY k").to_arrow_table()
42+
# -> Arrow error: Dictionary key bigger than the key type
43+
```
44+
45+
Rust equivalent (paste into a `datafusion` test):
46+
47+
```rust
48+
use std::sync::Arc;
49+
use arrow::array::{DictionaryArray, Float64Array, Int8Array, Int64Array};
50+
use arrow::record_batch::RecordBatch;
51+
use datafusion::prelude::*;
52+
53+
#[tokio::test]
54+
async fn dict_key_overflow() -> datafusion::error::Result<()> {
55+
let ctx = SessionContext::new();
56+
let mut partitions = Vec::new();
57+
for p in 0..50i64 {
58+
let values = Int64Array::from_iter_values((p * 100)..(p * 100 + 100));
59+
let keys = Int8Array::from_iter_values(0..100i8);
60+
let k = DictionaryArray::new(keys, Arc::new(values));
61+
let v = Float64Array::from(vec![1.0; 100]);
62+
partitions.push(vec![RecordBatch::try_from_iter([
63+
("k", Arc::new(k) as _), ("v", Arc::new(v) as _),
64+
])?]);
65+
}
66+
ctx.register_batches("t", partitions)?;
67+
ctx.sql("SELECT k, SUM(v) FROM t GROUP BY k").await?.collect().await?;
68+
Ok(())
69+
}
70+
```
71+
72+
## Expected behavior
73+
74+
Combining dictionary-typed columns across partitions should not fail on a key type that was valid for each input batch. It should widen the key (`Int8` → `Int16` → …) or decode, since a producer cannot know per batch how large the *combined* dictionary will become downstream.
75+
76+
## Additional context
77+
78+
Version: datafusion 54.0.0 / pyarrow 23.0.0 (arrow-rs 58.3.0).
79+
80+
Downstream tracking issue with the same repro (xarray-sql): xqlsystems/xarray-sql#220. It blocks dictionary-encoding coordinate columns there; a chunked coordinate whose partitions hold disjoint slices hits this under streaming aggregation.
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<!-- Draft to file at apache/datafusion. Title below; body is everything under it. -->
2+
3+
# Title
4+
5+
`date_part` over a `Dictionary(Timestamp)` returns a type that violates its declared `Int32` (crashes GROUP BY / JOIN)
6+
7+
# Body
8+
9+
> Filed with AI assistance, per the project's AI policy. The reproduction was written and verified by running it against `datafusion 54.0.0` (`pyarrow 23.0.0`, arrow-rs 58.3.0); a human reviewed it before filing.
10+
11+
## Describe the bug
12+
13+
`date_part('hour', t)` where `t` is `Dictionary(Int32, Timestamp)` preserves the dictionary encoding and returns a `Dictionary(Int32, Int32)`, but the expression's **declared logical type is plain `Int32`**. The physical/logical mismatch surfaces as soon as the column is materialized:
14+
15+
```
16+
Arrow error: Invalid argument error: column types must match schema types,
17+
expected Int32 but found Dictionary(Int32, Int32) at column index 0
18+
```
19+
20+
A `GROUP BY` / `JOIN` on the same expression is worse — it **panics a worker thread**:
21+
22+
```
23+
thread 'tokio-rt-worker' panicked at arrow-array-58.3.0/src/cast.rs:849: primitive array
24+
```
25+
26+
(a failed downcast of the dictionary array to a primitive array). `GROUP BY date_part('hour', time)` is the canonical way to write a diurnal climatology, so this is not an edge case for time-series workloads.
27+
28+
## To Reproduce
29+
30+
Pure `datafusion` + `pyarrow`, no other deps, no network:
31+
32+
```python
33+
import numpy as np, pyarrow as pa
34+
from datafusion import SessionContext
35+
36+
N = 240
37+
stamps = (np.datetime64("2020-01-01") + np.arange(N) * np.timedelta64(1, "h")).astype("datetime64[us]")
38+
distinct = np.unique(stamps)
39+
values = pa.array(distinct, type=pa.timestamp("us"))
40+
indices = pa.array(np.searchsorted(distinct, stamps).astype(np.int32))
41+
t = pa.DictionaryArray.from_arrays(indices, values) # Dictionary(Int32, Timestamp)
42+
43+
ctx = SessionContext()
44+
ctx.register_record_batches("x", [pa.table({"t": t}).to_batches()])
45+
ctx.sql("SELECT date_part('hour', t) AS h FROM x").to_arrow_table()
46+
# -> expected Int32 but found Dictionary(Int32, Int32) at column index 0
47+
# and: SELECT date_part('hour', t), COUNT(*) FROM x GROUP BY 1 -> panics ("primitive array")
48+
```
49+
50+
Rust equivalent (paste into a `datafusion` test):
51+
52+
```rust
53+
use std::sync::Arc;
54+
use arrow::array::{DictionaryArray, Int32Array, TimestampMicrosecondArray};
55+
use arrow::record_batch::RecordBatch;
56+
use datafusion::prelude::*;
57+
58+
#[tokio::test]
59+
async fn date_part_over_dictionary_timestamp() -> datafusion::error::Result<()> {
60+
let ctx = SessionContext::new();
61+
let values = TimestampMicrosecondArray::from(vec![0, 3_600_000_000, 7_200_000_000]);
62+
let keys = Int32Array::from(vec![0, 1, 2, 0, 1, 2]);
63+
let t = DictionaryArray::new(keys, Arc::new(values));
64+
ctx.register_batch("x", RecordBatch::try_from_iter([("t", Arc::new(t) as _)])?)?;
65+
ctx.sql("SELECT date_part('hour', t) AS h FROM x").await?.collect().await?;
66+
Ok(())
67+
}
68+
```
69+
70+
## Expected behavior
71+
72+
`date_part` (and temporal scalar functions generally) over a dictionary-encoded input should return a plain `Int32` (decode the dictionary), or declare the `Dictionary(Int32, Int32)` it actually produces — logical and physical types must agree, and a downstream `GROUP BY` / `JOIN` must never panic on the result.
73+
74+
## Additional context
75+
76+
Version: datafusion 54.0.0 / pyarrow 23.0.0 (arrow-rs 58.3.0).
77+
78+
Downstream tracking issue with the same repro (xarray-sql): xqlsystems/xarray-sql#221. It blocks dictionary-encoding the time coordinate there; because time is where `date_part`/`date_trunc`/`extract` are applied most, encoding it breaks climatology and anomaly queries.

0 commit comments

Comments
 (0)