1
0
mirror of https://github.com/postgres/postgres.git synced 2025-06-30 21:42:05 +03:00

Enable BEFORE row-level triggers for partitioned tables

... with the limitation that the tuple must remain in the same
partition.

Reviewed-by: Ashutosh Bapat
Discussion: https://postgr.es/m/20200227165158.GA2071@alvherre.pgsql
This commit is contained in:
Alvaro Herrera
2020-03-18 18:58:05 -03:00
parent b029395f5e
commit 487e9861d0
8 changed files with 161 additions and 23 deletions

View File

@ -221,18 +221,6 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
*/
if (stmt->row)
{
/*
* BEFORE triggers FOR EACH ROW are forbidden, because they would
* allow the user to direct the row to another partition, which
* isn't implemented in the executor.
*/
if (stmt->timing != TRIGGER_TYPE_AFTER)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is a partitioned table",
RelationGetRelationName(rel)),
errdetail("Partitioned tables cannot have BEFORE / FOR EACH ROW triggers.")));
/*
* Disallow use of transition tables.
*
@ -1658,6 +1646,7 @@ RelationBuildTriggers(Relation relation)
build->tgtype = pg_trigger->tgtype;
build->tgenabled = pg_trigger->tgenabled;
build->tgisinternal = pg_trigger->tgisinternal;
build->tgisclone = OidIsValid(pg_trigger->tgparentid);
build->tgconstrrelid = pg_trigger->tgconstrrelid;
build->tgconstrindid = pg_trigger->tgconstrindid;
build->tgconstraint = pg_trigger->tgconstraint;
@ -1961,6 +1950,8 @@ equalTriggerDescs(TriggerDesc *trigdesc1, TriggerDesc *trigdesc2)
return false;
if (trig1->tgisinternal != trig2->tgisinternal)
return false;
if (trig1->tgisclone != trig2->tgisclone)
return false;
if (trig1->tgconstrrelid != trig2->tgconstrrelid)
return false;
if (trig1->tgconstrindid != trig2->tgconstrindid)
@ -2247,6 +2238,21 @@ ExecBRInsertTriggers(EState *estate, ResultRelInfo *relinfo,
{
ExecForceStoreHeapTuple(newtuple, slot, false);
/*
* After a tuple in a partition goes through a trigger, the user
* could have changed the partition key enough that the tuple
* no longer fits the partition. Verify that.
*/
if (trigger->tgisclone &&
!ExecPartitionCheck(relinfo, slot, estate, false))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("moving row to another partition during a BEFORE FOR EACH ROW trigger is not supported"),
errdetail("Before executing trigger \"%s\", the row was to be in partition \"%s.%s\".",
trigger->tgname,
get_namespace_name(RelationGetNamespace(relinfo->ri_RelationDesc)),
RelationGetRelationName(relinfo->ri_RelationDesc))));
if (should_free)
heap_freetuple(oldtuple);
@ -2741,6 +2747,16 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
{
ExecForceStoreHeapTuple(newtuple, newslot, false);
if (trigger->tgisclone &&
!ExecPartitionCheck(relinfo, newslot, estate, false))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("moving row to another partition during a BEFORE trigger is not supported"),
errdetail("Before executing trigger \"%s\", the row was to be in partition \"%s.%s\".",
trigger->tgname,
get_namespace_name(RelationGetNamespace(relinfo->ri_RelationDesc)),
RelationGetRelationName(relinfo->ri_RelationDesc))));
/*
* If the tuple returned by the trigger / being stored, is the old
* row version, and the heap tuple passed to the trigger was