1 /*------------------------------------------------------------------------- 2 * 3 * gist_private.h 4 * private declarations for GiST -- declarations related to the 5 * internal implementation of GiST, not the public API 6 * 7 * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group 8 * Portions Copyright (c) 1994, Regents of the University of California 9 * 10 * src/include/access/gist_private.h 11 * 12 *------------------------------------------------------------------------- 13 */ 14 #ifndef GIST_PRIVATE_H 15 #define GIST_PRIVATE_H 16 17 #include "access/amapi.h" 18 #include "access/gist.h" 19 #include "access/itup.h" 20 #include "access/xlogreader.h" 21 #include "fmgr.h" 22 #include "lib/pairingheap.h" 23 #include "storage/bufmgr.h" 24 #include "storage/buffile.h" 25 #include "utils/hsearch.h" 26 #include "access/genam.h" 27 28 /* 29 * Maximum number of "halves" a page can be split into in one operation. 30 * Typically a split produces 2 halves, but can be more if keys have very 31 * different lengths, or when inserting multiple keys in one operation (as 32 * when inserting downlinks to an internal node). There is no theoretical 33 * limit on this, but in practice if you get more than a handful page halves 34 * in one split, there's something wrong with the opclass implementation. 35 * GIST_MAX_SPLIT_PAGES is an arbitrary limit on that, used to size some 36 * local arrays used during split. Note that there is also a limit on the 37 * number of buffers that can be held locked at a time, MAX_SIMUL_LWLOCKS, 38 * so if you raise this higher than that limit, you'll just get a different 39 * error. 40 */ 41 #define GIST_MAX_SPLIT_PAGES 75 42 43 /* Buffer lock modes */ 44 #define GIST_SHARE BUFFER_LOCK_SHARE 45 #define GIST_EXCLUSIVE BUFFER_LOCK_EXCLUSIVE 46 #define GIST_UNLOCK BUFFER_LOCK_UNLOCK 47 48 typedef struct 49 { 50 BlockNumber prev; 51 uint32 freespace; 52 char tupledata[FLEXIBLE_ARRAY_MEMBER]; 53 } GISTNodeBufferPage; 54 55 #define BUFFER_PAGE_DATA_OFFSET MAXALIGN(offsetof(GISTNodeBufferPage, tupledata)) 56 /* Returns free space in node buffer page */ 57 #define PAGE_FREE_SPACE(nbp) (nbp->freespace) 58 /* Checks if node buffer page is empty */ 59 #define PAGE_IS_EMPTY(nbp) (nbp->freespace == BLCKSZ - BUFFER_PAGE_DATA_OFFSET) 60 /* Checks if node buffers page don't contain sufficient space for index tuple */ 61 #define PAGE_NO_SPACE(nbp, itup) (PAGE_FREE_SPACE(nbp) < \ 62 MAXALIGN(IndexTupleSize(itup))) 63 64 /* 65 * GISTSTATE: information needed for any GiST index operation 66 * 67 * This struct retains call info for the index's opclass-specific support 68 * functions (per index column), plus the index's tuple descriptor. 69 * 70 * scanCxt holds the GISTSTATE itself as well as any data that lives for the 71 * lifetime of the index operation. We pass this to the support functions 72 * via fn_mcxt, so that they can store scan-lifespan data in it. The 73 * functions are invoked in tempCxt, which is typically short-lifespan 74 * (that is, it's reset after each tuple). However, tempCxt can be the same 75 * as scanCxt if we're not bothering with per-tuple context resets. 76 */ 77 typedef struct GISTSTATE 78 { 79 MemoryContext scanCxt; /* context for scan-lifespan data */ 80 MemoryContext tempCxt; /* short-term context for calling functions */ 81 82 TupleDesc tupdesc; /* index's tuple descriptor */ 83 TupleDesc fetchTupdesc; /* tuple descriptor for tuples returned in an 84 * index-only scan */ 85 86 FmgrInfo consistentFn[INDEX_MAX_KEYS]; 87 FmgrInfo unionFn[INDEX_MAX_KEYS]; 88 FmgrInfo compressFn[INDEX_MAX_KEYS]; 89 FmgrInfo decompressFn[INDEX_MAX_KEYS]; 90 FmgrInfo penaltyFn[INDEX_MAX_KEYS]; 91 FmgrInfo picksplitFn[INDEX_MAX_KEYS]; 92 FmgrInfo equalFn[INDEX_MAX_KEYS]; 93 FmgrInfo distanceFn[INDEX_MAX_KEYS]; 94 FmgrInfo fetchFn[INDEX_MAX_KEYS]; 95 96 /* Collations to pass to the support functions */ 97 Oid supportCollation[INDEX_MAX_KEYS]; 98 } GISTSTATE; 99 100 101 /* 102 * During a GiST index search, we must maintain a queue of unvisited items, 103 * which can be either individual heap tuples or whole index pages. If it 104 * is an ordered search, the unvisited items should be visited in distance 105 * order. Unvisited items at the same distance should be visited in 106 * depth-first order, that is heap items first, then lower index pages, then 107 * upper index pages; this rule avoids doing extra work during a search that 108 * ends early due to LIMIT. 109 * 110 * To perform an ordered search, we use a pairing heap to manage the 111 * distance-order queue. In a non-ordered search (no order-by operators), 112 * we use it to return heap tuples before unvisited index pages, to 113 * ensure depth-first order, but all entries are otherwise considered 114 * equal. 115 */ 116 117 /* Individual heap tuple to be visited */ 118 typedef struct GISTSearchHeapItem 119 { 120 ItemPointerData heapPtr; 121 bool recheck; /* T if quals must be rechecked */ 122 bool recheckDistances; /* T if distances must be rechecked */ 123 IndexTuple ftup; /* data fetched back from the index, used in 124 * index-only scans */ 125 OffsetNumber offnum; /* track offset in page to mark tuple as 126 * LP_DEAD */ 127 } GISTSearchHeapItem; 128 129 /* Unvisited item, either index page or heap tuple */ 130 typedef struct GISTSearchItem 131 { 132 pairingheap_node phNode; 133 BlockNumber blkno; /* index page number, or InvalidBlockNumber */ 134 union 135 { 136 GistNSN parentlsn; /* parent page's LSN, if index page */ 137 /* we must store parentlsn to detect whether a split occurred */ 138 GISTSearchHeapItem heap; /* heap info, if heap tuple */ 139 } data; 140 141 /* numberOfOrderBys entries */ 142 IndexOrderByDistance distances[FLEXIBLE_ARRAY_MEMBER]; 143 } GISTSearchItem; 144 145 #define GISTSearchItemIsHeap(item) ((item).blkno == InvalidBlockNumber) 146 147 #define SizeOfGISTSearchItem(n_distances) \ 148 (offsetof(GISTSearchItem, distances) + \ 149 sizeof(IndexOrderByDistance) * (n_distances)) 150 151 /* 152 * GISTScanOpaqueData: private state for a scan of a GiST index 153 */ 154 typedef struct GISTScanOpaqueData 155 { 156 GISTSTATE *giststate; /* index information, see above */ 157 Oid *orderByTypes; /* datatypes of ORDER BY expressions */ 158 159 pairingheap *queue; /* queue of unvisited items */ 160 MemoryContext queueCxt; /* context holding the queue */ 161 bool qual_ok; /* false if qual can never be satisfied */ 162 bool firstCall; /* true until first gistgettuple call */ 163 164 /* pre-allocated workspace arrays */ 165 IndexOrderByDistance *distances; /* output area for gistindex_keytest */ 166 167 /* info about killed items if any (killedItems is NULL if never used) */ 168 OffsetNumber *killedItems; /* offset numbers of killed items */ 169 int numKilled; /* number of currently stored items */ 170 BlockNumber curBlkno; /* current number of block */ 171 GistNSN curPageLSN; /* pos in the WAL stream when page was read */ 172 173 /* In a non-ordered search, returnable heap items are stored here: */ 174 GISTSearchHeapItem pageData[BLCKSZ / sizeof(IndexTupleData)]; 175 OffsetNumber nPageData; /* number of valid items in array */ 176 OffsetNumber curPageData; /* next item to return */ 177 MemoryContext pageDataCxt; /* context holding the fetched tuples, for 178 * index-only scans */ 179 } GISTScanOpaqueData; 180 181 typedef GISTScanOpaqueData *GISTScanOpaque; 182 183 184 /* XLog stuff */ 185 186 #define XLOG_GIST_PAGE_UPDATE 0x00 187 /* #define XLOG_GIST_NEW_ROOT 0x20 */ /* not used anymore */ 188 #define XLOG_GIST_PAGE_SPLIT 0x30 189 /* #define XLOG_GIST_INSERT_COMPLETE 0x40 */ /* not used anymore */ 190 #define XLOG_GIST_CREATE_INDEX 0x50 191 /* #define XLOG_GIST_PAGE_DELETE 0x60 */ /* not used anymore */ 192 193 /* 194 * Backup Blk 0: updated page. 195 * Backup Blk 1: If this operation completes a page split, by inserting a 196 * downlink for the split page, the left half of the split 197 */ 198 typedef struct gistxlogPageUpdate 199 { 200 /* number of deleted offsets */ 201 uint16 ntodelete; 202 uint16 ntoinsert; 203 204 /* 205 * In payload of blk 0 : 1. todelete OffsetNumbers 2. tuples to insert 206 */ 207 } gistxlogPageUpdate; 208 209 /* 210 * Backup Blk 0: If this operation completes a page split, by inserting a 211 * downlink for the split page, the left half of the split 212 * Backup Blk 1 - npage: split pages (1 is the original page) 213 */ 214 typedef struct gistxlogPageSplit 215 { 216 BlockNumber origrlink; /* rightlink of the page before split */ 217 GistNSN orignsn; /* NSN of the page before split */ 218 bool origleaf; /* was splitted page a leaf page? */ 219 220 uint16 npage; /* # of pages in the split */ 221 bool markfollowright; /* set F_FOLLOW_RIGHT flags */ 222 223 /* 224 * follow: 1. gistxlogPage and array of IndexTupleData per page 225 */ 226 } gistxlogPageSplit; 227 228 typedef struct gistxlogPage 229 { 230 BlockNumber blkno; 231 int num; /* number of index tuples following */ 232 } gistxlogPage; 233 234 /* SplitedPageLayout - gistSplit function result */ 235 typedef struct SplitedPageLayout 236 { 237 gistxlogPage block; 238 IndexTupleData *list; 239 int lenlist; 240 IndexTuple itup; /* union key for page */ 241 Page page; /* to operate */ 242 Buffer buffer; /* to write after all proceed */ 243 244 struct SplitedPageLayout *next; 245 } SplitedPageLayout; 246 247 /* 248 * GISTInsertStack used for locking buffers and transfer arguments during 249 * insertion 250 */ 251 typedef struct GISTInsertStack 252 { 253 /* current page */ 254 BlockNumber blkno; 255 Buffer buffer; 256 Page page; 257 258 /* 259 * log sequence number from page->lsn to recognize page update and compare 260 * it with page's nsn to recognize page split 261 */ 262 GistNSN lsn; 263 264 /* offset of the downlink in the parent page, that points to this page */ 265 OffsetNumber downlinkoffnum; 266 267 /* pointer to parent */ 268 struct GISTInsertStack *parent; 269 } GISTInsertStack; 270 271 /* Working state and results for multi-column split logic in gistsplit.c */ 272 typedef struct GistSplitVector 273 { 274 GIST_SPLITVEC splitVector; /* passed to/from user PickSplit method */ 275 276 Datum spl_lattr[INDEX_MAX_KEYS]; /* Union of subkeys in 277 * splitVector.spl_left */ 278 bool spl_lisnull[INDEX_MAX_KEYS]; 279 280 Datum spl_rattr[INDEX_MAX_KEYS]; /* Union of subkeys in 281 * splitVector.spl_right */ 282 bool spl_risnull[INDEX_MAX_KEYS]; 283 284 bool *spl_dontcare; /* flags tuples which could go to either side 285 * of the split for zero penalty */ 286 } GistSplitVector; 287 288 typedef struct 289 { 290 Relation r; 291 Relation heapRel; 292 Size freespace; /* free space to be left */ 293 294 GISTInsertStack *stack; 295 } GISTInsertState; 296 297 /* root page of a gist index */ 298 #define GIST_ROOT_BLKNO 0 299 300 /* 301 * Before PostgreSQL 9.1, we used to rely on so-called "invalid tuples" on 302 * inner pages to finish crash recovery of incomplete page splits. If a crash 303 * happened in the middle of a page split, so that the downlink pointers were 304 * not yet inserted, crash recovery inserted a special downlink pointer. The 305 * semantics of an invalid tuple was that it if you encounter one in a scan, 306 * it must always be followed, because we don't know if the tuples on the 307 * child page match or not. 308 * 309 * We no longer create such invalid tuples, we now mark the left-half of such 310 * an incomplete split with the F_FOLLOW_RIGHT flag instead, and finish the 311 * split properly the next time we need to insert on that page. To retain 312 * on-disk compatibility for the sake of pg_upgrade, we still store 0xffff as 313 * the offset number of all inner tuples. If we encounter any invalid tuples 314 * with 0xfffe during insertion, we throw an error, though scans still handle 315 * them. You should only encounter invalid tuples if you pg_upgrade a pre-9.1 316 * gist index which already has invalid tuples in it because of a crash. That 317 * should be rare, and you are recommended to REINDEX anyway if you have any 318 * invalid tuples in an index, so throwing an error is as far as we go with 319 * supporting that. 320 */ 321 #define TUPLE_IS_VALID 0xffff 322 #define TUPLE_IS_INVALID 0xfffe 323 324 #define GistTupleIsInvalid(itup) ( ItemPointerGetOffsetNumber( &((itup)->t_tid) ) == TUPLE_IS_INVALID ) 325 #define GistTupleSetValid(itup) ItemPointerSetOffsetNumber( &((itup)->t_tid), TUPLE_IS_VALID ) 326 327 328 329 330 /* 331 * A buffer attached to an internal node, used when building an index in 332 * buffering mode. 333 */ 334 typedef struct 335 { 336 BlockNumber nodeBlocknum; /* index block # this buffer is for */ 337 int32 blocksCount; /* current # of blocks occupied by buffer */ 338 339 BlockNumber pageBlocknum; /* temporary file block # */ 340 GISTNodeBufferPage *pageBuffer; /* in-memory buffer page */ 341 342 /* is this buffer queued for emptying? */ 343 bool queuedForEmptying; 344 345 /* is this a temporary copy, not in the hash table? */ 346 bool isTemp; 347 348 int level; /* 0 == leaf */ 349 } GISTNodeBuffer; 350 351 /* 352 * Does specified level have buffers? (Beware of multiple evaluation of 353 * arguments.) 354 */ 355 #define LEVEL_HAS_BUFFERS(nlevel, gfbb) \ 356 ((nlevel) != 0 && (nlevel) % (gfbb)->levelStep == 0 && \ 357 (nlevel) != (gfbb)->rootlevel) 358 359 /* Is specified buffer at least half-filled (should be queued for emptying)? */ 360 #define BUFFER_HALF_FILLED(nodeBuffer, gfbb) \ 361 ((nodeBuffer)->blocksCount > (gfbb)->pagesPerBuffer / 2) 362 363 /* 364 * Is specified buffer full? Our buffers can actually grow indefinitely, 365 * beyond the "maximum" size, so this just means whether the buffer has grown 366 * beyond the nominal maximum size. 367 */ 368 #define BUFFER_OVERFLOWED(nodeBuffer, gfbb) \ 369 ((nodeBuffer)->blocksCount > (gfbb)->pagesPerBuffer) 370 371 /* 372 * Data structure with general information about build buffers. 373 */ 374 typedef struct GISTBuildBuffers 375 { 376 /* Persistent memory context for the buffers and metadata. */ 377 MemoryContext context; 378 379 BufFile *pfile; /* Temporary file to store buffers in */ 380 long nFileBlocks; /* Current size of the temporary file */ 381 382 /* 383 * resizable array of free blocks. 384 */ 385 long *freeBlocks; 386 int nFreeBlocks; /* # of currently free blocks in the array */ 387 int freeBlocksLen; /* current allocated length of the array */ 388 389 /* Hash for buffers by block number */ 390 HTAB *nodeBuffersTab; 391 392 /* List of buffers scheduled for emptying */ 393 List *bufferEmptyingQueue; 394 395 /* 396 * Parameters to the buffering build algorithm. levelStep determines which 397 * levels in the tree have buffers, and pagesPerBuffer determines how 398 * large each buffer is. 399 */ 400 int levelStep; 401 int pagesPerBuffer; 402 403 /* Array of lists of buffers on each level, for final emptying */ 404 List **buffersOnLevels; 405 int buffersOnLevelsLen; 406 407 /* 408 * Dynamically-sized array of buffers that currently have their last page 409 * loaded in main memory. 410 */ 411 GISTNodeBuffer **loadedBuffers; 412 int loadedBuffersCount; /* # of entries in loadedBuffers */ 413 int loadedBuffersLen; /* allocated size of loadedBuffers */ 414 415 /* Level of the current root node (= height of the index tree - 1) */ 416 int rootlevel; 417 } GISTBuildBuffers; 418 419 /* 420 * Storage type for GiST's reloptions 421 */ 422 typedef struct GiSTOptions 423 { 424 int32 vl_len_; /* varlena header (do not touch directly!) */ 425 int fillfactor; /* page fill factor in percent (0..100) */ 426 int bufferingModeOffset; /* use buffering build? */ 427 } GiSTOptions; 428 429 /* gist.c */ 430 extern Datum gisthandler(PG_FUNCTION_ARGS); 431 extern void gistbuildempty(Relation index); 432 extern bool gistinsert(Relation r, Datum *values, bool *isnull, 433 ItemPointer ht_ctid, Relation heapRel, 434 IndexUniqueCheck checkUnique); 435 extern MemoryContext createTempGistContext(void); 436 extern GISTSTATE *initGISTstate(Relation index); 437 extern void freeGISTstate(GISTSTATE *giststate); 438 extern void gistdoinsert(Relation r, 439 IndexTuple itup, 440 Size freespace, 441 GISTSTATE *GISTstate, 442 Relation heapRel); 443 444 /* A List of these is returned from gistplacetopage() in *splitinfo */ 445 typedef struct 446 { 447 Buffer buf; /* the split page "half" */ 448 IndexTuple downlink; /* downlink for this half. */ 449 } GISTPageSplitInfo; 450 451 extern bool gistplacetopage(Relation rel, Size freespace, GISTSTATE *giststate, 452 Buffer buffer, 453 IndexTuple *itup, int ntup, 454 OffsetNumber oldoffnum, BlockNumber *newblkno, 455 Buffer leftchildbuf, 456 List **splitinfo, 457 bool markleftchild, 458 Relation heapRel); 459 460 extern SplitedPageLayout *gistSplit(Relation r, Page page, IndexTuple *itup, 461 int len, GISTSTATE *giststate); 462 463 /* gistxlog.c */ 464 extern void gist_redo(XLogReaderState *record); 465 extern void gist_desc(StringInfo buf, XLogReaderState *record); 466 extern const char *gist_identify(uint8 info); 467 extern void gist_xlog_startup(void); 468 extern void gist_xlog_cleanup(void); 469 470 extern XLogRecPtr gistXLogUpdate(Buffer buffer, 471 OffsetNumber *todelete, int ntodelete, 472 IndexTuple *itup, int ntup, 473 Buffer leftchild, RelFileNode *hnode); 474 475 extern XLogRecPtr gistXLogSplit(bool page_is_leaf, 476 SplitedPageLayout *dist, 477 BlockNumber origrlink, GistNSN oldnsn, 478 Buffer leftchild, bool markfollowright); 479 480 /* gistget.c */ 481 extern bool gistgettuple(IndexScanDesc scan, ScanDirection dir); 482 extern int64 gistgetbitmap(IndexScanDesc scan, TIDBitmap *tbm); 483 extern bool gistcanreturn(Relation index, int attno); 484 485 /* gistvalidate.c */ 486 extern bool gistvalidate(Oid opclassoid); 487 488 /* gistutil.c */ 489 490 #define GiSTPageSize \ 491 ( BLCKSZ - SizeOfPageHeaderData - MAXALIGN(sizeof(GISTPageOpaqueData)) ) 492 493 #define GIST_MIN_FILLFACTOR 10 494 #define GIST_DEFAULT_FILLFACTOR 90 495 496 extern bytea *gistoptions(Datum reloptions, bool validate); 497 extern bool gistproperty(Oid index_oid, int attno, 498 IndexAMProperty prop, const char *propname, 499 bool *res, bool *isnull); 500 extern bool gistfitpage(IndexTuple *itvec, int len); 501 extern bool gistnospace(Page page, IndexTuple *itvec, int len, OffsetNumber todelete, Size freespace); 502 extern void gistcheckpage(Relation rel, Buffer buf); 503 extern Buffer gistNewBuffer(Relation r); 504 extern void gistfillbuffer(Page page, IndexTuple *itup, int len, 505 OffsetNumber off); 506 extern IndexTuple *gistextractpage(Page page, int *len /* out */ ); 507 extern IndexTuple *gistjoinvector( 508 IndexTuple *itvec, int *len, 509 IndexTuple *additvec, int addlen); 510 extern IndexTupleData *gistfillitupvec(IndexTuple *vec, int veclen, int *memlen); 511 512 extern IndexTuple gistunion(Relation r, IndexTuple *itvec, 513 int len, GISTSTATE *giststate); 514 extern IndexTuple gistgetadjusted(Relation r, 515 IndexTuple oldtup, 516 IndexTuple addtup, 517 GISTSTATE *giststate); 518 extern IndexTuple gistFormTuple(GISTSTATE *giststate, 519 Relation r, Datum *attdata, bool *isnull, bool isleaf); 520 521 extern OffsetNumber gistchoose(Relation r, Page p, 522 IndexTuple it, 523 GISTSTATE *giststate); 524 525 extern void GISTInitBuffer(Buffer b, uint32 f); 526 extern void gistdentryinit(GISTSTATE *giststate, int nkey, GISTENTRY *e, 527 Datum k, Relation r, Page pg, OffsetNumber o, 528 bool l, bool isNull); 529 530 extern float gistpenalty(GISTSTATE *giststate, int attno, 531 GISTENTRY *key1, bool isNull1, 532 GISTENTRY *key2, bool isNull2); 533 extern void gistMakeUnionItVec(GISTSTATE *giststate, IndexTuple *itvec, int len, 534 Datum *attr, bool *isnull); 535 extern bool gistKeyIsEQ(GISTSTATE *giststate, int attno, Datum a, Datum b); 536 extern void gistDeCompressAtt(GISTSTATE *giststate, Relation r, IndexTuple tuple, Page p, 537 OffsetNumber o, GISTENTRY *attdata, bool *isnull); 538 extern IndexTuple gistFetchTuple(GISTSTATE *giststate, Relation r, 539 IndexTuple tuple); 540 extern void gistMakeUnionKey(GISTSTATE *giststate, int attno, 541 GISTENTRY *entry1, bool isnull1, 542 GISTENTRY *entry2, bool isnull2, 543 Datum *dst, bool *dstisnull); 544 545 extern XLogRecPtr gistGetFakeLSN(Relation rel); 546 547 /* gistvacuum.c */ 548 extern IndexBulkDeleteResult *gistbulkdelete(IndexVacuumInfo *info, 549 IndexBulkDeleteResult *stats, 550 IndexBulkDeleteCallback callback, 551 void *callback_state); 552 extern IndexBulkDeleteResult *gistvacuumcleanup(IndexVacuumInfo *info, 553 IndexBulkDeleteResult *stats); 554 555 /* gistsplit.c */ 556 extern void gistSplitByKey(Relation r, Page page, IndexTuple *itup, 557 int len, GISTSTATE *giststate, 558 GistSplitVector *v, 559 int attno); 560 561 /* gistbuild.c */ 562 extern IndexBuildResult *gistbuild(Relation heap, Relation index, 563 struct IndexInfo *indexInfo); 564 extern void gistValidateBufferingOption(char *value); 565 566 /* gistbuildbuffers.c */ 567 extern GISTBuildBuffers *gistInitBuildBuffers(int pagesPerBuffer, int levelStep, 568 int maxLevel); 569 extern GISTNodeBuffer *gistGetNodeBuffer(GISTBuildBuffers *gfbb, 570 GISTSTATE *giststate, 571 BlockNumber blkno, int level); 572 extern void gistPushItupToNodeBuffer(GISTBuildBuffers *gfbb, 573 GISTNodeBuffer *nodeBuffer, IndexTuple item); 574 extern bool gistPopItupFromNodeBuffer(GISTBuildBuffers *gfbb, 575 GISTNodeBuffer *nodeBuffer, IndexTuple *item); 576 extern void gistFreeBuildBuffers(GISTBuildBuffers *gfbb); 577 extern void gistRelocateBuildBuffersOnSplit(GISTBuildBuffers *gfbb, 578 GISTSTATE *giststate, Relation r, 579 int level, Buffer buffer, 580 List *splitinfo); 581 extern void gistUnloadNodeBuffers(GISTBuildBuffers *gfbb); 582 583 #endif /* GIST_PRIVATE_H */ 584