/* ------------------------------------------------------------------------- * * temporal_tables.c * * Copyright (c) 2012-2017 Vladislav Arkhipov * * ------------------------------------------------------------------------- */ #include "postgres.h" #include "fmgr.h" #include "nodes/pg_list.h" #include "utils/memutils.h" #include "temporal_tables.h" #include "utils/elog.h" PG_MODULE_MAGIC; PGDLLEXPORT void _PG_init(void); static void temporal_tables_xact_callback(XactEvent event, void *arg); static void temporal_tables_subxact_callback(SubXactEvent event, SubTransactionId mySubid, SubTransactionId parentSubid, void *arg); /* TemporalContext stack */ static List *temporal_contexts; void _PG_init(void) { MemoryContext oldcxt; TemporalContext *ctx; // Create the TemporalContexts stack and add the top temporal context // into it. We use the special value InvalidSubTransactionId to denote // the top TemporalContext. This context is never modified directly // since we will set it as the current context in the case of a // transaction rolling back. If a transaction commits, we will copy the // current TemporalContext content to this context. Note that this // context is created in the TopMemoryContext as it must survive // transaction boundaries. oldcxt = MemoryContextSwitchTo(TopMemoryContext); ctx = palloc0(sizeof(TemporalContext)); ctx->system_time_mode = CurrentTransactionStartTimestamp; ctx->subid = InvalidSubTransactionId; temporal_contexts = list_make1(ctx); MemoryContextSwitchTo(oldcxt); // Register some callback functions that manage the TemporalContexts // stack. RegisterXactCallback(temporal_tables_xact_callback, NULL); RegisterSubXactCallback(temporal_tables_subxact_callback, NULL); } static void copy_temporal_context(TemporalContext *dest, TemporalContext *src, SubTransactionId subid) { memcpy(dest, src, sizeof(TemporalContext)); dest->subid = subid; } static TemporalContext * push_temporal_context(SubTransactionId subid) { MemoryContext oldcxt; TemporalContext *ctx; // We use the TopTransactionContext because we sometimes propagate the // context to the parent subtransaction without copying it. oldcxt = MemoryContextSwitchTo(TopTransactionContext); ctx = palloc(sizeof(TemporalContext)); copy_temporal_context(ctx, linitial(temporal_contexts), subid); temporal_contexts = lcons(ctx, temporal_contexts); MemoryContextSwitchTo(oldcxt); return ctx; } /* * A callback routine on a transaction commit/abort. It pops the top from * the TemporalContexts stack and copy its content to the top * TemporalContext in the case of the transaction committed. */ static void temporal_tables_xact_callback(XactEvent event, void *arg) { if (event == XACT_EVENT_COMMIT || event == XACT_EVENT_ABORT) { TemporalContext *ctx = linitial(temporal_contexts); if (ctx->subid != InvalidSubTransactionId) { temporal_contexts = list_delete_first(temporal_contexts); if (event == XACT_EVENT_COMMIT) { TemporalContext *top_ctx = linitial(temporal_contexts); Assert(top_ctx->subid == InvalidSubTransactionId); copy_temporal_context(top_ctx, ctx, InvalidSubTransactionId); } } } } /* * A callback routine on a subtransaction commit/abort. It pops the top from * the TemporalContexts stack. */ static void temporal_tables_subxact_callback(SubXactEvent event, SubTransactionId mySubid, SubTransactionId parentSubid, void *arg) { if (event == SUBXACT_EVENT_COMMIT_SUB || event == SUBXACT_EVENT_ABORT_SUB) { TemporalContext *ctx = linitial(temporal_contexts); // If subid does not equal to the current subtransaction id, that // means that we have not pushed a new temporal context in this // subtransaction, so there is nothing to pop here. if (ctx->subid == GetCurrentSubTransactionId()) { if (event == SUBXACT_EVENT_ABORT_SUB) { temporal_contexts = list_delete_first(temporal_contexts); pfree(ctx); } else // SUBXACT_EVENT_COMMIT_SUB { TemporalContext *parent_ctx = lsecond(temporal_contexts); if (parent_ctx->subid != parentSubid) { ctx->subid = parentSubid; } else { copy_temporal_context(parent_ctx, ctx, parentSubid); temporal_contexts = list_delete_first(temporal_contexts); pfree(ctx); } } } } } TemporalContext * get_current_temporal_context(bool will_modify) { TemporalContext *ctx = linitial(temporal_contexts); SubTransactionId subid; // Since the operation is read-only, we can safely return the top from // the TemporalContexts stack as that TemporalContext has not been // changed since the transaction it was created in. if (!will_modify) return ctx; subid = GetCurrentSubTransactionId(); if (ctx->subid == subid) return ctx; return push_temporal_context(subid); }