1use crate::ast::FunctionBinding;
10use crate::plan::{ProjectionPlan, QueryBlockPlan, SourcePlan};
11use crate::{FunctionTypeResolver, RowSchema, ScalarExpr, ScalarFrameBound};
12use crate::{SQLError, SQLParam};
13
14use super::{PhysicalProjection, SetFunctionCatalog};
15use crate::binding::context::BindingContext;
16use crate::semantics::aggregates::is_aggregate;
17
18pub fn builtin_returns_set(name: &str) -> bool {
19 matches!(
20 name,
21 "generate_series"
22 | "pg_listening_channels"
23 | "unnest"
24 | "regexp_split_to_table"
25 | "string_to_table"
26 | "json_array_elements"
27 | "jsonb_array_elements"
28 | "json_array_elements_text"
29 | "jsonb_array_elements_text"
30 | "json_each"
31 | "jsonb_each"
32 | "json_each_text"
33 | "jsonb_each_text"
34 | "json_object_keys"
35 | "jsonb_object_keys"
36 )
37}
38
39pub fn function_may_return_set(
40 engine: &dyn SetFunctionCatalog,
41 resolver: &dyn FunctionTypeResolver,
42 name: &str,
43 binding: Option<&FunctionBinding>,
44 args: &[ScalarExpr],
45 schema: &RowSchema,
46 params: &[SQLParam],
47) -> Result<bool, SQLError> {
48 if binding.and_then(|binding| binding.dispatch).is_some() {
49 return Ok(false);
50 }
51 if binding.is_some_and(FunctionBinding::is_polymorphic_builtin_syntax) {
52 return Ok(false);
53 }
54 if binding.is_some_and(|binding| !binding.builtin) {
55 let (argument_names, argument_types, explicit_variadic) =
56 crate::function_call_argument_signature(args, schema, params, Some(resolver))?;
57 let function = engine.resolve_static_sql_function(
58 name,
59 binding,
60 &argument_names,
61 &argument_types,
62 explicit_variadic,
63 )?;
64 return Ok(function.is_some_and(|function| function.def.returns_set()));
65 }
66 let identity = name.to_ascii_lowercase();
67 let builtin = crate::semantics::builtin_function_dispatch_name(&identity);
68 if builtin_returns_set(&builtin) || engine.has_registered_table_function(&identity) {
69 return Ok(true);
70 }
71 let (argument_names, argument_types, explicit_variadic) =
72 crate::function_call_argument_signature(args, schema, params, Some(resolver))?;
73 let Some(overloads) = engine.lookup_visible_sql_functions_for_analysis(name)? else {
74 return Ok(false);
75 };
76 if !crate::is_fixed_builtin(name) {
77 let mut setness = overloads
78 .iter()
79 .filter(|function| !function.def.is_procedure)
80 .map(|function| function.def.returns_set());
81 if let Some(first) = setness.next() {
82 if setness.all(|returns_set| returns_set == first) {
83 return Ok(first);
84 }
85 }
86 }
87 if let Some(resolved) = crate::resolve_fixed_builtin_call(
88 name,
89 binding,
90 &argument_names,
91 &argument_types,
92 explicit_variadic,
93 Some(resolver),
94 )? {
95 if resolved.selected.binding.builtin {
96 return Ok(false);
97 }
98 let function = engine.resolve_static_sql_function(
99 name,
100 Some(&resolved.selected.binding),
101 &argument_names,
102 &argument_types,
103 explicit_variadic,
104 )?;
105 return Ok(function.is_some_and(|function| function.def.returns_set()));
106 }
107 match engine.resolve_static_sql_function(
108 name,
109 binding,
110 &argument_names,
111 &argument_types,
112 explicit_variadic,
113 ) {
114 Ok(function) => Ok(function.is_some_and(|function| function.def.returns_set())),
115 Err(error) if binding.is_none() && error.sqlstate() == Some("42883") => {
116 match crate::type_resolution::builtin_function_type(&builtin, args, &[], schema, params)
117 {
118 Ok(Some(_)) => Ok(false),
119 Ok(None) | Err(_) => Err(error),
120 }
121 }
122 Err(error) => Err(error),
123 }
124}
125
126pub fn resolve_set_function_binding(
127 engine: &dyn SetFunctionCatalog,
128 resolver: &dyn FunctionTypeResolver,
129 name: &str,
130 binding: Option<&FunctionBinding>,
131 args: &[ScalarExpr],
132 schema: &RowSchema,
133 params: &[SQLParam],
134) -> Result<Option<FunctionBinding>, SQLError> {
135 let (argument_names, argument_types, explicit_variadic) =
136 crate::function_call_argument_signature(args, schema, params, Some(resolver))?;
137 if binding.is_none()
138 && engine
139 .lookup_visible_sql_functions_for_analysis(name)?
140 .is_none()
141 {
142 return Ok(binding.cloned());
143 }
144 if let Some(resolved) = crate::resolve_fixed_builtin_call(
145 name,
146 binding,
147 &argument_names,
148 &argument_types,
149 explicit_variadic,
150 Some(resolver),
151 )? {
152 return Ok((!resolved.selected.binding.builtin).then_some(resolved.selected.binding));
153 }
154 let Some(function) = engine.resolve_static_sql_function_match(
155 name,
156 binding,
157 &argument_names,
158 &argument_types,
159 explicit_variadic,
160 )?
161 else {
162 return Ok(binding.cloned());
163 };
164 Ok(Some(function.binding()))
165}
166
167pub fn projections_may_return_set(
168 engine: &dyn SetFunctionCatalog,
169 resolver: &dyn FunctionTypeResolver,
170 projections: &[PhysicalProjection],
171 schema: &RowSchema,
172 params: &[SQLParam],
173) -> Result<bool, SQLError> {
174 for (_, expression) in projections {
175 if expression_may_return_set(engine, resolver, expression, schema, params)? {
176 return Ok(true);
177 }
178 }
179 Ok(false)
180}
181
182#[expect(
183 clippy::too_many_lines,
184 reason = "preserves SELECT schema and row identity"
185)]
186pub fn expression_may_return_set(
187 engine: &dyn SetFunctionCatalog,
188 resolver: &dyn FunctionTypeResolver,
189 expression: &ScalarExpr,
190 schema: &RowSchema,
191 params: &[SQLParam],
192) -> Result<bool, SQLError> {
193 match expression {
194 ScalarExpr::Func {
195 name,
196 binding,
197 args,
198 order_by,
199 filter,
200 ..
201 } => {
202 if function_may_return_set(
203 engine,
204 resolver,
205 name,
206 binding.as_ref(),
207 args,
208 schema,
209 params,
210 )? || expressions_may_return_set(engine, resolver, args, schema, params)?
211 || expressions_may_return_set(
212 engine,
213 resolver,
214 order_by.iter().map(|order| &order.expr),
215 schema,
216 params,
217 )?
218 {
219 return Ok(true);
220 }
221 filter.as_deref().map_or(Ok(false), |filter| {
222 expression_may_return_set(engine, resolver, filter, schema, params)
223 })
224 }
225 ScalarExpr::Array(items)
226 | ScalarExpr::Row(items)
227 | ScalarExpr::And(items)
228 | ScalarExpr::Or(items) => {
229 expressions_may_return_set(engine, resolver, items, schema, params)
230 }
231 ScalarExpr::Binary { lhs, rhs, .. } => Ok(expression_may_return_set(
232 engine, resolver, lhs, schema, params,
233 )? || expression_may_return_set(
234 engine, resolver, rhs, schema, params,
235 )?),
236 ScalarExpr::Not(inner)
237 | ScalarExpr::UnaryMinus(inner)
238 | ScalarExpr::IsNull { expr: inner, .. }
239 | ScalarExpr::Cast { expr: inner, .. } => {
240 expression_may_return_set(engine, resolver, inner, schema, params)
241 }
242 ScalarExpr::Between { expr, low, high } => {
243 Ok(
244 expression_may_return_set(engine, resolver, expr, schema, params)?
245 || expression_may_return_set(engine, resolver, low, schema, params)?
246 || expression_may_return_set(engine, resolver, high, schema, params)?,
247 )
248 }
249 ScalarExpr::InList { expr, list, .. } => Ok(expression_may_return_set(
250 engine, resolver, expr, schema, params,
251 )? || expressions_may_return_set(
252 engine, resolver, list, schema, params,
253 )?),
254 ScalarExpr::WindowCall { args, spec, .. } => {
255 if expressions_may_return_set(engine, resolver, args, schema, params)?
256 || expressions_may_return_set(engine, resolver, &spec.partition_by, schema, params)?
257 || expressions_may_return_set(
258 engine,
259 resolver,
260 spec.order_by.iter().map(|order| &order.expr),
261 schema,
262 params,
263 )?
264 {
265 return Ok(true);
266 }
267 let Some(frame) = spec.frame.as_ref() else {
268 return Ok(false);
269 };
270 Ok(
271 frame_bound_may_return_set(engine, resolver, &frame.start, schema, params)?
272 || frame_bound_may_return_set(engine, resolver, &frame.end, schema, params)?,
273 )
274 }
275 ScalarExpr::Case {
276 base,
277 when,
278 else_branch,
279 } => {
280 if let Some(base) = base {
281 if expression_may_return_set(engine, resolver, base, schema, params)? {
282 return Ok(true);
283 }
284 }
285 for (condition, result) in when {
286 if expression_may_return_set(engine, resolver, condition, schema, params)?
287 || expression_may_return_set(engine, resolver, result, schema, params)?
288 {
289 return Ok(true);
290 }
291 }
292 if let Some(branch) = else_branch {
293 if expression_may_return_set(engine, resolver, branch, schema, params)? {
294 return Ok(true);
295 }
296 }
297 Ok(false)
298 }
299 ScalarExpr::InSubquery { expr, .. } => {
300 expression_may_return_set(engine, resolver, expr, schema, params)
301 }
302 ScalarExpr::Default
303 | ScalarExpr::Star
304 | ScalarExpr::QualifiedStar(_)
305 | ScalarExpr::Column(_)
306 | ScalarExpr::Position(_)
307 | ScalarExpr::InternalColumn(_)
308 | ScalarExpr::QualifiedColumn { .. }
309 | ScalarExpr::Literal(_)
310 | ScalarExpr::TypedLiteral { .. }
311 | ScalarExpr::Param(_)
312 | ScalarExpr::ScalarSubquery(_)
313 | ScalarExpr::Exists { .. } => Ok(false),
314 }
315}
316
317fn expressions_may_return_set<'a>(
318 engine: &dyn SetFunctionCatalog,
319 resolver: &dyn FunctionTypeResolver,
320 expressions: impl IntoIterator<Item = &'a ScalarExpr>,
321 schema: &RowSchema,
322 params: &[SQLParam],
323) -> Result<bool, SQLError> {
324 for expression in expressions {
325 if expression_may_return_set(engine, resolver, expression, schema, params)? {
326 return Ok(true);
327 }
328 }
329 Ok(false)
330}
331
332fn frame_bound_may_return_set(
333 engine: &dyn SetFunctionCatalog,
334 resolver: &dyn FunctionTypeResolver,
335 bound: &ScalarFrameBound,
336 schema: &RowSchema,
337 params: &[SQLParam],
338) -> Result<bool, SQLError> {
339 match bound {
340 ScalarFrameBound::Preceding(expression) | ScalarFrameBound::Following(expression) => {
341 expression_may_return_set(engine, resolver, expression, schema, params)
342 }
343 ScalarFrameBound::UnboundedPreceding
344 | ScalarFrameBound::UnboundedFollowing
345 | ScalarFrameBound::CurrentRow => Ok(false),
346 }
347}
348
349fn frame_bound_expression(bound: &ScalarFrameBound) -> Option<&ScalarExpr> {
350 match bound {
351 ScalarFrameBound::Preceding(expression) | ScalarFrameBound::Following(expression) => {
352 Some(expression)
353 }
354 ScalarFrameBound::UnboundedPreceding
355 | ScalarFrameBound::UnboundedFollowing
356 | ScalarFrameBound::CurrentRow => None,
357 }
358}
359
360fn set_context_error(message: impl Into<String>) -> SQLError {
361 SQLError::Routine {
362 sqlstate: "0A000".into(),
363 message: message.into(),
364 }
365}
366
367fn reject_set_descendant<'a>(
368 engine: &dyn SetFunctionCatalog,
369 resolver: &dyn FunctionTypeResolver,
370 expressions: impl IntoIterator<Item = &'a ScalarExpr>,
371 schema: &RowSchema,
372 params: &[SQLParam],
373 message: &str,
374) -> Result<(), SQLError> {
375 if expressions_may_return_set(engine, resolver, expressions, schema, params)? {
376 return Err(set_context_error(message));
377 }
378 Ok(())
379}
380
381#[expect(
382 clippy::too_many_lines,
383 reason = "preserves SELECT schema and row identity"
384)]
385fn validate_set_context(
386 engine: &dyn SetFunctionCatalog,
387 resolver: &dyn FunctionTypeResolver,
388 expression: &ScalarExpr,
389 schema: &RowSchema,
390 params: &[SQLParam],
391) -> Result<(), SQLError> {
392 match expression {
393 ScalarExpr::Func {
394 name,
395 args,
396 order_by,
397 filter,
398 ..
399 } => {
400 let lower = crate::semantics::builtin_function_dispatch_name(name);
401 if is_aggregate(engine, expression) {
402 reject_set_descendant(
403 engine,
404 resolver,
405 args.iter()
406 .chain(order_by.iter().map(|order| &order.expr))
407 .chain(filter.iter().map(AsRef::as_ref)),
408 schema,
409 params,
410 "aggregate function calls cannot contain set-returning function calls",
411 )?;
412 } else if lower == "coalesce" {
413 reject_set_descendant(
414 engine,
415 resolver,
416 args,
417 schema,
418 params,
419 "set-returning functions are not allowed in COALESCE",
420 )?;
421 }
422 for argument in args {
423 validate_set_context(engine, resolver, argument, schema, params)?;
424 }
425 for order in order_by {
426 validate_set_context(engine, resolver, &order.expr, schema, params)?;
427 }
428 if let Some(filter) = filter {
429 validate_set_context(engine, resolver, filter, schema, params)?;
430 }
431 }
432 ScalarExpr::WindowCall { args, spec, .. } => {
433 reject_set_descendant(
434 engine,
435 resolver,
436 args.iter()
437 .chain(spec.partition_by.iter())
438 .chain(spec.order_by.iter().map(|order| &order.expr))
439 .chain(
440 spec.frame
441 .iter()
442 .flat_map(|frame| [&frame.start, &frame.end])
443 .filter_map(|bound| frame_bound_expression(bound)),
444 ),
445 schema,
446 params,
447 "window function calls cannot contain set-returning function calls",
448 )?;
449 }
450 ScalarExpr::Case {
451 base,
452 when,
453 else_branch,
454 } => {
455 let descendants = base
456 .iter()
457 .map(AsRef::as_ref)
458 .chain(
459 when.iter()
460 .flat_map(|(condition, result)| [condition, result]),
461 )
462 .chain(else_branch.iter().map(AsRef::as_ref));
463 reject_set_descendant(
464 engine,
465 resolver,
466 descendants,
467 schema,
468 params,
469 "set-returning functions are not allowed in CASE",
470 )?;
471 }
472 ScalarExpr::Not(inner) => {
473 reject_set_descendant(
474 engine,
475 resolver,
476 [inner.as_ref()],
477 schema,
478 params,
479 "argument of NOT must not return a set",
480 )?;
481 }
482 ScalarExpr::UnaryMinus(inner) => {
483 validate_set_context(engine, resolver, inner, schema, params)?;
484 }
485 ScalarExpr::And(items) => {
486 reject_set_descendant(
487 engine,
488 resolver,
489 items,
490 schema,
491 params,
492 "argument of AND must not return a set",
493 )?;
494 }
495 ScalarExpr::Or(items) => {
496 reject_set_descendant(
497 engine,
498 resolver,
499 items,
500 schema,
501 params,
502 "argument of OR must not return a set",
503 )?;
504 }
505 ScalarExpr::InList { expr, list, .. } => {
506 validate_set_context(engine, resolver, expr, schema, params)?;
507 reject_set_descendant(
508 engine,
509 resolver,
510 list,
511 schema,
512 params,
513 "argument of IN must not return a set",
514 )?;
515 }
516 ScalarExpr::Between { expr, low, high } => {
517 reject_set_descendant(
518 engine,
519 resolver,
520 [expr.as_ref(), low.as_ref(), high.as_ref()],
521 schema,
522 params,
523 "argument of AND must not return a set",
524 )?;
525 }
526 ScalarExpr::Array(items) | ScalarExpr::Row(items) => {
527 for item in items {
528 validate_set_context(engine, resolver, item, schema, params)?;
529 }
530 }
531 ScalarExpr::Binary { lhs, rhs, .. } => {
532 validate_set_context(engine, resolver, lhs, schema, params)?;
533 validate_set_context(engine, resolver, rhs, schema, params)?;
534 }
535 ScalarExpr::IsNull { expr, .. } | ScalarExpr::Cast { expr, .. } => {
536 validate_set_context(engine, resolver, expr, schema, params)?;
537 }
538 ScalarExpr::InSubquery { expr, .. } => {
539 reject_set_descendant(
540 engine,
541 resolver,
542 [expr.as_ref()],
543 schema,
544 params,
545 "row comparison operator must not return a set",
546 )?;
547 }
548 ScalarExpr::Default
549 | ScalarExpr::Star
550 | ScalarExpr::QualifiedStar(_)
551 | ScalarExpr::Column(_)
552 | ScalarExpr::Position(_)
553 | ScalarExpr::InternalColumn(_)
554 | ScalarExpr::QualifiedColumn { .. }
555 | ScalarExpr::Literal(_)
556 | ScalarExpr::TypedLiteral { .. }
557 | ScalarExpr::Param(_)
558 | ScalarExpr::ScalarSubquery(_)
559 | ScalarExpr::Exists { .. } => {}
560 }
561 Ok(())
562}
563
564pub fn validate_projection_set_contexts(
565 engine: &dyn SetFunctionCatalog,
566 resolver: &dyn FunctionTypeResolver,
567 projections: &[ProjectionPlan],
568 schema: &RowSchema,
569 params: &[SQLParam],
570) -> Result<(), SQLError> {
571 for projection in projections {
572 validate_set_context(engine, resolver, &projection.expr, schema, params)?;
573 }
574 Ok(())
575}
576
577pub fn validate_query_set_contexts(
578 engine: &dyn SetFunctionCatalog,
579 resolver: &dyn FunctionTypeResolver,
580 statement: &QueryBlockPlan,
581 schema: &RowSchema,
582 params: &[SQLParam],
583) -> Result<(), SQLError> {
584 validate_projection_set_contexts(engine, resolver, &statement.projections, schema, params)?;
585 if let Some(locking) = statement.locking.first() {
586 for projection in &statement.projections {
587 if expression_may_return_set(engine, resolver, &projection.expr, schema, params)? {
588 return Err(SQLError::Unsupported(format!(
589 "{} is not allowed with set-returning functions in the target list",
590 locking.strength.sql_name()
591 )));
592 }
593 }
594 }
595 for expression in statement
596 .group_by
597 .iter()
598 .chain(statement.grouping_sets.iter().flatten())
599 .chain(statement.order_by.iter().map(|order| &order.expr))
600 .chain(statement.distinct_on.iter())
601 {
602 validate_set_context(engine, resolver, expression, schema, params)?;
603 }
604 if let Some(predicate) = statement.r#where.as_ref() {
605 reject_set_descendant(
606 engine,
607 resolver,
608 [predicate],
609 schema,
610 params,
611 "set-returning functions are not allowed in WHERE",
612 )?;
613 }
614 if let Some(having) = statement.having.as_ref() {
615 reject_set_descendant(
616 engine,
617 resolver,
618 [having],
619 schema,
620 params,
621 "set-returning functions are not allowed in HAVING",
622 )?;
623 }
624 if let Some(limit) = statement.limit.as_ref() {
625 reject_set_descendant(
626 engine,
627 resolver,
628 [limit],
629 schema,
630 params,
631 "set-returning functions are not allowed in LIMIT",
632 )?;
633 }
634 if let Some(offset) = statement.offset.as_ref() {
635 reject_set_descendant(
636 engine,
637 resolver,
638 [offset],
639 schema,
640 params,
641 "set-returning functions are not allowed in OFFSET",
642 )?;
643 }
644 if let Some(source) = statement.from.as_ref() {
645 validate_source_set_contexts(engine, resolver, source, schema, params)?;
646 }
647 Ok(())
648}
649
650fn validate_source_set_contexts(
651 engine: &dyn SetFunctionCatalog,
652 resolver: &dyn FunctionTypeResolver,
653 source: &SourcePlan,
654 schema: &RowSchema,
655 params: &[SQLParam],
656) -> Result<(), SQLError> {
657 match source {
658 SourcePlan::Join {
659 left, right, on, ..
660 } => {
661 validate_source_set_contexts(engine, resolver, left, schema, params)?;
662 validate_source_set_contexts(engine, resolver, right, schema, params)?;
663 if let Some(condition) = on {
664 reject_set_descendant(
665 engine,
666 resolver,
667 [condition],
668 schema,
669 params,
670 "set-returning functions are not allowed in JOIN conditions",
671 )?;
672 }
673 }
674 SourcePlan::Values { rows, .. } => {
675 validate_values_set_contexts(engine, resolver, rows, schema, params)?;
676 }
677 SourcePlan::Function { args, .. } => {
678 reject_set_descendant(
679 engine,
680 resolver,
681 args,
682 schema,
683 params,
684 "set-returning functions must appear at top level of FROM",
685 )?;
686 }
687 SourcePlan::FunctionGroup { functions, .. } => {
688 for function in functions {
689 reject_set_descendant(
690 engine,
691 resolver,
692 &function.args,
693 schema,
694 params,
695 "set-returning functions must appear at top level of FROM",
696 )?;
697 }
698 }
699 SourcePlan::Subquery { .. } | SourcePlan::Table { .. } => {}
700 }
701 Ok(())
702}
703
704pub fn validate_source_set_contexts_before_build(
705 engine: &dyn SetFunctionCatalog,
706 resolver: &dyn FunctionTypeResolver,
707 source: &SourcePlan,
708 params: &[SQLParam],
709 ctes: &BindingContext<'_>,
710 outer: Option<&RowSchema>,
711) -> Result<(), SQLError> {
712 match source {
713 SourcePlan::Join {
714 left,
715 right,
716 lateral,
717 ..
718 } => {
719 validate_source_set_contexts_before_build(engine, resolver, left, params, ctes, outer)?;
720 let left_schema =
721 crate::binding::bind_source_plan_schema(engine, left, params, ctes, outer)?;
722 let implicit_lateral_function = matches!(
723 right.as_ref(),
724 SourcePlan::Function { .. } | SourcePlan::FunctionGroup { .. }
725 );
726 let right_scope = (*lateral || implicit_lateral_function)
727 .then(|| overlay_set_validation_scope(&left_schema, outer));
728 validate_source_set_contexts_before_build(
729 engine,
730 resolver,
731 right,
732 params,
733 ctes,
734 right_scope.as_ref().or(outer),
735 )
736 }
737 SourcePlan::Values { rows, .. } => {
738 let empty = RowSchema::default();
739 validate_values_set_contexts(engine, resolver, rows, outer.unwrap_or(&empty), params)
740 }
741 SourcePlan::Function { args, .. } => {
742 let empty = RowSchema::default();
743 reject_set_descendant(
744 engine,
745 resolver,
746 args,
747 outer.unwrap_or(&empty),
748 params,
749 "set-returning functions must appear at top level of FROM",
750 )
751 }
752 SourcePlan::FunctionGroup { functions, .. } => {
753 let empty = RowSchema::default();
754 for function in functions {
755 reject_set_descendant(
756 engine,
757 resolver,
758 &function.args,
759 outer.unwrap_or(&empty),
760 params,
761 "set-returning functions must appear at top level of FROM",
762 )?;
763 }
764 Ok(())
765 }
766 SourcePlan::Subquery { .. } | SourcePlan::Table { .. } => Ok(()),
767 }
768}
769
770fn overlay_set_validation_scope(current: &RowSchema, outer: Option<&RowSchema>) -> RowSchema {
771 let Some(outer) = outer else {
772 return current.clone();
773 };
774 let columns = outer
775 .identities()
776 .iter()
777 .enumerate()
778 .map(|(position, identity)| (identity.clone(), outer.column_type(position).cloned()))
779 .collect::<Vec<_>>();
780 RowSchema::with_typed_outer_identities(current, &columns)
781}
782
783pub fn validate_values_set_contexts(
784 engine: &dyn SetFunctionCatalog,
785 resolver: &dyn FunctionTypeResolver,
786 rows: &[Vec<ScalarExpr>],
787 schema: &RowSchema,
788 params: &[SQLParam],
789) -> Result<(), SQLError> {
790 reject_set_descendant(
791 engine,
792 resolver,
793 rows.iter().flatten(),
794 schema,
795 params,
796 "set-returning functions are not allowed in VALUES",
797 )
798}