mirror of
https://github.com/postgres/postgres.git
synced 2025-07-28 23:42:10 +03:00
Avoid failure when altering state of partitioned foreign-key triggers.
Beginning in v15, if you apply ALTER TABLE ENABLE/DISABLE TRIGGER to a partitioned table, it also affects the partitions' cloned versions of the affected trigger(s). The initial implementation of this located the clones by name, but that fails on foreign-key triggers which have names incorporating their own OIDs. We can fix that, and also make the behavior more bulletproof in the face of user-initiated trigger renames, by identifying the cloned triggers by tgparentid. Following the lead of earlier commits in this area, I took care not to break ABI in the v15 branch, even though I rather doubt there are any external callers of EnableDisableTrigger. While here, update the documentation, which was not touched when the semantics were changed. Per bug #17817 from Alan Hodgson. Back-patch to v15; older versions do not have this behavior. Discussion: https://postgr.es/m/17817-31dfb7c2100d9f3d@postgresql.org
This commit is contained in:
@ -14768,8 +14768,9 @@ ATExecEnableDisableTrigger(Relation rel, const char *trigname,
|
||||
char fires_when, bool skip_system, bool recurse,
|
||||
LOCKMODE lockmode)
|
||||
{
|
||||
EnableDisableTriggerNew(rel, trigname, fires_when, skip_system, recurse,
|
||||
lockmode);
|
||||
EnableDisableTriggerNew2(rel, trigname, InvalidOid,
|
||||
fires_when, skip_system, recurse,
|
||||
lockmode);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -1754,7 +1754,8 @@ renametrig_partition(Relation tgrel, Oid partitionId, Oid parentTriggerOid,
|
||||
* to change 'tgenabled' field for the specified trigger(s)
|
||||
*
|
||||
* rel: relation to process (caller must hold suitable lock on it)
|
||||
* tgname: trigger to process, or NULL to scan all triggers
|
||||
* tgname: name of trigger to process, or NULL to scan all triggers
|
||||
* tgparent: if not zero, process only triggers with this tgparentid
|
||||
* fires_when: new value for tgenabled field. In addition to generic
|
||||
* enablement/disablement, this also defines when the trigger
|
||||
* should be fired in session replication roles.
|
||||
@ -1766,9 +1767,9 @@ renametrig_partition(Relation tgrel, Oid partitionId, Oid parentTriggerOid,
|
||||
* system triggers
|
||||
*/
|
||||
void
|
||||
EnableDisableTriggerNew(Relation rel, const char *tgname,
|
||||
char fires_when, bool skip_system, bool recurse,
|
||||
LOCKMODE lockmode)
|
||||
EnableDisableTriggerNew2(Relation rel, const char *tgname, Oid tgparent,
|
||||
char fires_when, bool skip_system, bool recurse,
|
||||
LOCKMODE lockmode)
|
||||
{
|
||||
Relation tgrel;
|
||||
int nkeys;
|
||||
@ -1805,6 +1806,9 @@ EnableDisableTriggerNew(Relation rel, const char *tgname,
|
||||
{
|
||||
Form_pg_trigger oldtrig = (Form_pg_trigger) GETSTRUCT(tuple);
|
||||
|
||||
if (OidIsValid(tgparent) && tgparent != oldtrig->tgparentid)
|
||||
continue;
|
||||
|
||||
if (oldtrig->tgisinternal)
|
||||
{
|
||||
/* system trigger ... ok to process? */
|
||||
@ -1855,9 +1859,10 @@ EnableDisableTriggerNew(Relation rel, const char *tgname,
|
||||
Relation part;
|
||||
|
||||
part = relation_open(partdesc->oids[i], lockmode);
|
||||
EnableDisableTriggerNew(part, NameStr(oldtrig->tgname),
|
||||
fires_when, skip_system, recurse,
|
||||
lockmode);
|
||||
/* Match on child triggers' tgparentid, not their name */
|
||||
EnableDisableTriggerNew2(part, NULL, oldtrig->oid,
|
||||
fires_when, skip_system, recurse,
|
||||
lockmode);
|
||||
table_close(part, NoLock); /* keep lock till commit */
|
||||
}
|
||||
}
|
||||
@ -1886,16 +1891,27 @@ EnableDisableTriggerNew(Relation rel, const char *tgname,
|
||||
}
|
||||
|
||||
/*
|
||||
* ABI-compatible wrapper for the above. To keep as close possible to the old
|
||||
* behavior, this never recurses. Do not call this function in new code.
|
||||
* ABI-compatible wrappers to emulate old versions of the above function.
|
||||
* Do not call these versions in new code.
|
||||
*/
|
||||
void
|
||||
EnableDisableTriggerNew(Relation rel, const char *tgname,
|
||||
char fires_when, bool skip_system, bool recurse,
|
||||
LOCKMODE lockmode)
|
||||
{
|
||||
EnableDisableTriggerNew2(rel, tgname, InvalidOid,
|
||||
fires_when, skip_system,
|
||||
recurse, lockmode);
|
||||
}
|
||||
|
||||
void
|
||||
EnableDisableTrigger(Relation rel, const char *tgname,
|
||||
char fires_when, bool skip_system,
|
||||
LOCKMODE lockmode)
|
||||
{
|
||||
EnableDisableTriggerNew(rel, tgname, fires_when, skip_system,
|
||||
true, lockmode);
|
||||
EnableDisableTriggerNew2(rel, tgname, InvalidOid,
|
||||
fires_when, skip_system,
|
||||
true, lockmode);
|
||||
}
|
||||
|
||||
|
||||
|
@ -170,6 +170,9 @@ extern Oid get_trigger_oid(Oid relid, const char *name, bool missing_ok);
|
||||
|
||||
extern ObjectAddress renametrig(RenameStmt *stmt);
|
||||
|
||||
extern void EnableDisableTriggerNew2(Relation rel, const char *tgname, Oid tgparent,
|
||||
char fires_when, bool skip_system, bool recurse,
|
||||
LOCKMODE lockmode);
|
||||
extern void EnableDisableTriggerNew(Relation rel, const char *tgname,
|
||||
char fires_when, bool skip_system, bool recurse,
|
||||
LOCKMODE lockmode);
|
||||
|
@ -2718,6 +2718,40 @@ select tgrelid::regclass, tgname, tgenabled from pg_trigger
|
||||
parent | tg_stmt | A
|
||||
(3 rows)
|
||||
|
||||
drop table parent, child1;
|
||||
-- Check processing of foreign key triggers
|
||||
create table parent (a int primary key, f int references parent)
|
||||
partition by list (a);
|
||||
create table child1 partition of parent for values in (1);
|
||||
select tgrelid::regclass, rtrim(tgname, '0123456789') as tgname,
|
||||
tgfoid::regproc, tgenabled
|
||||
from pg_trigger where tgrelid in ('parent'::regclass, 'child1'::regclass)
|
||||
order by tgrelid::regclass::text, tgfoid;
|
||||
tgrelid | tgname | tgfoid | tgenabled
|
||||
---------+-------------------------+------------------------+-----------
|
||||
child1 | RI_ConstraintTrigger_c_ | "RI_FKey_check_ins" | O
|
||||
child1 | RI_ConstraintTrigger_c_ | "RI_FKey_check_upd" | O
|
||||
parent | RI_ConstraintTrigger_c_ | "RI_FKey_check_ins" | O
|
||||
parent | RI_ConstraintTrigger_c_ | "RI_FKey_check_upd" | O
|
||||
parent | RI_ConstraintTrigger_a_ | "RI_FKey_noaction_del" | O
|
||||
parent | RI_ConstraintTrigger_a_ | "RI_FKey_noaction_upd" | O
|
||||
(6 rows)
|
||||
|
||||
alter table parent disable trigger all;
|
||||
select tgrelid::regclass, rtrim(tgname, '0123456789') as tgname,
|
||||
tgfoid::regproc, tgenabled
|
||||
from pg_trigger where tgrelid in ('parent'::regclass, 'child1'::regclass)
|
||||
order by tgrelid::regclass::text, tgfoid;
|
||||
tgrelid | tgname | tgfoid | tgenabled
|
||||
---------+-------------------------+------------------------+-----------
|
||||
child1 | RI_ConstraintTrigger_c_ | "RI_FKey_check_ins" | D
|
||||
child1 | RI_ConstraintTrigger_c_ | "RI_FKey_check_upd" | D
|
||||
parent | RI_ConstraintTrigger_c_ | "RI_FKey_check_ins" | D
|
||||
parent | RI_ConstraintTrigger_c_ | "RI_FKey_check_upd" | D
|
||||
parent | RI_ConstraintTrigger_a_ | "RI_FKey_noaction_del" | D
|
||||
parent | RI_ConstraintTrigger_a_ | "RI_FKey_noaction_upd" | D
|
||||
(6 rows)
|
||||
|
||||
drop table parent, child1;
|
||||
-- Verify that firing state propagates correctly on creation, too
|
||||
CREATE TABLE trgfire (i int) PARTITION BY RANGE (i);
|
||||
|
@ -1883,6 +1883,21 @@ select tgrelid::regclass, tgname, tgenabled from pg_trigger
|
||||
order by tgrelid::regclass::text, tgname;
|
||||
drop table parent, child1;
|
||||
|
||||
-- Check processing of foreign key triggers
|
||||
create table parent (a int primary key, f int references parent)
|
||||
partition by list (a);
|
||||
create table child1 partition of parent for values in (1);
|
||||
select tgrelid::regclass, rtrim(tgname, '0123456789') as tgname,
|
||||
tgfoid::regproc, tgenabled
|
||||
from pg_trigger where tgrelid in ('parent'::regclass, 'child1'::regclass)
|
||||
order by tgrelid::regclass::text, tgfoid;
|
||||
alter table parent disable trigger all;
|
||||
select tgrelid::regclass, rtrim(tgname, '0123456789') as tgname,
|
||||
tgfoid::regproc, tgenabled
|
||||
from pg_trigger where tgrelid in ('parent'::regclass, 'child1'::regclass)
|
||||
order by tgrelid::regclass::text, tgfoid;
|
||||
drop table parent, child1;
|
||||
|
||||
-- Verify that firing state propagates correctly on creation, too
|
||||
CREATE TABLE trgfire (i int) PARTITION BY RANGE (i);
|
||||
CREATE TABLE trgfire1 PARTITION OF trgfire FOR VALUES FROM (1) TO (10);
|
||||
|
Reference in New Issue
Block a user