diff options
author | Pavan Deolasee | 2017-07-13 09:22:22 +0000 |
---|---|---|
committer | Pavan Deolasee | 2017-07-13 09:22:22 +0000 |
commit | 03162cb93078de77532bf08498d96345fe14ea68 (patch) | |
tree | e165038efc76fc408b6541edc3eccb121cce1eb5 /src/backend/commands | |
parent | e26a0e07d863777079bfe9fc335ca2b71377b731 (diff) | |
parent | bc2d716ad09fceeb391c755f78c256ddac9d3b9f (diff) |
Merge remote-tracking branch 'remotes/PGSQL/master' of PG 10
This merge includes all commits upto bc2d716ad09fceeb391c755f78c256ddac9d3b9f
of PG 10.
Diffstat (limited to 'src/backend/commands')
-rw-r--r-- | src/backend/commands/collationcmds.c | 28 | ||||
-rw-r--r-- | src/backend/commands/copy.c | 70 | ||||
-rw-r--r-- | src/backend/commands/subscriptioncmds.c | 3 | ||||
-rw-r--r-- | src/backend/commands/tablecmds.c | 28 | ||||
-rw-r--r-- | src/backend/commands/trigger.c | 385 | ||||
-rw-r--r-- | src/backend/commands/vacuumlazy.c | 2 |
6 files changed, 385 insertions, 131 deletions
diff --git a/src/backend/commands/collationcmds.c b/src/backend/commands/collationcmds.c index 7f2ce4db4c..b6c14c920d 100644 --- a/src/backend/commands/collationcmds.c +++ b/src/backend/commands/collationcmds.c @@ -64,7 +64,7 @@ DefineCollation(ParseState *pstate, List *names, List *parameters, bool if_not_e char *collcollate = NULL; char *collctype = NULL; char *collproviderstr = NULL; - int collencoding; + int collencoding = 0; char collprovider = 0; char *collversion = NULL; Oid newoid; @@ -126,6 +126,7 @@ DefineCollation(ParseState *pstate, List *names, List *parameters, bool if_not_e collcollate = pstrdup(NameStr(((Form_pg_collation) GETSTRUCT(tp))->collcollate)); collctype = pstrdup(NameStr(((Form_pg_collation) GETSTRUCT(tp))->collctype)); collprovider = ((Form_pg_collation) GETSTRUCT(tp))->collprovider; + collencoding = ((Form_pg_collation) GETSTRUCT(tp))->collencoding; ReleaseSysCache(tp); @@ -185,12 +186,15 @@ DefineCollation(ParseState *pstate, List *names, List *parameters, bool if_not_e (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("parameter \"lc_ctype\" must be specified"))); - if (collprovider == COLLPROVIDER_ICU) - collencoding = -1; - else + if (!fromEl) { - collencoding = GetDatabaseEncoding(); - check_encoding_locale_matches(collencoding, collcollate, collctype); + if (collprovider == COLLPROVIDER_ICU) + collencoding = -1; + else + { + collencoding = GetDatabaseEncoding(); + check_encoding_locale_matches(collencoding, collcollate, collctype); + } } if (!collversion) @@ -353,6 +357,12 @@ pg_collation_actual_version(PG_FUNCTION_ARGS) } +/* will we use "locale -a" in pg_import_system_collations? */ +#if defined(HAVE_LOCALE_T) && !defined(WIN32) +#define READ_LOCALE_A_OUTPUT +#endif + +#if defined(READ_LOCALE_A_OUTPUT) || defined(USE_ICU) /* * Check a string to see if it is pure ASCII */ @@ -367,11 +377,7 @@ is_all_ascii(const char *str) } return true; } - -/* will we use "locale -a" in pg_import_system_collations? */ -#if defined(HAVE_LOCALE_T) && !defined(WIN32) -#define READ_LOCALE_A_OUTPUT -#endif +#endif /* READ_LOCALE_A_OUTPUT || USE_ICU */ #ifdef READ_LOCALE_A_OUTPUT /* diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index 3f692b6441..37d9a898cf 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -193,6 +193,8 @@ typedef struct CopyStateData ResultRelInfo *partitions; /* Per partition result relation */ TupleConversionMap **partition_tupconv_maps; TupleTableSlot *partition_tuple_slot; + TransitionCaptureState *transition_capture; + TupleConversionMap **transition_tupconv_maps; /* * These variables are used to reduce overhead in textual COPY FROM. @@ -1463,6 +1465,12 @@ BeginCopy(ParseState *pstate, errmsg("table \"%s\" does not have OIDs", RelationGetRelationName(cstate->rel)))); + /* + * If there are any triggers with transition tables on the named + * relation, we need to be prepared to capture transition tuples. + */ + cstate->transition_capture = MakeTransitionCaptureState(rel->trigdesc); + /* Initialize state for CopyFrom tuple routing. */ if (is_from && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) { @@ -1485,6 +1493,28 @@ BeginCopy(ParseState *pstate, cstate->num_partitions = num_partitions; cstate->partition_tupconv_maps = partition_tupconv_maps; cstate->partition_tuple_slot = partition_tuple_slot; + + /* + * If we are capturing transition tuples, they may need to be + * converted from partition format back to partitioned table + * format (this is only ever necessary if a BEFORE trigger + * modifies the tuple). + */ + if (cstate->transition_capture != NULL) + { + int i; + + cstate->transition_tupconv_maps = (TupleConversionMap **) + palloc0(sizeof(TupleConversionMap *) * + cstate->num_partitions); + for (i = 0; i < cstate->num_partitions; ++i) + { + cstate->transition_tupconv_maps[i] = + convert_tuples_by_name(RelationGetDescr(cstate->partitions[i].ri_RelationDesc), + RelationGetDescr(rel), + gettext_noop("could not convert row type")); + } + } } #ifdef PGXC /* Get copy statement and execution node information */ @@ -2779,6 +2809,35 @@ CopyFrom(CopyState cstate) estate->es_result_relation_info = resultRelInfo; /* + * If we're capturing transition tuples, we might need to convert + * from the partition rowtype to parent rowtype. + */ + if (cstate->transition_capture != NULL) + { + if (resultRelInfo->ri_TrigDesc && + (resultRelInfo->ri_TrigDesc->trig_insert_before_row || + resultRelInfo->ri_TrigDesc->trig_insert_instead_row)) + { + /* + * If there are any BEFORE or INSTEAD triggers on the + * partition, we'll have to be ready to convert their + * result back to tuplestore format. + */ + cstate->transition_capture->tcs_original_insert_tuple = NULL; + cstate->transition_capture->tcs_map = + cstate->transition_tupconv_maps[leaf_part_index]; + } + else + { + /* + * Otherwise, just remember the original unconverted + * tuple, to avoid a needless round trip conversion. + */ + cstate->transition_capture->tcs_original_insert_tuple = tuple; + cstate->transition_capture->tcs_map = NULL; + } + } + /* * We might need to convert from the parent rowtype to the * partition rowtype. */ @@ -2890,7 +2949,7 @@ CopyFrom(CopyState cstate) /* AFTER ROW INSERT Triggers */ ExecARInsertTriggers(estate, resultRelInfo, tuple, - recheckIndexes); + recheckIndexes, cstate->transition_capture); list_free(recheckIndexes); } @@ -2956,7 +3015,7 @@ CopyFrom(CopyState cstate) pq_endmsgread(); /* Execute AFTER STATEMENT insertion triggers */ - ExecASInsertTriggers(estate, resultRelInfo); + ExecASInsertTriggers(estate, resultRelInfo, cstate->transition_capture); /* Handle queued AFTER triggers */ AfterTriggerEndQuery(estate); @@ -3066,7 +3125,7 @@ CopyFromInsertBatch(CopyState cstate, EState *estate, CommandId mycid, estate, false, NULL, NIL); ExecARInsertTriggers(estate, resultRelInfo, bufferedTuples[i], - recheckIndexes); + recheckIndexes, cstate->transition_capture); list_free(recheckIndexes); } } @@ -3076,14 +3135,15 @@ CopyFromInsertBatch(CopyState cstate, EState *estate, CommandId mycid, * anyway. */ else if (resultRelInfo->ri_TrigDesc != NULL && - resultRelInfo->ri_TrigDesc->trig_insert_after_row) + (resultRelInfo->ri_TrigDesc->trig_insert_after_row || + resultRelInfo->ri_TrigDesc->trig_insert_new_table)) { for (i = 0; i < nBufferedTuples; i++) { cstate->cur_lineno = firstBufferedLineNo + i; ExecARInsertTriggers(estate, resultRelInfo, bufferedTuples[i], - NIL); + NIL, cstate->transition_capture); } } diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index 9cbd36f646..6dc3f6ee00 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -644,6 +644,9 @@ AlterSubscription(AlterSubscriptionStmt *stmt) subid = HeapTupleGetOid(tup); sub = GetSubscription(subid, false); + /* Lock the subscription so nobody else can do anything with it. */ + LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock); + /* Form a new tuple. */ memset(values, 0, sizeof(values)); memset(nulls, false, sizeof(nulls)); diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index d20670b065..1665433145 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -11172,6 +11172,7 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode) Relation parent_rel; List *children; ObjectAddress address; + const char *trigger_name; /* * A self-exclusive lock is needed here. See the similar case in @@ -11253,6 +11254,19 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode) RelationGetRelationName(child_rel), RelationGetRelationName(parent_rel)))); + /* + * If child_rel has row-level triggers with transition tables, we + * currently don't allow it to become an inheritance child. See also + * prohibitions in ATExecAttachPartition() and CreateTrigger(). + */ + trigger_name = FindTriggerIncompatibleWithInheritance(child_rel->trigdesc); + if (trigger_name != NULL) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("trigger \"%s\" prevents table \"%s\" from becoming an inheritance child", + trigger_name, RelationGetRelationName(child_rel)), + errdetail("ROW triggers with transition tables are not supported in inheritance hierarchies"))); + /* OK to create inheritance */ CreateInheritance(child_rel, parent_rel); @@ -14335,6 +14349,7 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd) TupleDesc tupleDesc; bool skip_validate = false; ObjectAddress address; + const char *trigger_name; attachRel = heap_openrv(cmd->name, AccessExclusiveLock); @@ -14464,6 +14479,19 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd) errdetail("New partition should contain only the columns present in parent."))); } + /* + * If child_rel has row-level triggers with transition tables, we + * currently don't allow it to become a partition. See also prohibitions + * in ATExecAddInherit() and CreateTrigger(). + */ + trigger_name = FindTriggerIncompatibleWithInheritance(attachRel->trigdesc); + if (trigger_name != NULL) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("trigger \"%s\" prevents table \"%s\" from becoming a partition", + trigger_name, RelationGetRelationName(attachRel)), + errdetail("ROW triggers with transition tables are not supported on partitions"))); + /* OK to create inheritance. Rest of the checks performed there */ CreateInheritance(attachRel, rel); diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 8f6de3fc69..576fa19705 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -25,6 +25,7 @@ #include "catalog/objectaccess.h" #include "catalog/pg_constraint.h" #include "catalog/pg_constraint_fn.h" +#include "catalog/pg_inherits_fn.h" #include "catalog/pg_proc.h" #include "catalog/pg_trigger.h" #include "catalog/pg_type.h" @@ -100,7 +101,8 @@ static HeapTuple ExecCallTriggerFunc(TriggerData *trigdata, static void AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo, int event, bool row_trigger, HeapTuple oldtup, HeapTuple newtup, - List *recheckIndexes, Bitmapset *modifiedCols); + List *recheckIndexes, Bitmapset *modifiedCols, + TransitionCaptureState *transition_capture); static void AfterTriggerEnlargeQueryState(void); #ifdef XCP @@ -361,13 +363,6 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString, * adjustments will be needed below. */ - if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("\"%s\" is a partitioned table", - RelationGetRelationName(rel)), - errdetail("Triggers on partitioned tables cannot have transition tables."))); - if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), @@ -382,6 +377,27 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString, RelationGetRelationName(rel)), errdetail("Triggers on views cannot have transition tables."))); + /* + * We currently don't allow row-level triggers with transition + * tables on partition or inheritance children. Such triggers + * would somehow need to see tuples converted to the format of the + * table they're attached to, and it's not clear which subset of + * tuples each child should see. See also the prohibitions in + * ATExecAttachPartition() and ATExecAddInherit(). + */ + if (TRIGGER_FOR_ROW(tgtype) && has_superclass(rel->rd_id)) + { + /* Use appropriate error message. */ + if (rel->rd_rel->relispartition) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("ROW triggers with transition tables are not supported on partitions"))); + else + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("ROW triggers with transition tables are not supported on inheritance children"))); + } + if (stmt->timing != TRIGGER_TYPE_AFTER) ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), @@ -392,6 +408,23 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("TRUNCATE triggers with transition tables are not supported"))); + /* + * We currently don't allow multi-event triggers ("INSERT OR + * UPDATE") with transition tables, because it's not clear how to + * handle INSERT ... ON CONFLICT statements which can fire both + * INSERT and UPDATE triggers. We show the inserted tuples to + * INSERT triggers and the updated tuples to UPDATE triggers, but + * it's not yet clear what INSERT OR UPDATE trigger should see. + * This restriction could be lifted if we can decide on the right + * semantics in a later release. + */ + if (((TRIGGER_FOR_INSERT(tgtype) ? 1 : 0) + + (TRIGGER_FOR_UPDATE(tgtype) ? 1 : 0) + + (TRIGGER_FOR_DELETE(tgtype) ? 1 : 0)) != 1) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("Transition tables cannot be specified for triggers with more than one event"))); + if (tt->isNew) { if (!(TRIGGER_FOR_INSERT(tgtype) || @@ -2038,6 +2071,120 @@ equalTriggerDescs(TriggerDesc *trigdesc1, TriggerDesc *trigdesc2) #endif /* NOT_USED */ /* + * Check if there is a row-level trigger with transition tables that prevents + * a table from becoming an inheritance child or partition. Return the name + * of the first such incompatible trigger, or NULL if there is none. + */ +const char * +FindTriggerIncompatibleWithInheritance(TriggerDesc *trigdesc) +{ + if (trigdesc != NULL) + { + int i; + + for (i = 0; i < trigdesc->numtriggers; ++i) + { + Trigger *trigger = &trigdesc->triggers[i]; + + if (trigger->tgoldtable != NULL || trigger->tgnewtable != NULL) + return trigger->tgname; + } + } + + return NULL; +} + +/* + * Make a TransitionCaptureState object from a given TriggerDesc. The + * resulting object holds the flags which control whether transition tuples + * are collected when tables are modified, and the tuplestores themselves. + * Note that we copy the flags from a parent table into this struct (rather + * than using each relation's TriggerDesc directly) so that we can use it to + * control the collection of transition tuples from child tables. + * + * If there are no triggers with transition tables configured for 'trigdesc', + * then return NULL. + * + * The resulting object can be passed to the ExecAR* functions. The caller + * should set tcs_map or tcs_original_insert_tuple as appropriate when dealing + * with child tables. + */ +TransitionCaptureState * +MakeTransitionCaptureState(TriggerDesc *trigdesc) +{ + TransitionCaptureState *state = NULL; + + if (trigdesc != NULL && + (trigdesc->trig_delete_old_table || trigdesc->trig_update_old_table || + trigdesc->trig_update_new_table || trigdesc->trig_insert_new_table)) + { + MemoryContext oldcxt; + ResourceOwner saveResourceOwner; + + /* + * Normally DestroyTransitionCaptureState should be called after + * executing all AFTER triggers for the current statement. + * + * To handle error cleanup, TransitionCaptureState and the tuplestores + * it contains will live in the current [sub]transaction's memory + * context. Likewise for the current resource owner, because we also + * want to clean up temporary files spilled to disk by the tuplestore + * in that scenario. This scope is sufficient, because AFTER triggers + * with transition tables cannot be deferred (only constraint triggers + * can be deferred, and constraint triggers cannot have transition + * tables). The AFTER trigger queue may contain pointers to this + * TransitionCaptureState, but any such entries will be processed or + * discarded before the end of the current [sub]transaction. + * + * If a future release allows deferred triggers with transition + * tables, we'll need to reconsider the scope of the + * TransitionCaptureState object. + */ + oldcxt = MemoryContextSwitchTo(CurTransactionContext); + saveResourceOwner = CurrentResourceOwner; + + state = (TransitionCaptureState *) + palloc0(sizeof(TransitionCaptureState)); + state->tcs_delete_old_table = trigdesc->trig_delete_old_table; + state->tcs_update_old_table = trigdesc->trig_update_old_table; + state->tcs_update_new_table = trigdesc->trig_update_new_table; + state->tcs_insert_new_table = trigdesc->trig_insert_new_table; + PG_TRY(); + { + CurrentResourceOwner = CurTransactionResourceOwner; + if (trigdesc->trig_delete_old_table || trigdesc->trig_update_old_table) + state->tcs_old_tuplestore = tuplestore_begin_heap(false, false, work_mem); + if (trigdesc->trig_insert_new_table) + state->tcs_insert_tuplestore = tuplestore_begin_heap(false, false, work_mem); + if (trigdesc->trig_update_new_table) + state->tcs_update_tuplestore = tuplestore_begin_heap(false, false, work_mem); + } + PG_CATCH(); + { + CurrentResourceOwner = saveResourceOwner; + PG_RE_THROW(); + } + PG_END_TRY(); + CurrentResourceOwner = saveResourceOwner; + MemoryContextSwitchTo(oldcxt); + } + + return state; +} + +void +DestroyTransitionCaptureState(TransitionCaptureState *tcs) +{ + if (tcs->tcs_insert_tuplestore != NULL) + tuplestore_end(tcs->tcs_insert_tuplestore); + if (tcs->tcs_update_tuplestore != NULL) + tuplestore_end(tcs->tcs_update_tuplestore); + if (tcs->tcs_old_tuplestore != NULL) + tuplestore_end(tcs->tcs_old_tuplestore); + pfree(tcs); +} + +/* * Call a trigger function. * * trigdata: trigger descriptor. @@ -2195,13 +2342,14 @@ ExecBSInsertTriggers(EState *estate, ResultRelInfo *relinfo) } void -ExecASInsertTriggers(EState *estate, ResultRelInfo *relinfo) +ExecASInsertTriggers(EState *estate, ResultRelInfo *relinfo, + TransitionCaptureState *transition_capture) { TriggerDesc *trigdesc = relinfo->ri_TrigDesc; if (trigdesc && trigdesc->trig_insert_after_statement) AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_INSERT, - false, NULL, NULL, NIL, NULL); + false, NULL, NULL, NIL, NULL, transition_capture); } TupleTableSlot * @@ -2272,14 +2420,17 @@ ExecBRInsertTriggers(EState *estate, ResultRelInfo *relinfo, void ExecARInsertTriggers(EState *estate, ResultRelInfo *relinfo, - HeapTuple trigtuple, List *recheckIndexes) + HeapTuple trigtuple, List *recheckIndexes, + TransitionCaptureState *transition_capture) { TriggerDesc *trigdesc = relinfo->ri_TrigDesc; - if (trigdesc && - (trigdesc->trig_insert_after_row || trigdesc->trig_insert_new_table)) + if ((trigdesc && trigdesc->trig_insert_after_row) || + (transition_capture && transition_capture->tcs_insert_new_table)) AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_INSERT, - true, NULL, trigtuple, recheckIndexes, NULL); + true, NULL, trigtuple, + recheckIndexes, NULL, + transition_capture); } TupleTableSlot * @@ -2401,13 +2552,14 @@ ExecBSDeleteTriggers(EState *estate, ResultRelInfo *relinfo) } void -ExecASDeleteTriggers(EState *estate, ResultRelInfo *relinfo) +ExecASDeleteTriggers(EState *estate, ResultRelInfo *relinfo, + TransitionCaptureState *transition_capture) { TriggerDesc *trigdesc = relinfo->ri_TrigDesc; if (trigdesc && trigdesc->trig_delete_after_statement) AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_DELETE, - false, NULL, NULL, NIL, NULL); + false, NULL, NULL, NIL, NULL, transition_capture); } bool @@ -2482,12 +2634,13 @@ ExecBRDeleteTriggers(EState *estate, EPQState *epqstate, void ExecARDeleteTriggers(EState *estate, ResultRelInfo *relinfo, ItemPointer tupleid, - HeapTuple fdw_trigtuple) + HeapTuple fdw_trigtuple, + TransitionCaptureState *transition_capture) { TriggerDesc *trigdesc = relinfo->ri_TrigDesc; - if (trigdesc && - (trigdesc->trig_delete_after_row || trigdesc->trig_delete_old_table)) + if ((trigdesc && trigdesc->trig_delete_after_row) || + (transition_capture && transition_capture->tcs_delete_old_table)) { HeapTuple trigtuple; @@ -2503,7 +2656,8 @@ ExecARDeleteTriggers(EState *estate, ResultRelInfo *relinfo, trigtuple = fdw_trigtuple; AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_DELETE, - true, trigtuple, NULL, NIL, NULL); + true, trigtuple, NULL, NIL, NULL, + transition_capture); if (trigtuple != fdw_trigtuple) heap_freetuple(trigtuple); } @@ -2612,14 +2766,16 @@ ExecBSUpdateTriggers(EState *estate, ResultRelInfo *relinfo) } void -ExecASUpdateTriggers(EState *estate, ResultRelInfo *relinfo) +ExecASUpdateTriggers(EState *estate, ResultRelInfo *relinfo, + TransitionCaptureState *transition_capture) { TriggerDesc *trigdesc = relinfo->ri_TrigDesc; if (trigdesc && trigdesc->trig_update_after_statement) AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_UPDATE, false, NULL, NULL, NIL, - GetUpdatedColumns(relinfo, estate)); + GetUpdatedColumns(relinfo, estate), + transition_capture); } TupleTableSlot * @@ -2744,12 +2900,15 @@ ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo, ItemPointer tupleid, HeapTuple fdw_trigtuple, HeapTuple newtuple, - List *recheckIndexes) + List *recheckIndexes, + TransitionCaptureState *transition_capture) { TriggerDesc *trigdesc = relinfo->ri_TrigDesc; - if (trigdesc && (trigdesc->trig_update_after_row || - trigdesc->trig_update_old_table || trigdesc->trig_update_new_table)) + if ((trigdesc && trigdesc->trig_update_after_row) || + (transition_capture && + (transition_capture->tcs_update_old_table || + transition_capture->tcs_update_new_table))) { HeapTuple trigtuple; @@ -2766,7 +2925,8 @@ ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo, AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_UPDATE, true, trigtuple, newtuple, recheckIndexes, - GetUpdatedColumns(relinfo, estate)); + GetUpdatedColumns(relinfo, estate), + transition_capture); if (trigtuple != fdw_trigtuple) heap_freetuple(trigtuple); } @@ -2897,7 +3057,7 @@ ExecASTruncateTriggers(EState *estate, ResultRelInfo *relinfo) if (trigdesc && trigdesc->trig_truncate_after_statement) AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_TRUNCATE, - false, NULL, NULL, NIL, NULL); + false, NULL, NULL, NIL, NULL, NULL); } @@ -3285,6 +3445,7 @@ typedef struct AfterTriggerSharedData Oid ats_tgoid; /* the trigger's ID */ Oid ats_relid; /* the relation it's on */ CommandId ats_firing_id; /* ID for firing cycle */ + TransitionCaptureState *ats_transition_capture; } AfterTriggerSharedData; typedef struct AfterTriggerEventData *AfterTriggerEvent; @@ -3390,9 +3551,6 @@ typedef struct AfterTriggerEventList * fdw_tuplestores[query_depth] is a tuplestore containing the foreign tuples * needed for the current query. * - * old_tuplestores[query_depth] and new_tuplestores[query_depth] hold the - * transition relations for the current query. - * * maxquerydepth is just the allocated length of query_stack and the * tuplestores. * @@ -3425,8 +3583,6 @@ typedef struct AfterTriggersData AfterTriggerEventList *query_stack; /* events pending from each query */ Tuplestorestate **fdw_tuplestores; /* foreign tuples for one row from * each query */ - Tuplestorestate **old_tuplestores; /* all old tuples from each query */ - Tuplestorestate **new_tuplestores; /* all new tuples from each query */ int maxquerydepth; /* allocated len of above array */ MemoryContext event_cxt; /* memory context for events, if any */ @@ -3447,7 +3603,8 @@ static void AfterTriggerExecute(AfterTriggerEvent event, Instrumentation *instr, MemoryContext per_tuple_context, TupleTableSlot *trig_tuple_slot1, - TupleTableSlot *trig_tuple_slot2); + TupleTableSlot *trig_tuple_slot2, + TransitionCaptureState *transition_capture); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState state); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -3456,8 +3613,6 @@ static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, /* * Gets a current query transition tuplestore and initializes it if necessary. - * This can be holding a single transition row tuple (in the case of an FDW) - * or a transition table (for an AFTER trigger). */ static Tuplestorestate * GetTriggerTransitionTuplestore(Tuplestorestate **tss) @@ -3637,6 +3792,7 @@ afterTriggerAddEvent(AfterTriggerEventList *events, if (newshared->ats_tgoid == evtshared->ats_tgoid && newshared->ats_relid == evtshared->ats_relid && newshared->ats_event == evtshared->ats_event && + newshared->ats_transition_capture == evtshared->ats_transition_capture && newshared->ats_firing_id == 0) break; } @@ -3748,7 +3904,8 @@ AfterTriggerExecute(AfterTriggerEvent event, FmgrInfo *finfo, Instrumentation *instr, MemoryContext per_tuple_context, TupleTableSlot *trig_tuple_slot1, - TupleTableSlot *trig_tuple_slot2) + TupleTableSlot *trig_tuple_slot2, + TransitionCaptureState *transition_capture) { AfterTriggerShared evtshared = GetTriggerSharedData(event); Oid tgoid = evtshared->ats_tgoid; @@ -3863,16 +4020,36 @@ AfterTriggerExecute(AfterTriggerEvent event, /* * Set up the tuplestore information. */ - if (LocTriggerData.tg_trigger->tgoldtable) - LocTriggerData.tg_oldtable = - GetTriggerTransitionTuplestore(afterTriggers.old_tuplestores); - else - LocTriggerData.tg_oldtable = NULL; - if (LocTriggerData.tg_trigger->tgnewtable) - LocTriggerData.tg_newtable = - GetTriggerTransitionTuplestore(afterTriggers.new_tuplestores); - else - LocTriggerData.tg_newtable = NULL; + LocTriggerData.tg_oldtable = LocTriggerData.tg_newtable = NULL; + if (transition_capture != NULL) + { + if (LocTriggerData.tg_trigger->tgoldtable) + LocTriggerData.tg_oldtable = transition_capture->tcs_old_tuplestore; + if (LocTriggerData.tg_trigger->tgnewtable) + { + /* + * Currently a trigger with transition tables may only be defined + * for a single event type (here AFTER INSERT or AFTER UPDATE, but + * not AFTER INSERT OR ...). + */ + Assert((TRIGGER_FOR_INSERT(LocTriggerData.tg_trigger->tgtype) != 0) ^ + (TRIGGER_FOR_UPDATE(LocTriggerData.tg_trigger->tgtype) != 0)); + + /* + * Show either the insert or update new tuple images, depending on + * which event type the trigger was registered for. A single + * statement may have produced both in the case of INSERT ... ON + * CONFLICT ... DO UPDATE, and in that case the event determines + * which tuplestore the trigger sees as the NEW TABLE. + */ + if (TRIGGER_FOR_INSERT(LocTriggerData.tg_trigger->tgtype)) + LocTriggerData.tg_newtable = + transition_capture->tcs_insert_tuplestore; + else + LocTriggerData.tg_newtable = + transition_capture->tcs_update_tuplestore; + } + } /* * Setup the remaining trigger information @@ -4080,7 +4257,8 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, * won't try to re-fire it. */ AfterTriggerExecute(event, rel, trigdesc, finfo, instr, - per_tuple_context, slot1, slot2); + per_tuple_context, slot1, slot2, + evtshared->ats_transition_capture); /* * Mark the event as done. @@ -4154,8 +4332,6 @@ AfterTriggerBeginXact(void) Assert(afterTriggers.state == NULL); Assert(afterTriggers.query_stack == NULL); Assert(afterTriggers.fdw_tuplestores == NULL); - Assert(afterTriggers.old_tuplestores == NULL); - Assert(afterTriggers.new_tuplestores == NULL); Assert(afterTriggers.maxquerydepth == 0); Assert(afterTriggers.event_cxt == NULL); Assert(afterTriggers.events.head == NULL); @@ -4224,8 +4400,6 @@ AfterTriggerEndQuery(EState *estate) { AfterTriggerEventList *events; Tuplestorestate *fdw_tuplestore; - Tuplestorestate *old_tuplestore; - Tuplestorestate *new_tuplestore; /* Must be inside a query, too */ Assert(afterTriggers.query_depth >= 0); @@ -4284,18 +4458,6 @@ AfterTriggerEndQuery(EState *estate) tuplestore_end(fdw_tuplestore); afterTriggers.fdw_tuplestores[afterTriggers.query_depth] = NULL; } - old_tuplestore = afterTriggers.old_tuplestores[afterTriggers.query_depth]; - if (old_tuplestore) - { - tuplestore_end(old_tuplestore); - afterTriggers.old_tuplestores[afterTriggers.query_depth] = NULL; - } - new_tuplestore = afterTriggers.new_tuplestores[afterTriggers.query_depth]; - if (new_tuplestore) - { - tuplestore_end(new_tuplestore); - afterTriggers.new_tuplestores[afterTriggers.query_depth] = NULL; - } afterTriggerFreeEventList(&afterTriggers.query_stack[afterTriggers.query_depth]); afterTriggers.query_depth--; @@ -4409,8 +4571,6 @@ AfterTriggerEndXact(bool isCommit) */ afterTriggers.query_stack = NULL; afterTriggers.fdw_tuplestores = NULL; - afterTriggers.old_tuplestores = NULL; - afterTriggers.new_tuplestores = NULL; afterTriggers.maxquerydepth = 0; afterTriggers.state = NULL; @@ -4543,18 +4703,6 @@ AfterTriggerEndSubXact(bool isCommit) tuplestore_end(ts); afterTriggers.fdw_tuplestores[afterTriggers.query_depth] = NULL; } - ts = afterTriggers.old_tuplestores[afterTriggers.query_depth]; - if (ts) - { - tuplestore_end(ts); - afterTriggers.old_tuplestores[afterTriggers.query_depth] = NULL; - } - ts = afterTriggers.new_tuplestores[afterTriggers.query_depth]; - if (ts) - { - tuplestore_end(ts); - afterTriggers.new_tuplestores[afterTriggers.query_depth] = NULL; - } afterTriggerFreeEventList(&afterTriggers.query_stack[afterTriggers.query_depth]); } @@ -4634,12 +4782,6 @@ AfterTriggerEnlargeQueryState(void) afterTriggers.fdw_tuplestores = (Tuplestorestate **) MemoryContextAllocZero(TopTransactionContext, new_alloc * sizeof(Tuplestorestate *)); - afterTriggers.old_tuplestores = (Tuplestorestate **) - MemoryContextAllocZero(TopTransactionContext, - new_alloc * sizeof(Tuplestorestate *)); - afterTriggers.new_tuplestores = (Tuplestorestate **) - MemoryContextAllocZero(TopTransactionContext, - new_alloc * sizeof(Tuplestorestate *)); afterTriggers.maxquerydepth = new_alloc; } else @@ -4655,19 +4797,9 @@ AfterTriggerEnlargeQueryState(void) afterTriggers.fdw_tuplestores = (Tuplestorestate **) repalloc(afterTriggers.fdw_tuplestores, new_alloc * sizeof(Tuplestorestate *)); - afterTriggers.old_tuplestores = (Tuplestorestate **) - repalloc(afterTriggers.old_tuplestores, - new_alloc * sizeof(Tuplestorestate *)); - afterTriggers.new_tuplestores = (Tuplestorestate **) - repalloc(afterTriggers.new_tuplestores, - new_alloc * sizeof(Tuplestorestate *)); /* Clear newly-allocated slots for subsequent lazy initialization. */ memset(afterTriggers.fdw_tuplestores + old_alloc, 0, (new_alloc - old_alloc) * sizeof(Tuplestorestate *)); - memset(afterTriggers.old_tuplestores + old_alloc, - 0, (new_alloc - old_alloc) * sizeof(Tuplestorestate *)); - memset(afterTriggers.new_tuplestores + old_alloc, - 0, (new_alloc - old_alloc) * sizeof(Tuplestorestate *)); afterTriggers.maxquerydepth = new_alloc; } @@ -5134,7 +5266,8 @@ static void AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo, int event, bool row_trigger, HeapTuple oldtup, HeapTuple newtup, - List *recheckIndexes, Bitmapset *modifiedCols) + List *recheckIndexes, Bitmapset *modifiedCols, + TransitionCaptureState *transition_capture) { Relation rel = relinfo->ri_RelationDesc; TriggerDesc *trigdesc = relinfo->ri_TrigDesc; @@ -5159,40 +5292,63 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo, AfterTriggerEnlargeQueryState(); /* - * If the relation has AFTER ... FOR EACH ROW triggers, capture rows into - * transition tuplestores for this depth. + * If the directly named relation has any triggers with transition tables, + * then we need to capture transition tuples. */ - if (row_trigger) + if (row_trigger && transition_capture != NULL) { - if ((event == TRIGGER_EVENT_DELETE && - trigdesc->trig_delete_old_table) || - (event == TRIGGER_EVENT_UPDATE && - trigdesc->trig_update_old_table)) + HeapTuple original_insert_tuple = transition_capture->tcs_original_insert_tuple; + TupleConversionMap *map = transition_capture->tcs_map; + bool delete_old_table = transition_capture->tcs_delete_old_table; + bool update_old_table = transition_capture->tcs_update_old_table; + bool update_new_table = transition_capture->tcs_update_new_table; + bool insert_new_table = transition_capture->tcs_insert_new_table;; + + if ((event == TRIGGER_EVENT_DELETE && delete_old_table) || + (event == TRIGGER_EVENT_UPDATE && update_old_table)) { Tuplestorestate *old_tuplestore; Assert(oldtup != NULL); - old_tuplestore = - GetTriggerTransitionTuplestore - (afterTriggers.old_tuplestores); - tuplestore_puttuple(old_tuplestore, oldtup); + old_tuplestore = transition_capture->tcs_old_tuplestore; + + if (map != NULL) + { + HeapTuple converted = do_convert_tuple(oldtup, map); + + tuplestore_puttuple(old_tuplestore, converted); + pfree(converted); + } + else + tuplestore_puttuple(old_tuplestore, oldtup); } - if ((event == TRIGGER_EVENT_INSERT && - trigdesc->trig_insert_new_table) || - (event == TRIGGER_EVENT_UPDATE && - trigdesc->trig_update_new_table)) + if ((event == TRIGGER_EVENT_INSERT && insert_new_table) || + (event == TRIGGER_EVENT_UPDATE && update_new_table)) { Tuplestorestate *new_tuplestore; Assert(newtup != NULL); - new_tuplestore = - GetTriggerTransitionTuplestore - (afterTriggers.new_tuplestores); - tuplestore_puttuple(new_tuplestore, newtup); + if (event == TRIGGER_EVENT_INSERT) + new_tuplestore = transition_capture->tcs_insert_tuplestore; + else + new_tuplestore = transition_capture->tcs_update_tuplestore; + + if (original_insert_tuple != NULL) + tuplestore_puttuple(new_tuplestore, original_insert_tuple); + else if (map != NULL) + { + HeapTuple converted = do_convert_tuple(newtup, map); + + tuplestore_puttuple(new_tuplestore, converted); + pfree(converted); + } + else + tuplestore_puttuple(new_tuplestore, newtup); } /* If transition tables are the only reason we're here, return. */ - if ((event == TRIGGER_EVENT_DELETE && !trigdesc->trig_delete_after_row) || + if (trigdesc == NULL || + (event == TRIGGER_EVENT_DELETE && !trigdesc->trig_delete_after_row) || (event == TRIGGER_EVENT_INSERT && !trigdesc->trig_insert_after_row) || (event == TRIGGER_EVENT_UPDATE && !trigdesc->trig_update_after_row)) return; @@ -5362,6 +5518,7 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo, new_shared.ats_tgoid = trigger->tgoid; new_shared.ats_relid = RelationGetRelid(rel); new_shared.ats_firing_id = 0; + new_shared.ats_transition_capture = transition_capture; afterTriggerAddEvent(&afterTriggers.query_stack[afterTriggers.query_depth], &new_event, &new_shared); diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c index 881c7356db..30a0050ae1 100644 --- a/src/backend/commands/vacuumlazy.c +++ b/src/backend/commands/vacuumlazy.c @@ -529,7 +529,7 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats, * safely set for relfrozenxid or relminmxid. * * Before entering the main loop, establish the invariant that - * next_unskippable_block is the next block number >= blkno that's not we + * next_unskippable_block is the next block number >= blkno that we * can't skip based on the visibility map, either all-visible for a * regular scan or all-frozen for an aggressive scan. We set it to * nblocks if there's no such block. We also set up the skipping_blocks |