1use 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
25const TRIGGER_AFTER: i16 = 0;
27
28fn invalid(message: &str) -> Error {
30 Error::new(SqlState::INVALID_OBJECT_DEFINITION, message)
31}
32
33fn unsupported(message: &str) -> Error {
35 Error::new(SqlState::FEATURE_NOT_SUPPORTED, message)
36}
37
38fn 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
46fn 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
56fn 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
62fn when_base(an: &mut Analyzer<'_>) -> Query {
64 Query { relations: std::mem::take(&mut an.scope.relations), ..Query::default() }
65}
66
67pub 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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
701fn 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}