namanjain24-sudo opened a new issue, #25190:
URL: https://github.com/apache/datafusion/issues/25190

   ### Describe the bug
   
   Substrait's `IfThen` has no notion of a base expression. Every `IfClause` is 
a
   standalone boolean condition, and `then` is the value that clause yields:
   
   ```proto
   message IfThen {
     // A list of one or more IfClauses
     repeated IfClause ifs = 1;
     // The returned Expression if no IfClauses are satisified
     Expression else = 2;
   
     message IfClause {
       Expression if = 1;
       Expression then = 2;
     }
   }
   ```
   
   The producer nevertheless uses `IfThen` for `CASE <base> WHEN <value> THEN 
...`,
   encoding the base expression as a leading `IfClause` whose `then` is left 
unset
   
([`from_case`](https://github.com/apache/datafusion/blob/517ac6d53133621c99a9a7dda470549a26348f6a/datafusion/substrait/src/logical_plan/producer/expr/if_then.rs#L36-L50)):
   
   ```rust
   // Parse base
   if let Some(e) = expr {
       // Base expression exists
       ifs.push(IfClause {
           r#if: Some(producer.handle_expr(e, schema)?),
           then: None,
       });
   }
   // Parse `when`s
   for (r#if, then) in when_then_expr {
       ifs.push(IfClause {
           r#if: Some(producer.handle_expr(r#if, schema)?),
           then: Some(producer.handle_expr(then, schema)?),
       });
   }
   ```
   
   The following clauses then carry the raw WHEN operand in `if`. Neither the
   leading clause nor the WHEN operands are boolean, so the emitted `IfThen` is 
not
   something a spec-conforming consumer can evaluate.
   
   That convention is private to DataFusion. The consumer decodes it back
   
([`from_if_then`](https://github.com/apache/datafusion/blob/517ac6d53133621c99a9a7dda470549a26348f6a/datafusion/substrait/src/logical_plan/consumer/expr/if_then.rs#L29-L43)):
   
   ```rust
   // If the first element does not have a `then` part, then we can assume it's 
a base expression
   ```
   
   so a DataFusion-to-DataFusion round trip is unaffected and no existing test
   fails. An engine that reads the plan as Substrait defines it sees something
   different.
   
   ### To Reproduce
   
   ```sql
   SELECT CASE a WHEN 1 THEN 'x' WHEN 2 THEN 'y' ELSE 'z' END FROM data
   ```
   
   Produce the plan with `to_substrait_plan` and inspect the `IfThen` in the
   projection directly, rather than round-tripping it through the consumer:
   
   ```
   IfThen with 3 ifs, else present: true
     ifs[0]: if_set=true then_set=false
       if   = Selection(FieldReference { ... StructField { field: 0 } ... })   
// a, an i64
       then = None
     ifs[1]: if_set=true then_set=true
       if   = Literal(I64(1))
       then = Literal(String("x"))
     ifs[2]: if_set=true then_set=true
       if   = Literal(I64(2))
       then = Literal(String("y"))
   ```
   
   Three clauses, with conditions `a`, `1` and `2`. All three are `i64`, and the
   first has no result at all.
   
   For comparison, the searched form `CASE WHEN a = 1 THEN 'x' ELSE 'z' END` is
   emitted correctly, as a single clause whose condition is an `equal` call with
   `output_type` boolean.
   
   ### Expected behavior
   
   Every emitted `IfClause` should have a boolean condition and a `then` value.
   
   Substrait has no switch-style construct that fits this case in general:
   `SwitchExpression` exists, but its `IfValue.if` is a `Literal`, so it cannot
   express `CASE a WHEN b + 1 THEN ...`, and DataFusion's own consumer currently
   rejects it with `not_impl_err!("Switch expression not supported")`. The 
general
   translation is therefore to desugar the base expression, emitting `<base> =
   <value>` as each clause condition:
   
   ```
   ifs[0]: if = equal(a, 1), then = 'x'
   ifs[1]: if = equal(a, 2), then = 'y'
   else  : 'z'
   ```
   
   DataFusion matches a base expression with `=` semantics
   
([`compare_with_eq`](https://github.com/apache/datafusion/blob/517ac6d53133621c99a9a7dda470549a26348f6a/datafusion/physical-expr-common/src/datum.rs#L138)
   uses Arrow's `eq`), so this preserves the plan's meaning, including a NULL 
WHEN
   operand never matching. The producer already applies exactly this kind of
   desugaring to `BETWEEN` in `from_between`.
   
   A base `CASE` would then round trip as the equivalent searched `CASE`, 
keeping
   its projection name and schema.
   
   ### Additional context
   
   - Distinct from the round-trip failures collected under #16248: this round 
trip
     succeeds. The problem is only visible by reading the emitted protobuf, 
because
     producer and consumer share the private convention.
   - Same shape as #25100 (`phase` left at `AGGREGATION_PHASE_UNSPECIFIED`): a
     producer field whose value is wrong by the spec but invisible to 
DataFusion's
     own consumer.
   - Adding `SwitchExpression` support, on both sides, would preserve the 
base-CASE
     structure for the subset where every WHEN operand is a literal. That is 
worth
     doing separately; it does not cover the general case, and the emitted plan
     should be valid regardless.
   - Verified against the `algebra.proto` shipped in the pinned `substrait` 
0.63.0
     crate.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to