You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
COPY … PARTITIONED BY writes partition values containing = unescaped (p0=x=y); DuckDB and Polars cannot read the dataset or silently drop the partition column #26492
COPY … TO 'dir/' PARTITIONED BY (p0) names the directory p0=<value>. The value is not escaped with Hive's
escaping rules. It goes through object_store::path::Path::join, which percent-encodes only the characters that
object stores advise against (/, %, #, ?, *, \, ", [, ], {, }, ^, |, <, >, ~, `, control
and non-ASCII characters). The key/value separator = is written as is, and so are : and ', which Hive's escapePathName escapes, and spaces, which pyarrow, DuckDB and Polars escape.
A value x=y gives the directory p0=x=y.
DataFusion and pyarrow split at the first = and read the dataset back. DuckDB and Polars do not:
next to other partitions, DuckDB fails with Hive partition mismatch … key "p0" not found, and Polars fails as well;
when p0=x=y is the only partition, both return the rows without the p0 column, with no error.
For the same table, pyarrow, DuckDB and Polars write p0=x%3Dy (and p0=a%20b), which every reader, including
DataFusion, reads back.
The path is built in compute_hive_style_file_path (datafusion/datasource/src/write/demux.rs:636): file_path.join(format!("{}={}", partition_by[j].0, part_key[j])). Escaping the value with the Hive rules (at least = and %, which ListingTable already decodes on read since #23226) would make the output portable.
CREATE EXTERNAL TABLE src STORED AS PARQUET LOCATION 'src.parquet';
COPY (SELECT*FROM src) TO 'd/' STORED AS PARQUET PARTITIONED BY (p0);
$ ls d
'p0=%C3%A9' 'p0=a b' 'p0=a%2Fb' 'p0=x=y'
importduckdb, polarsasplduckdb.sql("SELECT rid, p0 FROM read_parquet('d/**/*.parquet', hive_partitioning = true)")
# Binder Error: Hive partition mismatch between file "d/p0=%C3%A9/….parquet" and "d/p0=x=y/….parquet": key "p0" not foundpl.scan_parquet('d/**/*.parquet', hive_partitioning=True, hive_schema={'p0': pl.String}).collect()
# could not create a new DataFrame: height of column 'p0' (3) does not match DataFrame height (4)
Only the x=y partition:
COPY (SELECT1AS rid, 'x=y'AS p0) TO 'one/' STORED AS PARQUET PARTITIONED BY (p0); -- one/p0=x=y/…
duckdb.sql("SELECT * FROM read_parquet('one/**/*.parquet', hive_partitioning = true)").columns# ['rid'] — p0 is gonepl.scan_parquet('one/**/*.parquet', hive_partitioning=True).collect() # only column rid
Expected behavior
Partition values are escaped as Hive, pyarrow, DuckDB and Polars do (p0=x%3Dy; the last three also write p0=a%20b), so that DuckDB, Polars, Spark and Hive read the
dataset back with the right p0 values.
Additional context
DataFusion main 290ad15 (2026-10-18), built from source; also 91cd7ff (2026-10-15).
Activity
akshat-lakhera commentedon Oct 19, 2026
take
Plan: escape the partition value with Hive escapePathName rules when building the directory name, plus a sqllogictest.