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