@@ -35,6 +35,7 @@ use substrait::proto::read_rel::local_files::file_or_files::PathType::UriFile;
3535use substrait:: proto:: { Expression , ReadRel } ;
3636use url:: Url ;
3737
38+ #[ expect( deprecated) ]
3839pub 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+
247298pub fn apply_masking (
248299 schema : DFSchema ,
249300 mask_expression : & :: core:: option:: Option < MaskExpression > ,
0 commit comments