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.target.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.target.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 { target, value } in &mut plan.assignments {
201 for expression in target.expressions_mut() {
202 rewrite_target_expression(
203 services,
204 expression,
205 &layer,
206 ordinary_scope,
207 &mut plan.subqueries,
208 )?;
209 }
210 rewrite_target_expression(
211 services,
212 value,
213 &layer,
214 ordinary_scope,
215 &mut plan.subqueries,
216 )?;
217 if !layer_suppresses && !rewrite_suppressed {
218 target.column = writable_column(&layer, &target.column, "UPDATE")?;
219 }
220 }
221 let mapped = plan
222 .assignments
223 .iter()
224 .map(|assignment| assignment.target.clone())
225 .collect::<Vec<_>>();
226 validate_mapped_columns(&mapped, duplicate_assignment)?;
227 if let Some(predicate) = &mut plan.predicate {
228 rewrite_target_expression(
229 services,
230 predicate,
231 &layer,
232 ordinary_scope,
233 &mut plan.subqueries,
234 )?;
235 }
236 rewrite_existing_view_checks(
237 services,
238 &mut plan.view_checks,
239 &layer,
240 &target_qualifier,
241 &mut plan.subqueries,
242 )?;
243 let (returning, boundaries) = rewrite_returning(
244 services,
245 plan.returning,
246 &layer,
247 &target_qualifier,
248 &plan.returning_aliases,
249 source_schema.as_ref(),
250 &mut plan.subqueries,
251 )?;
252 plan.returning = returning;
253 if visited.len() == 1 {
254 source_star_boundaries = boundaries;
255 }
256 plan.predicate = combine_view_predicate(
257 services,
258 plan.predicate,
259 &layer,
260 &target_qualifier,
261 &mut plan.subqueries,
262 )?;
263 add_check_option(
264 services,
265 &mut plan.view_checks,
266 &layer,
267 &target_qualifier,
268 &mut cascaded,
269 &mut plan.subqueries,
270 )?;
271 plan.table = layer.source_name;
272 plan.include_descendants = layer.source_include_descendants;
273 rewrite_suppressed |= layer_suppresses;
274 if !super::context::target_is_view(services, &plan.table)? {
275 break;
276 }
277 }
278 let input_columns = plan
279 .assignments
280 .iter()
281 .map(|assignment| assignment.target.column.clone())
282 .collect::<Vec<_>>();
283 for update_plan in &mut plan.view_rule_update_plans {
284 update_plan.input_columns.clone_from(&input_columns);
285 }
286 if let Some(source) = source_schema.as_ref() {
287 let target_width = dml_target_width(services, &plan.table)?;
288 for assignment in &mut plan.assignments {
289 for expression in assignment.expressions_mut() {
290 bind_unqualified_source_positions(expression, source, target_width);
291 }
292 }
293 if let Some(predicate) = &mut plan.predicate {
294 bind_unqualified_source_positions(predicate, source, target_width);
295 }
296 for projection in &mut plan.returning {
297 bind_unqualified_source_positions(&mut projection.expr, source, target_width);
298 }
299 }
300 plan.returning = finalize_source_returning(
301 services,
302 &plan.table,
303 plan.returning,
304 source_schema.as_ref(),
305 &source_star_boundaries,
306 )?;
307 Ok(plan)
308}
309
310#[expect(
311 clippy::too_many_lines,
312 reason = "preserves view qualifier and row identity"
313)]
314pub fn rewrite_delete_to_base(
315 services: ViewRewriteContext<'_>,
316 statement: &DeletePlan,
317 params: &[crate::SQLParam],
318 inherited_ctes: Option<&super::CteScope>,
319) -> Result<DeletePlan, SQLError> {
320 let source_schema = dml_source_schema(
321 services,
322 statement.source.as_deref(),
323 &statement.ctes,
324 &statement.subqueries,
325 params,
326 inherited_ctes,
327 )?;
328 validate_public_delete_contract(services, statement, source_schema.as_ref())?;
329 validate_direct_view_rule_path(
330 services,
331 &statement.table,
332 crate::ast::RuleEvent::Delete,
333 "DELETE",
334 )?;
335 let mut plan = statement.clone();
336 let next_privilege_subject =
337 crate::semantics::view_privileges::ensure_delete(services.authorization, &plan)?;
338 plan.target_privilege_subject = Some(next_privilege_subject);
339 let mut visited = BTreeSet::new();
340 let mut source_star_boundaries = Vec::new();
341 let mut rewrite_suppressed = false;
342 loop {
343 let Some(layer) = automatic_view_layer(services, &plan.table)? else {
344 if active_unconditional_instead_rule(
345 services,
346 &plan.table,
347 crate::ast::RuleEvent::Delete,
348 )? {
349 break;
350 }
351 return Err(not_automatically_updatable(&plan.table, "DELETE"));
352 };
353 if !visited.insert(layer.canonical_name.clone()) {
354 return Err(SQLError::Internal(format!(
355 "cycle while rewriting automatically updatable view `{}`",
356 layer.canonical_name
357 )));
358 }
359 if !rewrite_suppressed {
360 validate_direct_view_rule_path(
361 services,
362 &layer.canonical_name,
363 crate::ast::RuleEvent::Delete,
364 "DELETE",
365 )?;
366 }
367 if !rewrite_suppressed
368 && visited.len() > 1
369 && instead_of_trigger_definition(services, &layer.canonical_name, TriggerEvent::Delete)?
370 {
371 return Err(not_automatically_updatable(&layer.canonical_name, "DELETE"));
372 }
373 let has_view_rules = if rewrite_suppressed {
374 false
375 } else {
376 record_view_rule_relation(
377 services,
378 &mut plan.view_rule_relations,
379 &layer,
380 crate::ast::RuleEvent::Delete,
381 )?
382 };
383 let layer_suppresses = has_view_rules
384 && super::context::relation_suppresses_original_query(
385 services,
386 &layer.canonical_name,
387 crate::ast::RuleEvent::Delete,
388 )?;
389 if visited.len() > 1 && !rewrite_suppressed && !layer_suppresses {
390 let next_privilege_subject =
391 crate::semantics::view_privileges::ensure_delete(services.authorization, &plan)?;
392 plan.target_privilege_subject = Some(next_privilege_subject);
393 }
394 if has_view_rules
395 && super::context::relation_has_returning_provider(
396 services,
397 &layer.canonical_name,
398 crate::ast::RuleEvent::Delete,
399 )?
400 {
401 preserve_view_rule_returning(
402 &mut plan.view_rule_returning,
403 &layer.canonical_name,
404 &plan.target_qualifier,
405 &plan.returning,
406 &plan.returning_aliases,
407 &plan.subqueries,
408 );
409 }
410 let target_qualifier = plan.target_qualifier.clone();
411 if visited.len() == 1 {
412 validate_delete_expressions(
413 services,
414 &plan,
415 &layer,
416 source_schema.as_ref(),
417 params,
418 inherited_ctes,
419 )?;
420 }
421 let ordinary_subquery_ids = delete_ordinary_subquery_ids(&plan);
422 rewrite_correlated_dml_context(
423 CorrelatedDmlContext {
424 inherited_ctes,
425 services,
426 layer: &layer,
427 target_qualifier: &target_qualifier,
428 source: source_schema.as_ref(),
429 returning_aliases: None,
430 include_excluded: false,
431 ctes: &plan.ctes,
432 ids: &ordinary_subquery_ids,
433 params,
434 },
435 &mut plan.subqueries,
436 )?;
437 let returning_subquery_ids = returning_subquery_ids(&plan.returning);
438 rewrite_correlated_dml_context(
439 CorrelatedDmlContext {
440 inherited_ctes,
441 services,
442 layer: &layer,
443 target_qualifier: &target_qualifier,
444 source: source_schema.as_ref(),
445 returning_aliases: Some(&plan.returning_aliases),
446 include_excluded: false,
447 ctes: &plan.ctes,
448 ids: &returning_subquery_ids,
449 params,
450 },
451 &mut plan.subqueries,
452 )?;
453 let ordinary_scope = ExpressionScope {
454 target_qualifier: &target_qualifier,
455 returning_aliases: None,
456 source: source_schema.as_ref(),
457 include_excluded: false,
458 };
459 if let Some(predicate) = &mut plan.predicate {
460 rewrite_target_expression(
461 services,
462 predicate,
463 &layer,
464 ordinary_scope,
465 &mut plan.subqueries,
466 )?;
467 }
468 let (returning, boundaries) = rewrite_returning(
469 services,
470 plan.returning,
471 &layer,
472 &target_qualifier,
473 &plan.returning_aliases,
474 source_schema.as_ref(),
475 &mut plan.subqueries,
476 )?;
477 plan.returning = returning;
478 if visited.len() == 1 {
479 source_star_boundaries = boundaries;
480 }
481 plan.predicate = combine_view_predicate(
482 services,
483 plan.predicate,
484 &layer,
485 &target_qualifier,
486 &mut plan.subqueries,
487 )?;
488 plan.table = layer.source_name;
489 plan.include_descendants = layer.source_include_descendants;
490 rewrite_suppressed |= layer_suppresses;
491 if !super::context::target_is_view(services, &plan.table)? {
492 break;
493 }
494 }
495 if let Some(source) = source_schema.as_ref() {
496 let target_width = dml_target_width(services, &plan.table)?;
497 if let Some(predicate) = &mut plan.predicate {
498 bind_unqualified_source_positions(predicate, source, target_width);
499 }
500 for projection in &mut plan.returning {
501 bind_unqualified_source_positions(&mut projection.expr, source, target_width);
502 }
503 }
504 plan.returning = finalize_source_returning(
505 services,
506 &plan.table,
507 plan.returning,
508 source_schema.as_ref(),
509 &source_star_boundaries,
510 )?;
511 Ok(plan)
512}