1use super::{
10 projection_columns, projection_star_columns, ColumnType, RowSchema, SQLError, SQLParam,
11 ScalarExpr, SchemaScope, SourcePlan,
12};
13use crate::plan::{CommandPlan, CtePlanBody};
14use crate::routines::RoutineResolution;
15
16pub fn analyze_prepared_command_schema(
17 routines: &dyn RoutineResolution,
18 command: &CommandPlan,
19 params: &[SQLParam],
20 ctes: &super::BindingContext,
21) -> Result<Option<RowSchema>, SQLError> {
22 if command.mutation_target().is_none() {
23 return Ok(None);
24 }
25 let mut scope = SchemaScope::for_analysis(ctes)?;
26 scope.set_command_lookup_mode(command);
27 let previous = scope.bind_cte_schemas(routines, command.ctes(), params, None)?;
28 let result = (|| {
29 let (target, expression) = scope.command_expression_schema(routines, command, params)?;
30 let excluded = RowSchema::with_qualified_types(
31 "excluded",
32 target.columns().to_vec(),
33 target.column_types().to_vec(),
34 );
35 let conflict_input = RowSchema::join(&expression, &excluded, std::iter::empty::<String>());
36 let conflict_expressions = match command {
37 CommandPlan::Insert(insert) => insert
38 .on_conflict
39 .as_ref()
40 .map(|conflict| match &conflict.action {
41 crate::plan::ConflictActionPlan::Update {
42 assignments,
43 predicate,
44 } => assignments
45 .iter()
46 .map(|assignment| &assignment.value)
47 .chain(predicate.as_deref())
48 .collect::<Vec<_>>(),
49 crate::plan::ConflictActionPlan::Nothing => Vec::new(),
50 })
51 .unwrap_or_default(),
52 _ => Vec::new(),
53 };
54 if let CommandPlan::Insert(insert) = command {
55 if let Some(source) = &insert.source {
56 scope.bind_query(routines, source, params, None)?;
57 }
58 }
59 for scalar in command.expressions() {
60 if matches!(command, CommandPlan::Merge(_)) {
61 continue;
62 }
63 scope.bind_expression_type(
64 routines,
65 scalar,
66 if conflict_expressions
67 .iter()
68 .any(|candidate| std::ptr::eq(*candidate, scalar))
69 {
70 &conflict_input
71 } else {
72 &expression
73 },
74 command.scalar_subqueries(),
75 params,
76 None,
77 )?;
78 }
79 if let CommandPlan::Merge(merge) = command {
80 scope.bind_merge_expressions(routines, merge, params, &target, &expression)?;
81 }
82 let result = scope.bind_command_returning(routines, command, params)?;
83 Ok(command
84 .returning()
85 .filter(|returning| !returning.is_empty())
86 .map(|_| result))
87 })();
88 scope.restore_cte_schemas(previous);
89 result
90}
91
92impl SchemaScope {
93 pub(super) fn set_command_lookup_mode(&mut self, command: &CommandPlan) {
94 let bound = match command {
95 CommandPlan::Insert(plan) => plan.relations_bound,
96 CommandPlan::Update(plan) => plan.relations_bound,
97 CommandPlan::Delete(plan) => plan.relations_bound,
98 _ => false,
99 };
100 self.resolution.set_lookup_mode(if bound {
101 crate::catalog::resolution::RelationLookupMode::Bound
102 } else {
103 crate::catalog::resolution::RelationLookupMode::Dynamic
104 });
105 }
106
107 pub(super) fn bind_cte_body(
108 &mut self,
109 routines: &dyn RoutineResolution,
110 body: &CtePlanBody,
111 params: &[SQLParam],
112 outer: Option<&RowSchema>,
113 ) -> Result<RowSchema, SQLError> {
114 match body {
115 CtePlanBody::Query(query) => self.bind_query(routines, query, params, outer),
116 CtePlanBody::Command(command) => self.bind_command_returning(routines, command, params),
117 }
118 }
119
120 fn bind_command_target(
121 &mut self,
122 routines: &dyn RoutineResolution,
123 command: &CommandPlan,
124 params: &[SQLParam],
125 ) -> Result<RowSchema, SQLError> {
126 let table = command
127 .mutation_target()
128 .ok_or_else(|| SQLError::Internal("non-DML command in a WITH definition".into()))?;
129 let source = SourcePlan::Table {
130 bound_columns: None,
131 name: table.to_string(),
132 qualifier: command.target_qualifier().unwrap_or(table).to_string(),
133 alias: None,
134 column_aliases: Vec::new(),
135 include_descendants: true,
136 };
137 let previous = std::mem::take(&mut self.ctes);
139 let deferred = std::mem::take(&mut self.deferred_ctes);
140 let bound = match command {
141 CommandPlan::Insert(plan) => plan.target_relation_bound,
142 CommandPlan::Update(plan) => plan.target_relation_bound,
143 CommandPlan::Delete(plan) => plan.target_relation_bound,
144 _ => false,
145 };
146 let lookup = bound.then(|| {
147 self.resolution
148 .set_lookup_mode(crate::catalog::resolution::RelationLookupMode::Bound)
149 });
150 let result = self.bind_source(routines, &source, &[], params, None);
151 if let Some(lookup) = lookup {
152 self.resolution.set_lookup_mode(lookup);
153 }
154 self.ctes = previous;
155 self.deferred_ctes = deferred;
156 let target = result?;
157 let columns = target
158 .columns()
159 .iter()
160 .enumerate()
161 .map(|(position, column)| target.public_name(position).unwrap_or(column).to_string())
162 .collect();
163 let schema = RowSchema::with_types(columns, target.column_types().to_vec());
164 Ok(if target.columns_are_open(None) {
165 RowSchema::with_open_columns(&schema, None)
166 } else {
167 schema
168 })
169 }
170
171 pub(super) fn command_expression_schema(
172 &mut self,
173 routines: &dyn RoutineResolution,
174 command: &CommandPlan,
175 params: &[SQLParam],
176 ) -> Result<(RowSchema, RowSchema), SQLError> {
177 let target = self.bind_command_target(routines, command, params)?;
178 let source = command
179 .source_input()
180 .map(|source| {
181 self.bind_source(routines, source, command.scalar_subqueries(), params, None)
182 })
183 .transpose()?;
184 let aliases = command
185 .returning_aliases()
186 .ok_or_else(|| SQLError::Internal("command CTE has no RETURNING namespace".into()))?;
187 let mut expression = crate::semantics::returning_expression_schema(
188 &target,
189 command.target_qualifier().unwrap_or_default(),
190 aliases,
191 source.as_ref(),
192 );
193 if target.columns_are_open(None) {
194 expression = RowSchema::with_open_columns(&expression, command.target_qualifier());
195 }
196 Ok((target, expression))
197 }
198
199 pub(super) fn bind_command_returning(
200 &mut self,
201 routines: &dyn RoutineResolution,
202 command: &CommandPlan,
203 params: &[SQLParam],
204 ) -> Result<RowSchema, SQLError> {
205 let previous = self.bind_cte_schemas(routines, command.ctes(), params, None)?;
206 let result = (|| {
207 let (target, expression) = self.command_expression_schema(routines, command, params)?;
208 if let CommandPlan::Insert(insert) = command {
209 if let Some(source) = &insert.source {
210 self.bind_query(routines, source, params, None)?;
211 }
212 }
213 let returning = command.returning().unwrap_or_default();
214 let labels = projection_columns(returning);
215 let mut columns = Vec::new();
216 let mut types: Vec<Option<ColumnType>> = Vec::new();
217 for (position, projection) in returning.iter().enumerate() {
218 let expansion = if matches!(projection.expr, ScalarExpr::QualifiedStar(_)) {
219 &expression
220 } else {
221 &target
222 };
223 if let Some(star) = projection_star_columns(&projection.expr, expansion)? {
224 for (column, ty) in star {
225 columns.push(column);
226 types.push(ty);
227 }
228 } else {
229 columns.push(labels[position].clone());
230 types.push(self.bind_expression_type(
231 routines,
232 &projection.expr,
233 &expression,
234 command.scalar_subqueries(),
235 params,
236 Some(&expression),
237 )?);
238 }
239 }
240 if let Some(error) =
241 crate::semantics::virtual_relation_mutation_error(&self.resolution, command)
242 {
243 return Err(error);
244 }
245 Ok(RowSchema::with_types(columns, types))
246 })();
247 self.restore_cte_schemas(previous);
248 result
249 }
250}