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