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