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,
+												&regular_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, &regular_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(&parallel_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 (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to