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 | ScalarExpr::Collated { expr: inner, .. } => {
249 expression_may_return_set(engine, resolver, inner, schema, params)
250 }
251 ScalarExpr::Between { expr, low, high } => {
252 Ok(
253 expression_may_return_set(engine, resolver, expr, schema, params)?
254 || expression_may_return_set(engine, resolver, low, schema, params)?
255 || expression_may_return_set(engine, resolver, high, schema, params)?,
256 )
257 }
258 ScalarExpr::InList { expr, list, .. } => Ok(expression_may_return_set(
259 engine, resolver, expr, schema, params,
260 )? || expressions_may_return_set(
261 engine, resolver, list, schema, params,
262 )?),
263 ScalarExpr::WindowCall {
264 args, spec, filter, ..
265 } => {
266 if expressions_may_return_set(engine, resolver, args, schema, params)?
267 || expressions_may_return_set(
268 engine,
269 resolver,
270 filter.iter().map(AsRef::as_ref),
271 schema,
272 params,
273 )?
274 || expressions_may_return_set(engine, resolver, &spec.partition_by, schema, params)?
275 || expressions_may_return_set(
276 engine,
277 resolver,
278 spec.order_by.iter().map(|order| &order.expr),
279 schema,
280 params,
281 )?
282 {
283 return Ok(true);
284 }
285 let Some(frame) = spec.frame.as_ref() else {
286 return Ok(false);
287 };
288 Ok(
289 frame_bound_may_return_set(engine, resolver, &frame.start, schema, params)?
290 || frame_bound_may_return_set(engine, resolver, &frame.end, schema, params)?,
291 )
292 }
293 ScalarExpr::Case {
294 base,
295 when,
296 else_branch,
297 } => {
298 if let Some(base) = base {
299 if expression_may_return_set(engine, resolver, base, schema, params)? {
300 return Ok(true);
301 }
302 }
303 for (condition, result) in when {
304 if expression_may_return_set(engine, resolver, condition, schema, params)?
305 || expression_may_return_set(engine, resolver, result, schema, params)?
306 {
307 return Ok(true);
308 }
309 }
310 if let Some(branch) = else_branch {
311 if expression_may_return_set(engine, resolver, branch, schema, params)? {
312 return Ok(true);
313 }
314 }
315 Ok(false)
316 }
317 ScalarExpr::InSubquery { expr, .. } => {
318 expression_may_return_set(engine, resolver, expr, schema, params)
319 }
320 ScalarExpr::Default
321 | ScalarExpr::Star
322 | ScalarExpr::QualifiedStar(_)
323 | ScalarExpr::Column(_)
324 | ScalarExpr::Position(_)
325 | ScalarExpr::InternalColumn(_)
326 | ScalarExpr::QualifiedColumn { .. }
327 | ScalarExpr::Literal(_)
328 | ScalarExpr::TypedLiteral { .. }
329 | ScalarExpr::Param(_)
330 | ScalarExpr::ScalarSubquery(_)
331 | ScalarExpr::Exists { .. } => Ok(false),
332 }
333}
334
335fn expressions_may_return_set<'a>(
336 engine: &dyn crate::routines::RoutineResolution,
337 resolver: &dyn FunctionTypeResolver,
338 expressions: impl IntoIterator<Item = &'a ScalarExpr>,
339 schema: &RowSchema,
340 params: &[SQLParam],
341) -> Result<bool, SQLError> {
342 for expression in expressions {
343 if expression_may_return_set(engine, resolver, expression, schema, params)? {
344 return Ok(true);
345 }
346 }
347 Ok(false)
348}
349
350fn frame_bound_may_return_set(
351 engine: &dyn crate::routines::RoutineResolution,
352 resolver: &dyn FunctionTypeResolver,
353 bound: &ScalarFrameBound,
354 schema: &RowSchema,
355 params: &[SQLParam],
356) -> Result<bool, SQLError> {
357 match bound {
358 ScalarFrameBound::Preceding(expression) | ScalarFrameBound::Following(expression) => {
359 expression_may_return_set(engine, resolver, expression, schema, params)
360 }
361 ScalarFrameBound::UnboundedPreceding
362 | ScalarFrameBound::UnboundedFollowing
363 | ScalarFrameBound::CurrentRow => Ok(false),
364 }
365}
366
367fn frame_bound_expression(bound: &ScalarFrameBound) -> Option<&ScalarExpr> {
368 match bound {
369 ScalarFrameBound::Preceding(expression) | ScalarFrameBound::Following(expression) => {
370 Some(expression)
371 }
372 ScalarFrameBound::UnboundedPreceding
373 | ScalarFrameBound::UnboundedFollowing
374 | ScalarFrameBound::CurrentRow => None,
375 }
376}
377
378fn set_context_error(message: impl Into<String>) -> SQLError {
379 SQLError::Routine {
380 sqlstate: "0A000".into(),
381 message: message.into(),
382 }
383}
384
385fn reject_set_descendant<'a>(
386 engine: &dyn SetFunctionCatalog,
387 resolver: &dyn FunctionTypeResolver,
388 expressions: impl IntoIterator<Item = &'a ScalarExpr>,
389 schema: &RowSchema,
390 params: &[SQLParam],
391 message: &str,
392) -> Result<(), SQLError> {
393 if expressions_may_return_set(engine, resolver, expressions, schema, params)? {
394 return Err(set_context_error(message));
395 }
396 Ok(())
397}
398
399#[expect(
400 clippy::too_many_lines,
401 reason = "preserves SELECT schema and row identity"
402)]
403fn validate_set_context(
404 engine: &dyn SetFunctionCatalog,
405 resolver: &dyn FunctionTypeResolver,
406 expression: &ScalarExpr,
407 schema: &RowSchema,
408 params: &[SQLParam],
409) -> Result<(), SQLError> {
410 match expression {
411 ScalarExpr::Func {
412 name,
413 args,
414 order_by,
415 filter,
416 ..
417 } => {
418 let lower = crate::semantics::builtin_function_dispatch_name(name);
419 if is_aggregate(engine, expression) {
420 reject_set_descendant(
421 engine,
422 resolver,
423 args.iter()
424 .chain(order_by.iter().map(|order| &order.expr))
425 .chain(filter.iter().map(AsRef::as_ref)),
426 schema,
427 params,
428 "aggregate function calls cannot contain set-returning function calls",
429 )?;
430 } else if lower == "coalesce" {
431 reject_set_descendant(
432 engine,
433 resolver,
434 args,
435 schema,
436 params,
437 "set-returning functions are not allowed in COALESCE",
438 )?;
439 }
440 for argument in args {
441 validate_set_context(engine, resolver, argument, schema, params)?;
442 }
443 for order in order_by {
444 validate_set_context(engine, resolver, &order.expr, schema, params)?;
445 }
446 if let Some(filter) = filter {
447 validate_set_context(engine, resolver, filter, schema, params)?;
448 }
449 }
450 ScalarExpr::WindowCall {
451 args, spec, filter, ..
452 } => {
453 reject_set_descendant(
454 engine,
455 resolver,
456 args.iter()
457 .chain(filter.iter().map(AsRef::as_ref))
458 .chain(spec.partition_by.iter())
459 .chain(spec.order_by.iter().map(|order| &order.expr))
460 .chain(
461 spec.frame
462 .iter()
463 .flat_map(|frame| [&frame.start, &frame.end])
464 .filter_map(|bound| frame_bound_expression(bound)),
465 ),
466 schema,
467 params,
468 "window function calls cannot contain set-returning function calls",
469 )?;
470 }
471 ScalarExpr::Case {
472 base,
473 when,
474 else_branch,
475 } => {
476 let descendants = base
477 .iter()
478 .map(AsRef::as_ref)
479 .chain(
480 when.iter()
481 .flat_map(|(condition, result)| [condition, result]),
482 )
483 .chain(else_branch.iter().map(AsRef::as_ref));
484 reject_set_descendant(
485 engine,
486 resolver,
487 descendants,
488 schema,
489 params,
490 "set-returning functions are not allowed in CASE",
491 )?;
492 }
493 ScalarExpr::Not(inner) => {
494 reject_set_descendant(
495 engine,
496 resolver,
497 [inner.as_ref()],
498 schema,
499 params,
500 "argument of NOT must not return a set",
501 )?;
502 }
503 ScalarExpr::UnaryMinus(inner) => {
504 validate_set_context(engine, resolver, inner, schema, params)?;
505 }
506 ScalarExpr::And(items) => {
507 reject_set_descendant(
508 engine,
509 resolver,
510 items,
511 schema,
512 params,
513 "argument of AND must not return a set",
514 )?;
515 }
516 ScalarExpr::Or(items) => {
517 reject_set_descendant(
518 engine,
519 resolver,
520 items,
521 schema,
522 params,
523 "argument of OR must not return a set",
524 )?;
525 }
526 ScalarExpr::InList { expr, list, .. } => {
527 validate_set_context(engine, resolver, expr, schema, params)?;
528 reject_set_descendant(
529 engine,
530 resolver,
531 list,
532 schema,
533 params,
534 "argument of IN must not return a set",
535 )?;
536 }
537 ScalarExpr::Between { expr, low, high } => {
538 reject_set_descendant(
539 engine,
540 resolver,
541 [expr.as_ref(), low.as_ref(), high.as_ref()],
542 schema,
543 params,
544 "argument of AND must not return a set",
545 )?;
546 }
547 ScalarExpr::Array(items)
548 | ScalarExpr::Row(items)
549 | ScalarExpr::CompositeRow { items, .. } => {
550 for item in items {
551 validate_set_context(engine, resolver, item, schema, params)?;
552 }
553 }
554 ScalarExpr::Binary { lhs, rhs, .. } => {
555 validate_set_context(engine, resolver, lhs, schema, params)?;
556 validate_set_context(engine, resolver, rhs, schema, params)?;
557 }
558 ScalarExpr::IsNull { expr, .. }
559 | ScalarExpr::Cast { expr, .. }
560 | ScalarExpr::Collated { expr, .. } => {
561 validate_set_context(engine, resolver, expr, schema, params)?;
562 }
563 ScalarExpr::InSubquery { expr, .. } => {
564 reject_set_descendant(
565 engine,
566 resolver,
567 [expr.as_ref()],
568 schema,
569 params,
570 "row comparison operator must not return a set",
571 )?;
572 }
573 ScalarExpr::Default
574 | ScalarExpr::Star
575 | ScalarExpr::QualifiedStar(_)
576 | ScalarExpr::Column(_)
577 | ScalarExpr::Position(_)
578 | ScalarExpr::InternalColumn(_)
579 | ScalarExpr::QualifiedColumn { .. }
580 | ScalarExpr::Literal(_)
581 | ScalarExpr::TypedLiteral { .. }
582 | ScalarExpr::Param(_)
583 | ScalarExpr::ScalarSubquery(_)
584 | ScalarExpr::Exists { .. } => {}
585 }
586 Ok(())
587}
588
589pub fn validate_projection_set_contexts(
590 engine: &dyn SetFunctionCatalog,
591 resolver: &dyn FunctionTypeResolver,
592 projections: &[ProjectionPlan],
593 schema: &RowSchema,
594 params: &[SQLParam],
595) -> Result<(), SQLError> {
596 for projection in projections {
597 validate_set_context(engine, resolver, &projection.expr, schema, params)?;
598 }
599 Ok(())
600}
601
602pub fn validate_query_set_contexts(
603 engine: &dyn SetFunctionCatalog,
604 resolver: &dyn FunctionTypeResolver,
605 statement: &QueryBlockPlan,
606 schema: &RowSchema,
607 params: &[SQLParam],
608) -> Result<(), SQLError> {
609 validate_projection_set_contexts(engine, resolver, &statement.projections, schema, params)?;
610 if let Some(locking) = statement.locking.first() {
611 for projection in &statement.projections {
612 if expression_may_return_set(engine, resolver, &projection.expr, schema, params)? {
613 return Err(SQLError::Unsupported(format!(
614 "{} is not allowed with set-returning functions in the target list",
615 locking.strength.sql_name()
616 )));
617 }
618 }
619 }
620 for expression in statement
621 .group_by
622 .iter()
623 .chain(statement.grouping_sets.iter().flatten())
624 .chain(statement.order_by.iter().map(|order| &order.expr))
625 .chain(statement.distinct_on.iter())
626 {
627 validate_set_context(engine, resolver, expression, schema, params)?;
628 }
629 if let Some(predicate) = statement.r#where.as_ref() {
630 reject_set_descendant(
631 engine,
632 resolver,
633 [predicate],
634 schema,
635 params,
636 "set-returning functions are not allowed in WHERE",
637 )?;
638 }
639 if let Some(having) = statement.having.as_ref() {
640 reject_set_descendant(
641 engine,
642 resolver,
643 [having],
644 schema,
645 params,
646 "set-returning functions are not allowed in HAVING",
647 )?;
648 }
649 if let Some(limit) = statement.limit.as_ref() {
650 reject_set_descendant(
651 engine,
652 resolver,
653 [limit],
654 schema,
655 params,
656 "set-returning functions are not allowed in LIMIT",
657 )?;
658 }
659 if let Some(offset) = statement.offset.as_ref() {
660 reject_set_descendant(
661 engine,
662 resolver,
663 [offset],
664 schema,
665 params,
666 "set-returning functions are not allowed in OFFSET",
667 )?;
668 }
669 if let Some(source) = statement.from.as_ref() {
670 validate_source_set_contexts(engine, resolver, source, schema, params)?;
671 }
672 Ok(())
673}
674
675fn validate_source_set_contexts(
676 engine: &dyn SetFunctionCatalog,
677 resolver: &dyn FunctionTypeResolver,
678 source: &SourcePlan,
679 schema: &RowSchema,
680 params: &[SQLParam],
681) -> Result<(), SQLError> {
682 match source {
683 SourcePlan::Join {
684 left, right, on, ..
685 } => {
686 validate_source_set_contexts(engine, resolver, left, schema, params)?;
687 validate_source_set_contexts(engine, resolver, right, schema, params)?;
688 if let Some(condition) = on {
689 reject_set_descendant(
690 engine,
691 resolver,
692 [condition],
693 schema,
694 params,
695 "set-returning functions are not allowed in JOIN conditions",
696 )?;
697 }
698 }
699 SourcePlan::Values { rows, .. } => {
700 validate_values_set_contexts(engine, resolver, rows, schema, params)?;
701 }
702 SourcePlan::Function { args, .. } => {
703 reject_set_descendant(
704 engine,
705 resolver,
706 args,
707 schema,
708 params,
709 "set-returning functions must appear at top level of FROM",
710 )?;
711 }
712 SourcePlan::FunctionGroup { functions, .. } => {
713 for function in functions {
714 reject_set_descendant(
715 engine,
716 resolver,
717 &function.args,
718 schema,
719 params,
720 "set-returning functions must appear at top level of FROM",
721 )?;
722 }
723 }
724 SourcePlan::Subquery { .. } | SourcePlan::Table { .. } => {}
725 }
726 Ok(())
727}
728
729pub fn validate_source_set_contexts_before_build(
730 engine: &dyn SetFunctionCatalog,
731 resolver: &dyn FunctionTypeResolver,
732 source: &SourcePlan,
733 params: &[SQLParam],
734 ctes: &BindingContext<'_>,
735 outer: Option<&RowSchema>,
736) -> Result<(), SQLError> {
737 match source {
738 SourcePlan::Join {
739 left,
740 right,
741 lateral,
742 ..
743 } => {
744 validate_source_set_contexts_before_build(engine, resolver, left, params, ctes, outer)?;
745 let left_schema =
746 crate::binding::bind_source_plan_schema(engine, left, params, ctes, outer)?;
747 let implicit_lateral_function = matches!(
748 right.as_ref(),
749 SourcePlan::Function { .. } | SourcePlan::FunctionGroup { .. }
750 );
751 let right_scope = (*lateral || implicit_lateral_function)
752 .then(|| overlay_set_validation_scope(&left_schema, outer));
753 validate_source_set_contexts_before_build(
754 engine,
755 resolver,
756 right,
757 params,
758 ctes,
759 right_scope.as_ref().or(outer),
760 )
761 }
762 SourcePlan::Values { rows, .. } => {
763 let empty = RowSchema::default();
764 validate_values_set_contexts(engine, resolver, rows, outer.unwrap_or(&empty), params)
765 }
766 SourcePlan::Function { args, .. } => {
767 let empty = RowSchema::default();
768 reject_set_descendant(
769 engine,
770 resolver,
771 args,
772 outer.unwrap_or(&empty),
773 params,
774 "set-returning functions must appear at top level of FROM",
775 )
776 }
777 SourcePlan::FunctionGroup { functions, .. } => {
778 let empty = RowSchema::default();
779 for function in functions {
780 reject_set_descendant(
781 engine,
782 resolver,
783 &function.args,
784 outer.unwrap_or(&empty),
785 params,
786 "set-returning functions must appear at top level of FROM",
787 )?;
788 }
789 Ok(())
790 }
791 SourcePlan::Subquery { .. } | SourcePlan::Table { .. } => Ok(()),
792 }
793}
794
795fn overlay_set_validation_scope(current: &RowSchema, outer: Option<&RowSchema>) -> RowSchema {
796 let Some(outer) = outer else {
797 return current.clone();
798 };
799 let columns = outer
800 .identities()
801 .iter()
802 .enumerate()
803 .map(|(position, identity)| (identity.clone(), outer.column_type(position).cloned()))
804 .collect::<Vec<_>>();
805 RowSchema::with_typed_outer_identities(current, &columns)
806}
807
808pub fn validate_values_set_contexts(
809 engine: &dyn SetFunctionCatalog,
810 resolver: &dyn FunctionTypeResolver,
811 rows: &[Vec<ScalarExpr>],
812 schema: &RowSchema,
813 params: &[SQLParam],
814) -> Result<(), SQLError> {
815 reject_set_descendant(
816 engine,
817 resolver,
818 rows.iter().flatten(),
819 schema,
820 params,
821 "set-returning functions are not allowed in VALUES",
822 )
823}