Skip to main content

rupg_analyze/ddl/
trigger.rs

1//! `CreateTrigger`: `CREATE TRIGGER`, `DROP TRIGGER`, `ALTER TRIGGER ... RENAME`, `COMMENT ON TRIGGER` and `ALTER TABLE ... ENABLE TRIGGER`.
2//!
3//! The analyzer runs the checks of `CreateTriggerFiringOn` in their order and reads the condition of `WHEN` with the relation of the trigger as `old` and as `new`. The catalog keeps the condition in its stored form, as `tgqual` does, and [`trigger_when`] reads it back for `pg_get_triggerdef`. The executor does not fire the triggers yet, so a write to a table with a trigger that fires gives `0A000`.
4
5use rupg_catalog::{
6    NewTrigger, ObjRef, PG_TRIGGER, RelKind, TRIGGER_BEFORE, TRIGGER_DELETE, TRIGGER_INSERT,
7    TRIGGER_INSTEAD, TRIGGER_ROW, TRIGGER_TRUNCATE, TRIGGER_UPDATE,
8};
9use rupg_common::{Error, Result, SqlState};
10use rupg_pgcatalog::builtin;
11use rupg_sql::nodes::{CreateTrigStmt, DropBehavior, DropStmt, Node, RangeVar, RenameStmt};
12use rupg_types::oid;
13
14use super::comment::{name_list, not_owner, range_var};
15use super::drop::owner_word;
16use super::view::expr_references;
17use super::{Definer, Found, not_yet, relkind_detail, relname};
18use crate::agg::Kind;
19use crate::coerce::AtOpt;
20use crate::expr::{Expr, ExprKind};
21use crate::select::Query;
22use crate::typename::names;
23use crate::{Analyzer, Env, Params};
24
25/// `TRIGGER_TYPE_AFTER`: the timing of `AFTER`, which has no bit.
26const TRIGGER_AFTER: i16 = 0;
27
28/// An error with `ERRCODE_INVALID_OBJECT_DEFINITION`.
29fn invalid(message: &str) -> Error {
30    Error::new(SqlState::INVALID_OBJECT_DEFINITION, message)
31}
32
33/// An error with `ERRCODE_FEATURE_NOT_SUPPORTED`.
34fn unsupported(message: &str) -> Error {
35    Error::new(SqlState::FEATURE_NOT_SUPPORTED, message)
36}
37
38/// The error of `get_trigger_oid` for a trigger that does not exist.
39fn no_trigger(trigger: &str, table: &str) -> Error {
40    Error::new(
41        SqlState::UNDEFINED_OBJECT,
42        format!("trigger \"{trigger}\" for table \"{table}\" does not exist"),
43    )
44}
45
46/// An argument of the function of a trigger as text. The grammar makes each argument a string.
47fn arg_text(node: &Node) -> Result<String> {
48    match node {
49        Node::String(s) => Ok(s.to_string()),
50        Node::Integer(n) => Ok(n.to_string()),
51        Node::Float(f) => Ok(f.to_string()),
52        _ => Err(Error::internal("an argument of a trigger that is not a string")),
53    }
54}
55
56/// The relations `old` and `new` of the condition of `WHEN` for the relation `oid` with the name `name`.
57fn when_scope(an: &mut Analyzer<'_>, oid: u32, name: &str) -> Result<Query> {
58    an.rule_scope(oid, name, [true, true], true)?;
59    Ok(when_base(an))
60}
61
62/// The query that has the relations of the scope of [`when_scope`]. The scope keeps no relations after it.
63fn when_base(an: &mut Analyzer<'_>) -> Query {
64    Query { relations: std::mem::take(&mut an.scope.relations), ..Query::default() }
65}
66
67/// The condition of `WHEN` of the trigger of the catalog with the OID, with the query that has `old` and `new` as its relations, or `None` when the catalog has no such trigger or the trigger has no condition. `pg_get_triggerdef` shows the condition.
68pub fn trigger_when(env: &dyn Env, oid: u32) -> Result<Option<(Query, Expr)>> {
69    let Some(catalog) = env.catalog() else { return Ok(None) };
70    let Some(trigger) = catalog.trigger(oid) else { return Ok(None) };
71    let Some(qual) = &trigger.qual else { return Ok(None) };
72    let name = catalog.relation(trigger.relation).map_or("", |r| r.name.as_str());
73    let mut an = Analyzer::new(env, &Params::default());
74    let base = when_scope(&mut an, trigger.relation, name)?;
75    Ok(Some((base, crate::node::read(qual)?)))
76}
77
78impl Definer<'_, '_> {
79    /// `CreateTriggerFiringOn` for a trigger of `CREATE TRIGGER`.
80    pub(super) fn create_trigger(&mut self, stmt: &CreateTrigStmt) -> Result<()> {
81        let rv = stmt.relation.as_deref().cloned().unwrap_or_default();
82        let found = self.lookup_relation(&rv)?;
83        let name = relname(&rv).to_string();
84        let code = self.kind_of(found);
85        let timed = matches!(stmt.timing, TRIGGER_BEFORE | TRIGGER_AFTER);
86        let wrong = |what: &str, detail: &str| {
87            Error::new(SqlState::WRONG_OBJECT_TYPE, format!("\"{name}\" is a {what}"))
88                .with_detail(detail.to_string())
89        };
90        match code {
91            b'r' | b'p' if !timed => {
92                return Err(wrong("table", "Tables cannot have INSTEAD OF triggers."));
93            }
94            b'p' if stmt.row && !stmt.transitionRels.is_empty() => {
95                return Err(Error::new(
96                    SqlState::FEATURE_NOT_SUPPORTED,
97                    format!("\"{name}\" is a partitioned table"),
98                )
99                .with_detail(
100                    "ROW triggers with transition tables are not supported on partitioned tables.",
101                ));
102            }
103            b'r' | b'p' => {}
104            b'v' if stmt.timing != TRIGGER_INSTEAD && stmt.row => {
105                return Err(wrong("view", "Views cannot have row-level BEFORE or AFTER triggers."));
106            }
107            b'v' if stmt.events & TRIGGER_TRUNCATE != 0 => {
108                return Err(wrong("view", "Views cannot have TRUNCATE triggers."));
109            }
110            b'v' => {}
111            b'f' if !timed => {
112                return Err(wrong(
113                    "foreign table",
114                    "Foreign tables cannot have INSTEAD OF triggers.",
115                ));
116            }
117            // A constraint trigger on a foreign table could defer a check of a foreign key.
118            b'f' if stmt.isconstraint => {
119                return Err(wrong(
120                    "foreign table",
121                    "Foreign tables cannot have constraint triggers.",
122                ));
123            }
124            b'f' => {}
125            _ => {
126                return Err(Error::new(
127                    SqlState::WRONG_OBJECT_TYPE,
128                    format!("relation \"{name}\" cannot have triggers"),
129                )
130                .with_detail(relkind_detail(code)));
131            }
132        }
133        if self.is_system(found) && !self.an.env.allow_system_table_mods() {
134            return Err(Error::new(
135                SqlState::INSUFFICIENT_PRIVILEGE,
136                format!("permission denied: \"{name}\" is a system catalog"),
137            ));
138        }
139        let Found::User(oid) = found else {
140            return Err(not_yet("a trigger on a system catalog", None));
141        };
142        let constraint_relation = match stmt.constrrel.as_deref().filter(|_| stmt.isconstraint) {
143            Some(rv) => match self.lookup_relation(rv)? {
144                Found::User(oid) => oid,
145                Found::Builtin(row) => row.oid,
146            },
147            None => 0,
148        };
149        // rupg has no `GRANT`, so the privilege `TRIGGER` is the privilege of the owner.
150        let owner = self.catalog.relation(oid).map_or(0, |r| r.owner);
151        if !self.is_superuser() && owner != self.user {
152            return Err(Error::new(
153                SqlState::INSUFFICIENT_PRIVILEGE,
154                format!("permission denied for {} {name}", owner_word(code)),
155            ));
156        }
157        let kind = if stmt.row { TRIGGER_ROW } else { 0 } | stmt.timing | stmt.events;
158        let row = kind & TRIGGER_ROW != 0;
159        if row && kind & TRIGGER_TRUNCATE != 0 {
160            return Err(unsupported("TRUNCATE FOR EACH ROW triggers are not supported"));
161        }
162        if kind & TRIGGER_INSTEAD != 0 {
163            if !row {
164                return Err(unsupported("INSTEAD OF triggers must be FOR EACH ROW"));
165            }
166            if stmt.whenClause.is_some() {
167                return Err(unsupported("INSTEAD OF triggers cannot have WHEN conditions"));
168            }
169            if !stmt.columns.is_empty() {
170                return Err(unsupported("INSTEAD OF triggers cannot have column lists"));
171            }
172        }
173        let partition = self.catalog.relation(oid).is_some_and(|r| r.partition_of.is_some());
174        if row && partition && !stmt.transitionRels.is_empty() {
175            return Err(unsupported(
176                "ROW triggers with transition tables are not supported on partitions",
177            ));
178        }
179        let (old_table, new_table) = self.transition_tables(stmt, kind, code, &name)?;
180        let mut qual = None;
181        let mut qual_refs = Vec::new();
182        if let Some(node) = &stmt.whenClause {
183            self.an.rule_scope(oid, &name, [true, true], true)?;
184            let expr = self.an.with_kind(Kind::TriggerWhen, |a| a.transform(Some(node)))?;
185            let expr = self.an.coerce_to_boolean(expr, "WHEN")?;
186            let base = when_base(&mut self.an);
187            let generated: Vec<(String, bool)> = self
188                .catalog
189                .relation(oid)
190                .map(|r| r.columns.iter().map(|c| (c.name.clone(), c.generated != 0)).collect())
191                .unwrap_or_default();
192            check_when_vars(&expr, kind, &generated)?;
193            expr_references(self.catalog, &expr, &[&base], &mut qual_refs);
194            qual = Some(crate::node::write(&expr)?);
195        }
196        let function = self.trigger_function(stmt)?;
197        let trigname = stmt.trigname.as_deref().unwrap_or_default();
198        if let Some(existing) = self.catalog.trigger_by_name(oid, trigname) {
199            if !stmt.replace {
200                return Err(Error::new(
201                    SqlState::DUPLICATE_OBJECT,
202                    format!("trigger \"{trigname}\" for relation \"{name}\" already exists"),
203                ));
204            }
205            if existing.internal || existing.parent != 0 {
206                return Err(Error::new(
207                    SqlState::DUPLICATE_OBJECT,
208                    format!(
209                        "trigger \"{trigname}\" for relation \"{name}\" is an internal or a child trigger"
210                    ),
211                ));
212            }
213            // The pending events of a constraint trigger and the facts of its constraint make a replace hard.
214            if existing.constraint != 0 {
215                return Err(Error::new(
216                    SqlState::DUPLICATE_OBJECT,
217                    format!(
218                        "trigger \"{trigname}\" for relation \"{name}\" is a constraint trigger"
219                    ),
220                ));
221            }
222        }
223        let args = stmt.args.iter().flatten().map(arg_text).collect::<Result<Vec<_>>>()?;
224        if args.len() > usize::from(i16::MAX.unsigned_abs()) {
225            return Err(Error::new(
226                SqlState::TOO_MANY_ARGUMENTS,
227                format!("triggers cannot have more than {} arguments", i16::MAX),
228            ));
229        }
230        let columns = self.trigger_columns(oid, &name, &names(&stmt.columns))?;
231        let new = NewTrigger {
232            name: trigname.to_string(),
233            relation: oid,
234            function,
235            kind,
236            args,
237            columns,
238            old_table,
239            new_table,
240            qual,
241            constraint: stmt.isconstraint,
242            constraint_relation,
243            deferrable: stmt.deferrable,
244            deferred: stmt.initdeferred,
245            parent: 0,
246            enabled: 0,
247        };
248        let trigger = self.catalog.create_trigger(new, stmt.replace, &qual_refs)?;
249        if code == b'p' && row {
250            let children: Vec<u32> = self.catalog.partitions(oid).iter().map(|r| r.oid).collect();
251            for child in children {
252                self.clone_trigger(trigger, child, stmt.replace)?;
253            }
254        }
255        Ok(())
256    }
257
258    /// The part of `CreateTriggerFiringOn` that clones a row trigger of a partitioned table to a partition, and on to the partitions of a partitioned partition. The clone has the columns of `UPDATE OF` by name and the condition of `WHEN` with the numbers of the columns of the partition, as `map_partition_varattnos` gives them, but no transition tables.
259    ///
260    /// # Errors
261    ///
262    /// `42710` when the partition has a trigger with the name and `replace` is false.
263    pub(super) fn clone_trigger(&mut self, parent: u32, child: u32, replace: bool) -> Result<()> {
264        let Some(from) = self.catalog.trigger(parent).cloned() else { return Ok(()) };
265        let columns = self.cloned_columns(from.relation, child, &from.columns);
266        let map = self.att_map(from.relation, child)?;
267        let qual = from.qual.as_deref().map(|text| map.text(text)).transpose()?;
268        let new = NewTrigger {
269            name: from.name.clone(),
270            relation: child,
271            function: from.function,
272            kind: from.kind,
273            args: from.args.clone(),
274            columns,
275            old_table: None,
276            new_table: None,
277            qual,
278            constraint: from.constraint != 0,
279            constraint_relation: from.constraint_relation,
280            deferrable: from.deferrable,
281            deferred: from.deferred,
282            parent,
283            enabled: from.enabled,
284        };
285        let trigger = self.catalog.create_trigger(new, replace, &[])?;
286        if self.catalog.relation(child).is_some_and(|r| r.kind == RelKind::Partitioned) {
287            let children: Vec<u32> = self.catalog.partitions(child).iter().map(|r| r.oid).collect();
288            for grandchild in children {
289                self.clone_trigger(trigger, grandchild, replace)?;
290            }
291        }
292        Ok(())
293    }
294
295    /// `CloneRowTriggersToPartition`: clones each row trigger of `parent` that is not internal to the new partition `child`.
296    pub(super) fn clone_triggers(&mut self, parent: u32, child: u32) -> Result<()> {
297        let mut triggers: Vec<(String, u32)> = self
298            .catalog
299            .triggers_of(parent)
300            .into_iter()
301            .filter(|t| t.kind & TRIGGER_ROW != 0 && !t.internal)
302            .map(|t| (t.name.clone(), t.oid))
303            .collect();
304        triggers.sort();
305        for (_, trigger) in triggers {
306            self.clone_trigger(trigger, child, false)?;
307        }
308        Ok(())
309    }
310
311    /// `DropClonedTriggersFromPartition`: drops the clones in a partition that `DETACH PARTITION` leaves, and with them the clones in its partitions.
312    pub(super) fn drop_cloned_triggers(&mut self, partition: u32) -> Result<()> {
313        let clones: Vec<u32> = self
314            .catalog
315            .triggers_of(partition)
316            .into_iter()
317            .filter(|t| t.parent != 0 && t.constraint_relation == 0)
318            .map(|t| t.oid)
319            .collect();
320        for &trigger in &clones {
321            self.catalog.clear_trigger_parent(trigger);
322        }
323        let objects: Vec<ObjRef> = clones.iter().map(|&t| ObjRef::new(PG_TRIGGER, t)).collect();
324        self.perform(&objects, false)
325    }
326
327    /// The attribute numbers in `child` of the columns of `UPDATE OF` in `parent`, matched by name.
328    fn cloned_columns(&self, parent: u32, child: u32, columns: &[i16]) -> Vec<i16> {
329        let (Some(parent), Some(child)) =
330            (self.catalog.relation(parent), self.catalog.relation(child))
331        else {
332            return Vec::new();
333        };
334        columns.iter().filter_map(|&num| child.column_number(&parent.column(num)?.name)).collect()
335    }
336
337    /// The checks of `REFERENCING` of `CreateTriggerFiringOn`: the names of the transition tables `OLD TABLE` and `NEW TABLE`.
338    fn transition_tables(
339        &self,
340        stmt: &CreateTrigStmt,
341        kind: i16,
342        code: u8,
343        name: &str,
344    ) -> Result<(Option<String>, Option<String>)> {
345        let (mut old, mut new) = (None, None);
346        for node in stmt.transitionRels.iter().flatten() {
347            let Node::TriggerTransition(tt) = node else { continue };
348            if !tt.isTable {
349                return Err(unsupported(
350                    "ROW variable naming in the REFERENCING clause is not supported",
351                )
352                .with_hint("Use OLD TABLE or NEW TABLE for naming transition tables."));
353            }
354            if code == b'f' {
355                return Err(Error::new(
356                    SqlState::WRONG_OBJECT_TYPE,
357                    format!("\"{name}\" is a foreign table"),
358                )
359                .with_detail("Triggers on foreign tables cannot have transition tables."));
360            }
361            if code == b'v' {
362                return Err(Error::new(
363                    SqlState::WRONG_OBJECT_TYPE,
364                    format!("\"{name}\" is a view"),
365                )
366                .with_detail("Triggers on views cannot have transition tables."));
367            }
368            if stmt.timing != TRIGGER_AFTER {
369                return Err(invalid(
370                    "transition table name can only be specified for an AFTER trigger",
371                ));
372            }
373            if kind & TRIGGER_TRUNCATE != 0 {
374                return Err(unsupported(
375                    "TRUNCATE triggers with transition tables are not supported",
376                ));
377            }
378            let events = [TRIGGER_INSERT, TRIGGER_UPDATE, TRIGGER_DELETE];
379            if events.iter().filter(|&&e| kind & e != 0).count() != 1 {
380                return Err(unsupported(
381                    "transition tables cannot be specified for triggers with more than one event",
382                ));
383            }
384            if !stmt.columns.is_empty() {
385                return Err(unsupported(
386                    "transition tables cannot be specified for triggers with column lists",
387                ));
388            }
389            let table = tt.name.as_deref().unwrap_or_default().to_string();
390            if tt.isNew {
391                if kind & (TRIGGER_INSERT | TRIGGER_UPDATE) == 0 {
392                    return Err(invalid(
393                        "NEW TABLE can only be specified for an INSERT or UPDATE trigger",
394                    ));
395                }
396                if new.is_some() {
397                    return Err(invalid("NEW TABLE cannot be specified multiple times"));
398                }
399                new = Some(table);
400            } else {
401                if kind & (TRIGGER_DELETE | TRIGGER_UPDATE) == 0 {
402                    return Err(invalid(
403                        "OLD TABLE can only be specified for a DELETE or UPDATE trigger",
404                    ));
405                }
406                if old.is_some() {
407                    return Err(invalid("OLD TABLE cannot be specified multiple times"));
408                }
409                old = Some(table);
410            }
411        }
412        if old.is_some() && old == new {
413            return Err(invalid("OLD TABLE name and NEW TABLE name cannot be the same"));
414        }
415        Ok((old, new))
416    }
417
418    /// `LookupFuncName` with no arguments for the function of a trigger, which must give the type `trigger`.
419    fn trigger_function(&self, stmt: &CreateTrigStmt) -> Result<u32> {
420        let list = names(&stmt.funcname);
421        let (schema, name) = self.an.split_name(&list, None)?;
422        let schemas = match schema {
423            Some(namespace) => vec![namespace],
424            None => self.an.path.clone(),
425        };
426        let found = schemas.iter().find_map(|&namespace| {
427            let user = self
428                .catalog
429                .functions()
430                .find(|f| f.namespace == namespace && f.name == name && f.argtypes.is_empty())
431                .map(|f| (f.oid, f.rettype));
432            user.or_else(|| {
433                builtin::procs_named(name)
434                    .find(|p| p.namespace == namespace && p.argtypes.is_empty())
435                    .map(|p| (p.oid, p.rettype))
436            })
437        });
438        let Some((function, rettype)) = found else {
439            return Err(Error::new(
440                SqlState::UNDEFINED_FUNCTION,
441                format!("function {}() does not exist", list.join(".")),
442            ));
443        };
444        if rettype != oid::TRIGGER {
445            return Err(invalid(&format!("function {} must return type trigger", list.join("."))));
446        }
447        Ok(function)
448    }
449
450    /// The numbers of the columns of `UPDATE OF`, as `attnameAttNum` finds them. A system column or a dropped column is not a column for this list.
451    fn trigger_columns(&self, oid: u32, name: &str, list: &[&str]) -> Result<Vec<i16>> {
452        let rel = self.catalog.relation(oid);
453        let mut columns: Vec<i16> = Vec::with_capacity(list.len());
454        for &column in list {
455            let number =
456                rel.and_then(|r| r.columns.iter().position(|c| !c.dropped && c.name == column));
457            let Some(number) = number.and_then(|n| i16::try_from(n + 1).ok()) else {
458                return Err(Error::new(
459                    SqlState::UNDEFINED_COLUMN,
460                    format!("column \"{column}\" of relation \"{name}\" does not exist"),
461                ));
462            };
463            if columns.contains(&number) {
464                return Err(Error::new(
465                    SqlState::DUPLICATE_COLUMN,
466                    format!("column \"{column}\" specified more than once"),
467                ));
468            }
469            columns.push(number);
470        }
471        Ok(columns)
472    }
473
474    /// The relation of `DROP TRIGGER`, `ALTER TRIGGER` or `COMMENT ON TRIGGER` and the trigger with the name, or `None` with `missing_ok` when one of them does not exist. The notice of `does_not_exist_skipping` names the relation as the statement gives it.
475    fn find_trigger(
476        &mut self,
477        rv: &RangeVar,
478        trigger: &str,
479        missing_ok: bool,
480    ) -> Result<Option<(u32, u32)>> {
481        let found = match self.lookup_relation(rv) {
482            Ok(found) => found,
483            Err(error)
484                if missing_ok
485                    && matches!(
486                        error.state(),
487                        SqlState::UNDEFINED_TABLE | SqlState::UNDEFINED_SCHEMA
488                    ) =>
489            {
490                let message = error.message().to_string();
491                self.notice(SqlState::SUCCESSFUL_COMPLETION, format!("{message}, skipping"));
492                return Ok(None);
493            }
494            Err(error) => return Err(error),
495        };
496        let name = relname(rv);
497        if matches!(self.kind_of(found), b'i' | b'I') {
498            return Err(Error::new(SqlState::WRONG_OBJECT_TYPE, format!("\"{name}\" is an index")));
499        }
500        let Found::User(relation) = found else {
501            if missing_ok {
502                return Ok(None);
503            }
504            return Err(no_trigger(trigger, name));
505        };
506        match self.catalog.trigger_by_name(relation, trigger) {
507            Some(t) => Ok(Some((relation, t.oid))),
508            None if missing_ok => {
509                let shown = [rv.schemaname.as_deref(), Some(name)]
510                    .into_iter()
511                    .flatten()
512                    .collect::<Vec<_>>()
513                    .join(".");
514                self.notice(
515                    SqlState::SUCCESSFUL_COMPLETION,
516                    format!(
517                        "trigger \"{trigger}\" for relation \"{shown}\" does not exist, skipping"
518                    ),
519                );
520                Ok(None)
521            }
522            None => Err(no_trigger(trigger, name)),
523        }
524    }
525
526    /// The check of `check_object_ownership` for a trigger: the role must own its relation.
527    fn check_trigger_owner(&self, relation: u32) -> Result<()> {
528        let rel = self.catalog.relation(relation);
529        let owner = rel.map_or(0, |r| r.owner);
530        if !self.is_superuser() && owner != self.user {
531            return Err(not_owner("relation", rel.map_or("", |r| r.name.as_str())));
532        }
533        Ok(())
534    }
535
536    /// `RemoveObjects` for `DROP TRIGGER`.
537    pub(super) fn drop_trigger(&mut self, stmt: &DropStmt) -> Result<()> {
538        let mut objects = Vec::new();
539        for item in stmt.objects.iter().flatten() {
540            let list = name_list(Some(item));
541            let Some((&trigger, parent)) = list.split_last() else { continue };
542            let rv = range_var(parent)?;
543            let Some((relation, oid)) = self.find_trigger(&rv, trigger, stmt.missing_ok)? else {
544                continue;
545            };
546            self.check_trigger_owner(relation)?;
547            objects.push(ObjRef::new(PG_TRIGGER, oid));
548        }
549        self.perform(&objects, stmt.behavior == DropBehavior::DROP_CASCADE)
550    }
551
552    /// `get_object_address_relobject` for `COMMENT ON TRIGGER`.
553    pub(super) fn comment_trigger(&mut self, object: Option<&Node>) -> Result<ObjRef> {
554        let list = name_list(object);
555        let Some((&trigger, parent)) = list.split_last().filter(|(_, r)| !r.is_empty()) else {
556            return Err(Error::new(
557                SqlState::SYNTAX_ERROR,
558                "must specify relation and object name",
559            ));
560        };
561        let rv = range_var(parent)?;
562        let Some((relation, oid)) = self.find_trigger(&rv, trigger, false)? else {
563            return Err(no_trigger(trigger, relname(&rv)));
564        };
565        self.check_trigger_owner(relation)?;
566        Ok(ObjRef::new(PG_TRIGGER, oid))
567    }
568
569    /// `renametrig` with the checks of `RangeVarCallbackForRenameTrigger`.
570    pub(super) fn rename_trigger(&mut self, stmt: &RenameStmt, newname: &str) -> Result<()> {
571        let rv = stmt.relation.as_deref().cloned().unwrap_or_default();
572        let found = self.lookup_relation(&rv)?;
573        let name = relname(&rv).to_string();
574        let code = self.kind_of(found);
575        if !matches!(code, b'r' | b'v' | b'f' | b'p') {
576            return Err(Error::new(
577                SqlState::WRONG_OBJECT_TYPE,
578                format!("relation \"{name}\" cannot have triggers"),
579            )
580            .with_detail(relkind_detail(code)));
581        }
582        let owner = match found {
583            Found::User(oid) => self.catalog.relation(oid).map_or(0, |r| r.owner),
584            Found::Builtin(row) => row.owner,
585        };
586        if !self.is_superuser() && owner != self.user {
587            return Err(not_owner(owner_word(code), &name));
588        }
589        if self.is_system(found) && !self.an.env.allow_system_table_mods() {
590            return Err(Error::new(
591                SqlState::INSUFFICIENT_PRIVILEGE,
592                format!("permission denied: \"{name}\" is a system catalog"),
593            ));
594        }
595        let subname = stmt.subname.as_deref().unwrap_or_default();
596        let Found::User(relation) = found else { return Err(no_trigger(subname, &name)) };
597        let Some((oid, parent)) =
598            self.catalog.trigger_by_name(relation, subname).map(|t| (t.oid, t.parent))
599        else {
600            return Err(no_trigger(subname, &name));
601        };
602        if parent != 0 {
603            let table = self.catalog.relation(relation).and_then(|r| r.partition_of.as_ref());
604            let table = table.and_then(|p| self.catalog.relation(p.parent));
605            return Err(unsupported(&format!(
606                "cannot rename trigger \"{subname}\" on table \"{name}\""
607            ))
608            .with_hint(format!(
609                "Rename the trigger on the partitioned table \"{}\" instead.",
610                table.map_or("", |r| r.name.as_str())
611            )));
612        }
613        self.rename_trigger_tree(oid, newname)
614    }
615
616    /// `renametrig_internal` for a trigger, then `renametrig_partition` for its clones in the partitions.
617    fn rename_trigger_tree(&mut self, oid: u32, newname: &str) -> Result<()> {
618        if self.catalog.trigger(oid).is_some_and(|t| t.name != newname) {
619            self.catalog.rename_trigger(oid, newname)?;
620        }
621        for child in self.clones_of(oid) {
622            self.rename_trigger_tree(child, newname)?;
623        }
624        Ok(())
625    }
626
627    /// The triggers of the partitions that are clones of the trigger.
628    fn clones_of(&self, oid: u32) -> Vec<u32> {
629        let mut clones: Vec<(u32, u32)> = self
630            .catalog
631            .triggers()
632            .filter(|t| t.parent == oid)
633            .map(|t| (t.relation, t.oid))
634            .collect();
635        clones.sort_unstable();
636        clones.into_iter().map(|(_, t)| t).collect()
637    }
638
639    /// `EnableDisableTrigger`: sets `tgenabled` of the trigger with the name, or of all the triggers of the table with no name. With `skip_system`, the internal triggers keep their state; without it, only a superuser can change an internal trigger. With `recurse`, a row trigger of a partitioned table gives its state to its clones.
640    pub(super) fn enable_trigger(
641        &mut self,
642        table: u32,
643        trigger: Option<&str>,
644        (enabled, skip_system): (u8, bool),
645        recurse: bool,
646    ) -> Result<()> {
647        let partitioned =
648            self.catalog.relation(table).is_some_and(|r| r.kind == RelKind::Partitioned);
649        let list: Vec<(u32, bool, String)> = self
650            .catalog
651            .triggers_of(table)
652            .into_iter()
653            .filter(|t| trigger.is_none_or(|n| t.name == n))
654            .map(|t| (t.oid, t.internal, t.name.clone()))
655            .collect();
656        let mut found = false;
657        for (oid, internal, name) in list {
658            if internal {
659                if skip_system {
660                    continue;
661                }
662                if !self.is_superuser() {
663                    return Err(Error::new(
664                        SqlState::INSUFFICIENT_PRIVILEGE,
665                        format!("permission denied: \"{name}\" is a system trigger"),
666                    ));
667                }
668            }
669            found = true;
670            self.catalog.set_trigger_enabled(oid, enabled);
671            let row = self.catalog.trigger(oid).is_some_and(|t| t.kind & TRIGGER_ROW != 0);
672            if recurse && partitioned && row {
673                self.enable_clones(oid, (enabled, skip_system))?;
674            }
675        }
676        if let Some(trigger) = trigger
677            && !found
678        {
679            let name = self.catalog.relation(table).map_or("", |r| r.name.as_str());
680            return Err(no_trigger(trigger, name));
681        }
682        Ok(())
683    }
684}
685
686impl Definer<'_, '_> {
687    /// The recursion of `EnableDisableTrigger` into the partitions, which finds the clones by `tgparentid` and not by name.
688    fn enable_clones(&mut self, parent: u32, (enabled, skip_system): (u8, bool)) -> Result<()> {
689        for child in self.clones_of(parent) {
690            let Some(trigger) = self.catalog.trigger(child) else { continue };
691            if trigger.internal && skip_system {
692                continue;
693            }
694            self.catalog.set_trigger_enabled(child, enabled);
695            self.enable_clones(child, (enabled, skip_system))?;
696        }
697        Ok(())
698    }
699}
700
701/// The checks of `CreateTriggerFiringOn` for the columns that the condition of `WHEN` reads. The relation 0 is `old` and the relation 1 is `new`. `columns` has the name of each column of the table and true for a generated column.
702fn check_when_vars(expr: &Expr, kind: i16, columns: &[(String, bool)]) -> Result<()> {
703    let row = kind & TRIGGER_ROW != 0;
704    let before = kind & TRIGGER_BEFORE != 0;
705    let has_generated = columns.iter().any(|(_, generated)| *generated);
706    let error = expr.find(0, &mut |e, depth| {
707        let ExprKind::Var(var) = &e.kind else { return None };
708        if var.levels_up != depth {
709            return None;
710        }
711        let at = e.location;
712        let message = match var.relation {
713            _ if !row => {
714                Some(invalid("statement trigger's WHEN condition cannot reference column values"))
715            }
716            0 if kind & TRIGGER_INSERT != 0 => {
717                Some(invalid("INSERT trigger's WHEN condition cannot reference OLD values"))
718            }
719            1 if kind & TRIGGER_DELETE != 0 => {
720                Some(invalid("DELETE trigger's WHEN condition cannot reference NEW values"))
721            }
722            1 if var.attnum < 0 && before => Some(unsupported(
723                "BEFORE trigger's WHEN condition cannot reference NEW system columns",
724            )),
725            1 if var.attnum == 0 && before && has_generated => Some(
726                invalid("BEFORE trigger's WHEN condition cannot reference NEW generated columns")
727                    .with_detail(
728                        "A whole-row reference is used and the table contains generated columns.",
729                    ),
730            ),
731            1 if before => {
732                let column = usize::try_from(var.attnum - 1).ok().and_then(|i| columns.get(i));
733                column.filter(|(_, generated)| *generated).map(|(name, _)| {
734                    invalid(
735                        "BEFORE trigger's WHEN condition cannot reference NEW generated columns",
736                    )
737                    .with_detail(format!("Column \"{name}\" is a generated column."))
738                })
739            }
740            _ => None,
741        };
742        message.map(|m| m.at_opt(at))
743    });
744    match error {
745        Some(error) => Err(error),
746        None => Ok(()),
747    }
748}