-
Notifications
You must be signed in to change notification settings - Fork 2.2k
feat: removed all instances of deprecated virtualtable values field #23672
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,7 +18,7 @@ | |
| use crate::logical_plan::consumer::SubstraitConsumer; | ||
| use crate::logical_plan::consumer::from_substrait_literal; | ||
| use crate::logical_plan::consumer::from_substrait_named_struct; | ||
| use crate::logical_plan::consumer::utils::ensure_schema_compatibility; | ||
| use crate::logical_plan::consumer::utils::{ensure_schema_compatibility, rename_field}; | ||
| use datafusion::common::{ | ||
| DFSchema, DFSchemaRef, TableReference, not_impl_err, plan_err, | ||
| substrait_datafusion_err, substrait_err, | ||
|
|
@@ -35,7 +35,6 @@ use substrait::proto::read_rel::local_files::file_or_files::PathType::UriFile; | |
| use substrait::proto::{Expression, ReadRel}; | ||
| use url::Url; | ||
|
|
||
| #[expect(deprecated)] | ||
| pub async fn from_read_rel( | ||
| consumer: &impl SubstraitConsumer, | ||
| read: &ReadRel, | ||
|
|
@@ -114,29 +113,17 @@ pub async fn from_read_rel( | |
| .await | ||
| } | ||
| Some(ReadType::VirtualTable(vt)) => { | ||
| if vt.values.is_empty() && vt.expressions.is_empty() { | ||
| return Ok(LogicalPlan::EmptyRelation(EmptyRelation { | ||
| produce_one_row: false, | ||
| schema: DFSchemaRef::new(substrait_schema), | ||
| })); | ||
| } | ||
|
|
||
| // Check for produce_one_row pattern in both old (values) and new (expressions) formats. | ||
| // Check for produce_one_row pattern. | ||
| // A VirtualTable with exactly one row containing only empty/default fields represents | ||
| // an EmptyRelation with produce_one_row=true. This pattern is used for queries without | ||
| // a FROM clause (e.g., "SELECT 1 AS one") where a single phantom row is needed to | ||
| // provide a context for evaluating scalar expressions. This is conceptually similar to | ||
| // the SQL "DUAL" table (see: https://en.wikipedia.org/wiki/DUAL_table) which some | ||
| // databases provide as a single-row source for selecting constant expressions when no | ||
| // real table is present. | ||
| let is_produce_one_row = (vt.values.len() == 1 | ||
| && vt.expressions.is_empty() | ||
| let is_produce_one_row = vt.expressions.len() == 1 | ||
| && substrait_schema.fields().is_empty() | ||
| && vt.values[0].fields.is_empty()) | ||
| || (vt.expressions.len() == 1 | ||
| && vt.values.is_empty() | ||
| && substrait_schema.fields().is_empty() | ||
| && vt.expressions[0].fields.is_empty()); | ||
| && vt.expressions[0].fields.is_empty(); | ||
|
|
||
| if is_produce_one_row { | ||
| return Ok(LogicalPlan::EmptyRelation(EmptyRelation { | ||
|
|
@@ -145,30 +132,65 @@ pub async fn from_read_rel( | |
| })); | ||
| } | ||
|
|
||
| let values = if !vt.expressions.is_empty() { | ||
| let mut exprs = vec![]; | ||
| for row in &vt.expressions { | ||
| let mut row_exprs = vec![]; | ||
| for expression in &row.fields { | ||
| let expr = consumer | ||
| .consume_expression(expression, &substrait_schema) | ||
| .await?; | ||
| row_exprs.push(expr); | ||
| } | ||
| // For expressions, validate against top-level schema fields, not nested names | ||
| if row_exprs.len() != substrait_schema.fields().len() { | ||
| return substrait_err!( | ||
| "Field count mismatch: expected {} fields but found {} in virtual table row", | ||
| substrait_schema.fields().len(), | ||
| row_exprs.len() | ||
| ); | ||
| } | ||
| exprs.push(row_exprs); | ||
| if vt.expressions.is_empty() { | ||
| return Ok(LogicalPlan::EmptyRelation(EmptyRelation { | ||
| produce_one_row: false, | ||
| schema: DFSchemaRef::new(substrait_schema), | ||
| })); | ||
| } | ||
|
|
||
| let mut values = vec![]; | ||
| for row in &vt.expressions { | ||
| if row.fields.len() != substrait_schema.fields().len() { | ||
| return substrait_err!( | ||
| "Field count mismatch: expected {} fields but found {} in virtual table row", | ||
| substrait_schema.fields().len(), | ||
| row.fields.len() | ||
| ); | ||
| } | ||
| exprs | ||
| } else { | ||
| convert_literal_rows(consumer, vt, named_struct)? | ||
| }; | ||
|
|
||
| let mut row_exprs = vec![]; | ||
| let mut name_idx = 0; | ||
| for (field_idx, expression) in row.fields.iter().enumerate() { | ||
| let expr = match expression.rex_type.as_ref() { | ||
| Some(substrait::proto::expression::RexType::Literal(lit)) => { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we actually need to special case the Literal consumer here? I would have assumed that had does everything we needed for the conversion here. Put another way, it looks like you've inlined some of the |
||
| if !named_struct.names.is_empty() { | ||
| name_idx += 1; // top-level names are provided through schema | ||
| } | ||
| Expr::Literal( | ||
| from_substrait_literal( | ||
| consumer, | ||
| lit, | ||
| &named_struct.names, | ||
| &mut name_idx, | ||
| )?, | ||
| None, | ||
| ) | ||
| } | ||
| _ => { | ||
| rename_field( | ||
| substrait_schema.field(field_idx).as_ref(), | ||
| &named_struct.names, | ||
| field_idx, | ||
| &mut name_idx, | ||
| )?; | ||
| consumer | ||
| .consume_expression(expression, &substrait_schema) | ||
| .await? | ||
| } | ||
| }; | ||
| row_exprs.push(expr); | ||
| } | ||
|
|
||
| if name_idx != named_struct.names.len() { | ||
| return substrait_err!( | ||
| "Names list must match exactly to nested schema, but found {} uses for {} names", | ||
| name_idx, | ||
| named_struct.names.len() | ||
| ); | ||
| } | ||
| values.push(row_exprs); | ||
| } | ||
|
|
||
| Ok(LogicalPlan::Values(Values { | ||
| schema: DFSchemaRef::new(substrait_schema), | ||
|
|
@@ -222,46 +244,6 @@ pub async fn from_read_rel( | |
| } | ||
| } | ||
|
|
||
| /// Converts Substrait literal rows from a VirtualTable into DataFusion expressions. | ||
| /// | ||
| /// This function processes the deprecated `values` field of VirtualTable, converting | ||
| /// each literal value into a `Expr::Literal` while tracking and validating the name | ||
| /// indices against the provided named struct schema. | ||
| fn convert_literal_rows( | ||
| consumer: &impl SubstraitConsumer, | ||
| vt: &substrait::proto::read_rel::VirtualTable, | ||
| named_struct: &substrait::proto::NamedStruct, | ||
| ) -> datafusion::common::Result<Vec<Vec<Expr>>> { | ||
| #[expect(deprecated)] | ||
| vt.values | ||
| .iter() | ||
| .map(|row| { | ||
| let mut name_idx = 0; | ||
| let lits = row | ||
| .fields | ||
| .iter() | ||
| .map(|lit| { | ||
| name_idx += 1; // top-level names are provided through schema | ||
| Ok(Expr::Literal(from_substrait_literal( | ||
| consumer, | ||
| lit, | ||
| &named_struct.names, | ||
| &mut name_idx, | ||
| )?, None)) | ||
| }) | ||
| .collect::<datafusion::common::Result<_>>()?; | ||
| if name_idx != named_struct.names.len() { | ||
| return substrait_err!( | ||
| "Names list must match exactly to nested schema, but found {} uses for {} names", | ||
| name_idx, | ||
| named_struct.names.len() | ||
| ); | ||
| } | ||
| Ok(lits) | ||
| }) | ||
| .collect::<datafusion::common::Result<_>>() | ||
| } | ||
|
|
||
| pub fn apply_masking( | ||
| schema: DFSchema, | ||
| mask_expression: &::core::option::Option<MaskExpression>, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can still check and short-circuit on
vt.expressions.is_empty()here.