Skip to content

Commit e3d3aca

Browse files
committed
fix: reverted consumer changes so now producer only emits expressions
1 parent b2e4cb5 commit e3d3aca

1 file changed

Lines changed: 109 additions & 58 deletions

File tree

  • datafusion/substrait/src/logical_plan/consumer/rel

datafusion/substrait/src/logical_plan/consumer/rel/read_rel.rs

Lines changed: 109 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ use substrait::proto::read_rel::local_files::file_or_files::PathType::UriFile;
3535
use substrait::proto::{Expression, ReadRel};
3636
use url::Url;
3737

38+
#[expect(deprecated)]
3839
pub async fn from_read_rel(
3940
consumer: &impl SubstraitConsumer,
4041
read: &ReadRel,
@@ -113,17 +114,29 @@ pub async fn from_read_rel(
113114
.await
114115
}
115116
Some(ReadType::VirtualTable(vt)) => {
116-
// Check for produce_one_row pattern.
117+
if vt.values.is_empty() && vt.expressions.is_empty() {
118+
return Ok(LogicalPlan::EmptyRelation(EmptyRelation {
119+
produce_one_row: false,
120+
schema: DFSchemaRef::new(substrait_schema),
121+
}));
122+
}
123+
124+
// Check for produce_one_row pattern in both old (values) and new (expressions) formats.
117125
// A VirtualTable with exactly one row containing only empty/default fields represents
118126
// an EmptyRelation with produce_one_row=true. This pattern is used for queries without
119127
// a FROM clause (e.g., "SELECT 1 AS one") where a single phantom row is needed to
120128
// provide a context for evaluating scalar expressions. This is conceptually similar to
121129
// the SQL "DUAL" table (see: https://en.wikipedia.org/wiki/DUAL_table) which some
122130
// databases provide as a single-row source for selecting constant expressions when no
123131
// real table is present.
124-
let is_produce_one_row = vt.expressions.len() == 1
132+
let is_produce_one_row = (vt.values.len() == 1
133+
&& vt.expressions.is_empty()
125134
&& substrait_schema.fields().is_empty()
126-
&& vt.expressions[0].fields.is_empty();
135+
&& vt.values[0].fields.is_empty())
136+
|| (vt.expressions.len() == 1
137+
&& vt.values.is_empty()
138+
&& substrait_schema.fields().is_empty()
139+
&& vt.expressions[0].fields.is_empty());
127140

128141
if is_produce_one_row {
129142
return Ok(LogicalPlan::EmptyRelation(EmptyRelation {
@@ -132,65 +145,63 @@ pub async fn from_read_rel(
132145
}));
133146
}
134147

135-
if vt.expressions.is_empty() {
136-
return Ok(LogicalPlan::EmptyRelation(EmptyRelation {
137-
produce_one_row: false,
138-
schema: DFSchemaRef::new(substrait_schema),
139-
}));
140-
}
141-
142-
let mut values = vec![];
143-
for row in &vt.expressions {
144-
if row.fields.len() != substrait_schema.fields().len() {
145-
return substrait_err!(
146-
"Field count mismatch: expected {} fields but found {} in virtual table row",
147-
substrait_schema.fields().len(),
148-
row.fields.len()
149-
);
150-
}
151-
152-
let mut row_exprs = vec![];
153-
let mut name_idx = 0;
154-
for (field_idx, expression) in row.fields.iter().enumerate() {
155-
let expr = match expression.rex_type.as_ref() {
156-
Some(substrait::proto::expression::RexType::Literal(lit)) => {
157-
if !named_struct.names.is_empty() {
158-
name_idx += 1; // top-level names are provided through schema
148+
let values = if !vt.expressions.is_empty() {
149+
let mut exprs = vec![];
150+
for row in &vt.expressions {
151+
if row.fields.len() != substrait_schema.fields().len() {
152+
return substrait_err!(
153+
"Field count mismatch: expected {} fields but found {} in virtual table row",
154+
substrait_schema.fields().len(),
155+
row.fields.len()
156+
);
157+
}
158+
159+
let mut row_exprs = vec![];
160+
let mut name_idx = 0;
161+
for (field_idx, expression) in row.fields.iter().enumerate() {
162+
let expr = match expression.rex_type.as_ref() {
163+
Some(substrait::proto::expression::RexType::Literal(lit)) => {
164+
if !named_struct.names.is_empty() {
165+
name_idx += 1; // top-level names are provided through schema
166+
}
167+
Expr::Literal(
168+
from_substrait_literal(
169+
consumer,
170+
lit,
171+
&named_struct.names,
172+
&mut name_idx,
173+
)?,
174+
None,
175+
)
159176
}
160-
Expr::Literal(
161-
from_substrait_literal(
162-
consumer,
163-
lit,
177+
_ => {
178+
rename_field(
179+
substrait_schema.field(field_idx).as_ref(),
164180
&named_struct.names,
181+
field_idx,
165182
&mut name_idx,
166-
)?,
167-
None,
168-
)
169-
}
170-
_ => {
171-
rename_field(
172-
substrait_schema.field(field_idx).as_ref(),
173-
&named_struct.names,
174-
field_idx,
175-
&mut name_idx,
176-
)?;
177-
consumer
178-
.consume_expression(expression, &substrait_schema)
179-
.await?
180-
}
181-
};
182-
row_exprs.push(expr);
183-
}
184-
185-
if name_idx != named_struct.names.len() {
186-
return substrait_err!(
187-
"Names list must match exactly to nested schema, but found {} uses for {} names",
188-
name_idx,
189-
named_struct.names.len()
190-
);
183+
)?;
184+
consumer
185+
.consume_expression(expression, &substrait_schema)
186+
.await?
187+
}
188+
};
189+
row_exprs.push(expr);
190+
}
191+
192+
if name_idx != named_struct.names.len() {
193+
return substrait_err!(
194+
"Names list must match exactly to nested schema, but found {} uses for {} names",
195+
name_idx,
196+
named_struct.names.len()
197+
);
198+
}
199+
exprs.push(row_exprs);
191200
}
192-
values.push(row_exprs);
193-
}
201+
exprs
202+
} else {
203+
convert_literal_rows(consumer, vt, named_struct)?
204+
};
194205

195206
Ok(LogicalPlan::Values(Values {
196207
schema: DFSchemaRef::new(substrait_schema),
@@ -244,6 +255,46 @@ pub async fn from_read_rel(
244255
}
245256
}
246257

258+
/// Converts Substrait literal rows from a VirtualTable into DataFusion expressions.
259+
///
260+
/// This function processes the deprecated `values` field of VirtualTable, converting
261+
/// each literal value into a `Expr::Literal` while tracking and validating the name
262+
/// indices against the provided named struct schema.
263+
fn convert_literal_rows(
264+
consumer: &impl SubstraitConsumer,
265+
vt: &substrait::proto::read_rel::VirtualTable,
266+
named_struct: &substrait::proto::NamedStruct,
267+
) -> datafusion::common::Result<Vec<Vec<Expr>>> {
268+
#[expect(deprecated)]
269+
vt.values
270+
.iter()
271+
.map(|row| {
272+
let mut name_idx = 0;
273+
let lits = row
274+
.fields
275+
.iter()
276+
.map(|lit| {
277+
name_idx += 1; // top-level names are provided through schema
278+
Ok(Expr::Literal(from_substrait_literal(
279+
consumer,
280+
lit,
281+
&named_struct.names,
282+
&mut name_idx,
283+
)?, None))
284+
})
285+
.collect::<datafusion::common::Result<_>>()?;
286+
if name_idx != named_struct.names.len() {
287+
return substrait_err!(
288+
"Names list must match exactly to nested schema, but found {} uses for {} names",
289+
name_idx,
290+
named_struct.names.len()
291+
);
292+
}
293+
Ok(lits)
294+
})
295+
.collect::<datafusion::common::Result<_>>()
296+
}
297+
247298
pub fn apply_masking(
248299
schema: DFSchema,
249300
mask_expression: &::core::option::Option<MaskExpression>,

0 commit comments

Comments
 (0)