Describe the bug
HashJoinExec.projection: Option<Vec<usize>> (and the same field on NestedLoopJoinExec) is serialized as a bare repeated uint32. Proto3 cannot tell None from Some(vec![]), and the decoder treats empty as None:
let projection = if !hashjoin.projection.is_empty() {
Some(hashjoin.projection.iter().map(|i| *i as usize).collect())
} else {
None // Some(vec![]) folded in here
};
Some(vec![]) means "emit zero columns"; None means "emit the full join schema". The round-trip silently changes the output schema. FilterExec already has a workaround for the same proto3 limitation — these two execs were missed.
To Reproduce
No response
Expected behavior
No response
Additional context
datafusion 53.x, 54.x, and main
Describe the bug
HashJoinExec.projection: Option<Vec<usize>>(and the same field onNestedLoopJoinExec) is serialized as a barerepeated uint32. Proto3 cannot tellNonefromSome(vec![]), and the decoder treats empty asNone:Some(vec![])means "emit zero columns";Nonemeans "emit the full join schema". The round-trip silently changes the output schema.FilterExecalready has a workaround for the same proto3 limitation — these two execs were missed.To Reproduce
No response
Expected behavior
No response
Additional context
datafusion53.x, 54.x, andmain