Hello
Some time ago we discussed an idea of "fast temporary tables":
https://www.postgresql.org/message-id/20160301182500.2c81c3dc%40fujitsu
In two words the idea is following.
<The Idea>
PostgreSQL stores information about all relations in pg_catalog. Some
applications create and delete a lot of temporary tables. It causes a
bloating of pg_catalog and running auto vacuum on it. It's quite an
expensive operation which affects entire database performance.
We could introduce a new type of temporary tables. Information about
these tables is stored not in a catalog but in backend's memory. This
way user can solve a pg_catalog bloating problem and improve overall
database performance.
</The Idea>
I took me a few months but eventually I made it work. Attached patch
has some flaws. I decided not to invest a lot of time in documenting
it or pgindent'ing all files yet. In my experience it will be rewritten
entirely 3 or 4 times before merging anyway :) But it _works_ and
passes all tests I could think of, including non-trivial cases like
index-only or bitmap scans of catalog tables.
Usage example:
```
CREATE FAST TEMP TABLE fasttab_test1(x int, s text);
INSERT INTO fasttab_test1 VALUES (1, 'aaa'), (2, 'bbb'), (3, 'ccc');
UPDATE fasttab_test1 SET s = 'ddd' WHERE x = 2;
DELETE FROM fasttab_test1 WHERE x = 3;
SELECT * FROM fasttab_test1 ORDER BY x;
DROP TABLE fasttab_test1;
```
More sophisticated examples could be find in regression tests:
./src/test/regress/sql/fast_temp.sql
Any feedback on this patch will be much appreciated!
--
Best regards,
Aleksander Alekseev
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 0689cc9..940210b 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1693,7 +1693,7 @@
<entry></entry>
<entry>
<literal>p</> = permanent table, <literal>u</> = unlogged table,
- <literal>t</> = temporary table
+ <literal>t</> = temporary table, <literal>f</> = fast temporary table
</entry>
</row>
diff --git a/src/backend/access/common/Makefile b/src/backend/access/common/Makefile
index 1fa6de0..56de4dc 100644
--- a/src/backend/access/common/Makefile
+++ b/src/backend/access/common/Makefile
@@ -12,7 +12,7 @@ subdir = src/backend/access/common
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
-OBJS = heaptuple.o indextuple.o printtup.o reloptions.o scankey.o \
+OBJS = fasttab.o heaptuple.o indextuple.o printtup.o reloptions.o scankey.o \
tupconvert.o tupdesc.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/access/common/fasttab.c b/src/backend/access/common/fasttab.c
new file mode 100644
index 0000000..0a20247
--- /dev/null
+++ b/src/backend/access/common/fasttab.c
@@ -0,0 +1,1678 @@
+/* TODO TODO comment the general idea - in-memory tuples and indexes, hooks principle, FasttabSnapshots, etc */
+
+#include "c.h"
+#include "postgres.h"
+#include "pgstat.h"
+#include "miscadmin.h"
+#include "access/amapi.h"
+#include "access/fasttab.h"
+#include "access/relscan.h"
+#include "access/valid.h"
+#include "access/sysattr.h"
+#include "access/htup_details.h"
+#include "catalog/pg_class.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_depend.h"
+#include "catalog/pg_inherits.h"
+#include "catalog/pg_statistic.h"
+#include "storage/bufmgr.h"
+#include "utils/rel.h"
+#include "utils/inval.h"
+#include "utils/memutils.h"
+
+/*****************************************************************************
+ TYPEDEFS, MACRO DECLARATIONS AND CONST STATIC VARIABLES
+ *****************************************************************************/
+
+/* #define FASTTAB_DEBUG 1 */
+
+#ifdef FASTTAB_DEBUG
+static int32 fasttab_scan_tuples_counter = -1;
+#endif
+
+/* list of in-memory catalog tuples */
+typedef struct
+{
+ dlist_node node;
+ HeapTuple tup;
+} DListHeapTupleData;
+
+typedef DListHeapTupleData *DListHeapTuple;
+
+#define FasttabSnapshotIsAnonymous(sn) ( !PointerIsValid((sn)->name) )
+#define FasttabSnapshotIsRoot(sn) ( !PointerIsValid((sn)->prev) )
+#define FasttabTransactionInProgress() \
+ ( PointerIsValid(FasttabSnapshotGetCurrent()->prev))
+/* like strcmp but for integer types --- int, uint32, Oid, etc */
+#define FasttabCompareInts(x, y) ( (x) == (y) ? 0 : ( (x) > (y) ? 1 : -1 ))
+
+struct FasttabSnapshotData; /* forward declaration required for
+ * relation_is_inmem_tuple_function typedef */
+typedef struct FasttabSnapshotData *FasttabSnapshot;
+
+typedef bool (*relation_is_inmem_tuple_function)
+ (Relation relation, HeapTuple tup, FasttabSnapshot fasttab_snapshot,
+ int tableIdx);
+
+#define FasttabRelationMaxOidAttributes 2
+
+typedef const struct
+{
+ Oid relationId;
+ relation_is_inmem_tuple_function is_inmem_tuple_fn;
+ AttrNumber noidattr;
+ AttrNumber attrNumbers[FasttabRelationMaxOidAttributes];
+} FasttabRelationMethodsData;
+
+typedef FasttabRelationMethodsData const *FasttabRelationMethods;
+
+static bool generic_is_inmem_tuple(Relation relation, HeapTuple tup,
+ FasttabSnapshot fasttab_snapshot, int tableIdx);
+static bool pg_class_is_inmem_tuple(Relation relation, HeapTuple tup,
+ FasttabSnapshot fasttab_snapshot, int tableIdx);
+
+/* NB: keep this array sorted by relationId */
+static FasttabRelationMethodsData FasttabRelationMethodsTable[] =
+{
+ /* 1247 */
+ {TypeRelationId, &generic_is_inmem_tuple, 1,
+ {Anum_pg_type_typrelid, 0}
+ },
+ /* 1249 */
+ {AttributeRelationId, &generic_is_inmem_tuple, 1,
+ {Anum_pg_attribute_attrelid, 0}
+ },
+ /* 1259 */
+ {RelationRelationId, &pg_class_is_inmem_tuple, 0,
+ {0, 0}
+ },
+ /* 2608 */
+ {DependRelationId, &generic_is_inmem_tuple, 2,
+ {Anum_pg_depend_objid, Anum_pg_depend_refobjid}
+ },
+ /* 2611 */
+ {InheritsRelationId, &generic_is_inmem_tuple, 2,
+ {Anum_pg_inherits_inhrelid, Anum_pg_inherits_inhparent}
+ },
+ /* 2619 */
+ {StatisticRelationId, &generic_is_inmem_tuple, 1,
+ {Anum_pg_statistic_starelid, 0}
+ },
+};
+
+#define FasttabIndexMaxAttributes 3
+
+typedef enum FasttabCompareMethod
+{
+ CompareInvalid,
+ CompareOid,
+ CompareCString,
+ CompareInt16,
+ CompareInt64,
+ CompareBoolean,
+} FasttabCompareMethod;
+
+/* typedef is in fasttab.h */
+struct FasttabIndexMethodsData
+{
+ Oid indexId;
+ AttrNumber nattr;
+ AttrNumber attrNumbers[FasttabIndexMaxAttributes]; /* NB: can be negative */
+ FasttabCompareMethod attrCompareMethod[FasttabIndexMaxAttributes];
+};
+
+/*
+ * NB: Keep this array sorted by indexId!
+ * NB: Uniqueness checks are not implemented and apparently are not required.
+ * Still please keep comments regarding uniqueness, just in case.
+ */
+static FasttabIndexMethodsData FasttabIndexMethodsTable[] =
+{
+ /* 2187, non-unique */
+ {InheritsParentIndexId, 1,
+ {Anum_pg_inherits_inhparent, 0, 0},
+ {CompareOid, CompareInvalid, CompareInvalid}
+ },
+ /* 2658, unique */
+ {AttributeRelidNameIndexId, 2,
+ {Anum_pg_attribute_attrelid, Anum_pg_attribute_attname, 0},
+ {CompareOid, CompareCString, CompareInvalid}
+ },
+ /* 2659, unique */
+ {AttributeRelidNumIndexId, 2,
+ {Anum_pg_attribute_attrelid, Anum_pg_attribute_attnum, 0},
+ {CompareOid, CompareInt16, CompareInvalid}
+ },
+ /* 2662, unique */
+ {ClassOidIndexId, 1,
+ {ObjectIdAttributeNumber, 0, 0},
+ {CompareOid, CompareInvalid, CompareInvalid}
+ },
+ /* 2663, unique */
+ {ClassNameNspIndexId, 2,
+ {Anum_pg_class_relname, Anum_pg_class_relnamespace, 0},
+ {CompareCString, CompareOid, CompareInvalid}
+ },
+ /* 2673, non-unique */
+ {DependDependerIndexId, 3,
+ {Anum_pg_depend_classid, Anum_pg_depend_objid, Anum_pg_depend_objsubid},
+ {CompareOid, CompareOid, CompareInt64}
+ },
+ /* 2674, non-unique */
+ {DependReferenceIndexId, 3,
+ {Anum_pg_depend_refclassid, Anum_pg_depend_refobjid,
+ Anum_pg_depend_refobjsubid},
+ {CompareOid, CompareOid, CompareInt64}
+ },
+ /* 2680, unique */
+ {InheritsRelidSeqnoIndexId, 2,
+ {Anum_pg_inherits_inhrelid, Anum_pg_inherits_inhseqno, 0},
+ {CompareOid, CompareOid, CompareInvalid}
+ },
+ /* 2696, unique */
+ {StatisticRelidAttnumInhIndexId, 3,
+ {Anum_pg_statistic_starelid, Anum_pg_statistic_staattnum,
+ Anum_pg_statistic_stainherit},
+ {CompareOid, CompareInt16, CompareBoolean}
+ },
+ /* 2703, unique */
+ {TypeOidIndexId, 1,
+ {ObjectIdAttributeNumber, 0, 0},
+ {CompareOid, CompareInvalid, CompareInvalid}
+ },
+ /* 2704, unique */
+ {TypeNameNspIndexId, 2,
+ {Anum_pg_type_typname, Anum_pg_type_typnamespace, 0},
+ {CompareCString, CompareOid, CompareInvalid}
+ },
+ /* 3455, non-unique */
+ {ClassTblspcRelfilenodeIndexId, 2,
+ {Anum_pg_class_reltablespace, Anum_pg_class_relfilenode, 0},
+ {CompareOid, CompareOid, CompareInvalid}
+ },
+};
+
+#define FasttabSnapshotTablesNumber (lengthof(FasttabRelationMethodsTable))
+
+typedef struct
+{
+ int tuples_num;
+ dlist_head tuples;
+} FasttabSnapshotRelationData;
+
+/* snapshot (i.e. transaction/savepoint) representation */
+struct FasttabSnapshotData
+{
+ struct FasttabSnapshotData *prev;
+ char *name; /* NULL for root and anonymous snapshots */
+
+ /* NB: see GetSnapshotRelationIdxByOid */
+ FasttabSnapshotRelationData relationData[FasttabSnapshotTablesNumber];
+} FasttabSnapshotData;
+
+/*****************************************************************************
+ GLOBAL VARIABLES
+ *****************************************************************************/
+
+/* for GetLocalMemoryContext */
+static MemoryContext LocalMemoryContextPrivate = NULL;
+
+/* for GenFasttabItemPointerData */
+static uint32 CurrentFasttabBlockId = 0;
+static uint16 CurrentFasttabOffset = 1; /* 0 is considered invalid */
+
+/* for FasttabSnapshotGetCurrent */
+static FasttabSnapshot CurrentFasttabSnapshotPrivate = NULL;
+
+static char CurrentRelpersistenceHint = RELPERSISTENCE_UNDEFINED;
+
+/*****************************************************************************
+ UTILITY PROCEDURES
+ *****************************************************************************/
+
+void
+fasttab_set_relpersistence_hint(char relpersistence)
+{
+ CurrentRelpersistenceHint = relpersistence;
+}
+
+void
+fasttab_clear_relpersistence_hint(void)
+{
+ CurrentRelpersistenceHint = RELPERSISTENCE_UNDEFINED;
+}
+
+/*
+ * binary search in FasttabRelationMethodsTable
+ * == -1 - not found
+ * > 0 - found on N-th position
+ */
+static int
+GetSnapshotRelationIdxByOidInternal(Oid relId)
+{
+ int begin = 0;
+ int end = FasttabSnapshotTablesNumber - 1;
+
+#ifdef USE_ASSERT_CHECKING
+ /* test that FasttabRelationMethodsTable is properly sorted */
+ int i;
+
+ for (i = 0; i <= end; i++)
+ {
+ Assert(PointerIsValid(FasttabRelationMethodsTable[i].is_inmem_tuple_fn));
+ if (i > 0)
+ Assert(FasttabRelationMethodsTable[i - 1].relationId < FasttabRelationMethodsTable[i].relationId);
+ }
+#endif
+
+ while (begin < end)
+ {
+ int test = (begin + end) / 2;
+
+ if (FasttabRelationMethodsTable[test].relationId == relId)
+ {
+ begin = test;
+ break;
+ }
+
+ if (FasttabRelationMethodsTable[test].relationId < relId)
+ begin = test + 1; /* go right */
+ else
+ end = test - 1; /* go left */
+ }
+
+ if (FasttabRelationMethodsTable[begin].relationId == relId)
+ return begin; /* found */
+ else
+ return -1; /* not found */
+}
+
+bool
+IsFasttabHandledRelationId(Oid relId)
+{
+ return (GetSnapshotRelationIdxByOidInternal(relId) >= 0);
+}
+
+static int
+GetSnapshotRelationIdxByOid(Oid relId)
+{
+ int result;
+
+ Assert(IsFasttabHandledRelationId(relId));
+ result = GetSnapshotRelationIdxByOidInternal(relId);
+ Assert(result >= 0 && result < FasttabSnapshotTablesNumber);
+ return result;
+}
+
+/*
+ * binary search in FasttabIndexMethodsTable
+ * == NULL - not found
+ * != NULL - found
+ */
+static FasttabIndexMethods
+GetFasttabIndexMethodsInternal(Oid indexId)
+{
+ int begin = 0;
+ int end = (sizeof(FasttabIndexMethodsTable) /
+ sizeof(FasttabIndexMethodsTable[0]) - 1);
+
+#ifdef USE_ASSERT_CHECKING
+ /* test that FasttabIndexMethodsTable is properly sorted */
+ int i;
+
+ for (i = 0; i <= end; i++)
+ {
+ if (i > 0)
+ Assert(FasttabIndexMethodsTable[i - 1].indexId < FasttabIndexMethodsTable[i].indexId);
+ }
+#endif
+
+ while (begin < end)
+ {
+ int test = (begin + end) / 2;
+
+ if (FasttabIndexMethodsTable[test].indexId == indexId)
+ {
+ begin = test;
+ break;
+ }
+
+ if (FasttabIndexMethodsTable[test].indexId < indexId)
+ begin = test + 1; /* go right */
+ else
+ end = test - 1; /* go left */
+ }
+
+ if (FasttabIndexMethodsTable[begin].indexId == indexId)
+ return &FasttabIndexMethodsTable[begin]; /* found */
+ else
+ return NULL; /* not found */
+}
+
+bool
+IsFasttabHandledIndexId(Oid indexId)
+{
+ return (GetFasttabIndexMethodsInternal(indexId) != NULL);
+}
+
+/*
+ * same as GetFasttabIndexMethodsInternal
+ * but never returns NULL
+ */
+inline static FasttabIndexMethods
+GetFasttabIndexMethods(Oid indexId)
+{
+ Assert(IsFasttabHandledIndexId(indexId));
+ return GetFasttabIndexMethodsInternal(indexId);
+}
+
+static MemoryContext
+GetLocalMemoryContext(void)
+{
+ if (!PointerIsValid(LocalMemoryContextPrivate))
+ {
+ LocalMemoryContextPrivate = AllocSetContextCreate(
+ NULL,
+ "Fast temporary tables memory context",
+ ALLOCSET_DEFAULT_MINSIZE,
+ ALLOCSET_DEFAULT_INITSIZE,
+ ALLOCSET_DEFAULT_MAXSIZE);
+ }
+
+ return LocalMemoryContextPrivate;
+}
+
+static ItemPointerData
+GenFasttabItemPointerData(void)
+{
+ ItemPointerData res;
+
+ BlockIdSet(&(res.ip_blkid), CurrentFasttabBlockId);
+ res.ip_posid = CurrentFasttabOffset | FASTTAB_ITEM_POINTER_BIT;
+
+ CurrentFasttabOffset++;
+
+ if (CurrentFasttabOffset > MaxHeapTuplesPerPage)
+ {
+ CurrentFasttabOffset = 1;
+ CurrentFasttabBlockId++;
+
+#ifdef FASTTAB_DEBUG
+ elog(NOTICE, "FASTTAB: GenFasttabItemPointerData, CurrentFasttabOffset > MaxHeapTuplesPerPage (%d), new values - CurrentFasttabOffset = %d, CurrentFasttabBlockId = %d",
+ MaxHeapTuplesPerPage, CurrentFasttabOffset, CurrentFasttabBlockId);
+#endif
+ }
+
+ return res;
+}
+
+static FasttabSnapshot
+FasttabSnapshotCreateEmpty(void)
+{
+ FasttabSnapshot result;
+ MemoryContext oldctx = MemoryContextSwitchTo(GetLocalMemoryContext());
+
+ result = palloc0(sizeof(FasttabSnapshotData));
+ MemoryContextSwitchTo(oldctx);
+ return result;
+}
+
+static FasttabSnapshot
+FasttabSnapshotCopy(FasttabSnapshot src, const char *dst_name)
+{
+ int idx;
+ dlist_iter iter;
+ MemoryContext oldctx;
+ FasttabSnapshot dst = FasttabSnapshotCreateEmpty();
+
+ oldctx = MemoryContextSwitchTo(GetLocalMemoryContext());
+ dst->name = dst_name ? pstrdup(dst_name) : NULL;
+
+ for (idx = 0; idx < FasttabSnapshotTablesNumber; idx++)
+ {
+ dst->relationData[idx].tuples_num = src->relationData[idx].tuples_num;
+ dlist_foreach(iter, &src->relationData[idx].tuples)
+ {
+ DListHeapTuple src_dlist_tup = (DListHeapTuple) iter.cur;
+ DListHeapTuple dst_dlist_tup = palloc0(sizeof(DListHeapTupleData));
+
+ dst_dlist_tup->tup = heap_copytuple(src_dlist_tup->tup);
+ dlist_push_tail(&dst->relationData[idx].tuples,
+ &dst_dlist_tup->node);
+ }
+ }
+
+ MemoryContextSwitchTo(oldctx);
+ return dst;
+}
+
+static void
+DListHeapTupleFree(DListHeapTuple dlist_tup)
+{
+ heap_freetuple(dlist_tup->tup);
+ pfree(dlist_tup);
+}
+
+static void
+FasttabDListFree(dlist_head *head)
+{
+ while (!dlist_is_empty(head))
+ {
+ DListHeapTuple dlist_tup = (DListHeapTuple) dlist_pop_head_node(head);
+
+ DListHeapTupleFree(dlist_tup);
+ }
+}
+
+static void
+FasttabSnapshotFree(FasttabSnapshot fasttab_snapshot)
+{
+ int idx;
+
+ for (idx = 0; idx < FasttabSnapshotTablesNumber; idx++)
+ FasttabDListFree(&fasttab_snapshot->relationData[idx].tuples);
+
+ if (PointerIsValid(fasttab_snapshot->name))
+ pfree(fasttab_snapshot->name);
+
+ pfree(fasttab_snapshot);
+}
+
+static FasttabSnapshot
+FasttabSnapshotGetCurrent(void)
+{
+ if (!PointerIsValid(CurrentFasttabSnapshotPrivate))
+ CurrentFasttabSnapshotPrivate = FasttabSnapshotCreateEmpty();
+
+ return CurrentFasttabSnapshotPrivate;
+}
+
+static void
+FasttabSnapshotPushFront(FasttabSnapshot fasttab_snapshot)
+{
+ FasttabSnapshot temp = FasttabSnapshotGetCurrent();
+
+ while (!FasttabSnapshotIsRoot(temp))
+ temp = temp->prev;
+
+ temp->prev = fasttab_snapshot;
+ fasttab_snapshot->prev = NULL;
+}
+
+static inline void
+FasttabSnapshotPushBack(FasttabSnapshot fasttab_snapshot)
+{
+ fasttab_snapshot->prev = FasttabSnapshotGetCurrent();
+ CurrentFasttabSnapshotPrivate = fasttab_snapshot;
+}
+
+/* valid FasttabSnapshot or NULL if only root snapshot is left */
+static FasttabSnapshot
+FasttabSnapshotPopBack(void)
+{
+ FasttabSnapshot curr = FasttabSnapshotGetCurrent();
+
+ if (FasttabSnapshotIsRoot(curr))
+ return NULL;
+
+ CurrentFasttabSnapshotPrivate = curr->prev;
+ curr->prev = NULL;
+ return curr;
+}
+
+static void
+FasttabSnapshotCreate(const char *name)
+{
+ FasttabSnapshot src = FasttabSnapshotGetCurrent();
+ FasttabSnapshot dst = FasttabSnapshotCopy(src, name);
+
+ FasttabSnapshotPushBack(dst);
+}
+
+/*****************************************************************************
+ HOOKS IMPLEMENTATION
+ *****************************************************************************/
+
+/* on BEGIN (there could be already a transaction in progress!) */
+void
+fasttab_begin_transaction(void)
+{
+#ifdef FASTTAB_DEBUG
+ elog(NOTICE, "FASTTAB: fasttab_begin_transaction, transaction is already in progress: %u",
+ FasttabTransactionInProgress());
+#endif
+
+ if (FasttabTransactionInProgress())
+ return;
+
+ /* begin transaction */
+ FasttabSnapshotCreate(NULL);
+ Assert(FasttabTransactionInProgress());
+ Assert(FasttabSnapshotIsAnonymous(FasttabSnapshotGetCurrent()));
+}
+
+/* on COMMIT (there could be no transaction in progress!) */
+void
+fasttab_end_transaction(void)
+{
+#ifdef FASTTAB_DEBUG
+ elog(NOTICE, "FASTTAB: fasttab_end_transaction result = %u (1 - commit, 0 - rollback)"
+ ", transaction is in progress: %u", result, FasttabTransactionInProgress());
+#endif
+
+ if (!FasttabTransactionInProgress())
+ return;
+
+ Assert(FasttabSnapshotIsAnonymous(FasttabSnapshotGetCurrent()));
+
+ /*
+ * commit transaction - 1) save top snapshot to the bottom of snapshots
+ * stack 2) get rid of all snapshots except the root
+ */
+ FasttabSnapshotPushFront(FasttabSnapshotPopBack());
+ fasttab_abort_transaction();
+}
+
+/* on ROLLBACK (maybe there is in fact no transaction running!) */
+void
+fasttab_abort_transaction(void)
+{
+ FasttabSnapshot fasttab_snapshot;
+
+#ifdef FASTTAB_DEBUG
+ elog(NOTICE, "FASTTAB: fasttab_abort_transaction, transaction is in progress: %u (it's OK if this procedure is called from fasttab_end_transaction - see the code)",
+ FasttabTransactionInProgress());
+#endif
+
+ if (!FasttabTransactionInProgress())
+ return;
+
+ for (;;)
+ {
+ fasttab_snapshot = FasttabSnapshotPopBack();
+ if (!fasttab_snapshot) /* nothing left to pop */
+ break;
+
+ FasttabSnapshotFree(fasttab_snapshot);
+ }
+
+ Assert(!FasttabTransactionInProgress());
+}
+
+/* on SAVEPOINT name; */
+void
+fasttab_define_savepoint(const char *name)
+{
+ Assert(FasttabTransactionInProgress());
+ Assert(FasttabSnapshotIsAnonymous(FasttabSnapshotGetCurrent()));
+
+ /*
+ * name could be NULL in 'rollback to savepoint' case this case is already
+ * handled by fasttab_rollback_to_savepoint
+ */
+ if (!PointerIsValid(name))
+ return;
+
+#ifdef FASTTAB_DEBUG
+ elog(NOTICE, "FASTTAB: fasttab_define_safepoint, name = '%s'", name);
+#endif
+
+ FasttabSnapshotCreate(name); /* savepoint to rollback to */
+ FasttabSnapshotCreate(NULL); /* current snapshot to store changes */
+
+ Assert(FasttabTransactionInProgress());
+}
+
+/*
+ * on ROLLBACK TO SAVEPOINT name;
+ * NB: there is no need to re-check that this savepoint exists.
+ * case of name (upper/lower) is valid too, no need to re-check this
+ */
+void
+fasttab_rollback_to_savepoint(const char *name)
+{
+ Assert(PointerIsValid(name));
+ Assert(FasttabTransactionInProgress());
+ Assert(FasttabSnapshotIsAnonymous(FasttabSnapshotGetCurrent()));
+
+#ifdef FASTTAB_DEBUG
+ elog(NOTICE, "FASTTAB: fasttab_rollback_to_savepoint, name = '%s'", name);
+#endif
+
+ for (;;)
+ {
+ FasttabSnapshot fasttab_snapshot = FasttabSnapshotGetCurrent();
+
+ Assert(!FasttabSnapshotIsRoot(fasttab_snapshot));
+
+ if ((!FasttabSnapshotIsAnonymous(fasttab_snapshot)) &&
+ (strcmp(fasttab_snapshot->name, name) == 0))
+ break;
+
+ FasttabSnapshotFree(FasttabSnapshotPopBack());
+ }
+
+ /* create a new current snapshot to store changes */
+ FasttabSnapshotCreate(NULL);
+}
+
+/* before returning from heap_beginscan_internal or heap_rescan */
+void
+fasttab_beginscan(HeapScanDesc scan)
+{
+ int idx;
+ Oid relid = RelationGetRelid(scan->rs_rd);
+ FasttabSnapshot fasttab_snapshot;
+
+ if (!IsFasttabHandledRelationId(relid))
+ return;
+
+ fasttab_snapshot = FasttabSnapshotGetCurrent();
+
+ idx = GetSnapshotRelationIdxByOid(relid);
+ if (dlist_is_empty(&fasttab_snapshot->relationData[idx].tuples))
+ scan->rs_curr_inmem_tupnode = NULL;
+ else
+ scan->rs_curr_inmem_tupnode = dlist_head_node(&fasttab_snapshot->relationData[idx].tuples);
+
+#ifdef FASTTAB_DEBUG
+ fasttab_scan_tuples_counter = 0;
+ elog(NOTICE, "FASTTAB: fasttab_beginscan, returning scan = %p, rs_curr_inmem_tupnode = %p", scan, scan->rs_curr_inmem_tupnode);
+#endif
+}
+
+/*
+ * in heap_getnext
+ * returns valid heap tuple if return value should be replaced or NULL otherwise
+ */
+HeapTuple
+fasttab_getnext(HeapScanDesc scan, ScanDirection direction)
+{
+ bool match;
+ int idx;
+ FasttabSnapshot fasttab_snapshot;
+ DListHeapTuple dlist_tup;
+ dlist_node *ret_node;
+
+ if (!IsFasttabHandledRelationId(RelationGetRelid(scan->rs_rd)))
+ return NULL;
+
+ /* other directions are not used */
+ Assert(ScanDirectionIsForward(direction));
+
+ fasttab_snapshot = FasttabSnapshotGetCurrent();
+ idx = GetSnapshotRelationIdxByOid(RelationGetRelid(scan->rs_rd));
+
+ /*
+ * simple strategy - first return all in-memory tuples, then proceed with
+ * others
+ */
+ while (scan->rs_curr_inmem_tupnode) /* inmemory tuples enumiration is
+ * still in progress? */
+ {
+ ret_node = scan->rs_curr_inmem_tupnode;
+
+ if (dlist_has_next(&fasttab_snapshot->relationData[idx].tuples, ret_node))
+ scan->rs_curr_inmem_tupnode = dlist_next_node(&fasttab_snapshot->relationData[idx].tuples, ret_node);
+ else
+ scan->rs_curr_inmem_tupnode = NULL;
+
+ dlist_tup = (DListHeapTuple) ret_node;
+
+#ifdef FASTTAB_DEBUG
+ fasttab_scan_tuples_counter++;
+ elog(NOTICE, "FASTTAB: fasttab_getnext, scan = %p, counter = %u, direction = %d, return tuple t_self = %08X/%04X, oid = %d",
+ scan, fasttab_scan_tuples_counter, direction,
+ BlockIdGetBlockNumber(&dlist_tup->tup->t_self.ip_blkid), dlist_tup->tup->t_self.ip_posid, HeapTupleGetOid(dlist_tup->tup)
+ );
+#endif
+
+ /* HeapKeyTest is a macro, it changes `match` variable */
+ HeapKeyTest(dlist_tup->tup, RelationGetDescr(scan->rs_rd), scan->rs_nkeys, scan->rs_key, match);
+ if (!match)
+ continue;
+
+ return dlist_tup->tup;
+ }
+
+ return NULL;
+}
+
+/*
+ * Used in heap_hot_search_buffer
+ * true on override result, false otherwise
+ */
+bool
+fasttab_hot_search_buffer(ItemPointer tid, Relation relation,
+ HeapTuple heapTuple, bool *all_dead, bool *result)
+{
+ FasttabSnapshot fasttab_snapshot;
+ dlist_iter iter;
+ int idx;
+ bool found = false;
+
+ if (!IsFasttabItemPointer(tid))
+ return false;
+
+ Assert(IsFasttabHandledRelationId(RelationGetRelid(relation)));
+
+ fasttab_snapshot = FasttabSnapshotGetCurrent();
+ idx = GetSnapshotRelationIdxByOid(RelationGetRelid(relation));
+ dlist_foreach(iter, &fasttab_snapshot->relationData[idx].tuples)
+ {
+ DListHeapTuple dlist_tup = (DListHeapTuple) iter.cur;
+
+ if (ItemPointerEquals(&dlist_tup->tup->t_self, tid))
+ {
+ memcpy(heapTuple, dlist_tup->tup, sizeof(HeapTupleData));
+ found = true;
+ break;
+ }
+ }
+
+#ifdef FASTTAB_DEBUG
+ elog(NOTICE, "FASTTAB: fasttab_hot_search_buffer, tid = %08X/%04X, found = %u",
+ BlockIdGetBlockNumber(&tid->ip_blkid), tid->ip_posid, found);
+#endif
+
+ /* `all_dead` can be NULL during bitmap scan */
+ if (all_dead)
+ *all_dead = false;
+ /* apparently `result` can be false in ALTER TABLE case */
+ *result = found;
+ return true;
+}
+
+/*
+ * in heap_insert
+ * true on override, false otherwise
+ */
+bool
+fasttab_insert(Relation relation, HeapTuple tup, HeapTuple heaptup, Oid *result)
+{
+ FasttabSnapshot fasttab_snapshot;
+ MemoryContext oldctx;
+ DListHeapTuple dlist_tup;
+ int idx = GetSnapshotRelationIdxByOidInternal(RelationGetRelid(relation));
+
+ if (idx < 0) /* i.e. `!IsFasttabHandledRelationId` */
+ return false;
+
+ fasttab_snapshot = FasttabSnapshotGetCurrent();
+
+ /*
+ * NB: passing idx is kind of optimization, it could be actually
+ * re-calculated from relation argument
+ */
+ if (!FasttabRelationMethodsTable[idx].is_inmem_tuple_fn(relation,
+ tup, fasttab_snapshot, idx))
+ return false;
+
+ oldctx = MemoryContextSwitchTo(GetLocalMemoryContext());
+ heaptup->t_self = GenFasttabItemPointerData();
+ dlist_tup = palloc0(sizeof(DListHeapTupleData));
+ dlist_tup->tup = heap_copytuple(heaptup);
+ MemoryContextSwitchTo(oldctx);
+
+ dlist_push_tail(&fasttab_snapshot->relationData[idx].tuples,
+ &dlist_tup->node);
+ fasttab_snapshot->relationData[idx].tuples_num++;
+
+#ifdef FASTTAB_DEBUG
+ elog(NOTICE, "FASTTAB: fasttab_insert, dlist_tup->tup->t_self = %08X/%04X, oid = %d, inmemory tuples num = %d, heaptup oid = %d, idx = %d, relation relid = %d",
+ BlockIdGetBlockNumber(&dlist_tup->tup->t_self.ip_blkid),
+ dlist_tup->tup->t_self.ip_posid, HeapTupleGetOid(dlist_tup->tup),
+ fasttab_snapshot->relationData[idx].tuples_num,
+ HeapTupleGetOid(heaptup), idx, RelationGetRelid(relation)
+ );
+#endif
+
+ CacheInvalidateHeapTuple(relation, heaptup, NULL);
+ pgstat_count_heap_insert(relation, 1);
+ if (heaptup != tup)
+ {
+ tup->t_self = heaptup->t_self;
+ heap_freetuple(heaptup);
+ }
+
+ *result = HeapTupleGetOid(tup);
+ return true;
+}
+
+/*
+ * Remove pg_depend and pg_type records that would be kept in memory otherwise
+ * when relation with given Oid is deleted.
+ */
+static void
+fasttab_clean_catalog_on_relation_delete(Oid reloid)
+{
+ Oid curroid = reloid;
+ FasttabSnapshot fasttab_snapshot = FasttabSnapshotGetCurrent();
+ int dependIdx = GetSnapshotRelationIdxByOid(DependRelationId);
+ int typeIdx = GetSnapshotRelationIdxByOid(TypeRelationId);
+ Relation dependRel = relation_open(DependRelationId, AccessShareLock);
+ Relation typeRel = relation_open(TypeRelationId, AccessShareLock);
+ ItemPointerData itemPointerData;
+
+ for (;;)
+ {
+ dlist_iter iter;
+ bool isnull,
+ found = false;
+
+ /* Find pg_depend tuple with refobjid == curroid. */
+ dlist_foreach(iter, &fasttab_snapshot->relationData[dependIdx].tuples)
+ {
+ DListHeapTuple dlist_tup = (DListHeapTuple) iter.cur;
+ Oid refobjid = DatumGetObjectId(heap_getattr(dlist_tup->tup, Anum_pg_depend_refobjid,
+ RelationGetDescr(dependRel), &isnull));
+
+ if (refobjid == curroid)
+ {
+ found = true;
+ /* curroid := tuple.objid */
+ curroid = DatumGetObjectId(heap_getattr(dlist_tup->tup, Anum_pg_depend_objid,
+ RelationGetDescr(dependRel), &isnull));
+
+ /*
+ * Delete found tuple. Can't pass dlist_tup->tup->t_self as an
+ * argument - this memory is about to be freed.
+ */
+ itemPointerData = dlist_tup->tup->t_self;
+ fasttab_delete(dependRel, &itemPointerData);
+ break;
+ }
+ }
+
+ /* If not found - cleanup is done, end of loop */
+ if (!found)
+ break;
+
+ /* Find pg_type tuple with oid == curroid */
+ found = false;
+ dlist_foreach(iter, &fasttab_snapshot->relationData[typeIdx].tuples)
+ {
+ DListHeapTuple dlist_tup = (DListHeapTuple) iter.cur;
+ Oid oid = DatumGetObjectId(heap_getattr(dlist_tup->tup, ObjectIdAttributeNumber,
+ RelationGetDescr(typeRel), &isnull));
+
+ if (oid == curroid)
+ {
+ found = true;
+
+ /*
+ * Delete found tuple. Can't pass dlist_tup->tup->t_self as an
+ * argument - this memory is about to be freed.
+ */
+ itemPointerData = dlist_tup->tup->t_self;
+ fasttab_delete(typeRel, &itemPointerData);
+ break;
+ }
+ }
+
+ Assert(found);
+ }
+
+ relation_close(typeRel, AccessShareLock);
+ relation_close(dependRel, AccessShareLock);
+}
+
+/*
+ * on heap_delete
+ * true on success, false to proceeed as usual
+ */
+bool
+fasttab_delete(Relation relation, ItemPointer tid)
+{
+ FasttabSnapshot fasttab_snapshot;
+ dlist_iter iter;
+ int idx;
+
+ if (!IsFasttabItemPointer(tid))
+ return false;
+
+ Assert(IsFasttabHandledRelationId(RelationGetRelid(relation)));
+
+ fasttab_snapshot = FasttabSnapshotGetCurrent();
+ idx = GetSnapshotRelationIdxByOid(RelationGetRelid(relation));
+ dlist_foreach(iter, &fasttab_snapshot->relationData[idx].tuples)
+ {
+ DListHeapTuple dlist_tup = (DListHeapTuple) iter.cur;
+
+ if (ItemPointerEquals(&dlist_tup->tup->t_self, tid))
+ {
+ if (RelationGetRelid(relation) == RelationRelationId)
+ {
+ bool isnull;
+ Oid reloid = DatumGetObjectId(heap_getattr(dlist_tup->tup, ObjectIdAttributeNumber,
+ RelationGetDescr(relation), &isnull));
+
+ fasttab_clean_catalog_on_relation_delete(reloid);
+ }
+
+ pgstat_count_heap_delete(relation);
+ CacheInvalidateHeapTuple(relation, dlist_tup->tup, NULL);
+
+ dlist_delete(&dlist_tup->node);
+ DListHeapTupleFree(dlist_tup);
+ fasttab_snapshot->relationData[idx].tuples_num--;
+
+#ifdef FASTTAB_DEBUG
+ elog(NOTICE, "FASTTAB: fasttab_delete, tid = %08X/%04X - entry found and deleted, tuples_num = %d, idx = %d, rd_id = %d",
+ BlockIdGetBlockNumber(&tid->ip_blkid), tid->ip_posid,
+ fasttab_snapshot->relationData[idx].tuples_num, idx, relation->rd_id
+ );
+#endif
+
+ return true;
+ }
+ }
+
+ elog(ERROR, "in-memory tuple not found during delete");
+ return false; /* will be never reached */
+}
+
+/*
+ * on heap_update
+ * true on success, false to proceeed as usual
+ */
+bool
+fasttab_update(Relation relation, ItemPointer otid, HeapTuple newtup)
+{
+ FasttabSnapshot fasttab_snapshot;
+ dlist_iter iter;
+ int idx;
+
+ if (!IsFasttabItemPointer(otid))
+ return false;
+
+ Assert(IsFasttabHandledRelationId(RelationGetRelid(relation)));
+
+#ifdef FASTTAB_DEBUG
+ elog(NOTICE, "FASTTAB: fasttab_update, looking for otid = %08X/%04X",
+ BlockIdGetBlockNumber(&otid->ip_blkid), otid->ip_posid);
+#endif
+
+ fasttab_snapshot = FasttabSnapshotGetCurrent();
+ idx = GetSnapshotRelationIdxByOid(RelationGetRelid(relation));
+ dlist_foreach(iter, &fasttab_snapshot->relationData[idx].tuples)
+ {
+ DListHeapTuple dlist_tup = (DListHeapTuple) iter.cur;
+
+ if (ItemPointerEquals(&dlist_tup->tup->t_self, otid))
+ {
+ MemoryContext oldctx = MemoryContextSwitchTo(GetLocalMemoryContext());
+
+ heap_freetuple(dlist_tup->tup);
+
+ /*
+ * dont use old t_self for new tuple - it will cause an infinite
+ * loop, I checked :)
+ */
+ newtup->t_self = GenFasttabItemPointerData();
+ dlist_tup->tup = heap_copytuple(newtup);
+ MemoryContextSwitchTo(oldctx);
+
+ CacheInvalidateHeapTuple(relation, dlist_tup->tup, newtup);
+ pgstat_count_heap_update(relation, false);
+
+#ifdef FASTTAB_DEBUG
+ elog(NOTICE, "FASTTAB: fasttab_update - entry found and updated, newtup->t_self = %08X/%04X, oid = %d, tuples_num = %d, idx = %d",
+ BlockIdGetBlockNumber(&newtup->t_self.ip_blkid), newtup->t_self.ip_posid,
+ HeapTupleGetOid(dlist_tup->tup),
+ fasttab_snapshot->relationData[idx].tuples_num, idx);
+#endif
+ return true;
+ }
+ }
+
+ elog(ERROR, "in-memory tuple not found during update");
+ return false; /* will be never reached */
+}
+
+/*
+ * on heap_inplace_update
+ * true on success, false if proceed as usual
+ */
+bool
+fasttab_inplace_update(Relation relation, HeapTuple tuple)
+{
+ FasttabSnapshot fasttab_snapshot;
+ dlist_iter iter;
+ int idx;
+
+ if (!IsFasttabItemPointer(&tuple->t_self))
+ return false;
+
+ Assert(IsFasttabHandledRelationId(RelationGetRelid(relation)));
+
+#ifdef FASTTAB_DEBUG
+ elog(NOTICE, "FASTTAB: fasttab_heap_inplace_update, looking for tuple with tid = %08X/%04X, oid = %d...",
+ BlockIdGetBlockNumber(&tuple->t_self.ip_blkid), tuple->t_self.ip_posid,
+ HeapTupleGetOid(tuple));
+#endif
+
+ fasttab_snapshot = FasttabSnapshotGetCurrent();
+ idx = GetSnapshotRelationIdxByOid(RelationGetRelid(relation));
+ dlist_foreach(iter, &fasttab_snapshot->relationData[idx].tuples)
+ {
+ DListHeapTuple dlist_tup = (DListHeapTuple) iter.cur;
+
+ if (ItemPointerEquals(&dlist_tup->tup->t_self, &tuple->t_self))
+ {
+ MemoryContext oldctx = MemoryContextSwitchTo(GetLocalMemoryContext());
+
+ heap_freetuple(dlist_tup->tup);
+ dlist_tup->tup = heap_copytuple(tuple);
+ MemoryContextSwitchTo(oldctx);
+
+#ifdef FASTTAB_DEBUG
+ elog(NOTICE, "FASTTAB: fasttab_inplace_update - entry found and updated, tuples_num = %d, idx = %d",
+ fasttab_snapshot->relationData[idx].tuples_num, idx);
+#endif
+ if (!IsBootstrapProcessingMode())
+ CacheInvalidateHeapTuple(relation, tuple, NULL);
+
+ return true;
+ }
+ }
+
+ elog(ERROR, "in-memory tuple not found (heap_inplace_update)");
+ return false; /* will be never reached */
+}
+
+/*
+ * on index_insert
+ * true - override, false - continue
+ */
+bool
+fasttab_index_insert(Relation indexRelation, ItemPointer heap_t_ctid,
+ bool *result)
+{
+ Oid indexId = RelationGetRelid(indexRelation);
+
+ if (!IsFasttabItemPointer(heap_t_ctid))
+ return false;
+
+ Assert(IsFasttabHandledIndexId(indexId));
+
+#ifdef FASTTAB_DEBUG
+ elog(NOTICE, "FASTTAB: fasttab_index_insert, indexRelation relid = %u, heap_t_ctid = %08X/%04X",
+ RelationGetRelid(indexRelation),
+ BlockIdGetBlockNumber(&heap_t_ctid->ip_blkid),
+ heap_t_ctid->ip_posid);
+#endif
+
+ if (IsFasttabHandledIndexId(indexId))
+ {
+ *result = true;
+ return true; /* don't actually modify index */
+ }
+
+ return false;
+}
+
+/*
+ * > 0 - first is >
+ * 0 - tuples are equal
+ * < 0 - first is <
+ */
+static int
+fasttab_index_compare_tuples(HeapTuple first, HeapTuple second,
+ IndexScanDesc scan)
+{
+ TupleDesc tupledesc = RelationGetDescr(scan->heapRelation);
+ Datum datum1,
+ datum2;
+ bool isnull1,
+ isnull2;
+ int i,
+ result = 0;
+
+ for (i = 0; i < scan->indexMethods->nattr; i++)
+ {
+ Assert(scan->indexMethods->attrCompareMethod[i] != CompareInvalid);
+ datum1 = heap_getattr(first, scan->indexMethods->attrNumbers[i], tupledesc,
+ &isnull1);
+ datum2 = heap_getattr(second, scan->indexMethods->attrNumbers[i], tupledesc,
+ &isnull2);
+ Assert((!isnull1) && (!isnull2));
+
+ switch (scan->indexMethods->attrCompareMethod[i])
+ {
+ case CompareOid:
+ result = FasttabCompareInts(DatumGetObjectId(datum1),
+ DatumGetObjectId(datum2));
+ break;
+ case CompareCString:
+ result = strcmp(DatumGetCString(datum1),
+ DatumGetCString(datum2));
+ break;
+ case CompareInt16:
+ result = FasttabCompareInts(DatumGetInt16(datum1),
+ DatumGetInt16(datum2));
+ break;
+ case CompareInt64:
+ result = FasttabCompareInts(DatumGetInt64(datum1),
+ DatumGetInt64(datum2));
+ break;
+ case CompareBoolean:
+ result = FasttabCompareInts(DatumGetBool(datum1),
+ DatumGetBool(datum2));
+ break;
+ default: /* should never happen, can be useful during
+ * development though */
+ elog(ERROR, "Unexpected compare method: %d",
+ scan->indexMethods->attrCompareMethod[i]);
+ }
+
+ if (result != 0)
+ break;
+ }
+
+ return result;
+}
+
+/*
+ * for filling scan->xs_itup
+ * used during index-only scans
+ */
+static IndexTuple
+fasttab_index_form_tuple(HeapTuple tup, IndexScanDesc scan)
+{
+ TupleDesc heaptupledesc = RelationGetDescr(scan->heapRelation);
+ TupleDesc indextupledesc = RelationGetDescr(scan->indexRelation);
+ Datum values[FasttabIndexMaxAttributes];
+ bool isnull[FasttabIndexMaxAttributes];
+ int i;
+
+ for (i = 0; i < scan->indexMethods->nattr; i++)
+
+ /*
+ * NB: prcesses negative attribute numbers e.g.
+ * ObjectIdAttributeNumber just fine
+ */
+ values[i] = heap_getattr(tup, scan->indexMethods->attrNumbers[i],
+ heaptupledesc, &(isnull[i]));
+
+ return index_form_tuple(indextupledesc, values, isnull);
+}
+
+static inline AttrNumber
+fasttab_convert_index_attno_to_heap_attno(IndexScanDesc scan,
+ AttrNumber indexAttno)
+{
+ Assert(indexAttno > 0);
+ Assert(indexAttno <= FasttabIndexMaxAttributes);
+ Assert(indexAttno <= scan->indexMethods->nattr);
+ return scan->indexMethods->attrNumbers[indexAttno - 1];
+}
+
+static bool
+fasttab_index_tuple_matches_where_condition(IndexScanDesc scan, HeapTuple tup)
+{
+ int i;
+ bool insert;
+ AttrNumber attrNumbersBackup[FasttabIndexMaxAttributes];
+
+ if (scan->numberOfKeys == 0)
+ return true;
+
+ /* NB: scan->keyData[0].sk_strategy can be InvalidStrategy */
+ Assert(scan->keyData != NULL);
+ Assert(scan->keyData[0].sk_attno != InvalidAttrNumber);
+
+ /* convert index attribute numbers to tuple attribute numbers */
+ for (i = 0; i < scan->numberOfKeys; i++)
+ {
+ attrNumbersBackup[i] = scan->keyData[i].sk_attno;
+ scan->keyData[i].sk_attno = fasttab_convert_index_attno_to_heap_attno(scan, scan->keyData[i].sk_attno);
+ }
+
+ /* NB: HeapKeyTest is a macro, it changes `insert` variable */
+ HeapKeyTest(tup, RelationGetDescr(scan->heapRelation), scan->numberOfKeys,
+ scan->keyData, insert);
+
+ /* restore original attribute numbers */
+ for (i = 0; i < scan->numberOfKeys; i++)
+ scan->keyData[i].sk_attno = attrNumbersBackup[i];
+
+ return insert;
+}
+
+/*
+ * basically insert-sort implementation
+ * true - tuple added
+ * false - tuple not added, filtered by WHERE condition
+ */
+static bool
+fasttab_index_insert_tuple_in_sorted_list(IndexScanDesc scan, HeapTuple tup)
+{
+ DListHeapTuple dlist_tup;
+ dlist_node *insert_after = &scan->xs_inmem_tuplist.head;
+ dlist_iter iter;
+
+ /*
+ * apparently scan->orderByData is never specified in index-only scans for
+ * catalog tables
+ */
+ Assert(scan->numberOfOrderBys == 0);
+ Assert(scan->numberOfKeys >= 0 && scan->numberOfKeys <= FasttabIndexMaxAttributes);
+
+ if (!fasttab_index_tuple_matches_where_condition(scan, tup))
+ return false;
+
+ dlist_tup = palloc(sizeof(DListHeapTupleData));
+ dlist_tup->tup = heap_copytuple(tup);
+
+ dlist_foreach(iter, &scan->xs_inmem_tuplist)
+ {
+ DListHeapTuple dlist_curr = (DListHeapTuple) iter.cur;
+
+ if (fasttab_index_compare_tuples(dlist_curr->tup, tup, scan) >= 0)
+ break;
+
+ insert_after = iter.cur;
+ }
+
+ dlist_insert_after(insert_after, &dlist_tup->node);
+
+ /* NB: apparently HeapTupleGetOid(tup) == InvalidOid (0) case is OK */
+#ifdef FASTTAB_DEBUG
+ elog(NOTICE, "FASTTAB: fasttab_index_insert_tuple_in_sorted_list scan = %p, tup oid = %d, tuple added to list",
+ scan, HeapTupleGetOid(tup));
+#endif
+
+ return true;
+}
+
+/*
+ * on index_geinscan_internal
+ * NB: scan->keyData is not initialized here (usually filled with 0x7f's)
+ */
+void
+fasttab_index_beginscan(IndexScanDesc scan)
+{
+ Oid indexId = RelationGetRelid(scan->indexRelation);
+
+ Assert(PointerIsValid(scan->indexRelation));
+
+ if (!IsFasttabHandledIndexId(indexId))
+ return;
+
+ scan->xs_regular_tuple_enqueued = false;
+ scan->xs_regular_scan_finished = false;
+ scan->xs_scan_finish_returned = false;
+
+ /* indexMethods is accessed quite often so we memoize it */
+ scan->indexMethods = GetFasttabIndexMethods(indexId);
+
+ /*
+ * Delayed initialization of scan->xs_inmem_tuplist is required when
+ * fasttab_index_getnext_tid_merge is called first time. The idea heare
+ * is the same as in lazy evaluation 1) It's more efficient then
+ * initializing a list here, since sometimes beginscan/rescan are called
+ * without any scanning 2) We don't waste memory for tuples we don't need
+ * since they will be filtered anyway 3) Besides sometimes `scan` is
+ * passed to beginscan is not fully initilized so we can't actually filter
+ * tuples by WHERE condition here
+ */
+ scan->xs_inmem_tuplist_init_done = false;
+
+ dlist_init(&scan->xs_inmem_tuplist);
+
+ /*
+ * Make sure scan->xs_ctup.t_self has proper initial value (required in
+ * index_getnext_tid)
+ */
+ ItemPointerSetInvalid(&scan->xs_ctup.t_self);
+
+#ifdef FASTTAB_DEBUG
+ elog(NOTICE, "FASTTAB: fasttab_index_beginscan (could be called from rescan), scan = %p, indexId = %u "
+ "scan->numberOfKeys = %d, scan->keyData = %p, scan->numberOfOrderBys = %d, scan->orderByData = %p",
+ scan, indexId, scan->numberOfKeys, scan->keyData, scan->numberOfOrderBys,
+ scan->orderByData
+ );
+#endif
+
+}
+
+/* on index_endscan */
+void
+fasttab_index_endscan(IndexScanDesc scan)
+{
+ Assert(PointerIsValid(scan->indexRelation));
+
+ if (!IsFasttabHandledIndexId(RelationGetRelid(scan->indexRelation)))
+ return;
+
+ /* free in-memory tuples left */
+ FasttabDListFree(&scan->xs_inmem_tuplist);
+
+#ifdef FASTTAB_DEBUG
+ elog(NOTICE, "FASTTAB: fasttab_index_endscan (could be called from rescan), scan = %p, scan->indexRelation relid = %u",
+ scan, RelationGetRelid(scan->indexRelation)
+ );
+#endif
+
+}
+
+/*
+ * on index_rescan
+ * NB: scan->keyData is not initialized here (usually filled with 0x7f's)
+ */
+void
+fasttab_index_rescan(IndexScanDesc scan, ScanKey keys, int nkeys,
+ ScanKey orderbys, int norderbys)
+{
+ fasttab_index_endscan(scan);
+ fasttab_index_beginscan(scan);
+}
+
+/*
+ * almost as heap_fetch with keep_buf = true, but also understands HOT chains
+ * true - tuple found
+ * false - tuple not found
+ */
+bool
+fasttab_simple_heap_fetch(Relation relation, Snapshot snapshot,
+ HeapTuple tuple)
+{
+ Page page;
+ bool found;
+ Buffer buffer = InvalidBuffer;
+ ItemPointer tid = &(tuple->t_self);
+
+ /*
+ * No need to lock any buffers for in-memory tuple, they could not even
+ * exist!
+ */
+ if (IsFasttabItemPointer(tid))
+ return heap_hot_search_buffer(tid, relation, buffer, snapshot, tuple, NULL, true);
+
+ /* Fetch and pin the appropriate page of the relation. */
+ buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
+
+ /* Need share lock on buffer to examine tuple commit status. */
+ LockBuffer(buffer, BUFFER_LOCK_SHARE);
+ page = BufferGetPage(buffer);
+ TestForOldSnapshot(snapshot, relation, page);
+
+ found = heap_hot_search_buffer(tid, relation, buffer, snapshot, tuple,
+ NULL, true);
+
+ LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
+ ReleaseBuffer(buffer);
+
+ return found;
+}
+
+static void
+fasttab_index_make_sure_inmem_tuplist_init_done(IndexScanDesc scan)
+{
+ FasttabSnapshot fasttab_snapshot;
+ dlist_iter iter;
+ int idx;
+
+ Assert(PointerIsValid(scan->indexRelation));
+
+ /* initialize scan->xs_inmem_tuplist during first call */
+ if (scan->xs_inmem_tuplist_init_done)
+ return;
+
+ idx = GetSnapshotRelationIdxByOid(RelationGetRelid(scan->heapRelation));
+
+ fasttab_snapshot = FasttabSnapshotGetCurrent();
+ dlist_foreach(iter, &fasttab_snapshot->relationData[idx].tuples)
+ {
+ DListHeapTuple dlist_curr = (DListHeapTuple) iter.cur;
+
+ (void) fasttab_index_insert_tuple_in_sorted_list(scan, dlist_curr->tup);
+ }
+
+ scan->xs_inmem_tuplist_init_done = true;
+}
+
+/*
+ * on index_getnext_tid
+ * if found == true, &scan->xs_ctup.t_self is a regular current ItemPointer
+ * save resulting ItemPointer to &scan->xs_ctup.t_self
+ * NB: we filter tuples using scan->keyData HERE since it's not always
+ * initialized when fasttab_index_beginscan or _rescan is called (usually
+ * filled with 0x7f's)
+ */
+bool
+fasttab_index_getnext_tid_merge(IndexScanDesc scan, ScanDirection direction)
+{
+ bool fetched;
+ DListHeapTuple ret_node;
+
+ Assert(PointerIsValid(scan->indexRelation));
+
+ if (!IsFasttabHandledIndexId(RelationGetRelid(scan->indexRelation)))
+ /* regular logic */
+ return scan->indexRelation->rd_amroutine->amgettuple(scan, direction);
+
+ /* initialize scan->xs_inmem_tuplist during first call */
+ fasttab_index_make_sure_inmem_tuplist_init_done(scan);
+
+ if (dlist_is_empty(&scan->xs_inmem_tuplist)) /* in-memory tuples
+ * enumiration is over? */
+ {
+#ifdef FASTTAB_DEBUG
+ elog(NOTICE, "FASTTAB: fasttab_index_getnext_tid_merge, scan = %p, fake tuples list is empty, xs_regular_scan_finished = %u, xs_scan_finish_returned = %u",
+ scan, scan->xs_regular_scan_finished, scan->xs_scan_finish_returned);
+#endif
+
+ /*
+ * If ->amgettuple() already returned false we should not call it once
+ * again. In this case btree index will start a scan all over again,
+ * see btgettuple implementation. Still if user will call this
+ * procedure once again dispite of returned 'false' value she probably
+ * knows what she is doing.
+ */
+ if (scan->xs_regular_scan_finished && (!scan->xs_scan_finish_returned))
+ {
+ scan->xs_scan_finish_returned = true;
+ return false;
+ }
+
+ /* regular logic */
+ return scan->indexRelation->rd_amroutine->amgettuple(scan, direction);
+ }
+
+ /*
+ * Apparently other directions are not used in index-only scans for
+ * catalog tables. No need to check direction above this point since only
+ * here scan->xs_inmem_tuplist is both initialized and non-empty.
+ */
+ Assert(ScanDirectionIsForward(direction));
+
+ /* there is no regular tuple in in-memory queue, we should load one */
+ while ((!scan->xs_regular_tuple_enqueued) && (!scan->xs_regular_scan_finished))
+ {
+ if (scan->indexRelation->rd_amroutine->amgettuple(scan, direction))
+ {
+ HeapTupleData regular_tup;
+
+ regular_tup.t_self = scan->xs_ctup.t_self;
+ fetched = fasttab_simple_heap_fetch(scan->heapRelation, scan->xs_snapshot,
+ ®ular_tup);
+
+ if (!fetched)
+ {
+#ifdef FASTTAB_DEBUG
+ elog(NOTICE, "FASTTAB: fasttab_index_getnext_tid_merge, scan = %p, indexed tuple not found, 'continue;'",
+ scan);
+#endif
+ continue;
+ }
+ scan->xs_regular_tuple_enqueued = fasttab_index_insert_tuple_in_sorted_list(scan, ®ular_tup);
+ }
+ else
+ scan->xs_regular_scan_finished = true;
+ }
+
+ Assert(scan->xs_regular_scan_finished || scan->xs_regular_tuple_enqueued);
+
+ ret_node = (DListHeapTuple) dlist_pop_head_node(&scan->xs_inmem_tuplist);
+ Assert(PointerIsValid(ret_node));
+
+ scan->xs_recheck = false; /* see 'system catalog scans with lossy index
+ * conditions are not implemented' in genam.c */
+
+ /*
+ * we could write `heap_copytuple_with_tuple(ret_node->tup,
+ * &scan->xs_ctup)` here as well
+ */
+ ItemPointerCopy(&ret_node->tup->t_self, &scan->xs_ctup.t_self);
+
+ if (!IsFasttabItemPointer(&scan->xs_ctup.t_self))
+ scan->xs_regular_tuple_enqueued = false;
+
+#ifdef FASTTAB_DEBUG
+ elog(NOTICE, "FASTTAB: fasttab_index_getnext_tid_merge, scan = %p, direction = %d, scan->indexRelation relid = %u, return tuple tid = %08X/%04X",
+ scan, direction, RelationGetRelid(scan->indexRelation),
+ BlockIdGetBlockNumber(&scan->xs_ctup.t_self.ip_blkid),
+ scan->xs_ctup.t_self.ip_posid
+ );
+#endif
+
+ /* scan->xs_itup should not be NULL! */
+ scan->xs_itup = fasttab_index_form_tuple(ret_node->tup, scan);
+
+ DListHeapTupleFree(ret_node);
+ return true;
+}
+
+/*
+ * on index_getmap
+ * true - override done
+ * fasle - use regular logic
+ */
+bool
+fasttab_index_getbitmap(IndexScanDesc scan, TIDBitmap *bitmap, int64 *result)
+{
+ int64 ntids = 0;
+ bool heap_opened = false;
+
+ Assert(PointerIsValid(scan->indexRelation));
+
+ if (!IsFasttabHandledIndexId(RelationGetRelid(scan->indexRelation)))
+ return false;
+
+ /* fill scan->heapRelation if it's NULL, we require it in our hooks */
+ if (!scan->heapRelation)
+ {
+ scan->heapRelation = heap_open(scan->indexRelation->rd_index->indrelid,
+ NoLock);
+ heap_opened = true;
+ }
+
+ /* initialize scan->xs_inmem_tuplist during first call */
+ fasttab_index_make_sure_inmem_tuplist_init_done(scan);
+
+ if (dlist_is_empty(&scan->xs_inmem_tuplist)) /* there are if fact no
+ * in-memory tuples? */
+ {
+ if (heap_opened) /* cleanup */
+ {
+ heap_close(scan->heapRelation, NoLock);
+ scan->heapRelation = NULL;
+ }
+ return false;
+ }
+
+ while (fasttab_index_getnext_tid_merge(scan, ForwardScanDirection))
+ {
+ tbm_add_tuples(bitmap, &scan->xs_ctup.t_self, 1, false);
+ ntids++;
+ }
+
+ if (heap_opened) /* cleanup */
+ {
+ heap_close(scan->heapRelation, NoLock);
+ scan->heapRelation = NULL;
+ }
+
+ *result = ntids;
+ return true;
+}
+
+
+/*****************************************************************************
+ PROCEDURES USED IN FasttabRelationMethodsTable
+ *****************************************************************************/
+
+static bool
+generic_is_inmem_tuple(Relation relation, HeapTuple tup,
+ FasttabSnapshot fasttab_snapshot, int tableIdx)
+{
+ dlist_iter iter;
+ TupleDesc tupledesc;
+ Oid values[FasttabRelationMaxOidAttributes];
+ bool isnull;
+ int i,
+ pg_class_idx,
+ noidattr = FasttabRelationMethodsTable[tableIdx].noidattr;
+
+ Assert(IsFasttabHandledRelationId(RelationGetRelid(relation)));
+ Assert(tableIdx >= 0 && tableIdx < FasttabSnapshotTablesNumber);
+ Assert(noidattr > 0 && noidattr <= FasttabRelationMaxOidAttributes);
+
+ /*
+ * Special case. During table creation pg_type and pg_depend are modified
+ * before pg_class (see heap_create_with_catalog implementation) so there
+ * is no way to tell wheter tuples are in-memory without using
+ * relperistence hint. Also this check could be considered as an
+ * optimization.
+ */
+ if ((RelationGetRelid(relation) == TypeRelationId) || (RelationGetRelid(relation) == DependRelationId))
+ return (CurrentRelpersistenceHint == RELPERSISTENCE_FAST_TEMP);
+
+ tupledesc = RelationGetDescr(relation);
+
+ for (i = 0; i < noidattr; i++)
+ {
+ values[i] = DatumGetObjectId(heap_getattr(tup,
+ FasttabRelationMethodsTable[tableIdx].attrNumbers[i],
+ tupledesc, &isnull));
+ Assert(!isnull);
+ }
+
+ /*
+ * Check whether there is an in-memory pg_class tuple with oid from
+ * values[] array
+ */
+ pg_class_idx = GetSnapshotRelationIdxByOid(RelationRelationId);
+ dlist_foreach(iter, &fasttab_snapshot->relationData[pg_class_idx].tuples)
+ {
+ DListHeapTuple dlist_tup = (DListHeapTuple) iter.cur;
+ Oid oid = HeapTupleGetOid(dlist_tup->tup);
+
+ for (i = 0; i < noidattr; i++)
+ {
+ if (oid == values[i])
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static bool
+pg_class_is_inmem_tuple(Relation relation, HeapTuple tup,
+ FasttabSnapshot fasttab_snapshot, int tableIdx)
+{
+ bool isnull;
+ Datum relpersistencedat;
+ TupleDesc tupledesc;
+
+ Assert(RelationGetRelid(relation) == RelationRelationId);
+
+ tupledesc = RelationGetDescr(relation);
+ relpersistencedat = heap_getattr(tup, Anum_pg_class_relpersistence,
+ tupledesc, &isnull);
+ Assert(!isnull);
+ return ((char) relpersistencedat == RELPERSISTENCE_FAST_TEMP);
+}
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index fac166d..d952512 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -846,7 +846,8 @@ gistGetFakeLSN(Relation rel)
{
static XLogRecPtr counter = 1;
- if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+ if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+ rel->rd_rel->relpersistence == RELPERSISTENCE_FAST_TEMP)
{
/*
* Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 38bba16..779af83 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -53,6 +53,7 @@
#include "access/xlog.h"
#include "access/xloginsert.h"
#include "access/xlogutils.h"
+#include "access/fasttab.h"
#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "miscadmin.h"
@@ -72,6 +73,8 @@
#include "utils/snapmgr.h"
#include "utils/syscache.h"
#include "utils/tqual.h"
+#include "utils/memutils.h"
+#include "lib/ilist.h"
/* GUC variable */
@@ -1505,6 +1508,7 @@ heap_beginscan_internal(Relation relation, Snapshot snapshot,
scan->rs_key = NULL;
initscan(scan, key, false);
+ fasttab_beginscan(scan);
return scan;
}
@@ -1546,6 +1550,8 @@ heap_rescan(HeapScanDesc scan,
parallel_scan->phs_cblock = parallel_scan->phs_startblock;
SpinLockRelease(¶llel_scan->phs_mutex);
}
+
+ fasttab_beginscan(scan);
}
/* ----------------
@@ -1779,6 +1785,11 @@ retry:
HeapTuple
heap_getnext(HeapScanDesc scan, ScanDirection direction)
{
+ HeapTuple fasttab_result = fasttab_getnext(scan, direction);
+
+ if (HeapTupleIsValid(fasttab_result))
+ return fasttab_result;
+
/* Note: no locking manipulations needed */
HEAPDEBUG_1; /* heap_getnext( info ) */
@@ -1859,6 +1870,8 @@ heap_fetch(Relation relation,
OffsetNumber offnum;
bool valid;
+ Assert(!IsFasttabHandledRelationId(relation->rd_id));
+
/*
* Fetch and pin the appropriate page of the relation.
*/
@@ -1984,12 +1997,22 @@ heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer,
Snapshot snapshot, HeapTuple heapTuple,
bool *all_dead, bool first_call)
{
- Page dp = (Page) BufferGetPage(buffer);
+ Page dp;
TransactionId prev_xmax = InvalidTransactionId;
OffsetNumber offnum;
bool at_chain_start;
bool valid;
bool skip;
+ bool fasttab_result;
+
+ if (fasttab_hot_search_buffer(tid, relation, heapTuple, all_dead, &fasttab_result))
+ return fasttab_result;
+
+ /*
+ * `buffer` can be InvalidBuffer for in-memory tuples, so we should call
+ * BufferGetPage only after we verified it's not a case.
+ */
+ dp = (Page) BufferGetPage(buffer);
/* If this is not the first call, previous call returned a (live!) tuple */
if (all_dead)
@@ -2158,6 +2181,8 @@ heap_get_latest_tid(Relation relation,
ItemPointerData ctid;
TransactionId priorXmax;
+ Assert(!IsFasttabHandledRelationId(relation->rd_id));
+
/* this is to avoid Assert failures on bad input */
if (!ItemPointerIsValid(tid))
return;
@@ -2376,6 +2401,7 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
Buffer buffer;
Buffer vmbuffer = InvalidBuffer;
bool all_visible_cleared = false;
+ Oid fasttab_result;
/*
* Fill in tuple header fields, assign an OID, and toast the tuple if
@@ -2386,6 +2412,9 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid,
*/
heaptup = heap_prepare_insert(relation, tup, xid, cid, options);
+ if (fasttab_insert(relation, tup, heaptup, &fasttab_result))
+ return fasttab_result;
+
/*
* Find buffer to insert this tuple into. If the page is all visible,
* this will also pin the requisite visibility map page.
@@ -2644,6 +2673,8 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples,
bool need_tuple_data = RelationIsLogicallyLogged(relation);
bool need_cids = RelationIsAccessibleInLogicalDecoding(relation);
+ Assert(!IsFasttabHandledRelationId(relation->rd_id));
+
needwal = !(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relation);
saveFreeSpace = RelationGetTargetPageFreeSpace(relation,
HEAP_DEFAULT_FILLFACTOR);
@@ -3006,6 +3037,9 @@ heap_delete(Relation relation, ItemPointer tid,
Assert(ItemPointerIsValid(tid));
+ if (fasttab_delete(relation, tid))
+ return HeapTupleMayBeUpdated;
+
/*
* Forbid this during a parallel operation, lest it allocate a combocid.
* Other workers might need that combocid for visibility checks, and we
@@ -3488,6 +3522,10 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
errmsg("cannot update tuples during a parallel operation")));
+
+ if (fasttab_update(relation, otid, newtup))
+ return HeapTupleMayBeUpdated;
+
/*
* Fetch the list of attributes to be checked for HOT update. This is
* wasted effort if we fail to update or have to put the new tuple on a
@@ -4581,6 +4619,8 @@ heap_lock_tuple(Relation relation, HeapTuple tuple,
bool have_tuple_lock = false;
bool cleared_all_frozen = false;
+ Assert(!IsFasttabHandledRelationId(relation->rd_id));
+
*buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
block = ItemPointerGetBlockNumber(tid);
@@ -5212,6 +5252,8 @@ static bool
heap_acquire_tuplock(Relation relation, ItemPointer tid, LockTupleMode mode,
LockWaitPolicy wait_policy, bool *have_tuple_lock)
{
+ Assert(!IsFasttabHandledRelationId(relation->rd_id));
+
if (*have_tuple_lock)
return true;
@@ -5904,6 +5946,8 @@ static HTSU_Result
heap_lock_updated_tuple(Relation rel, HeapTuple tuple, ItemPointer ctid,
TransactionId xid, LockTupleMode mode)
{
+ Assert(!IsFasttabHandledRelationId(rel->rd_id));
+
if (!ItemPointerEquals(&tuple->t_self, ctid))
{
/*
@@ -5949,6 +5993,8 @@ heap_finish_speculative(Relation relation, HeapTuple tuple)
ItemId lp = NULL;
HeapTupleHeader htup;
+ Assert(!IsFasttabHandledRelationId(relation->rd_id));
+
buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(&(tuple->t_self)));
LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
page = (Page) BufferGetPage(buffer);
@@ -6042,6 +6088,7 @@ heap_abort_speculative(Relation relation, HeapTuple tuple)
Buffer buffer;
Assert(ItemPointerIsValid(tid));
+ Assert(!IsFasttabHandledRelationId(relation->rd_id));
block = ItemPointerGetBlockNumber(tid);
buffer = ReadBuffer(relation, block);
@@ -6179,6 +6226,9 @@ heap_inplace_update(Relation relation, HeapTuple tuple)
uint32 oldlen;
uint32 newlen;
+ if (fasttab_inplace_update(relation, tuple))
+ return;
+
/*
* For now, parallel operations are required to be strictly read-only.
* Unlike a regular update, this should never create a combo CID, so it
@@ -6553,6 +6603,8 @@ heap_prepare_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
TransactionId xid;
bool totally_frozen = true;
+ Assert(!IsFasttabItemPointer(&tuple->t_ctid));
+
frz->frzflags = 0;
frz->t_infomask2 = tuple->t_infomask2;
frz->t_infomask = tuple->t_infomask;
@@ -6723,6 +6775,8 @@ heap_prepare_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid,
void
heap_execute_freeze_tuple(HeapTupleHeader tuple, xl_heap_freeze_tuple *frz)
{
+ Assert(!IsFasttabItemPointer(&tuple->t_ctid));
+
HeapTupleHeaderSetXmax(tuple, frz->xmax);
if (frz->frzflags & XLH_FREEZE_XVAC)
@@ -7125,6 +7179,8 @@ heap_tuple_needs_eventual_freeze(HeapTupleHeader tuple)
{
TransactionId xid;
+ Assert(!IsFasttabItemPointer(&tuple->t_ctid));
+
/*
* If xmin is a normal transaction ID, this tuple is definitely not
* frozen.
@@ -7179,6 +7235,8 @@ heap_tuple_needs_freeze(HeapTupleHeader tuple, TransactionId cutoff_xid,
{
TransactionId xid;
+ Assert(!IsFasttabItemPointer(&tuple->t_ctid));
+
xid = HeapTupleHeaderGetXmin(tuple);
if (TransactionIdIsNormal(xid) &&
TransactionIdPrecedes(xid, cutoff_xid))
diff --git a/src/backend/access/index/indexam.c b/src/backend/access/index/indexam.c
index 54b71cb..a56b0c5 100644
--- a/src/backend/access/index/indexam.c
+++ b/src/backend/access/index/indexam.c
@@ -69,6 +69,7 @@
#include "access/relscan.h"
#include "access/transam.h"
#include "access/xlog.h"
+#include "access/fasttab.h"
#include "catalog/catalog.h"
#include "catalog/index.h"
#include "pgstat.h"
@@ -193,9 +194,14 @@ index_insert(Relation indexRelation,
Relation heapRelation,
IndexUniqueCheck checkUnique)
{
+ bool result;
+
RELATION_CHECKS;
CHECK_REL_PROCEDURE(aminsert);
+ if (fasttab_index_insert(indexRelation, heap_t_ctid, &result))
+ return result;
+
if (!(indexRelation->rd_amroutine->ampredlocks))
CheckForSerializableConflictIn(indexRelation,
(HeapTuple) NULL,
@@ -262,6 +268,7 @@ static IndexScanDesc
index_beginscan_internal(Relation indexRelation,
int nkeys, int norderbys, Snapshot snapshot)
{
+ IndexScanDesc result;
RELATION_CHECKS;
CHECK_REL_PROCEDURE(ambeginscan);
@@ -276,8 +283,11 @@ index_beginscan_internal(Relation indexRelation,
/*
* Tell the AM to open a scan.
*/
- return indexRelation->rd_amroutine->ambeginscan(indexRelation, nkeys,
- norderbys);
+ result = indexRelation->rd_amroutine->ambeginscan(indexRelation, nkeys,
+ norderbys);
+ fasttab_index_beginscan(result);
+
+ return result;
}
/* ----------------
@@ -316,6 +326,8 @@ index_rescan(IndexScanDesc scan,
scan->indexRelation->rd_amroutine->amrescan(scan, keys, nkeys,
orderbys, norderbys);
+
+ fasttab_index_rescan(scan, keys, nkeys, orderbys, norderbys);
}
/* ----------------
@@ -328,6 +340,8 @@ index_endscan(IndexScanDesc scan)
SCAN_CHECKS;
CHECK_SCAN_PROCEDURE(amendscan);
+ fasttab_index_endscan(scan);
+
/* Release any held pin on a heap page */
if (BufferIsValid(scan->xs_cbuf))
{
@@ -378,6 +392,7 @@ void
index_restrpos(IndexScanDesc scan)
{
Assert(IsMVCCSnapshot(scan->xs_snapshot));
+ Assert(!IsFasttabHandledIndexId(scan->indexRelation->rd_id));
SCAN_CHECKS;
CHECK_SCAN_PROCEDURE(amrestrpos);
@@ -412,7 +427,7 @@ index_getnext_tid(IndexScanDesc scan, ScanDirection direction)
* scan->xs_recheck and possibly scan->xs_itup, though we pay no attention
* to those fields here.
*/
- found = scan->indexRelation->rd_amroutine->amgettuple(scan, direction);
+ found = fasttab_index_getnext_tid_merge(scan, direction);
/* Reset kill flag immediately for safety */
scan->kill_prior_tuple = false;
@@ -460,6 +475,16 @@ index_fetch_heap(IndexScanDesc scan)
bool all_dead = false;
bool got_heap_tuple;
+ if (IsFasttabItemPointer(tid))
+ {
+ bool fasttab_result;
+
+ /* just get in-memory tuple by tid */
+ got_heap_tuple = fasttab_hot_search_buffer(tid, scan->heapRelation, &scan->xs_ctup, &all_dead, &fasttab_result);
+ Assert(got_heap_tuple && fasttab_result);
+ return &scan->xs_ctup;
+ }
+
/* We can skip the buffer-switching logic if we're in mid-HOT chain. */
if (!scan->xs_continue_hot)
{
@@ -594,10 +619,10 @@ index_getbitmap(IndexScanDesc scan, TIDBitmap *bitmap)
/* just make sure this is false... */
scan->kill_prior_tuple = false;
- /*
- * have the am's getbitmap proc do all the work.
- */
- ntids = scan->indexRelation->rd_amroutine->amgetbitmap(scan, bitmap);
+ /* try fasttab_ hook first ... */
+ if (!fasttab_index_getbitmap(scan, bitmap, &ntids))
+ /* if it failed - have the am's getbitmap proc do all the work. */
+ ntids = scan->indexRelation->rd_amroutine->amgetbitmap(scan, bitmap);
pgstat_count_index_tuples(scan->indexRelation, ntids);
diff --git a/src/backend/access/nbtree/nbtinsert.c b/src/backend/access/nbtree/nbtinsert.c
index ef69290..b081165 100644
--- a/src/backend/access/nbtree/nbtinsert.c
+++ b/src/backend/access/nbtree/nbtinsert.c
@@ -19,6 +19,7 @@
#include "access/nbtree.h"
#include "access/transam.h"
#include "access/xloginsert.h"
+#include "access/fasttab.h"
#include "miscadmin.h"
#include "storage/lmgr.h"
#include "storage/predicate.h"
@@ -330,6 +331,13 @@ _bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel,
TransactionId xwait;
/*
+ * If its in-memory tuple there is for sure no transaction
+ * to wait for.
+ */
+ if (IsFasttabItemPointer(&htid))
+ return InvalidTransactionId;
+
+ /*
* It is a duplicate. If we are only doing a partial
* check, then don't bother checking if the tuple is being
* updated in another transaction. Just return the fact
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 23f36ea..e21faf1 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -30,6 +30,7 @@
#include "access/xlog.h"
#include "access/xloginsert.h"
#include "access/xlogutils.h"
+#include "access/fasttab.h"
#include "catalog/catalog.h"
#include "catalog/namespace.h"
#include "catalog/storage.h"
@@ -1928,6 +1929,8 @@ StartTransaction(void)
s->state = TRANS_INPROGRESS;
ShowTransactionState("StartTransaction");
+
+ fasttab_begin_transaction();
}
@@ -2165,6 +2168,8 @@ CommitTransaction(void)
*/
s->state = TRANS_DEFAULT;
+ fasttab_end_transaction();
+
RESUME_INTERRUPTS();
}
@@ -2611,6 +2616,8 @@ AbortTransaction(void)
pgstat_report_xact_timestamp(0);
}
+ fasttab_abort_transaction();
+
/*
* State remains TRANS_ABORT until CleanupTransaction().
*/
@@ -3795,6 +3802,8 @@ DefineSavepoint(char *name)
BlockStateAsString(s->blockState));
break;
}
+
+ fasttab_define_savepoint(name);
}
/*
@@ -4034,6 +4043,8 @@ RollbackToSavepoint(List *options)
else
elog(FATAL, "RollbackToSavepoint: unexpected state %s",
BlockStateAsString(xact->blockState));
+
+ fasttab_rollback_to_savepoint(name);
}
/*
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 1baaa0b..76b9d4c 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -390,6 +390,7 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
switch (relpersistence)
{
case RELPERSISTENCE_TEMP:
+ case RELPERSISTENCE_FAST_TEMP:
backend = BackendIdForTempRelations();
break;
case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 04d7840..8984a7a 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -16,6 +16,7 @@
#include "access/htup_details.h"
#include "access/xact.h"
+#include "access/fasttab.h"
#include "catalog/dependency.h"
#include "catalog/heap.h"
#include "catalog/index.h"
@@ -583,6 +584,13 @@ findDependentObjects(const ObjectAddress *object,
{
Form_pg_depend foundDep = (Form_pg_depend) GETSTRUCT(tup);
+ /*
+ * just ignore in-memory tuples here, they are properly handled in
+ * fasttab.c already
+ */
+ if (IsFasttabItemPointer(&tup->t_self))
+ continue;
+
otherObject.classId = foundDep->refclassid;
otherObject.objectId = foundDep->refobjid;
otherObject.objectSubId = foundDep->refobjsubid;
@@ -760,6 +768,13 @@ findDependentObjects(const ObjectAddress *object,
Form_pg_depend foundDep = (Form_pg_depend) GETSTRUCT(tup);
int subflags;
+ /*
+ * just ignore in-memory tuples here, they are properly handled in
+ * fasttab.c already
+ */
+ if (IsFasttabItemPointer(&tup->t_self))
+ continue;
+
otherObject.classId = foundDep->classid;
otherObject.objectId = foundDep->objid;
otherObject.objectSubId = foundDep->objsubid;
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index e997b57..0eef1ee 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -35,6 +35,7 @@
#include "access/transam.h"
#include "access/xact.h"
#include "access/xlog.h"
+#include "access/fasttab.h"
#include "catalog/binary_upgrade.h"
#include "catalog/catalog.h"
#include "catalog/dependency.h"
@@ -996,7 +997,7 @@ AddNewRelationType(const char *typeName,
* tupdesc: tuple descriptor (source of column definitions)
* cooked_constraints: list of precooked check constraints and defaults
* relkind: relkind for new rel
- * relpersistence: rel's persistence status (permanent, temp, or unlogged)
+ * relpersistence: rel's persistence status (permanent, temp, fast temp or unlogged)
* shared_relation: TRUE if it's to be a shared relation
* mapped_relation: TRUE if the relation will use the relfilenode map
* oidislocal: TRUE if oid column (if any) should be marked attislocal
@@ -1184,70 +1185,101 @@ heap_create_with_catalog(const char *relname,
relkind == RELKIND_COMPOSITE_TYPE))
new_array_oid = AssignTypeArrayOid();
- /*
- * Since defining a relation also defines a complex type, we add a new
- * system type corresponding to the new relation. The OID of the type can
- * be preselected by the caller, but if reltypeid is InvalidOid, we'll
- * generate a new OID for it.
- *
- * NOTE: we could get a unique-index failure here, in case someone else is
- * creating the same type name in parallel but hadn't committed yet when
- * we checked for a duplicate name above.
- */
- new_type_addr = AddNewRelationType(relname,
- relnamespace,
- relid,
- relkind,
- ownerid,
- reltypeid,
- new_array_oid);
- new_type_oid = new_type_addr.objectId;
- if (typaddress)
- *typaddress = new_type_addr;
-
- /*
- * Now make the array type if wanted.
- */
- if (OidIsValid(new_array_oid))
+ PG_TRY();
{
- char *relarrayname;
+ /*
+ * Usualy to figure out wheter tuple should be stored in-memory or not
+ * we use in-memory part of pg_class table. Unfortunately during table
+ * creation some tuples are stored in catalog tables before
+ * modification of pg_class table. So there is no way to tell that
+ * these tuples should be in-memory.
+ *
+ * Thus we set a hint with relperistence value of a table we about to
+ * create. This not only solves a problem described above but also
+ * allows to run described check much faster.
+ */
+ fasttab_set_relpersistence_hint(relpersistence);
- relarrayname = makeArrayTypeName(relname, relnamespace);
+ /*
+ * Since defining a relation also defines a complex type, we add a new
+ * system type corresponding to the new relation. The OID of the type
+ * can be preselected by the caller, but if reltypeid is InvalidOid,
+ * we'll generate a new OID for it.
+ *
+ * NOTE: we could get a unique-index failure here, in case someone
+ * else is creating the same type name in parallel but hadn't
+ * committed yet when we checked for a duplicate name above.
+ */
+ new_type_addr = AddNewRelationType(relname,
+ relnamespace,
+ relid,
+ relkind,
+ ownerid,
+ reltypeid,
+ new_array_oid);
- TypeCreate(new_array_oid, /* force the type's OID to this */
- relarrayname, /* Array type name */
- relnamespace, /* Same namespace as parent */
- InvalidOid, /* Not composite, no relationOid */
- 0, /* relkind, also N/A here */
- ownerid, /* owner's ID */
- -1, /* Internal size (varlena) */
- TYPTYPE_BASE, /* Not composite - typelem is */
- TYPCATEGORY_ARRAY, /* type-category (array) */
- false, /* array types are never preferred */
- DEFAULT_TYPDELIM, /* default array delimiter */
- F_ARRAY_IN, /* array input proc */
- F_ARRAY_OUT, /* array output proc */
- F_ARRAY_RECV, /* array recv (bin) proc */
- F_ARRAY_SEND, /* array send (bin) proc */
- InvalidOid, /* typmodin procedure - none */
- InvalidOid, /* typmodout procedure - none */
- F_ARRAY_TYPANALYZE, /* array analyze procedure */
- new_type_oid, /* array element type - the rowtype */
- true, /* yes, this is an array type */
- InvalidOid, /* this has no array type */
- InvalidOid, /* domain base type - irrelevant */
- NULL, /* default value - none */
- NULL, /* default binary representation */
- false, /* passed by reference */
- 'd', /* alignment - must be the largest! */
- 'x', /* fully TOASTable */
- -1, /* typmod */
- 0, /* array dimensions for typBaseType */
- false, /* Type NOT NULL */
- InvalidOid); /* rowtypes never have a collation */
+ fasttab_clear_relpersistence_hint();
- pfree(relarrayname);
+ new_type_oid = new_type_addr.objectId;
+ if (typaddress)
+ *typaddress = new_type_addr;
+
+ /*
+ * Now make the array type if wanted.
+ */
+ if (OidIsValid(new_array_oid))
+ {
+ char *relarrayname;
+
+ relarrayname = makeArrayTypeName(relname, relnamespace);
+
+ fasttab_set_relpersistence_hint(relpersistence);
+
+ TypeCreate(new_array_oid, /* force the type's OID to this */
+ relarrayname, /* Array type name */
+ relnamespace, /* Same namespace as parent */
+ InvalidOid, /* Not composite, no relationOid */
+ 0, /* relkind, also N/A here */
+ ownerid, /* owner's ID */
+ -1, /* Internal size (varlena) */
+ TYPTYPE_BASE, /* Not composite - typelem is */
+ TYPCATEGORY_ARRAY, /* type-category (array) */
+ false, /* array types are never preferred */
+ DEFAULT_TYPDELIM, /* default array delimiter */
+ F_ARRAY_IN, /* array input proc */
+ F_ARRAY_OUT, /* array output proc */
+ F_ARRAY_RECV, /* array recv (bin) proc */
+ F_ARRAY_SEND, /* array send (bin) proc */
+ InvalidOid, /* typmodin procedure - none */
+ InvalidOid, /* typmodout procedure - none */
+ F_ARRAY_TYPANALYZE, /* array analyze procedure */
+ new_type_oid, /* array element type - the rowtype */
+ true, /* yes, this is an array type */
+ InvalidOid, /* this has no array type */
+ InvalidOid, /* domain base type - irrelevant */
+ NULL, /* default value - none */
+ NULL, /* default binary representation */
+ false, /* passed by reference */
+ 'd', /* alignment - must be the largest! */
+ 'x', /* fully TOASTable */
+ -1, /* typmod */
+ 0, /* array dimensions for typBaseType */
+ false, /* Type NOT NULL */
+ InvalidOid); /* rowtypes never have a collation */
+
+ fasttab_clear_relpersistence_hint();
+ pfree(relarrayname);
+ }
+
+ }
+ PG_CATCH();
+ {
+ /* clear relpersistence hint in case of error */
+ fasttab_clear_relpersistence_hint();
+ PG_RE_THROW();
}
+ PG_END_TRY();
+
/*
* now create an entry in pg_class for the relation.
@@ -1308,7 +1340,7 @@ heap_create_with_catalog(const char *relname,
recordDependencyOnOwner(RelationRelationId, relid, ownerid);
- if (relpersistence != RELPERSISTENCE_TEMP)
+ if (relpersistence != RELPERSISTENCE_TEMP && relpersistence != RELPERSISTENCE_FAST_TEMP)
recordDependencyOnCurrentExtension(&myself, false);
if (reloftypeid)
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 7b30e46..d113034 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -30,6 +30,7 @@
#include "access/transam.h"
#include "access/visibilitymap.h"
#include "access/xact.h"
+#include "access/fasttab.h"
#include "bootstrap/bootstrap.h"
#include "catalog/binary_upgrade.h"
#include "catalog/catalog.h"
@@ -2284,6 +2285,10 @@ IndexBuildHeapRangeScan(Relation heapRelation,
{
bool tupleIsAlive;
+ /* ignore in-memory tuples here */
+ if (IsFasttabItemPointer(&heapTuple->t_self))
+ continue;
+
CHECK_FOR_INTERRUPTS();
/*
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 8fd4c31..47dc2aa 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -284,7 +284,7 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
* operation, which must be careful to find the temp table, even when
* pg_temp is not first in the search path.
*/
- if (relation->relpersistence == RELPERSISTENCE_TEMP)
+ if (relation->relpersistence == RELPERSISTENCE_TEMP || relation->relpersistence == RELPERSISTENCE_FAST_TEMP)
{
if (!OidIsValid(myTempNamespace))
relId = InvalidOid; /* this probably can't happen? */
@@ -463,7 +463,7 @@ RangeVarGetCreationNamespace(const RangeVar *newRelation)
namespaceId = get_namespace_oid(newRelation->schemaname, false);
/* we do not check for USAGE rights here! */
}
- else if (newRelation->relpersistence == RELPERSISTENCE_TEMP)
+ else if (newRelation->relpersistence == RELPERSISTENCE_TEMP || newRelation->relpersistence == RELPERSISTENCE_FAST_TEMP)
{
/* Initialize temp namespace if first time through */
if (!OidIsValid(myTempNamespace))
@@ -631,6 +631,7 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
switch (newRelation->relpersistence)
{
case RELPERSISTENCE_TEMP:
+ case RELPERSISTENCE_FAST_TEMP:
if (!isTempOrTempToastNamespace(nspid))
{
if (isAnyTempNamespace(nspid))
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index 0d8311c..99bcf5b 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -85,6 +85,7 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
switch (relpersistence)
{
case RELPERSISTENCE_TEMP:
+ case RELPERSISTENCE_FAST_TEMP:
backend = BackendIdForTempRelations();
needs_wal = false;
break;
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 43bbd90..2036ece 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -25,6 +25,7 @@
#include "access/tuptoaster.h"
#include "access/xact.h"
#include "access/xlog.h"
+#include "access/fasttab.h"
#include "catalog/pg_am.h"
#include "catalog/catalog.h"
#include "catalog/dependency.h"
@@ -641,7 +642,7 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, char relpersistence,
if (isNull)
reloptions = (Datum) 0;
- if (relpersistence == RELPERSISTENCE_TEMP)
+ if (relpersistence == RELPERSISTENCE_TEMP || relpersistence == RELPERSISTENCE_FAST_TEMP)
namespaceid = LookupCreationNamespace("pg_temp");
else
namespaceid = RelationGetNamespace(OldHeap);
@@ -952,6 +953,10 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
if (tuple == NULL)
break;
+ /* No need to move in-memory tuple anywhere */
+ if (IsFasttabItemPointer(&tuple->t_self))
+ continue;
+
/* Since we used no scan keys, should never need to recheck */
if (indexScan->xs_recheck)
elog(ERROR, "CLUSTER does not support lossy index conditions");
@@ -964,6 +969,10 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
if (tuple == NULL)
break;
+ /* No need to move in-memory tuple anywhere */
+ if (IsFasttabItemPointer(&tuple->t_self))
+ continue;
+
buf = heapScan->rs_cbuf;
}
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index d14d540..49a0ab2 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -1945,7 +1945,7 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
continue;
/* Skip temp tables of other backends; we can't reindex them at all */
- if (classtuple->relpersistence == RELPERSISTENCE_TEMP &&
+ if ((classtuple->relpersistence == RELPERSISTENCE_TEMP || classtuple->relpersistence == RELPERSISTENCE_FAST_TEMP) &&
!isTempNamespace(classtuple->relnamespace))
continue;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 86e9814..f5cd689 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -22,6 +22,7 @@
#include "access/sysattr.h"
#include "access/xact.h"
#include "access/xlog.h"
+#include "access/fasttab.h"
#include "catalog/catalog.h"
#include "catalog/dependency.h"
#include "catalog/heap.h"
@@ -487,7 +488,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
* Check consistency of arguments
*/
if (stmt->oncommit != ONCOMMIT_NOOP
- && stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+ && stmt->relation->relpersistence != RELPERSISTENCE_TEMP
+ && stmt->relation->relpersistence != RELPERSISTENCE_FAST_TEMP)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("ON COMMIT can only be used on temporary tables")));
@@ -506,7 +508,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
* code. This is needed because calling code might not expect untrusted
* tables to appear in pg_temp at the front of its search path.
*/
- if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+ if ((stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+ stmt->relation->relpersistence == RELPERSISTENCE_FAST_TEMP)
&& InSecurityRestrictedOperation())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -1529,14 +1532,17 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
parent->relname)));
/* Permanent rels cannot inherit from temporary ones */
if (relpersistence != RELPERSISTENCE_TEMP &&
- relation->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+ relpersistence != RELPERSISTENCE_FAST_TEMP &&
+ (relation->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+ relation->rd_rel->relpersistence == RELPERSISTENCE_FAST_TEMP))
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit from temporary relation \"%s\"",
parent->relname)));
/* If existing rel is temp, it must belong to this session */
- if (relation->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
+ if ((relation->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+ relation->rd_rel->relpersistence == RELPERSISTENCE_FAST_TEMP) &&
!relation->rd_islocaltemp)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -6303,7 +6309,9 @@ ATAddForeignKeyConstraint(AlteredTableInfo *tab, Relation rel,
errmsg("constraints on unlogged tables may reference only permanent or unlogged tables")));
break;
case RELPERSISTENCE_TEMP:
- if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+ case RELPERSISTENCE_FAST_TEMP:
+ if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_TEMP &&
+ pkrel->rd_rel->relpersistence != RELPERSISTENCE_FAST_TEMP)
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("constraints on temporary tables may reference only temporary tables")));
@@ -10046,22 +10054,26 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode)
ATSimplePermissions(parent_rel, ATT_TABLE | ATT_FOREIGN_TABLE);
/* Permanent rels cannot inherit from temporary ones */
- if (parent_rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
- child_rel->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+ if ((parent_rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+ parent_rel->rd_rel->relpersistence == RELPERSISTENCE_FAST_TEMP) &&
+ (child_rel->rd_rel->relpersistence != RELPERSISTENCE_TEMP &&
+ child_rel->rd_rel->relpersistence != RELPERSISTENCE_FAST_TEMP))
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit from temporary relation \"%s\"",
RelationGetRelationName(parent_rel))));
/* If parent rel is temp, it must belong to this session */
- if (parent_rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
+ if ((parent_rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+ parent_rel->rd_rel->relpersistence == RELPERSISTENCE_FAST_TEMP) &&
!parent_rel->rd_islocaltemp)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot inherit from temporary relation of another session")));
/* Ditto for the child */
- if (child_rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP &&
+ if ((child_rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+ child_rel->rd_rel->relpersistence == RELPERSISTENCE_FAST_TEMP) &&
!child_rel->rd_islocaltemp)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
@@ -11264,6 +11276,7 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
switch (rel->rd_rel->relpersistence)
{
case RELPERSISTENCE_TEMP:
+ case RELPERSISTENCE_FAST_TEMP:
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("cannot change logged status of table \"%s\" because it is temporary",
diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c
index 7902d43..28c4603 100644
--- a/src/backend/commands/tablespace.c
+++ b/src/backend/commands/tablespace.c
@@ -1117,7 +1117,7 @@ GetDefaultTablespace(char relpersistence)
Oid result;
/* The temp-table case is handled elsewhere */
- if (relpersistence == RELPERSISTENCE_TEMP)
+ if (relpersistence == RELPERSISTENCE_TEMP || relpersistence == RELPERSISTENCE_FAST_TEMP)
{
PrepareTempTablespaces();
return GetNextTempTableSpace();
diff --git a/src/backend/executor/nodeBitmapHeapscan.c b/src/backend/executor/nodeBitmapHeapscan.c
index 449aacb..3e2cee8 100644
--- a/src/backend/executor/nodeBitmapHeapscan.c
+++ b/src/backend/executor/nodeBitmapHeapscan.c
@@ -37,6 +37,7 @@
#include "access/relscan.h"
#include "access/transam.h"
+#include "access/fasttab.h"
#include "executor/execdebug.h"
#include "executor/nodeBitmapHeapscan.h"
#include "pgstat.h"
@@ -153,28 +154,36 @@ BitmapHeapNext(BitmapHeapScanState *node)
}
#endif /* USE_PREFETCH */
- /*
- * Ignore any claimed entries past what we think is the end of the
- * relation. (This is probably not necessary given that we got at
- * least AccessShareLock on the table before performing any of the
- * indexscans, but let's be safe.)
- */
- if (tbmres->blockno >= scan->rs_nblocks)
- {
- node->tbmres = tbmres = NULL;
- continue;
- }
-
- /*
- * Fetch the current heap page and identify candidate tuples.
- */
- bitgetpage(scan, tbmres);
-
if (tbmres->ntuples >= 0)
node->exact_pages++;
else
node->lossy_pages++;
+ if(tbmres->blockno < scan->rs_nblocks)
+ {
+ /*
+ * Normal case. Fetch the current heap page and identify
+ * candidate tuples.
+ */
+ bitgetpage(scan, tbmres);
+ }
+ else
+ {
+ /*
+ * Probably we are looking for an in-memory tuple. This code
+ * executes in cases when CurrentFasttabBlockId is larger than
+ * normal block id's.
+ */
+ OffsetNumber i;
+
+ /* Check all tuples on our virtual page. NB: 0 is an invalid offset */
+ for(i = 1; i <= MaxHeapTuplesPerPage; i++)
+ scan->rs_vistuples[i-1] = FASTTAB_ITEM_POINTER_BIT | i;
+
+ scan->rs_ntuples = MaxHeapTuplesPerPage;
+ tbmres->recheck = true;
+ }
+
/*
* Set rs_cindex to first slot to examine
*/
@@ -257,15 +266,24 @@ BitmapHeapNext(BitmapHeapScanState *node)
* Okay to fetch the tuple
*/
targoffset = scan->rs_vistuples[scan->rs_cindex];
- dp = (Page) BufferGetPage(scan->rs_cbuf);
- lp = PageGetItemId(dp, targoffset);
- Assert(ItemIdIsNormal(lp));
-
- scan->rs_ctup.t_data = (HeapTupleHeader) PageGetItem((Page) dp, lp);
- scan->rs_ctup.t_len = ItemIdGetLength(lp);
- scan->rs_ctup.t_tableOid = scan->rs_rd->rd_id;
ItemPointerSet(&scan->rs_ctup.t_self, tbmres->blockno, targoffset);
+ if (IsFasttabItemPointer(&scan->rs_ctup.t_self))
+ {
+ if(!fasttab_simple_heap_fetch(scan->rs_rd, scan->rs_snapshot, &scan->rs_ctup))
+ continue;
+ }
+ else
+ {
+ dp = (Page) BufferGetPage(scan->rs_cbuf);
+ lp = PageGetItemId(dp, targoffset);
+ Assert(ItemIdIsNormal(lp));
+
+ scan->rs_ctup.t_data = (HeapTupleHeader) PageGetItem((Page) dp, lp);
+ scan->rs_ctup.t_len = ItemIdGetLength(lp);
+ scan->rs_ctup.t_tableOid = scan->rs_rd->rd_id;
+ }
+
pgstat_count_heap_fetch(scan->rs_rd);
/*
diff --git a/src/backend/nodes/tidbitmap.c b/src/backend/nodes/tidbitmap.c
index dfeb7d5..8ede443 100644
--- a/src/backend/nodes/tidbitmap.c
+++ b/src/backend/nodes/tidbitmap.c
@@ -41,17 +41,19 @@
#include <limits.h>
#include "access/htup_details.h"
+#include "access/fasttab.h"
#include "nodes/bitmapset.h"
#include "nodes/tidbitmap.h"
#include "utils/hsearch.h"
/*
* The maximum number of tuples per page is not large (typically 256 with
- * 8K pages, or 1024 with 32K pages). So there's not much point in making
- * the per-page bitmaps variable size. We just legislate that the size
+ * 8K pages, or 1024 with 32K pages). Also in-memory tuples have large fake
+ * offsets because of FASTTAB_ITEM_POINTER_BIT. So there's not much point in
+ * making the per-page bitmaps variable size. We just legislate that the size
* is this:
*/
-#define MAX_TUPLES_PER_PAGE MaxHeapTuplesPerPage
+#define MAX_TUPLES_PER_PAGE (FASTTAB_ITEM_POINTER_BIT | MaxHeapTuplesPerPage)
/*
* When we have to switch over to lossy storage, we use a data structure
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 88d833a..976ca06 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -501,6 +501,8 @@ static void
set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
RangeTblEntry *rte)
{
+ char relpersistence;
+
/*
* The flag has previously been initialized to false, so we can just
* return if it becomes clear that we can't safely set it.
@@ -529,7 +531,8 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
* the rest of the necessary infrastructure right now anyway. So
* for now, bail out if we see a temporary table.
*/
- if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+ relpersistence = get_rel_persistence(rte->relid);
+ if (relpersistence == RELPERSISTENCE_TEMP || relpersistence == RELPERSISTENCE_FAST_TEMP)
return;
/*
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 0cae446..e5220d2 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -586,7 +586,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
EXTENSION EXTERNAL EXTRACT
- FALSE_P FAMILY FETCH FILTER FIRST_P FLOAT_P FOLLOWING FOR
+ FALSE_P FAMILY FAST FETCH FILTER FIRST_P FLOAT_P FOLLOWING FOR
FORCE FOREIGN FORWARD FREEZE FROM FULL FUNCTION FUNCTIONS
GLOBAL GRANT GRANTED GREATEST GROUP_P GROUPING
@@ -2891,6 +2891,8 @@ OptTemp: TEMPORARY { $$ = RELPERSISTENCE_TEMP; }
| TEMP { $$ = RELPERSISTENCE_TEMP; }
| LOCAL TEMPORARY { $$ = RELPERSISTENCE_TEMP; }
| LOCAL TEMP { $$ = RELPERSISTENCE_TEMP; }
+ | FAST TEMPORARY { $$ = RELPERSISTENCE_FAST_TEMP; }
+ | FAST TEMP { $$ = RELPERSISTENCE_FAST_TEMP; }
| GLOBAL TEMPORARY
{
ereport(WARNING,
@@ -10280,6 +10282,16 @@ OptTempTableName:
$$ = $4;
$$->relpersistence = RELPERSISTENCE_TEMP;
}
+ | FAST TEMPORARY opt_table qualified_name
+ {
+ $$ = $4;
+ $$->relpersistence = RELPERSISTENCE_FAST_TEMP;
+ }
+ | FAST TEMP opt_table qualified_name
+ {
+ $$ = $4;
+ $$->relpersistence = RELPERSISTENCE_FAST_TEMP;
+ }
| GLOBAL TEMPORARY opt_table qualified_name
{
ereport(WARNING,
@@ -13807,6 +13819,7 @@ unreserved_keyword:
| EXTENSION
| EXTERNAL
| FAMILY
+ | FAST
| FILTER
| FIRST_P
| FOLLOWING
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 1e3ecbc..1ed569d 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -3141,7 +3141,7 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
char relpersistence = rel->rd_rel->relpersistence;
heap_close(rel, AccessShareLock);
- if (relpersistence == RELPERSISTENCE_TEMP)
+ if (relpersistence == RELPERSISTENCE_TEMP || relpersistence == RELPERSISTENCE_FAST_TEMP)
return true;
}
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e98fad0..7f71706 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -202,7 +202,8 @@ transformCreateStmt(CreateStmt *stmt, const char *queryString)
* specified to be in pg_temp, so no need for anything extra in that case.
*/
if (stmt->relation->schemaname == NULL
- && stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+ && stmt->relation->relpersistence != RELPERSISTENCE_TEMP
+ && stmt->relation->relpersistence != RELPERSISTENCE_FAST_TEMP)
stmt->relation->schemaname = get_namespace_name(namespaceid);
/* Set up CreateStmtContext */
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 3768f50..4fe00ee 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2037,7 +2037,8 @@ do_autovacuum(void)
* Check if it is a temp table (presumably, of some other backend's).
* We cannot safely process other backends' temp tables.
*/
- if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+ if (classForm->relpersistence == RELPERSISTENCE_TEMP ||
+ classForm->relpersistence == RELPERSISTENCE_FAST_TEMP)
{
int backendID;
@@ -2134,7 +2135,8 @@ do_autovacuum(void)
/*
* We cannot safely process other backends' temp tables, so skip 'em.
*/
- if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+ if (classForm->relpersistence == RELPERSISTENCE_TEMP ||
+ classForm->relpersistence == RELPERSISTENCE_FAST_TEMP)
continue;
relid = HeapTupleGetOid(tuple);
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 0776f3b..a0ebbad 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -1003,6 +1003,7 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
backend = InvalidBackendId;
break;
case RELPERSISTENCE_TEMP:
+ case RELPERSISTENCE_FAST_TEMP:
if (isTempOrTempToastNamespace(relform->relnamespace))
backend = BackendIdForTempRelations();
else
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 8d2ad01..f811afe 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -991,6 +991,7 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
relation->rd_islocaltemp = false;
break;
case RELPERSISTENCE_TEMP:
+ case RELPERSISTENCE_FAST_TEMP:
if (isTempOrTempToastNamespace(relation->rd_rel->relnamespace))
{
relation->rd_backend = BackendIdForTempRelations();
@@ -1937,6 +1938,7 @@ RelationReloadIndexInfo(Relation relation)
RelationGetRelid(relation));
relp = (Form_pg_class) GETSTRUCT(pg_class_tuple);
memcpy(relation->rd_rel, relp, CLASS_TUPLE_SIZE);
+
/* Reload reloptions in case they changed */
if (relation->rd_options)
pfree(relation->rd_options);
@@ -2974,6 +2976,7 @@ RelationBuildLocalRelation(const char *relname,
rel->rd_islocaltemp = false;
break;
case RELPERSISTENCE_TEMP:
+ case RELPERSISTENCE_FAST_TEMP:
Assert(isTempOrTempToastNamespace(relnamespace));
rel->rd_backend = BackendIdForTempRelations();
rel->rd_islocaltemp = true;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 8469d9f..d3ccac4 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -894,6 +894,7 @@ static const pgsql_thing_t words_after_create[] = {
{"DOMAIN", NULL, &Query_for_list_of_domains},
{"EVENT TRIGGER", NULL, NULL},
{"EXTENSION", Query_for_list_of_extensions},
+ {"FAST TEMP", NULL, NULL, THING_NO_DROP}, /* for CREATE FAST TEMP TABLE ... */
{"FOREIGN DATA WRAPPER", NULL, NULL},
{"FOREIGN TABLE", NULL, NULL},
{"FUNCTION", NULL, &Query_for_list_of_functions},
diff --git a/src/include/access/fasttab.h b/src/include/access/fasttab.h
new file mode 100644
index 0000000..2d38872
--- /dev/null
+++ b/src/include/access/fasttab.h
@@ -0,0 +1,83 @@
+/* FOR INTERNAL USAGE ONLY. Backward compatability is not guaranteed, dont use in extensions! */
+
+#ifndef FASTTAB_H
+#define FASTTAB_H
+
+#include "c.h"
+#include "postgres_ext.h"
+#include "access/htup.h"
+#include "access/heapam.h"
+#include "access/sdir.h"
+#include "access/genam.h"
+#include "catalog/indexing.h"
+#include "storage/itemptr.h"
+#include "utils/relcache.h"
+
+/*
+ * ItemPointerData.ip_posid is _never_ that large. See MaxHeapTuplesPerPage constant.
+ * This constant better be not too large since MAX_TUPLES_PER_PAGE depends on it value.
+ */
+#define FASTTAB_ITEM_POINTER_BIT 0x0800
+
+#define IsFasttabItemPointer(ptr) \
+ ( ((ptr)->ip_posid & FASTTAB_ITEM_POINTER_BIT) != 0 )
+
+typedef struct FasttabIndexMethodsData FasttabIndexMethodsData;
+
+typedef FasttabIndexMethodsData const *FasttabIndexMethods;
+
+extern bool IsFasttabHandledRelationId(Oid relId);
+
+extern bool IsFasttabHandledIndexId(Oid indexId);
+
+extern void fasttab_set_relpersistence_hint(char relpersistence);
+
+extern void fasttab_clear_relpersistence_hint(void);
+
+extern void fasttab_begin_transaction(void);
+
+extern void fasttab_end_transaction(void);
+
+extern void fasttab_abort_transaction(void);
+
+extern void fasttab_define_savepoint(const char *name);
+
+extern void fasttab_rollback_to_savepoint(const char *name);
+
+extern void fasttab_beginscan(HeapScanDesc scan);
+
+extern HeapTuple fasttab_getnext(HeapScanDesc scan, ScanDirection direction);
+
+extern bool fasttab_hot_search_buffer(ItemPointer tid, Relation relation,
+ HeapTuple heapTuple, bool *all_dead, bool *result);
+
+extern bool fasttab_insert(Relation relation, HeapTuple tup, HeapTuple heaptup,
+ Oid *result);
+
+extern bool fasttab_delete(Relation relation, ItemPointer tid);
+
+extern bool fasttab_update(Relation relation, ItemPointer otid,
+ HeapTuple newtup);
+
+extern bool fasttab_inplace_update(Relation relation, HeapTuple tuple);
+
+extern bool fasttab_index_insert(Relation indexRelation,
+ ItemPointer heap_t_ctid, bool *result);
+
+extern void fasttab_index_beginscan(IndexScanDesc scan);
+
+extern void fasttab_index_rescan(IndexScanDesc scan, ScanKey keys, int nkeys,
+ ScanKey orderbys, int norderbys);
+
+extern bool fasttab_simple_heap_fetch(Relation relation, Snapshot snapshot,
+ HeapTuple tuple);
+
+extern bool fasttab_index_getnext_tid_merge(IndexScanDesc scan,
+ ScanDirection direction);
+
+extern bool fasttab_index_getbitmap(IndexScanDesc scan, TIDBitmap *bitmap,
+ int64 *result);
+
+extern void fasttab_index_endscan(IndexScanDesc scan);
+
+#endif /* FASTTAB_H */
diff --git a/src/include/access/relscan.h b/src/include/access/relscan.h
index 49c2a6f..456a7ff 100644
--- a/src/include/access/relscan.h
+++ b/src/include/access/relscan.h
@@ -19,6 +19,7 @@
#include "access/htup_details.h"
#include "access/itup.h"
#include "access/tupdesc.h"
+#include "access/fasttab.h"
/*
* Shared state for parallel heap scan.
@@ -74,6 +75,8 @@ typedef struct HeapScanDescData
int rs_cindex; /* current tuple's index in vistuples */
int rs_ntuples; /* number of visible tuples on page */
OffsetNumber rs_vistuples[MaxHeapTuplesPerPage]; /* their offsets */
+
+ dlist_node *rs_curr_inmem_tupnode; /* current in-memory tuple, or NULL */
} HeapScanDescData;
/*
@@ -125,6 +128,13 @@ typedef struct IndexScanDescData
/* state data for traversing HOT chains in index_getnext */
bool xs_continue_hot; /* T if must keep walking HOT chain */
+
+ FasttabIndexMethods indexMethods;
+ dlist_head xs_inmem_tuplist;
+ bool xs_regular_tuple_enqueued;
+ bool xs_regular_scan_finished;
+ bool xs_scan_finish_returned;
+ bool xs_inmem_tuplist_init_done;
} IndexScanDescData;
/* Struct for heap-or-index scans of system tables */
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index e57b81c..f51a91a 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -162,9 +162,11 @@ DESCR("");
#define RELKIND_FOREIGN_TABLE 'f' /* foreign table */
#define RELKIND_MATVIEW 'm' /* materialized view */
+#define RELPERSISTENCE_UNDEFINED '?' /* invalid relpersistence value */
#define RELPERSISTENCE_PERMANENT 'p' /* regular table */
#define RELPERSISTENCE_UNLOGGED 'u' /* unlogged permanent table */
#define RELPERSISTENCE_TEMP 't' /* temporary table */
+#define RELPERSISTENCE_FAST_TEMP 'f' /* fast temporary table */
/* default selection for replica identity (primary key or nothing) */
#define REPLICA_IDENTITY_DEFAULT 'd'
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 17ffef5..a673176 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -157,6 +157,7 @@ PG_KEYWORD("external", EXTERNAL, UNRESERVED_KEYWORD)
PG_KEYWORD("extract", EXTRACT, COL_NAME_KEYWORD)
PG_KEYWORD("false", FALSE_P, RESERVED_KEYWORD)
PG_KEYWORD("family", FAMILY, UNRESERVED_KEYWORD)
+PG_KEYWORD("fast", FAST, UNRESERVED_KEYWORD)
PG_KEYWORD("fetch", FETCH, RESERVED_KEYWORD)
PG_KEYWORD("filter", FILTER, UNRESERVED_KEYWORD)
PG_KEYWORD("first", FIRST_P, UNRESERVED_KEYWORD)
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index ed14442..543fad0 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -463,9 +463,11 @@ typedef struct ViewOptions
/*
* RelationUsesLocalBuffers
* True if relation's pages are stored in local buffers.
+ *
+ * Beware of multiple eval of argument
*/
#define RelationUsesLocalBuffers(relation) \
- ((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+ (((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP) || ((relation)->rd_rel->relpersistence == RELPERSISTENCE_FAST_TEMP))
/*
* RELATION_IS_LOCAL
@@ -486,7 +488,7 @@ typedef struct ViewOptions
* Beware of multiple eval of argument
*/
#define RELATION_IS_OTHER_TEMP(relation) \
- ((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
+ ((((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP) || ((relation)->rd_rel->relpersistence == RELPERSISTENCE_FAST_TEMP)) && \
!(relation)->rd_islocaltemp)
diff --git a/src/test/regress/expected/fast_temp.out b/src/test/regress/expected/fast_temp.out
new file mode 100644
index 0000000..bbd741d
--- /dev/null
+++ b/src/test/regress/expected/fast_temp.out
@@ -0,0 +1,385 @@
+--
+-- FAST TEMP
+-- Test fast temporary tables
+--
+-- basic test
+CREATE FAST TEMP TABLE fasttab_test1(x int, s text);
+INSERT INTO fasttab_test1 VALUES (1, 'aaa'), (2, 'bbb'), (3, 'ccc'), (4, 'ddd');
+UPDATE fasttab_test1 SET s = 'eee' WHERE x = 4;
+UPDATE fasttab_test1 SET x = 5 WHERE s = 'bbb';
+DELETE FROM fasttab_test1 WHERE x = 3;
+SELECT * FROM fasttab_test1 ORDER BY x;
+ x | s
+---+-----
+ 1 | aaa
+ 4 | eee
+ 5 | bbb
+(3 rows)
+
+DROP TABLE fasttab_test1;
+-- kind of load test
+do $$
+declare
+ count_fast_table integer = 150;
+ count_attr integer = 20;
+ i integer;
+ j integer;
+ t_sql text;
+begin
+ for i in 1 .. count_fast_table
+ loop
+ t_sql = 'CREATE FAST TEMP TABLE fast_table_' || i :: text;
+ t_sql = t_sql || ' (';
+ for j in 1 .. count_attr
+ loop
+ t_sql = t_sql || ' attr' || j || ' text';
+ if j <> count_attr then
+ t_sql = t_sql || ', ';
+ end if;
+ end loop;
+ t_sql = t_sql || ' );';
+ execute t_sql;
+ -- raise info 't_sql %', t_sql;
+ end loop;
+end $$;
+SELECT * FROM fast_table_1;
+ attr1 | attr2 | attr3 | attr4 | attr5 | attr6 | attr7 | attr8 | attr9 | attr10 | attr11 | attr12 | attr13 | attr14 | attr15 | attr16 | attr17 | attr18 | attr19 | attr20
+-------+-------+-------+-------+-------+-------+-------+-------+-------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------
+(0 rows)
+
+-- test bitmap index scan
+SELECT count(*) FROM pg_class WHERE relname = 'fast_table_1' OR relname = 'fast_table_2';
+ count
+-------
+ 2
+(1 row)
+
+-- create / delete / create test
+CREATE FAST TEMP TABLE fasttab_test1(x int, s text);
+-- check index only scan
+SELECT COUNT(*) FROM pg_class WHERE relname = 'fasttab_test1';
+ count
+-------
+ 1
+(1 row)
+
+SELECT relname FROM pg_class WHERE relname = 'fasttab_test1';
+ relname
+---------------
+ fasttab_test1
+(1 row)
+
+DROP TABLE fasttab_test1;
+-- select from non-existend temp table
+SELECT COUNT(*) FROM fasttab_test1;
+ERROR: relation "fasttab_test1" does not exist
+LINE 1: SELECT COUNT(*) FROM fasttab_test1;
+ ^
+CREATE FAST TEMP TABLE fasttab_test1(x int, s text);
+CREATE FAST TEMP TABLE fasttab_test2(x int, s text);
+SELECT * FROM fasttab_test1;
+ x | s
+---+---
+(0 rows)
+
+-- check that ALTER is working as expected
+ALTER TABLE fasttab_test1 ADD COLUMN y int;
+SELECT * FROM fasttab_test1;
+ x | s | y
+---+---+---
+(0 rows)
+
+ALTER TABLE fasttab_test1 ADD COLUMN z int;
+SELECT * FROM fasttab_test1;
+ x | s | y | z
+---+---+---+---
+(0 rows)
+
+ALTER TABLE fasttab_test1 DROP COLUMN x;
+SELECT * FROM fasttab_test1;
+ s | y | z
+---+---+---
+(0 rows)
+
+ALTER TABLE fasttab_test1 DROP COLUMN y;
+SELECT * FROM fasttab_test1;
+ s | z
+---+---
+(0 rows)
+
+-- test transactions and savepoints
+BEGIN;
+INSERT INTO fasttab_test2 VALUES (1, 'aaa'), (2, 'bbb');
+SELECT * FROM fasttab_test2;
+ x | s
+---+-----
+ 1 | aaa
+ 2 | bbb
+(2 rows)
+
+ROLLBACK;
+SELECT * FROM fasttab_test2;
+ x | s
+---+---
+(0 rows)
+
+BEGIN;
+INSERT INTO fasttab_test2 VALUES (3, 'ccc'), (4, 'ddd');
+SELECT * FROM fasttab_test2;
+ x | s
+---+-----
+ 3 | ccc
+ 4 | ddd
+(2 rows)
+
+COMMIT;
+SELECT * FROM fasttab_test2;
+ x | s
+---+-----
+ 3 | ccc
+ 4 | ddd
+(2 rows)
+
+BEGIN;
+SAVEPOINT sp1;
+ALTER TABLE fasttab_test2 ADD COLUMN y int;
+SELECT * FROM fasttab_test2;
+ x | s | y
+---+-----+---
+ 3 | ccc |
+ 4 | ddd |
+(2 rows)
+
+SAVEPOINT sp2;
+INSERT INTO fasttab_test2 VALUES (5, 'eee', 6);
+SELECT * FROM fasttab_test2;
+ x | s | y
+---+-----+---
+ 3 | ccc |
+ 4 | ddd |
+ 5 | eee | 6
+(3 rows)
+
+ROLLBACK TO SAVEPOINT sp2;
+INSERT INTO fasttab_test2 VALUES (55, 'EEE', 66);
+SELECT * FROM fasttab_test2;
+ x | s | y
+----+-----+----
+ 3 | ccc |
+ 4 | ddd |
+ 55 | EEE | 66
+(3 rows)
+
+ROLLBACK TO SAVEPOINT sp2;
+SELECT * FROM fasttab_test2;
+ x | s | y
+---+-----+---
+ 3 | ccc |
+ 4 | ddd |
+(2 rows)
+
+COMMIT;
+-- test that exceptions are handled properly
+DO $$
+DECLARE
+BEGIN
+ CREATE FAST TEMP TABLE fast_exception_test(x int, y int, z int);
+ RAISE EXCEPTION 'test error';
+END $$;
+ERROR: test error
+CONTEXT: PL/pgSQL function inline_code_block line 5 at RAISE
+CREATE FAST TEMP TABLE fast_exception_test(x int, y int, z int);
+-- test that inheritance works as expected
+-- OK:
+CREATE TABLE cities (name text, population float, altitude int);
+CREATE TABLE capitals (state char(2)) INHERITS (cities);
+DROP TABLE capitals;
+DROP TABLE cities;
+-- OK:
+CREATE TABLE cities2 (name text, population float, altitude int);
+CREATE FAST TEMPORARY TABLE capitals2 (state char(2)) INHERITS (cities2);
+INSERT INTO capitals2 VALUES ('Moscow', 123.45, 789, 'RU');
+INSERT INTO capitals2 VALUES ('Paris', 543.21, 987, 'FR');
+SELECT * FROM capitals2;
+ name | population | altitude | state
+--------+------------+----------+-------
+ Moscow | 123.45 | 789 | RU
+ Paris | 543.21 | 987 | FR
+(2 rows)
+
+SELECT * FROM cities2;
+ name | population | altitude
+--------+------------+----------
+ Moscow | 123.45 | 789
+ Paris | 543.21 | 987
+(2 rows)
+
+DELETE FROM cities2 WHERE name = 'Moscow';
+SELECT * FROM capitals2;
+ name | population | altitude | state
+-------+------------+----------+-------
+ Paris | 543.21 | 987 | FR
+(1 row)
+
+SELECT * FROM cities2;
+ name | population | altitude
+-------+------------+----------
+ Paris | 543.21 | 987
+(1 row)
+
+DROP TABLE capitals2;
+DROP TABLE cities2;
+-- ERROR:
+CREATE FAST TEMPORARY TABLE cities3 (name text, population float, altitude int);
+-- cannot inherit from temporary relation "cities3"
+CREATE TABLE capitals3 (state char(2)) INHERITS (cities3);
+ERROR: cannot inherit from temporary relation "cities3"
+DROP TABLE cities3;
+-- OK:
+CREATE FAST TEMPORARY TABLE cities4 (name text, population float, altitude int);
+CREATE FAST TEMPORARY TABLE capitals4 (state char(2)) INHERITS (cities4);
+INSERT INTO capitals4 VALUES ('Moscow', 123.45, 789, 'RU');
+INSERT INTO capitals4 VALUES ('Paris', 543.21, 987, 'FR');
+SELECT * FROM capitals4;
+ name | population | altitude | state
+--------+------------+----------+-------
+ Moscow | 123.45 | 789 | RU
+ Paris | 543.21 | 987 | FR
+(2 rows)
+
+SELECT * FROM cities4;
+ name | population | altitude
+--------+------------+----------
+ Moscow | 123.45 | 789
+ Paris | 543.21 | 987
+(2 rows)
+
+DELETE FROM cities4 WHERE name = 'Moscow';
+SELECT * FROM capitals4;
+ name | population | altitude | state
+-------+------------+----------+-------
+ Paris | 543.21 | 987 | FR
+(1 row)
+
+SELECT * FROM cities4;
+ name | population | altitude
+-------+------------+----------
+ Paris | 543.21 | 987
+(1 row)
+
+DROP TABLE capitals4;
+DROP TABLE cities4;
+-- OK:
+CREATE TEMPORARY TABLE cities5 (name text, population float, altitude int);
+CREATE FAST TEMPORARY TABLE capitals5 (state char(2)) INHERITS (cities5);
+INSERT INTO capitals5 VALUES ('Moscow', 123.45, 789, 'RU');
+INSERT INTO capitals5 VALUES ('Paris', 543.21, 987, 'FR');
+SELECT * FROM capitals5;
+ name | population | altitude | state
+--------+------------+----------+-------
+ Moscow | 123.45 | 789 | RU
+ Paris | 543.21 | 987 | FR
+(2 rows)
+
+SELECT * FROM cities5;
+ name | population | altitude
+--------+------------+----------
+ Moscow | 123.45 | 789
+ Paris | 543.21 | 987
+(2 rows)
+
+DELETE FROM cities5 WHERE name = 'Moscow';
+SELECT * FROM capitals5;
+ name | population | altitude | state
+-------+------------+----------+-------
+ Paris | 543.21 | 987 | FR
+(1 row)
+
+SELECT * FROM cities5;
+ name | population | altitude
+-------+------------+----------
+ Paris | 543.21 | 987
+(1 row)
+
+DROP TABLE capitals5;
+DROP TABLE cities5;
+-- OK:
+CREATE FAST TEMPORARY TABLE cities6 (name text, population float, altitude int);
+CREATE TEMPORARY TABLE capitals6 (state char(2)) INHERITS (cities6);
+INSERT INTO capitals6 VALUES ('Moscow', 123.45, 789, 'RU');
+INSERT INTO capitals6 VALUES ('Paris', 543.21, 987, 'FR');
+SELECT * FROM capitals6;
+ name | population | altitude | state
+--------+------------+----------+-------
+ Moscow | 123.45 | 789 | RU
+ Paris | 543.21 | 987 | FR
+(2 rows)
+
+SELECT * FROM cities6;
+ name | population | altitude
+--------+------------+----------
+ Moscow | 123.45 | 789
+ Paris | 543.21 | 987
+(2 rows)
+
+DELETE FROM cities6 WHERE name = 'Moscow';
+SELECT * FROM capitals6;
+ name | population | altitude | state
+-------+------------+----------+-------
+ Paris | 543.21 | 987 | FR
+(1 row)
+
+SELECT * FROM cities6;
+ name | population | altitude
+-------+------------+----------
+ Paris | 543.21 | 987
+(1 row)
+
+DROP TABLE capitals6;
+DROP TABLE cities6;
+-- test index-only scan
+CREATE FAST TEMP TABLE fasttab_unique_prefix_beta(x int);
+CREATE TABLE fasttab_unique_prefix_alpha(x int);
+CREATE FAST TEMP TABLE fasttab_unique_prefix_delta(x int);
+CREATE TABLE fasttab_unique_prefix_epsilon(x int);
+CREATE TABLE fasttab_unique_prefix_gamma(x int);
+SELECT relname FROM pg_class WHERE relname > 'fasttab_unique_prefix_' ORDER BY relname LIMIT 5;
+ relname
+-------------------------------
+ fasttab_unique_prefix_alpha
+ fasttab_unique_prefix_beta
+ fasttab_unique_prefix_delta
+ fasttab_unique_prefix_epsilon
+ fasttab_unique_prefix_gamma
+(5 rows)
+
+DROP TABLE fasttab_unique_prefix_alpha;
+DROP TABLE fasttab_unique_prefix_beta;
+DROP TABLE fasttab_unique_prefix_gamma;
+DROP TABLE fasttab_unique_prefix_delta;
+DROP TABLE fasttab_unique_prefix_epsilon;
+-- test VACUUM / VACUUM FULL
+VACUUM;
+VACUUM FULL;
+SELECT * FROM fast_table_1;
+ attr1 | attr2 | attr3 | attr4 | attr5 | attr6 | attr7 | attr8 | attr9 | attr10 | attr11 | attr12 | attr13 | attr14 | attr15 | attr16 | attr17 | attr18 | attr19 | attr20
+-------+-------+-------+-------+-------+-------+-------+-------+-------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------
+(0 rows)
+
+-- test ANALYZE
+CREATE FAST TEMP TABLE fasttab_analyze_test(x int, s text);
+INSERT INTO fasttab_analyze_test SELECT x, '--> ' || x FROM generate_series(1,100) as x;
+ANALYZE fasttab_analyze_test;
+SELECT count(*) FROM pg_statistic WHERE starelid = (SELECT oid FROM pg_class WHERE relname = 'fasttab_analyze_test');
+ count
+-------
+ 2
+(1 row)
+
+DROP TABLE fasttab_analyze_test;
+SELECT count(*) FROM pg_statistic WHERE starelid = (SELECT oid FROM pg_class WHERE relname = 'fasttab_analyze_test');
+ count
+-------
+ 0
+(1 row)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 4ebad04..60b9bec 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -105,6 +105,11 @@ test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combo
# ----------
test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml
+# ----------
+# Another group of parallel tests
+# ----------
+test: fast_temp
+
# event triggers cannot run concurrently with any test that runs DDL
test: event_trigger
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 5c7038d..f5aa850 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -151,6 +151,7 @@ test: limit
test: plpgsql
test: copy2
test: temp
+test: fast_temp
test: domain
test: rangefuncs
test: prepare
diff --git a/src/test/regress/sql/fast_temp.sql b/src/test/regress/sql/fast_temp.sql
new file mode 100644
index 0000000..859e895
--- /dev/null
+++ b/src/test/regress/sql/fast_temp.sql
@@ -0,0 +1,238 @@
+--
+-- FAST TEMP
+-- Test fast temporary tables
+--
+
+-- basic test
+
+CREATE FAST TEMP TABLE fasttab_test1(x int, s text);
+
+INSERT INTO fasttab_test1 VALUES (1, 'aaa'), (2, 'bbb'), (3, 'ccc'), (4, 'ddd');
+
+UPDATE fasttab_test1 SET s = 'eee' WHERE x = 4;
+
+UPDATE fasttab_test1 SET x = 5 WHERE s = 'bbb';
+
+DELETE FROM fasttab_test1 WHERE x = 3;
+
+SELECT * FROM fasttab_test1 ORDER BY x;
+
+DROP TABLE fasttab_test1;
+
+-- kind of load test
+
+do $$
+declare
+ count_fast_table integer = 150;
+ count_attr integer = 20;
+ i integer;
+ j integer;
+ t_sql text;
+begin
+ for i in 1 .. count_fast_table
+ loop
+ t_sql = 'CREATE FAST TEMP TABLE fast_table_' || i :: text;
+ t_sql = t_sql || ' (';
+ for j in 1 .. count_attr
+ loop
+ t_sql = t_sql || ' attr' || j || ' text';
+ if j <> count_attr then
+ t_sql = t_sql || ', ';
+ end if;
+ end loop;
+ t_sql = t_sql || ' );';
+ execute t_sql;
+ -- raise info 't_sql %', t_sql;
+ end loop;
+end $$;
+
+SELECT * FROM fast_table_1;
+
+-- test bitmap index scan
+
+SELECT count(*) FROM pg_class WHERE relname = 'fast_table_1' OR relname = 'fast_table_2';
+
+-- create / delete / create test
+
+CREATE FAST TEMP TABLE fasttab_test1(x int, s text);
+
+-- check index only scan
+
+SELECT COUNT(*) FROM pg_class WHERE relname = 'fasttab_test1';
+SELECT relname FROM pg_class WHERE relname = 'fasttab_test1';
+
+DROP TABLE fasttab_test1;
+
+-- select from non-existend temp table
+
+SELECT COUNT(*) FROM fasttab_test1;
+
+CREATE FAST TEMP TABLE fasttab_test1(x int, s text);
+CREATE FAST TEMP TABLE fasttab_test2(x int, s text);
+SELECT * FROM fasttab_test1;
+
+-- check that ALTER is working as expected
+
+ALTER TABLE fasttab_test1 ADD COLUMN y int;
+SELECT * FROM fasttab_test1;
+
+ALTER TABLE fasttab_test1 ADD COLUMN z int;
+SELECT * FROM fasttab_test1;
+
+ALTER TABLE fasttab_test1 DROP COLUMN x;
+SELECT * FROM fasttab_test1;
+
+ALTER TABLE fasttab_test1 DROP COLUMN y;
+SELECT * FROM fasttab_test1;
+
+-- test transactions and savepoints
+
+BEGIN;
+
+INSERT INTO fasttab_test2 VALUES (1, 'aaa'), (2, 'bbb');
+SELECT * FROM fasttab_test2;
+
+ROLLBACK;
+
+SELECT * FROM fasttab_test2;
+
+BEGIN;
+
+INSERT INTO fasttab_test2 VALUES (3, 'ccc'), (4, 'ddd');
+SELECT * FROM fasttab_test2;
+
+COMMIT;
+
+SELECT * FROM fasttab_test2;
+
+
+BEGIN;
+
+SAVEPOINT sp1;
+
+ALTER TABLE fasttab_test2 ADD COLUMN y int;
+SELECT * FROM fasttab_test2;
+
+SAVEPOINT sp2;
+
+INSERT INTO fasttab_test2 VALUES (5, 'eee', 6);
+SELECT * FROM fasttab_test2;
+ROLLBACK TO SAVEPOINT sp2;
+
+INSERT INTO fasttab_test2 VALUES (55, 'EEE', 66);
+SELECT * FROM fasttab_test2;
+ROLLBACK TO SAVEPOINT sp2;
+
+SELECT * FROM fasttab_test2;
+COMMIT;
+
+-- test that exceptions are handled properly
+
+DO $$
+DECLARE
+BEGIN
+ CREATE FAST TEMP TABLE fast_exception_test(x int, y int, z int);
+ RAISE EXCEPTION 'test error';
+END $$;
+
+CREATE FAST TEMP TABLE fast_exception_test(x int, y int, z int);
+
+-- test that inheritance works as expected
+-- OK:
+
+CREATE TABLE cities (name text, population float, altitude int);
+CREATE TABLE capitals (state char(2)) INHERITS (cities);
+DROP TABLE capitals;
+DROP TABLE cities;
+
+-- OK:
+
+CREATE TABLE cities2 (name text, population float, altitude int);
+CREATE FAST TEMPORARY TABLE capitals2 (state char(2)) INHERITS (cities2);
+INSERT INTO capitals2 VALUES ('Moscow', 123.45, 789, 'RU');
+INSERT INTO capitals2 VALUES ('Paris', 543.21, 987, 'FR');
+SELECT * FROM capitals2;
+SELECT * FROM cities2;
+DELETE FROM cities2 WHERE name = 'Moscow';
+SELECT * FROM capitals2;
+SELECT * FROM cities2;
+DROP TABLE capitals2;
+DROP TABLE cities2;
+
+-- ERROR:
+
+CREATE FAST TEMPORARY TABLE cities3 (name text, population float, altitude int);
+-- cannot inherit from temporary relation "cities3"
+CREATE TABLE capitals3 (state char(2)) INHERITS (cities3);
+DROP TABLE cities3;
+
+-- OK:
+
+CREATE FAST TEMPORARY TABLE cities4 (name text, population float, altitude int);
+CREATE FAST TEMPORARY TABLE capitals4 (state char(2)) INHERITS (cities4);
+INSERT INTO capitals4 VALUES ('Moscow', 123.45, 789, 'RU');
+INSERT INTO capitals4 VALUES ('Paris', 543.21, 987, 'FR');
+SELECT * FROM capitals4;
+SELECT * FROM cities4;
+DELETE FROM cities4 WHERE name = 'Moscow';
+SELECT * FROM capitals4;
+SELECT * FROM cities4;
+DROP TABLE capitals4;
+DROP TABLE cities4;
+
+-- OK:
+
+CREATE TEMPORARY TABLE cities5 (name text, population float, altitude int);
+CREATE FAST TEMPORARY TABLE capitals5 (state char(2)) INHERITS (cities5);
+INSERT INTO capitals5 VALUES ('Moscow', 123.45, 789, 'RU');
+INSERT INTO capitals5 VALUES ('Paris', 543.21, 987, 'FR');
+SELECT * FROM capitals5;
+SELECT * FROM cities5;
+DELETE FROM cities5 WHERE name = 'Moscow';
+SELECT * FROM capitals5;
+SELECT * FROM cities5;
+DROP TABLE capitals5;
+DROP TABLE cities5;
+
+-- OK:
+
+CREATE FAST TEMPORARY TABLE cities6 (name text, population float, altitude int);
+CREATE TEMPORARY TABLE capitals6 (state char(2)) INHERITS (cities6);
+INSERT INTO capitals6 VALUES ('Moscow', 123.45, 789, 'RU');
+INSERT INTO capitals6 VALUES ('Paris', 543.21, 987, 'FR');
+SELECT * FROM capitals6;
+SELECT * FROM cities6;
+DELETE FROM cities6 WHERE name = 'Moscow';
+SELECT * FROM capitals6;
+SELECT * FROM cities6;
+DROP TABLE capitals6;
+DROP TABLE cities6;
+
+-- test index-only scan
+
+CREATE FAST TEMP TABLE fasttab_unique_prefix_beta(x int);
+CREATE TABLE fasttab_unique_prefix_alpha(x int);
+CREATE FAST TEMP TABLE fasttab_unique_prefix_delta(x int);
+CREATE TABLE fasttab_unique_prefix_epsilon(x int);
+CREATE TABLE fasttab_unique_prefix_gamma(x int);
+SELECT relname FROM pg_class WHERE relname > 'fasttab_unique_prefix_' ORDER BY relname LIMIT 5;
+DROP TABLE fasttab_unique_prefix_alpha;
+DROP TABLE fasttab_unique_prefix_beta;
+DROP TABLE fasttab_unique_prefix_gamma;
+DROP TABLE fasttab_unique_prefix_delta;
+DROP TABLE fasttab_unique_prefix_epsilon;
+
+-- test VACUUM / VACUUM FULL
+
+VACUUM;
+VACUUM FULL;
+SELECT * FROM fast_table_1;
+
+-- test ANALYZE
+
+CREATE FAST TEMP TABLE fasttab_analyze_test(x int, s text);
+INSERT INTO fasttab_analyze_test SELECT x, '--> ' || x FROM generate_series(1,100) as x;
+ANALYZE fasttab_analyze_test;
+SELECT count(*) FROM pg_statistic WHERE starelid = (SELECT oid FROM pg_class WHERE relname = 'fasttab_analyze_test');
+DROP TABLE fasttab_analyze_test;
+SELECT count(*) FROM pg_statistic WHERE starelid = (SELECT oid FROM pg_class WHERE relname = 'fasttab_analyze_test');
--
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers