From 8bc00e8dc5e12caa6995b62346fd4c9f555eaba0 Mon Sep 17 00:00:00 2001 From: Hari Date: Thu, 1 Jun 2017 22:01:38 +1000 Subject: [PATCH 05/10] Adding storageam handler to slot Storage am handler to slot and also changing the slot structure according to new access methods --- src/backend/access/common/heaptuple.c | 25 +++-- src/backend/executor/execTuples.c | 172 +++++++++++++++++----------------- src/include/access/htup_details.h | 12 +++ src/include/executor/tuptable.h | 50 ++++++---- src/include/nodes/nodes.h | 1 + 5 files changed, 145 insertions(+), 115 deletions(-) diff --git a/src/backend/access/common/heaptuple.c b/src/backend/access/common/heaptuple.c index c0086de..f04c870 100644 --- a/src/backend/access/common/heaptuple.c +++ b/src/backend/access/common/heaptuple.c @@ -1033,7 +1033,8 @@ heap_deform_tuple(HeapTuple tuple, TupleDesc tupleDesc, static void slot_deform_tuple(TupleTableSlot *slot, int natts) { - HeapTuple tuple = slot->tts_tuple; + HeapamTuple *stuple = (HeapamTuple *) slot->tts_storage; + HeapTuple tuple = stuple ? stuple->hst_heaptuple : NULL; TupleDesc tupleDesc = slot->tts_tupleDescriptor; Datum *values = slot->tts_values; bool *isnull = slot->tts_isnull; @@ -1060,8 +1061,8 @@ slot_deform_tuple(TupleTableSlot *slot, int natts) else { /* Restore state from previous execution */ - off = slot->tts_off; - slow = slot->tts_slow; + off = stuple->hst_off; + slow = stuple->hst_slow; } tp = (char *) tup + tup->t_hoff; @@ -1121,8 +1122,8 @@ slot_deform_tuple(TupleTableSlot *slot, int natts) * Save state for next execution */ slot->tts_nvalid = attnum; - slot->tts_off = off; - slot->tts_slow = slow; + stuple->hst_off = off; + stuple->hst_slow = slow; } /* @@ -1140,7 +1141,8 @@ slot_deform_tuple(TupleTableSlot *slot, int natts) Datum slot_getattr(TupleTableSlot *slot, int attnum, bool *isnull) { - HeapTuple tuple = slot->tts_tuple; + HeapamTuple *stuple = (HeapamTuple *) slot->tts_storage; + HeapTuple tuple = stuple ? stuple->hst_heaptuple : NULL; TupleDesc tupleDesc = slot->tts_tupleDescriptor; HeapTupleHeader tup; @@ -1236,6 +1238,7 @@ slot_getattr(TupleTableSlot *slot, int attnum, bool *isnull) void slot_getallattrs(TupleTableSlot *slot) { + HeapamTuple *stuple; int tdesc_natts = slot->tts_tupleDescriptor->natts; int attnum; HeapTuple tuple; @@ -1248,7 +1251,8 @@ slot_getallattrs(TupleTableSlot *slot) * otherwise we had better have a physical tuple (tts_nvalid should equal * natts in all virtual-tuple cases) */ - tuple = slot->tts_tuple; + stuple = (HeapamTuple *) slot->tts_storage; + tuple = stuple->hst_heaptuple; if (tuple == NULL) /* internal error */ elog(ERROR, "cannot extract attribute from empty tuple slot"); @@ -1280,6 +1284,7 @@ slot_getallattrs(TupleTableSlot *slot) void slot_getsomeattrs(TupleTableSlot *slot, int attnum) { + HeapamTuple *stuple; HeapTuple tuple; int attno; @@ -1295,7 +1300,8 @@ slot_getsomeattrs(TupleTableSlot *slot, int attnum) * otherwise we had better have a physical tuple (tts_nvalid should equal * natts in all virtual-tuple cases) */ - tuple = slot->tts_tuple; + stuple = slot->tts_storage; /* XXX SlotGetTupleStorage(slot) ??? */ + tuple = stuple->hst_heaptuple; if (tuple == NULL) /* internal error */ elog(ERROR, "cannot extract attribute from empty tuple slot"); @@ -1327,7 +1333,8 @@ slot_getsomeattrs(TupleTableSlot *slot, int attnum) bool slot_attisnull(TupleTableSlot *slot, int attnum) { - HeapTuple tuple = slot->tts_tuple; + HeapamTuple *stuple = slot->tts_storage; + HeapTuple tuple = stuple ? stuple->hst_heaptuple : NULL; TupleDesc tupleDesc = slot->tts_tupleDescriptor; /* diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c index c4a9553..6912163 100644 --- a/src/backend/executor/execTuples.c +++ b/src/backend/executor/execTuples.c @@ -82,8 +82,10 @@ #include "postgres.h" #include "access/htup_details.h" +#include "access/storageamapi.h" #include "access/tuptoaster.h" #include "funcapi.h" +#include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "nodes/nodeFuncs.h" #include "storage/bufmgr.h" @@ -113,16 +115,17 @@ MakeTupleTableSlot(void) TupleTableSlot *slot = makeNode(TupleTableSlot); slot->tts_isempty = true; - slot->tts_shouldFree = false; slot->tts_shouldFreeMin = false; - slot->tts_tuple = NULL; slot->tts_tupleDescriptor = NULL; slot->tts_mcxt = CurrentMemoryContext; - slot->tts_buffer = InvalidBuffer; slot->tts_nvalid = 0; slot->tts_values = NULL; slot->tts_isnull = NULL; slot->tts_mintuple = NULL; + slot->tts_tupleOid = InvalidOid; + slot->tts_tableOid = InvalidOid; + slot->tts_storageam = GetStorageAmRoutine(HEAPAM_STORAGE_AM_HANDLER_OID); + slot->tts_storage = NULL; return slot; } @@ -247,6 +250,7 @@ void ExecSetSlotDescriptor(TupleTableSlot *slot, /* slot to change */ TupleDesc tupdesc) /* new tuple descriptor */ { + /* For safety, make sure slot is empty before changing it */ ExecClearTuple(slot); @@ -279,6 +283,30 @@ ExecSetSlotDescriptor(TupleTableSlot *slot, /* slot to change */ } /* -------------------------------- + * ExecSetSlotStorageRoutine + * + * This function sets the storage AM routine pointer for the slot. + * + * XXX should this be part of ExecSetSlotDescriptor? + * -------------------------------- + */ +void +ExecSetSlotStorageRoutine(TupleTableSlot *slot, + StorageAmRoutine *routine) +{ + if (slot->tts_storageam) + { + /* XXX do we need any cleanup here? */ + } + + /* + * XXX maybe we need refcounts for StorageAmRoutine objects? + * or perhaps we need to copy here? + */ + slot->tts_storageam = routine; +} + +/* -------------------------------- * ExecStoreTuple * * This function is used to store a physical tuple into a specified @@ -294,19 +322,6 @@ ExecSetSlotDescriptor(TupleTableSlot *slot, /* slot to change */ * on the buffer which is held until the slot is cleared, so that the tuple * won't go away on us. * - * shouldFree is normally set 'true' for tuples constructed on-the-fly. - * It must always be 'false' for tuples that are stored in disk pages, - * since we don't want to try to pfree those. - * - * Another case where it is 'false' is when the referenced tuple is held - * in a tuple table slot belonging to a lower-level executor Proc node. - * In this case the lower-level slot retains ownership and responsibility - * for eventually releasing the tuple. When this method is used, we must - * be certain that the upper-level Proc node will lose interest in the tuple - * sooner than the lower-level one does! If you're not certain, copy the - * lower-level tuple with heap_copytuple and let the upper-level table - * slot assume ownership of the copy! - * * Return value is just the passed-in slot pointer. * * NOTE: before PostgreSQL 8.1, this function would accept a NULL tuple @@ -317,7 +332,7 @@ ExecSetSlotDescriptor(TupleTableSlot *slot, /* slot to change */ * -------------------------------- */ TupleTableSlot * -ExecStoreTuple(HeapTuple tuple, +ExecStoreTuple(void *tuple, TupleTableSlot *slot, Buffer buffer, bool shouldFree) @@ -328,47 +343,29 @@ ExecStoreTuple(HeapTuple tuple, Assert(tuple != NULL); Assert(slot != NULL); Assert(slot->tts_tupleDescriptor != NULL); + Assert(slot->tts_storageam != NULL); /* passing shouldFree=true for a tuple on a disk page is not sane */ Assert(BufferIsValid(buffer) ? (!shouldFree) : true); /* * Free any old physical tuple belonging to the slot. */ - if (slot->tts_shouldFree) - heap_freetuple(slot->tts_tuple); - if (slot->tts_shouldFreeMin) - heap_free_minimal_tuple(slot->tts_mintuple); + slot->tts_storageam->slot_clear_tuple(slot); /* XXX ?? */ /* - * Store the new tuple into the specified slot. + * Store the new tuple into the specified slot, and mark the slot as no + * longer empty. This clears any previously stored physical tuple. */ + /* XXX should we pass the buffer down to the storageAM perhaps? */ + slot->tts_storageam->slot_store_tuple(slot, tuple, shouldFree); + slot->tts_isempty = false; - slot->tts_shouldFree = shouldFree; slot->tts_shouldFreeMin = false; - slot->tts_tuple = tuple; slot->tts_mintuple = NULL; /* Mark extracted state invalid */ slot->tts_nvalid = 0; - /* - * If tuple is on a disk page, keep the page pinned as long as we hold a - * pointer into it. We assume the caller already has such a pin. - * - * This is coded to optimize the case where the slot previously held a - * tuple on the same disk page: in that case releasing and re-acquiring - * the pin is a waste of cycles. This is a common situation during - * seqscans, so it's worth troubling over. - */ - if (slot->tts_buffer != buffer) - { - if (BufferIsValid(slot->tts_buffer)) - ReleaseBuffer(slot->tts_buffer); - slot->tts_buffer = buffer; - if (BufferIsValid(buffer)) - IncrBufferRefCount(buffer); - } - return slot; } @@ -395,10 +392,7 @@ ExecStoreMinimalTuple(MinimalTuple mtup, /* * Free any old physical tuple belonging to the slot. */ - if (slot->tts_shouldFree) - heap_freetuple(slot->tts_tuple); - if (slot->tts_shouldFreeMin) - heap_free_minimal_tuple(slot->tts_mintuple); + slot->tts_storageam->slot_clear_tuple(slot); /* XXX ?? */ /* * Drop the pin on the referenced buffer, if there is one. @@ -412,9 +406,15 @@ ExecStoreMinimalTuple(MinimalTuple mtup, * Store the new tuple into the specified slot. */ slot->tts_isempty = false; - slot->tts_shouldFree = false; slot->tts_shouldFreeMin = shouldFree; - slot->tts_tuple = &slot->tts_minhdr; + + /* + * Store the new tuple into the specified slot, and mark the slot as no + * longer empty. This clears any previously stored physical tuple. + */ + /* XXX should we pass the buffer down to the storageAM perhaps? */ + slot->tts_storageam->slot_store_tuple(slot, &slot->tts_minhdr, false); + slot->tts_mintuple = mtup; slot->tts_minhdr.t_len = mtup->t_len + MINIMAL_TUPLE_OFFSET; @@ -444,25 +444,20 @@ ExecClearTuple(TupleTableSlot *slot) /* slot in which to store tuple */ Assert(slot != NULL); /* - * Free the old physical tuple if necessary. + * Tell the storage AM to release any resource associated with the slot. */ - if (slot->tts_shouldFree) - heap_freetuple(slot->tts_tuple); - if (slot->tts_shouldFreeMin) - heap_free_minimal_tuple(slot->tts_mintuple); - - slot->tts_tuple = NULL; - slot->tts_mintuple = NULL; - slot->tts_shouldFree = false; - slot->tts_shouldFreeMin = false; + slot->tts_storageam->slot_clear_tuple(slot); /* * Drop the pin on the referenced buffer, if there is one. + * + * XXX probably this belongs in the storage AM rather than here. */ if (BufferIsValid(slot->tts_buffer)) ReleaseBuffer(slot->tts_buffer); slot->tts_buffer = InvalidBuffer; + slot->tts_mintuple = NULL; /* * Mark it empty. @@ -541,7 +536,7 @@ ExecStoreAllNullTuple(TupleTableSlot *slot) * however the "system columns" of the result will not be meaningful. * -------------------------------- */ -HeapTuple +StorageTuple ExecCopySlotTuple(TupleTableSlot *slot) { /* @@ -553,8 +548,8 @@ ExecCopySlotTuple(TupleTableSlot *slot) /* * If we have a physical tuple (either format) then just copy it. */ - if (TTS_HAS_PHYSICAL_TUPLE(slot)) - return heap_copytuple(slot->tts_tuple); + if (slot->tts_storage && slot->tts_storageam->slot_is_physical_tuple(slot)) + return slot->tts_storageam->slot_copy_tuple(slot); if (slot->tts_mintuple) return heap_tuple_from_minimal_tuple(slot->tts_mintuple); @@ -588,8 +583,9 @@ ExecCopySlotMinimalTuple(TupleTableSlot *slot) */ if (slot->tts_mintuple) return heap_copy_minimal_tuple(slot->tts_mintuple); - if (slot->tts_tuple) - return minimal_tuple_from_heap_tuple(slot->tts_tuple); + + if (slot->tts_storage) + return slot->tts_storageam->slot_copy_min_tuple(slot); /* * Otherwise we need to build a tuple from the Datum array. @@ -614,7 +610,7 @@ ExecCopySlotMinimalTuple(TupleTableSlot *slot) * Hence, the result must be treated as read-only. * -------------------------------- */ -HeapTuple +StorageTuple ExecFetchSlotTuple(TupleTableSlot *slot) { /* @@ -624,15 +620,9 @@ ExecFetchSlotTuple(TupleTableSlot *slot) Assert(!slot->tts_isempty); /* - * If we have a regular physical tuple then just return it. - */ - if (TTS_HAS_PHYSICAL_TUPLE(slot)) - return slot->tts_tuple; - - /* * Otherwise materialize the slot... */ - return ExecMaterializeSlot(slot); + return ExecHeapifySlot(slot); } /* -------------------------------- @@ -697,7 +687,7 @@ ExecFetchSlotMinimalTuple(TupleTableSlot *slot) Datum ExecFetchSlotTupleDatum(TupleTableSlot *slot) { - HeapTuple tup; + StorageTuple tup; TupleDesc tupdesc; /* Fetch slot's contents in regular-physical-tuple form */ @@ -713,18 +703,19 @@ ExecFetchSlotTupleDatum(TupleTableSlot *slot) * Force a slot into the "materialized" state. * * This causes the slot's tuple to be a local copy not dependent on - * any external storage. A pointer to the contained tuple is returned. + * any external storage. * * A typical use for this operation is to prepare a computed tuple * for being stored on disk. The original data may or may not be * virtual, but in any case we need a private copy for heap_insert - * to scribble on. + * to scribble on. XXX is this comment good? * -------------------------------- */ -HeapTuple +void ExecMaterializeSlot(TupleTableSlot *slot) { MemoryContext oldContext; + StorageTuple tuple; /* * sanity checks @@ -732,12 +723,14 @@ ExecMaterializeSlot(TupleTableSlot *slot) Assert(slot != NULL); Assert(!slot->tts_isempty); + /* * If we have a regular physical tuple, and it's locally palloc'd, we have * nothing to do. */ - if (slot->tts_tuple && slot->tts_shouldFree) - return slot->tts_tuple; + if (slot->tts_storage && + slot->tts_storageam->slot_is_physical_tuple(slot)) + return; /* * Otherwise, copy or build a physical tuple, and store it into the slot. @@ -747,8 +740,8 @@ ExecMaterializeSlot(TupleTableSlot *slot) * anyway. */ oldContext = MemoryContextSwitchTo(slot->tts_mcxt); - slot->tts_tuple = ExecCopySlotTuple(slot); - slot->tts_shouldFree = true; + tuple = ExecCopySlotTuple(slot); + slot->tts_storageam->slot_store_tuple(slot, tuple, true); MemoryContextSwitchTo(oldContext); /* @@ -777,8 +770,15 @@ ExecMaterializeSlot(TupleTableSlot *slot) */ if (!slot->tts_shouldFreeMin) slot->tts_mintuple = NULL; +} + +StorageTuple +ExecHeapifySlot(TupleTableSlot *slot) +{ + ExecMaterializeSlot(slot); + Assert(slot->tts_storage != NULL); - return slot->tts_tuple; + return slot->tts_storageam->slot_get_tuple(slot); } /* -------------------------------- @@ -794,7 +794,7 @@ ExecMaterializeSlot(TupleTableSlot *slot) TupleTableSlot * ExecCopySlot(TupleTableSlot *dstslot, TupleTableSlot *srcslot) { - HeapTuple newTuple; + StorageTuple newTuple; MemoryContext oldContext; /* @@ -1107,11 +1107,11 @@ TupleDescGetAttInMetadata(TupleDesc tupdesc) } /* - * BuildTupleFromCStrings - build a HeapTuple given user data in C string form. + * BuildTupleFromCStrings - build a StorageTuple given user data in C string form. * values is an array of C strings, one for each attribute of the return tuple. * A NULL string pointer indicates we want to create a NULL field. */ -HeapTuple +StorageTuple BuildTupleFromCStrings(AttInMetadata *attinmeta, char **values) { TupleDesc tupdesc = attinmeta->tupdesc; @@ -1119,7 +1119,7 @@ BuildTupleFromCStrings(AttInMetadata *attinmeta, char **values) Datum *dvalues; bool *nulls; int i; - HeapTuple tuple; + StorageTuple tuple; dvalues = (Datum *) palloc(natts * sizeof(Datum)); nulls = (bool *) palloc(natts * sizeof(bool)); @@ -1175,7 +1175,7 @@ BuildTupleFromCStrings(AttInMetadata *attinmeta, char **values) * for a composite Datum. However, we now require that composite Datums not * contain any external TOAST pointers. We do not want heap_form_tuple itself * to enforce that; more specifically, the rule applies only to actual Datums - * and not to HeapTuple structures. Therefore, HeapTupleHeaderGetDatum is + * and not to StorageTuple structures. Therefore, HeapTupleHeaderGetDatum is * now a function that detects whether there are externally-toasted fields * and constructs a new tuple with inlined fields if so. We still need * heap_form_tuple to insert the Datum header fields, because otherwise this @@ -1183,7 +1183,7 @@ BuildTupleFromCStrings(AttInMetadata *attinmeta, char **values) * * Note that if we do build a new tuple, it's palloc'd in the current * memory context. Beware of code that changes context between the initial - * heap_form_tuple/etc call and calling HeapTuple(Header)GetDatum. + * heap_form_tuple/etc call and calling StorageTuple(Header)GetDatum. * * For performance-critical callers, it could be worthwhile to take extra * steps to ensure that there aren't TOAST pointers in the output of diff --git a/src/include/access/htup_details.h b/src/include/access/htup_details.h index e365f4f..a8ab945 100644 --- a/src/include/access/htup_details.h +++ b/src/include/access/htup_details.h @@ -21,6 +21,18 @@ #include "storage/bufpage.h" /* + * Opaque tuple representation for executor's TupleTableSlot tts_storage + * (XXX This should probably live in a separate header) + */ +typedef struct HeapamTuple +{ + HeapTuple hst_heaptuple; + bool hst_slow; + bool hst_shouldFree; + long hst_off; +} HeapamTuple; + +/* * MaxTupleAttributeNumber limits the number of (user) columns in a tuple. * The key limit on this value is that the size of the fixed overhead for * a tuple, plus the size of the null-values bitmap (at 1 bit per column), diff --git a/src/include/executor/tuptable.h b/src/include/executor/tuptable.h index 32489ef..7521739 100644 --- a/src/include/executor/tuptable.h +++ b/src/include/executor/tuptable.h @@ -18,9 +18,25 @@ #include "access/tupdesc.h" #include "storage/buf.h" +/* + * Forward declare StorageAmRoutine to avoid including storageamapi.h here + */ +struct StorageAmRoutine; + +/* + * Forward declare StorageTuple to avoid including storageamapi.h here + */ +typedef void *StorageTuple; + /*---------- * The executor stores tuples in a "tuple table" which is a List of - * independent TupleTableSlots. There are several cases we need to handle: + * independent TupleTableSlots. + * + * XXX The "html-commented out" text below no longer reflects reality, as + * physical tuples are now responsibility of storage AMs. But we have kept + * "minimal tuples". Adjust this comment! + * + * * * The Datum/isnull arrays of a TupleTableSlot serve double duty. When the * slot contains a virtual tuple, they are the authoritative data. When the @@ -79,15 +96,6 @@ * mechanism to do more. However, the slot will increment the tupdesc * reference count if a reference-counted tupdesc is supplied.) * - * When tts_shouldFree is true, the physical tuple is "owned" by the slot - * and should be freed when the slot's reference to the tuple is dropped. - * - * If tts_buffer is not InvalidBuffer, then the slot is holding a pin - * on the indicated buffer page; drop the pin when we release the - * slot's reference to that buffer. (tts_shouldFree should always be - * false in such a case, since presumably tts_tuple is pointing at the - * buffer page.) - * * tts_nvalid indicates the number of valid columns in the tts_values/isnull * arrays. When the slot is holding a "virtual" tuple this must be equal * to the descriptor's natts. When the slot is holding a physical tuple @@ -114,24 +122,22 @@ typedef struct TupleTableSlot { NodeTag type; bool tts_isempty; /* true = slot is empty */ - bool tts_shouldFree; /* should pfree tts_tuple? */ bool tts_shouldFreeMin; /* should pfree tts_mintuple? */ - bool tts_slow; /* saved state for slot_deform_tuple */ - HeapTuple tts_tuple; /* physical tuple, or NULL if virtual */ TupleDesc tts_tupleDescriptor; /* slot's tuple descriptor */ + Oid tts_tableOid; /* XXX describe */ + Oid tts_tupleOid; /* XXX describe */ + Buffer tts_buffer; MemoryContext tts_mcxt; /* slot itself is in this context */ - Buffer tts_buffer; /* tuple's buffer, or InvalidBuffer */ int tts_nvalid; /* # of valid values in tts_values */ + uint32 tts_speculativeToken; /* XXX describe */ Datum *tts_values; /* current per-attribute values */ bool *tts_isnull; /* current per-attribute isnull flags */ MinimalTuple tts_mintuple; /* minimal tuple, or NULL if none */ HeapTupleData tts_minhdr; /* workspace for minimal-tuple-only case */ - long tts_off; /* saved state for slot_deform_tuple */ + struct StorageAmRoutine *tts_storageam; /* storage AM */ + void *tts_storage; /* storage AM's opaque space */ } TupleTableSlot; -#define TTS_HAS_PHYSICAL_TUPLE(slot) \ - ((slot)->tts_tuple != NULL && (slot)->tts_tuple != &((slot)->tts_minhdr)) - /* * TupIsNull -- is a TupleTableSlot empty? */ @@ -144,8 +150,11 @@ extern TupleTableSlot *ExecAllocTableSlot(List **tupleTable); extern void ExecResetTupleTable(List *tupleTable, bool shouldFree); extern TupleTableSlot *MakeSingleTupleTableSlot(TupleDesc tupdesc); extern void ExecDropSingleTupleTableSlot(TupleTableSlot *slot); -extern void ExecSetSlotDescriptor(TupleTableSlot *slot, TupleDesc tupdesc); -extern TupleTableSlot *ExecStoreTuple(HeapTuple tuple, +extern void ExecSetSlotDescriptor(TupleTableSlot *slot, + TupleDesc tupdesc); +extern void ExecSetSlotStorageRoutine(TupleTableSlot *slot, + struct StorageAmRoutine *routine); +extern TupleTableSlot *ExecStoreTuple(void *tuple, TupleTableSlot *slot, Buffer buffer, bool shouldFree); @@ -161,6 +170,7 @@ extern HeapTuple ExecFetchSlotTuple(TupleTableSlot *slot); extern MinimalTuple ExecFetchSlotMinimalTuple(TupleTableSlot *slot); extern Datum ExecFetchSlotTupleDatum(TupleTableSlot *slot); extern HeapTuple ExecMaterializeSlot(TupleTableSlot *slot); +extern StorageTuple ExecHeapifySlot(TupleTableSlot *slot); extern TupleTableSlot *ExecCopySlot(TupleTableSlot *dstslot, TupleTableSlot *srcslot); diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index f59d719..ac3375e 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -496,6 +496,7 @@ typedef enum NodeTag T_InlineCodeBlock, /* in nodes/parsenodes.h */ T_FdwRoutine, /* in foreign/fdwapi.h */ T_IndexAmRoutine, /* in access/amapi.h */ + T_StorageAmRoutine, /* in access/storageamapi.h */ T_TsmRoutine, /* in access/tsmapi.h */ T_ForeignKeyCacheInfo /* in utils/rel.h */ } NodeTag; -- 2.7.4.windows.1