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) =
30 scope.command_expression_schema(routines, command, params, None)?;
31 let excluded = RowSchema::with_qualified_types(
32 "excluded",
33 target.columns().to_vec(),
34 target.column_types().to_vec(),
35 );
36 let conflict_input = RowSchema::join(&expression, &excluded, std::iter::empty::<String>());
37 let conflict_expressions = match command {
38 CommandPlan::Insert(insert) => insert
39 .on_conflict
40 .as_ref()
41 .map(|conflict| match &conflict.action {
42 crate::plan::ConflictActionPlan::Update {
43 assignments,
44 predicate,
45 } => assignments
46 .iter()
47 .flat_map(crate::plan::AssignmentPlan::expressions)
48 .chain(predicate.as_deref())
49 .collect::<Vec<_>>(),
50 crate::plan::ConflictActionPlan::Nothing => Vec::new(),
51 })
52 .unwrap_or_default(),
53 _ => Vec::new(),
54 };
55 if let CommandPlan::Insert(insert) = command {
56 if let Some(source) = &insert.source {
57 scope.bind_query(routines, source, params, None)?;
58 }
59 }
60 for scalar in command.expressions() {
61 if matches!(command, CommandPlan::Merge(_)) {
62 continue;
63 }
64 scope.bind_expression_type(
65 routines,
66 scalar,
67 if conflict_expressions
68 .iter()
69 .any(|candidate| std::ptr::eq(*candidate, scalar))
70 {
71 &conflict_input
72 } else {
73 &expression
74 },
75 command.scalar_subqueries(),
76 params,
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 pub(super) 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 outer: Option<&RowSchema>,
177 ) -> Result<(RowSchema, RowSchema), SQLError> {
178 let target = self.bind_command_target(routines, command, params)?;
179 let source = command
180 .source_input()
181 .map(|source| {
182 self.bind_source(routines, source, command.scalar_subqueries(), params, outer)
183 })
184 .transpose()?;
185 let expression = Self::command_input_schema(command, &target, source.as_ref())?;
186 Ok((target, expression))
187 }
188
189 pub(super) fn command_input_schema(
190 command: &CommandPlan,
191 target: &RowSchema,
192 source: Option<&RowSchema>,
193 ) -> Result<RowSchema, SQLError> {
194 let aliases = command
195 .returning_aliases()
196 .ok_or_else(|| SQLError::Internal("command CTE has no RETURNING namespace".into()))?;
197 let mut expression = crate::semantics::returning_expression_schema(
198 target,
199 command.target_qualifier().unwrap_or_default(),
200 aliases,
201 source,
202 );
203 if target.columns_are_open(None) {
204 expression = RowSchema::with_open_columns(&expression, command.target_qualifier());
205 }
206 Ok(expression)
207 }
208
209 pub(super) fn bind_command_returning(
210 &mut self,
211 routines: &dyn RoutineResolution,
212 command: &CommandPlan,
213 params: &[SQLParam],
214 ) -> Result<RowSchema, SQLError> {
215 let previous = self.bind_cte_schemas(routines, command.ctes(), params, None)?;
216 let result = (|| {
217 let (target, expression) =
218 self.command_expression_schema(routines, command, params, None)?;
219 if let CommandPlan::Insert(insert) = command {
220 if let Some(source) = &insert.source {
221 self.bind_query(routines, source, params, None)?;
222 }
223 }
224 let returning = command.returning().unwrap_or_default();
225 let labels = projection_columns(returning);
226 let mut columns = Vec::new();
227 let mut types: Vec<Option<ColumnType>> = Vec::new();
228 for (position, projection) in returning.iter().enumerate() {
229 let expansion = if matches!(projection.expr, ScalarExpr::QualifiedStar(_)) {
230 &expression
231 } else {
232 &target
233 };
234 if let Some(star) = projection_star_columns(&projection.expr, expansion)? {
235 for (column, ty) in star {
236 columns.push(column);
237 types.push(ty);
238 }
239 } else {
240 columns.push(labels[position].clone());
241 types.push(self.bind_expression_type(
242 routines,
243 &projection.expr,
244 &expression,
245 command.scalar_subqueries(),
246 params,
247 )?);
248 }
249 }
250 if let Some(error) = crate::semantics::virtual_relation_mutation_error(
251 self.catalog.as_ref(),
252 &self.resolution,
253 command,
254 )? {
255 return Err(error);
256 }
257 Ok(RowSchema::with_types(columns, types))
258 })();
259 self.restore_cte_schemas(previous);
260 result
261 }
262}