Skip to main content

uqa_sql/semantics/view_rewrite/
rewrite_update_delete.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7use 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}