1use super::{
8 active_unconditional_instead_rule, add_check_option, automatic_view_layer,
9 bind_unqualified_source_positions, combine_view_predicate, delete_ordinary_subquery_ids,
10 dml_source_schema, dml_target_width, duplicate_assignment, finalize_source_returning,
11 instead_of_trigger_definition, not_automatically_updatable, preserve_view_rule_returning,
12 record_view_rule_relation, returning_subquery_ids, rewrite_correlated_dml_context,
13 rewrite_existing_view_checks, rewrite_returning, rewrite_target_expression,
14 update_ordinary_subquery_ids, validate_delete_expressions, validate_direct_view_rule_path,
15 validate_mapped_columns, validate_public_delete_contract, validate_public_update_contract,
16 validate_public_view_targets, validate_update_expressions, validate_update_targets,
17 view_updatability, writable_column, AssignmentPlan, BTreeSet, CorrelatedDmlContext, DeletePlan,
18 ExpressionScope, SQLError, TriggerEvent, UpdatePlan, ViewRewriteContext, ViewRuleUpdatePlan,
19};
20
21#[expect(
22 clippy::too_many_lines,
23 reason = "preserves view qualifier and row identity"
24)]
25pub fn rewrite_update_to_base(
26 services: ViewRewriteContext<'_>,
27 statement: &UpdatePlan,
28 params: &[crate::SQLParam],
29 inherited_ctes: Option<&super::CteScope>,
30) -> Result<UpdatePlan, SQLError> {
31 validate_public_view_targets(
32 services,
33 &statement.table,
34 statement
35 .assignments
36 .iter()
37 .map(|assignment| assignment.column.as_str()),
38 )?;
39 let source_schema = dml_source_schema(
40 services,
41 statement.source.as_deref(),
42 &statement.ctes,
43 &statement.subqueries,
44 params,
45 inherited_ctes,
46 )?;
47 validate_public_update_contract(services, statement, source_schema.as_ref())?;
48 let Some(initial_layer) = automatic_view_layer(services, &statement.table)? else {
49 return Err(not_automatically_updatable(&statement.table, "UPDATE"));
50 };
51 validate_update_targets(&initial_layer, statement)?;
52 validate_direct_view_rule_path(
53 services,
54 &initial_layer.canonical_name,
55 crate::ast::RuleEvent::Update,
56 "UPDATE",
57 )?;
58 if !view_updatability(services, &statement.table)?
59 .automatic
60 .updatable
61 {
62 return Err(not_automatically_updatable(&statement.table, "UPDATE"));
63 }
64 let mut plan = statement.clone();
65 let next_privilege_subject =
66 crate::semantics::view_privileges::ensure_update(services.authorization, &plan)?;
67 plan.target_privilege_subject = Some(next_privilege_subject);
68 let mut cascaded = false;
69 let mut visited = BTreeSet::new();
70 let mut source_star_boundaries = Vec::new();
71 let mut rewrite_suppressed = false;
72 loop {
73 let Some(layer) = automatic_view_layer(services, &plan.table)? else {
74 if active_unconditional_instead_rule(
75 services,
76 &plan.table,
77 crate::ast::RuleEvent::Update,
78 )? {
79 break;
80 }
81 return Err(not_automatically_updatable(&plan.table, "UPDATE"));
82 };
83 if !visited.insert(layer.canonical_name.clone()) {
84 return Err(SQLError::Internal(format!(
85 "cycle while rewriting automatically updatable view `{}`",
86 layer.canonical_name
87 )));
88 }
89 if !rewrite_suppressed {
90 validate_direct_view_rule_path(
91 services,
92 &layer.canonical_name,
93 crate::ast::RuleEvent::Update,
94 "UPDATE",
95 )?;
96 }
97 if !rewrite_suppressed
98 && visited.len() > 1
99 && instead_of_trigger_definition(services, &layer.canonical_name, TriggerEvent::Update)?
100 {
101 return Err(not_automatically_updatable(&layer.canonical_name, "UPDATE"));
102 }
103 let has_view_rules = if rewrite_suppressed {
104 false
105 } else {
106 record_view_rule_relation(
107 services,
108 &mut plan.view_rule_relations,
109 &layer,
110 crate::ast::RuleEvent::Update,
111 )?
112 };
113 let layer_suppresses = has_view_rules
114 && super::context::relation_suppresses_original_query(
115 services,
116 &layer.canonical_name,
117 crate::ast::RuleEvent::Update,
118 )?;
119 if visited.len() > 1 && !rewrite_suppressed && !layer_suppresses {
120 let next_privilege_subject =
121 crate::semantics::view_privileges::ensure_update(services.authorization, &plan)?;
122 plan.target_privilege_subject = Some(next_privilege_subject);
123 }
124 if has_view_rules
125 && super::context::relation_has_returning_provider(
126 services,
127 &layer.canonical_name,
128 crate::ast::RuleEvent::Update,
129 )?
130 {
131 preserve_view_rule_returning(
132 &mut plan.view_rule_returning,
133 &layer.canonical_name,
134 &plan.target_qualifier,
135 &plan.returning,
136 &plan.returning_aliases,
137 &plan.subqueries,
138 );
139 }
140 if has_view_rules {
141 plan.view_rule_update_plans.push(ViewRuleUpdatePlan {
142 relation: layer.canonical_name.clone(),
143 assigned_columns: plan
144 .assignments
145 .iter()
146 .map(|assignment| assignment.column.clone())
147 .collect(),
148 input_columns: Vec::new(),
149 });
150 }
151 let target_qualifier = plan.target_qualifier.clone();
152 if visited.len() == 1 {
153 validate_update_expressions(
154 services,
155 &plan,
156 &layer,
157 source_schema.as_ref(),
158 params,
159 inherited_ctes,
160 )?;
161 }
162 let ordinary_subquery_ids = update_ordinary_subquery_ids(&plan);
163 rewrite_correlated_dml_context(
164 CorrelatedDmlContext {
165 inherited_ctes,
166 services,
167 layer: &layer,
168 target_qualifier: &target_qualifier,
169 source: source_schema.as_ref(),
170 returning_aliases: None,
171 include_excluded: false,
172 ctes: &plan.ctes,
173 ids: &ordinary_subquery_ids,
174 params,
175 },
176 &mut plan.subqueries,
177 )?;
178 let returning_subquery_ids = returning_subquery_ids(&plan.returning);
179 rewrite_correlated_dml_context(
180 CorrelatedDmlContext {
181 inherited_ctes,
182 services,
183 layer: &layer,
184 target_qualifier: &target_qualifier,
185 source: source_schema.as_ref(),
186 returning_aliases: Some(&plan.returning_aliases),
187 include_excluded: false,
188 ctes: &plan.ctes,
189 ids: &returning_subquery_ids,
190 params,
191 },
192 &mut plan.subqueries,
193 )?;
194 let ordinary_scope = ExpressionScope {
195 target_qualifier: &target_qualifier,
196 returning_aliases: None,
197 source: source_schema.as_ref(),
198 include_excluded: false,
199 };
200 for AssignmentPlan { column, value } in &mut plan.assignments {
201 rewrite_target_expression(
202 services,
203 value,
204 &layer,
205 ordinary_scope,
206 &mut plan.subqueries,
207 )?;
208 if !layer_suppresses && !rewrite_suppressed {
209 *column = writable_column(&layer, column, "UPDATE")?;
210 }
211 }
212 let mapped = plan
213 .assignments
214 .iter()
215 .map(|assignment| assignment.column.clone())
216 .collect::<Vec<_>>();
217 validate_mapped_columns(&mapped, duplicate_assignment)?;
218 if let Some(predicate) = &mut plan.predicate {
219 rewrite_target_expression(
220 services,
221 predicate,
222 &layer,
223 ordinary_scope,
224 &mut plan.subqueries,
225 )?;
226 }
227 rewrite_existing_view_checks(
228 services,
229 &mut plan.view_checks,
230 &layer,
231 &target_qualifier,
232 &mut plan.subqueries,
233 )?;
234 let (returning, boundaries) = rewrite_returning(
235 services,
236 plan.returning,
237 &layer,
238 &target_qualifier,
239 &plan.returning_aliases,
240 source_schema.as_ref(),
241 &mut plan.subqueries,
242 )?;
243 plan.returning = returning;
244 if visited.len() == 1 {
245 source_star_boundaries = boundaries;
246 }
247 plan.predicate = combine_view_predicate(
248 services,
249 plan.predicate,
250 &layer,
251 &target_qualifier,
252 &mut plan.subqueries,
253 )?;
254 add_check_option(
255 services,
256 &mut plan.view_checks,
257 &layer,
258 &target_qualifier,
259 &mut cascaded,
260 &mut plan.subqueries,
261 )?;
262 plan.table = layer.source_name;
263 plan.include_descendants = layer.source_include_descendants;
264 rewrite_suppressed |= layer_suppresses;
265 if !super::context::target_is_view(services, &plan.table)? {
266 break;
267 }
268 }
269 let input_columns = plan
270 .assignments
271 .iter()
272 .map(|assignment| assignment.column.clone())
273 .collect::<Vec<_>>();
274 for update_plan in &mut plan.view_rule_update_plans {
275 update_plan.input_columns.clone_from(&input_columns);
276 }
277 if let Some(source) = source_schema.as_ref() {
278 let target_width = dml_target_width(services, &plan.table)?;
279 for assignment in &mut plan.assignments {
280 bind_unqualified_source_positions(&mut assignment.value, source, target_width);
281 }
282 if let Some(predicate) = &mut plan.predicate {
283 bind_unqualified_source_positions(predicate, source, target_width);
284 }
285 for projection in &mut plan.returning {
286 bind_unqualified_source_positions(&mut projection.expr, source, target_width);
287 }
288 }
289 plan.returning = finalize_source_returning(
290 services,
291 &plan.table,
292 plan.returning,
293 source_schema.as_ref(),
294 &source_star_boundaries,
295 )?;
296 Ok(plan)
297}
298
299#[expect(
300 clippy::too_many_lines,
301 reason = "preserves view qualifier and row identity"
302)]
303pub fn rewrite_delete_to_base(
304 services: ViewRewriteContext<'_>,
305 statement: &DeletePlan,
306 params: &[crate::SQLParam],
307 inherited_ctes: Option<&super::CteScope>,
308) -> Result<DeletePlan, SQLError> {
309 let source_schema = dml_source_schema(
310 services,
311 statement.source.as_deref(),
312 &statement.ctes,
313 &statement.subqueries,
314 params,
315 inherited_ctes,
316 )?;
317 validate_public_delete_contract(services, statement, source_schema.as_ref())?;
318 validate_direct_view_rule_path(
319 services,
320 &statement.table,
321 crate::ast::RuleEvent::Delete,
322 "DELETE",
323 )?;
324 let mut plan = statement.clone();
325 let next_privilege_subject =
326 crate::semantics::view_privileges::ensure_delete(services.authorization, &plan)?;
327 plan.target_privilege_subject = Some(next_privilege_subject);
328 let mut visited = BTreeSet::new();
329 let mut source_star_boundaries = Vec::new();
330 let mut rewrite_suppressed = false;
331 loop {
332 let Some(layer) = automatic_view_layer(services, &plan.table)? else {
333 if active_unconditional_instead_rule(
334 services,
335 &plan.table,
336 crate::ast::RuleEvent::Delete,
337 )? {
338 break;
339 }
340 return Err(not_automatically_updatable(&plan.table, "DELETE"));
341 };
342 if !visited.insert(layer.canonical_name.clone()) {
343 return Err(SQLError::Internal(format!(
344 "cycle while rewriting automatically updatable view `{}`",
345 layer.canonical_name
346 )));
347 }
348 if !rewrite_suppressed {
349 validate_direct_view_rule_path(
350 services,
351 &layer.canonical_name,
352 crate::ast::RuleEvent::Delete,
353 "DELETE",
354 )?;
355 }
356 if !rewrite_suppressed
357 && visited.len() > 1
358 && instead_of_trigger_definition(services, &layer.canonical_name, TriggerEvent::Delete)?
359 {
360 return Err(not_automatically_updatable(&layer.canonical_name, "DELETE"));
361 }
362 let has_view_rules = if rewrite_suppressed {
363 false
364 } else {
365 record_view_rule_relation(
366 services,
367 &mut plan.view_rule_relations,
368 &layer,
369 crate::ast::RuleEvent::Delete,
370 )?
371 };
372 let layer_suppresses = has_view_rules
373 && super::context::relation_suppresses_original_query(
374 services,
375 &layer.canonical_name,
376 crate::ast::RuleEvent::Delete,
377 )?;
378 if visited.len() > 1 && !rewrite_suppressed && !layer_suppresses {
379 let next_privilege_subject =
380 crate::semantics::view_privileges::ensure_delete(services.authorization, &plan)?;
381 plan.target_privilege_subject = Some(next_privilege_subject);
382 }
383 if has_view_rules
384 && super::context::relation_has_returning_provider(
385 services,
386 &layer.canonical_name,
387 crate::ast::RuleEvent::Delete,
388 )?
389 {
390 preserve_view_rule_returning(
391 &mut plan.view_rule_returning,
392 &layer.canonical_name,
393 &plan.target_qualifier,
394 &plan.returning,
395 &plan.returning_aliases,
396 &plan.subqueries,
397 );
398 }
399 let target_qualifier = plan.target_qualifier.clone();
400 if visited.len() == 1 {
401 validate_delete_expressions(
402 services,
403 &plan,
404 &layer,
405 source_schema.as_ref(),
406 params,
407 inherited_ctes,
408 )?;
409 }
410 let ordinary_subquery_ids = delete_ordinary_subquery_ids(&plan);
411 rewrite_correlated_dml_context(
412 CorrelatedDmlContext {
413 inherited_ctes,
414 services,
415 layer: &layer,
416 target_qualifier: &target_qualifier,
417 source: source_schema.as_ref(),
418 returning_aliases: None,
419 include_excluded: false,
420 ctes: &plan.ctes,
421 ids: &ordinary_subquery_ids,
422 params,
423 },
424 &mut plan.subqueries,
425 )?;
426 let returning_subquery_ids = returning_subquery_ids(&plan.returning);
427 rewrite_correlated_dml_context(
428 CorrelatedDmlContext {
429 inherited_ctes,
430 services,
431 layer: &layer,
432 target_qualifier: &target_qualifier,
433 source: source_schema.as_ref(),
434 returning_aliases: Some(&plan.returning_aliases),
435 include_excluded: false,
436 ctes: &plan.ctes,
437 ids: &returning_subquery_ids,
438 params,
439 },
440 &mut plan.subqueries,
441 )?;
442 let ordinary_scope = ExpressionScope {
443 target_qualifier: &target_qualifier,
444 returning_aliases: None,
445 source: source_schema.as_ref(),
446 include_excluded: false,
447 };
448 if let Some(predicate) = &mut plan.predicate {
449 rewrite_target_expression(
450 services,
451 predicate,
452 &layer,
453 ordinary_scope,
454 &mut plan.subqueries,
455 )?;
456 }
457 let (returning, boundaries) = rewrite_returning(
458 services,
459 plan.returning,
460 &layer,
461 &target_qualifier,
462 &plan.returning_aliases,
463 source_schema.as_ref(),
464 &mut plan.subqueries,
465 )?;
466 plan.returning = returning;
467 if visited.len() == 1 {
468 source_star_boundaries = boundaries;
469 }
470 plan.predicate = combine_view_predicate(
471 services,
472 plan.predicate,
473 &layer,
474 &target_qualifier,
475 &mut plan.subqueries,
476 )?;
477 plan.table = layer.source_name;
478 plan.include_descendants = layer.source_include_descendants;
479 rewrite_suppressed |= layer_suppresses;
480 if !super::context::target_is_view(services, &plan.table)? {
481 break;
482 }
483 }
484 if let Some(source) = source_schema.as_ref() {
485 let target_width = dml_target_width(services, &plan.table)?;
486 if let Some(predicate) = &mut plan.predicate {
487 bind_unqualified_source_positions(predicate, source, target_width);
488 }
489 for projection in &mut plan.returning {
490 bind_unqualified_source_positions(&mut projection.expr, source, target_width);
491 }
492 }
493 plan.returning = finalize_source_returning(
494 services,
495 &plan.table,
496 plan.returning,
497 source_schema.as_ref(),
498 &source_star_boundaries,
499 )?;
500 Ok(plan)
501}