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.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}