Skip to main content

uqa_sql/semantics/view_rewrite/
rewrite_insert.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    duplicate_assignment, insert_conflict_subquery_ids, insert_input_width,
10    instead_of_trigger_definition, not_automatically_updatable, preserve_view_rule_returning,
11    record_view_rule_relation, returning_subquery_ids, rewrite_correlated_dml_context,
12    rewrite_existing_view_checks, rewrite_returning, rewrite_target_expression,
13    validate_direct_view_rule_path, validate_insert_expressions, validate_insert_targets,
14    validate_mapped_columns, validate_public_insert_contract, validate_public_view_targets,
15    view_updatability, writable_column, BTreeSet, ConflictActionPlan, CorrelatedDmlContext,
16    ExpressionScope, InsertPlan, SQLError, TriggerEvent, ViewRewriteContext, ViewRuleInsertPlan,
17};
18
19#[expect(
20    clippy::too_many_lines,
21    reason = "preserves view qualifier and row identity"
22)]
23pub fn rewrite_insert_to_base(
24    services: ViewRewriteContext<'_>,
25    statement: &InsertPlan,
26    params: &[crate::SQLParam],
27    inherited_ctes: Option<&super::CteScope>,
28) -> Result<InsertPlan, SQLError> {
29    validate_public_view_targets(
30        services,
31        &statement.table,
32        statement.columns.iter().map(String::as_str),
33    )?;
34    validate_public_insert_contract(services, statement)?;
35    let Some(initial_layer) = automatic_view_layer(services, &statement.table)? else {
36        return Err(not_automatically_updatable(&statement.table, "INSERT"));
37    };
38    validate_insert_targets(&initial_layer, statement)?;
39    validate_direct_view_rule_path(
40        services,
41        &initial_layer.canonical_name,
42        crate::ast::RuleEvent::Insert,
43        "INSERT",
44    )?;
45    if !view_updatability(services, &statement.table)?
46        .automatic
47        .insertable
48    {
49        return Err(not_automatically_updatable(&statement.table, "INSERT"));
50    }
51    let mut plan = statement.clone();
52    let next_privilege_subject =
53        crate::semantics::view_privileges::ensure_insert(services.authorization, &plan)?;
54    plan.target_privilege_subject = Some(next_privilege_subject);
55    let mut implicit_width = if statement.columns.is_empty() {
56        Some(insert_input_width(
57            services,
58            statement,
59            params,
60            inherited_ctes,
61        )?)
62    } else {
63        None
64    };
65    let mut cascaded = false;
66    let mut visited = BTreeSet::new();
67    let mut rewrite_suppressed = false;
68    loop {
69        let Some(layer) = automatic_view_layer(services, &plan.table)? else {
70            if active_unconditional_instead_rule(
71                services,
72                &plan.table,
73                crate::ast::RuleEvent::Insert,
74            )? {
75                break;
76            }
77            return Err(not_automatically_updatable(&plan.table, "INSERT"));
78        };
79        if !visited.insert(layer.canonical_name.clone()) {
80            return Err(SQLError::Internal(format!(
81                "cycle while rewriting automatically updatable view `{}`",
82                layer.canonical_name
83            )));
84        }
85        if !rewrite_suppressed {
86            validate_direct_view_rule_path(
87                services,
88                &layer.canonical_name,
89                crate::ast::RuleEvent::Insert,
90                "INSERT",
91            )?;
92        }
93        if !rewrite_suppressed
94            && visited.len() > 1
95            && instead_of_trigger_definition(services, &layer.canonical_name, TriggerEvent::Insert)?
96        {
97            return Err(not_automatically_updatable(&layer.canonical_name, "INSERT"));
98        }
99        let has_view_rules = if rewrite_suppressed {
100            false
101        } else {
102            record_view_rule_relation(
103                services,
104                &mut plan.view_rule_relations,
105                &layer,
106                crate::ast::RuleEvent::Insert,
107            )?
108        };
109        let layer_suppresses = has_view_rules
110            && super::context::relation_suppresses_original_query(
111                services,
112                &layer.canonical_name,
113                crate::ast::RuleEvent::Insert,
114            )?;
115        if visited.len() > 1 && !rewrite_suppressed && !layer_suppresses {
116            let next_privilege_subject =
117                crate::semantics::view_privileges::ensure_insert(services.authorization, &plan)?;
118            plan.target_privilege_subject = Some(next_privilege_subject);
119        }
120        if has_view_rules
121            && super::context::relation_has_returning_provider(
122                services,
123                &layer.canonical_name,
124                crate::ast::RuleEvent::Insert,
125            )?
126        {
127            preserve_view_rule_returning(
128                &mut plan.view_rule_returning,
129                &layer.canonical_name,
130                &plan.target_qualifier,
131                &plan.returning,
132                &plan.returning_aliases,
133                &plan.subqueries,
134            );
135        }
136        let target_qualifier = plan.target_qualifier.clone();
137        if visited.len() == 1 {
138            validate_insert_expressions(services, &plan, &layer, params, inherited_ctes)?;
139        }
140        let conflict_subquery_ids = insert_conflict_subquery_ids(&plan);
141        rewrite_correlated_dml_context(
142            CorrelatedDmlContext {
143                inherited_ctes,
144                services,
145                layer: &layer,
146                target_qualifier: &plan.target_qualifier,
147                source: None,
148                returning_aliases: None,
149                include_excluded: true,
150                ctes: &plan.ctes,
151                ids: &conflict_subquery_ids,
152                params,
153            },
154            &mut plan.subqueries,
155        )?;
156        let returning_subquery_ids = returning_subquery_ids(&plan.returning);
157        rewrite_correlated_dml_context(
158            CorrelatedDmlContext {
159                inherited_ctes,
160                services,
161                layer: &layer,
162                target_qualifier: &plan.target_qualifier,
163                source: None,
164                returning_aliases: Some(&plan.returning_aliases),
165                include_excluded: false,
166                ctes: &plan.ctes,
167                ids: &returning_subquery_ids,
168                params,
169            },
170            &mut plan.subqueries,
171        )?;
172        let supplied_columns = if let Some(width) = implicit_width.take() {
173            layer
174                .columns
175                .iter()
176                .take(width)
177                .map(|column| column.name.clone())
178                .collect::<Vec<_>>()
179        } else {
180            plan.columns.clone()
181        };
182        let columns = if rewrite_suppressed || layer_suppresses {
183            supplied_columns.clone()
184        } else {
185            supplied_columns
186                .iter()
187                .map(|column| writable_column(&layer, column, "INSERT"))
188                .collect::<Result<Vec<_>, _>>()?
189        };
190        if has_view_rules {
191            plan.view_rule_insert_plans.push(ViewRuleInsertPlan {
192                relation: layer.canonical_name.clone(),
193                supplied_columns,
194                input_columns: Vec::new(),
195            });
196        }
197        validate_mapped_columns(&columns, duplicate_assignment)?;
198        if let Some(conflict) = &mut plan.on_conflict {
199            for predicate in conflict
200                .expressions
201                .iter_mut()
202                .chain(conflict.predicate.iter_mut().map(Box::as_mut))
203            {
204                rewrite_target_expression(
205                    services,
206                    predicate,
207                    &layer,
208                    ExpressionScope {
209                        target_qualifier: &target_qualifier,
210                        returning_aliases: None,
211                        source: None,
212                        include_excluded: false,
213                    },
214                    &mut plan.subqueries,
215                )?;
216            }
217            conflict.conflict_columns = conflict
218                .conflict_columns
219                .iter()
220                .map(|column| writable_column(&layer, column, "INSERT"))
221                .collect::<Result<Vec<_>, _>>()?;
222            if let ConflictActionPlan::Update {
223                assignments,
224                predicate,
225            } = &mut conflict.action
226            {
227                let scope = ExpressionScope {
228                    target_qualifier: &target_qualifier,
229                    returning_aliases: None,
230                    source: None,
231                    include_excluded: true,
232                };
233                for assignment in assignments.iter_mut() {
234                    assignment.column = writable_column(&layer, &assignment.column, "UPDATE")?;
235                    rewrite_target_expression(
236                        services,
237                        &mut assignment.value,
238                        &layer,
239                        scope,
240                        &mut plan.subqueries,
241                    )?;
242                }
243                let mapped = assignments
244                    .iter()
245                    .map(|assignment| assignment.column.clone())
246                    .collect::<Vec<_>>();
247                validate_mapped_columns(&mapped, duplicate_assignment)?;
248                if let Some(predicate) = predicate {
249                    rewrite_target_expression(
250                        services,
251                        predicate,
252                        &layer,
253                        scope,
254                        &mut plan.subqueries,
255                    )?;
256                }
257            }
258        }
259        rewrite_existing_view_checks(
260            services,
261            &mut plan.view_checks,
262            &layer,
263            &target_qualifier,
264            &mut plan.subqueries,
265        )?;
266        let (returning, _) = rewrite_returning(
267            services,
268            plan.returning,
269            &layer,
270            &target_qualifier,
271            &plan.returning_aliases,
272            None,
273            &mut plan.subqueries,
274        )?;
275        plan.returning = returning;
276        add_check_option(
277            services,
278            &mut plan.view_checks,
279            &layer,
280            &target_qualifier,
281            &mut cascaded,
282            &mut plan.subqueries,
283        )?;
284        plan.columns = columns;
285        plan.table = layer.source_name;
286        plan.include_descendants = true;
287        rewrite_suppressed |= layer_suppresses;
288        if !super::context::target_is_view(services, &plan.table)? {
289            break;
290        }
291    }
292    for insert_plan in &mut plan.view_rule_insert_plans {
293        insert_plan.input_columns.clone_from(&plan.columns);
294    }
295    Ok(plan)
296}