1use super::{
10 projection_star_columns, ColumnType, RowSchema, SQLError, SQLParam, ScalarExpr, SchemaScope,
11 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_relation_qualifier(&target, "excluded");
32 let conflict_input = RowSchema::join(&expression, &excluded, std::iter::empty::<String>());
33 let conflict_expressions = match command {
34 CommandPlan::Insert(insert) => insert
35 .on_conflict
36 .as_ref()
37 .map(|conflict| match &conflict.action {
38 crate::plan::ConflictActionPlan::Update {
39 assignments,
40 predicate,
41 } => assignments
42 .iter()
43 .flat_map(crate::plan::AssignmentPlan::expressions)
44 .chain(predicate.as_deref())
45 .collect::<Vec<_>>(),
46 crate::plan::ConflictActionPlan::Nothing => Vec::new(),
47 })
48 .unwrap_or_default(),
49 _ => Vec::new(),
50 };
51 if let CommandPlan::Insert(insert) = command {
52 if let Some(source) = &insert.source {
53 scope.bind_query(routines, source, params, None)?;
54 }
55 }
56 for scalar in command.expressions() {
57 if matches!(command, CommandPlan::Merge(_)) {
58 continue;
59 }
60 scope.bind_expression_type(
61 routines,
62 scalar,
63 if conflict_expressions
64 .iter()
65 .any(|candidate| std::ptr::eq(*candidate, scalar))
66 {
67 &conflict_input
68 } else {
69 &expression
70 },
71 command.scalar_subqueries(),
72 params,
73 )?;
74 }
75 if let CommandPlan::Merge(merge) = command {
76 scope.bind_merge_expressions(routines, merge, params, &target, &expression)?;
77 }
78 let result = scope.bind_command_returning(routines, command, params)?;
79 Ok(command
80 .returning()
81 .filter(|returning| !returning.is_empty())
82 .map(|_| result))
83 })();
84 scope.restore_cte_schemas(previous);
85 result
86}
87
88impl SchemaScope {
89 pub(super) fn set_command_lookup_mode(&mut self, command: &CommandPlan) {
90 let bound = match command {
91 CommandPlan::Insert(plan) => plan.relations_bound,
92 CommandPlan::Update(plan) => plan.relations_bound,
93 CommandPlan::Delete(plan) => plan.relations_bound,
94 _ => false,
95 };
96 self.resolution.set_lookup_mode(if bound {
97 crate::catalog::resolution::RelationLookupMode::Bound
98 } else {
99 crate::catalog::resolution::RelationLookupMode::Dynamic
100 });
101 }
102
103 pub(super) fn bind_cte_body(
104 &mut self,
105 routines: &dyn RoutineResolution,
106 body: &CtePlanBody,
107 params: &[SQLParam],
108 outer: Option<&RowSchema>,
109 ) -> Result<RowSchema, SQLError> {
110 match body {
111 CtePlanBody::Query(query) => self.bind_query(routines, query, params, outer),
112 CtePlanBody::Command(command) => self.bind_command_returning(routines, command, params),
113 }
114 }
115
116 pub(super) fn bind_command_target(
117 &mut self,
118 routines: &dyn RoutineResolution,
119 command: &CommandPlan,
120 params: &[SQLParam],
121 ) -> Result<RowSchema, SQLError> {
122 let table = command
123 .mutation_target()
124 .ok_or_else(|| SQLError::Internal("non-DML command in a WITH definition".into()))?;
125 let source = SourcePlan::Table {
126 bound_columns: None,
127 name: table.to_string(),
128 qualifier: command.target_qualifier().unwrap_or(table).to_string(),
129 alias: None,
130 column_aliases: Vec::new(),
131 include_descendants: true,
132 };
133 let previous = std::mem::take(&mut self.ctes);
135 let deferred = std::mem::take(&mut self.deferred_ctes);
136 let bound = match command {
137 CommandPlan::Insert(plan) => plan.target_relation_bound,
138 CommandPlan::Update(plan) => plan.target_relation_bound,
139 CommandPlan::Delete(plan) => plan.target_relation_bound,
140 _ => false,
141 };
142 let lookup = bound.then(|| {
143 self.resolution
144 .set_lookup_mode(crate::catalog::resolution::RelationLookupMode::Bound)
145 });
146 let result = self.bind_source(routines, &source, &[], params, None);
147 if let Some(lookup) = lookup {
148 self.resolution.set_lookup_mode(lookup);
149 }
150 self.ctes = previous;
151 self.deferred_ctes = deferred;
152 let target = result?;
153 let columns = target
154 .columns()
155 .iter()
156 .enumerate()
157 .map(|(position, column)| target.public_name(position).unwrap_or(column).to_string())
158 .collect();
159 let schema = RowSchema::with_types(columns, target.column_types().to_vec())
160 .with_column_collations(
161 (0..target.len()).map(|position| target.column_collation(position)),
162 );
163 Ok(if target.columns_are_open(None) {
164 RowSchema::with_open_columns(&schema, None)
165 } else {
166 schema
167 })
168 }
169
170 pub(super) fn command_expression_schema(
171 &mut self,
172 routines: &dyn RoutineResolution,
173 command: &CommandPlan,
174 params: &[SQLParam],
175 outer: Option<&RowSchema>,
176 ) -> Result<(RowSchema, 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, outer)
182 })
183 .transpose()?;
184 let expression = Self::command_input_schema(command, &target, source.as_ref())?;
185 let star = crate::semantics::returning::command_returning_star_schema(
186 command,
187 &target,
188 source.as_ref(),
189 )?;
190 Ok((target, expression, star))
191 }
192
193 pub(super) fn command_input_schema(
194 command: &CommandPlan,
195 target: &RowSchema,
196 source: Option<&RowSchema>,
197 ) -> Result<RowSchema, SQLError> {
198 let aliases = command
199 .returning_aliases()
200 .ok_or_else(|| SQLError::Internal("command CTE has no RETURNING namespace".into()))?;
201 let mut expression = crate::semantics::returning_expression_schema(
202 target,
203 command.target_qualifier().unwrap_or_default(),
204 aliases,
205 source,
206 );
207 if target.columns_are_open(None) {
208 expression = RowSchema::with_open_columns(&expression, command.target_qualifier());
209 }
210 Ok(expression)
211 }
212
213 pub(super) fn bind_command_returning(
214 &mut self,
215 routines: &dyn RoutineResolution,
216 command: &CommandPlan,
217 params: &[SQLParam],
218 ) -> Result<RowSchema, SQLError> {
219 let previous = self.bind_cte_schemas(routines, command.ctes(), params, None)?;
220 let result = (|| {
221 let (target, expression, star) =
222 self.command_expression_schema(routines, command, params, None)?;
223 if let CommandPlan::Insert(insert) = command {
224 if let Some(source) = &insert.source {
225 self.bind_query(routines, source, params, None)?;
226 }
227 }
228 let returning =
229 crate::semantics::returning::command_returning_projections(command, &target)?;
230 let mut columns = Vec::new();
231 let mut types: Vec<Option<ColumnType>> = Vec::new();
232 let mut records = Vec::new();
233 let mut collations = super::projection::OutputCollations::default();
234 for projection in &returning {
235 let expansion = if matches!(projection.expr, ScalarExpr::QualifiedStar(_)) {
236 &expression
237 } else {
238 &star
239 };
240 if let Some(star) = projection_star_columns(&projection.expr, expansion)? {
241 records.extend(
242 super::record_fields::star_metadata(&projection.expr, expansion)
243 .unwrap_or_default(),
244 );
245 for (column, ty, collation) in star {
246 collations.push(types.len(), ty.as_ref(), collation);
247 columns.push(column);
248 types.push(ty);
249 }
250 } else {
251 columns.push(crate::semantics::projection_label_at(projection));
252 let metadata = self.bind_projection_metadata(
253 routines,
254 &projection.expr,
255 &expression,
256 command.scalar_subqueries(),
257 params,
258 )?;
259 collations.push(types.len(), metadata.ty.as_ref(), metadata.collation);
260 types.push(metadata.ty);
261 records.push(metadata.record);
262 }
263 }
264 crate::semantics::returning::validate_returning_width(
265 command.returning().unwrap_or_default(),
266 columns.len(),
267 )?;
268 if let Some(error) = crate::semantics::virtual_relation_mutation_error(
269 self.catalog.as_ref(),
270 &self.resolution,
271 command,
272 )? {
273 return Err(error);
274 }
275 for projection in &returning {
276 self.collations.validate_expression(&projection.expr)?;
277 }
278 Ok(collations
279 .apply(RowSchema::with_types(columns, types))
280 .with_record_metadata(
281 records
282 .into_iter()
283 .enumerate()
284 .filter_map(|(index, record)| record.map(|record| (index, record))),
285 ))
286 })();
287 self.restore_cte_schemas(previous);
288 result
289 }
290}