Repository navigation
Description
Describe the bug
In read_side_padding.rs, the three-argument arm ([Array, Scalar(Int32), Scalar(Utf8)]) ignores the pad string when the input is a dictionary array. The Utf8 and LargeUtf8 branches pass string to spark_read_side_padding_internal, but both Dictionary branches pass SPACE. So rpad/lpad with a custom pad pads with spaces instead.
datafusion-comet/native/spark-expr/src/static_invoke/char_varchar_utils/read_side_padding.rs
Lines 115 to 132 in 644a870
| DataType::Dictionary(_, value_type) => { | |
| let dict = as_dictionary_array::<Int32Type>(array); | |
| let col = if value_type.as_ref() == &DataType::Utf8 { | |
| spark_read_side_padding_internal::<i32>( | |
| dict.values(), | |
| truncate, | |
| ColumnarValue::Scalar(ScalarValue::Int32(Some(*length))), | |
| SPACE, | |
| is_left_pad, | |
| )? | |
| } else { | |
| spark_read_side_padding_internal::<i64>( | |
| dict.values(), | |
| truncate, | |
| ColumnarValue::Scalar(ScalarValue::Int32(Some(*length))), | |
| SPACE, | |
| is_left_pad, | |
| )? |
Steps to reproduce
Kernel-level, on main at 644a870:
let dict: DictionaryArray<Int32Type> = vec!["ab", "ab"].into_iter().collect();
let args = vec![
ColumnarValue::Array(Arc::new(dict)),
ColumnarValue::Scalar(ScalarValue::Int32(Some(5))),
ColumnarValue::Scalar(ScalarValue::Utf8(Some("x".to_string()))),
];
spark_rpad(&args) // ["ab ", "ab "]I couldn't find a SQL query that reaches this today. The native scan input unpacks dictionary-encoded strings (see test_unpack_dictionary_string in planner.rs), and the existing string_rpad.sql tests with a custom pad pass. So this looks latent, but it would give silent wrong results if a dictionary array ever reaches the kernel, which is what the Dictionary arm (added for SPARK-48498) is there for.
Expected behavior
["abxxx", "abxxx"], the same as for a Utf8 input.
Additional context
Found while reviewing my change for #7245 (PR #7450). The fix is to pass string instead of SPACE in the two Dictionary branches and add a unit test with a dictionary input and a non-space pad.
Willingness to contribute
I can contribute a fix for this bug independently
AI disclosure: this issue was investigated and written with an AI coding assistant, and reviewed by me.
Activity
kazuyukitanimura commentedon Nov 9, 2026
Hi @tlfua
Thank you for reporting. Would you mind checking if this bug is reachable? It could be we are always converting to non-dictionary...
tlfua commentedon Nov 10, 2026
Thanks @kazuyukitanimura, you're right. I traced every path that feeds a native expression on
main(8c145b7), and none of them can handread_side_paddinga dictionary array today:ScanExec, which covers JVM shuffle reads, broadcasts and local table scans):schema_from_data_typesunpacks every dictionary type, andbuild_record_batchcasts each column to that schema.datafusion-comet/native/core/src/execution/operators/scan.rs
Lines 195 to 208 in 8c145b7
datafusion-comet/native/core/src/execution/operators/scan.rs
Lines 309 to 333 in 8c145b7
datafusion-comet/native/core/src/execution/operators/shuffle_scan.rs
Lines 221 to 229 in 8c145b7
ARROW:schemadeclares them. This is covered bydictionary_columns_are_unwrapped_to_required_types.datafusion-comet/native/core/src/parquet/schema_adapter.rs
Line 3121 in 8c145b7
datafusion-comet/native/core/src/execution/operators/iceberg_scan.rs
Line 240 in 8c145b7
to_arrow_datatype, which mapsStringTypetoUtf8and never toDictionary.datafusion-comet/native/core/src/execution/serde.rs
Line 80 in 8c145b7
native/builds a dictionary array from a plain one. The only place that creates one isread_side_paddingitself, and only when its input is already a dictionary.The arm was reachable when it was written. #1482 added the
Dictionaryarm, and #2436 copied it into the three-argument case withSPACE. At that timeScanExecstill kept string dictionaries. #2635 then changedScanExecto unpack every dictionary type, which made the arm dead code.datafusion-comet/native/spark-expr/src/static_invoke/char_varchar_utils/read_side_padding.rs
Lines 113 to 137 in 8c145b7
So this is latent rather than a user-facing wrong result, and I don't think it should be
priority:critical. I'll leave the exact priority to the maintainers. I'm happy to send a small PR that passes the pad string in bothDictionarybranches and adds a kernel unit test, so the arm stays correct if a dictionary ever reaches it again. Would you prefer that, or should I close this as not reachable?