From 32d31f362b00bd3be143a33d2414691d0910e056 Mon Sep 17 00:00:00 2001 From: Alexander Korotkov Date: Thu, 8 Jun 2023 04:20:29 +0300 Subject: [PATCH 06/13] Generalize relation analyze in table AM interface Currently, there is just one algorithm for sampling tuples from a table written in acquire_sample_rows(). Custom table AM can just redefine the way to get the next block/tuple by implementing scan_analyze_next_block() and scan_analyze_next_tuple() API functions. This approach doesn't seem general enough. For instance, it's unclear how to sample this way index-organized tables. This commit allows table AM to encapsulate the whole sampling algorithm (currently implemented in acquire_sample_rows()) into the relation_analyze() API function. --- src/backend/access/heap/heapam_handler.c | 289 ++++++++++++++++++++++- src/backend/access/table/tableamapi.c | 2 - src/backend/commands/analyze.c | 288 +--------------------- src/include/access/tableam.h | 92 ++------ src/include/commands/vacuum.h | 5 + src/include/foreign/fdwapi.h | 6 +- 6 files changed, 320 insertions(+), 362 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 6abfe36dec7..5d125aad6bc 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -19,6 +19,8 @@ */ #include "postgres.h" +#include + #include "access/genam.h" #include "access/heapam.h" #include "access/heaptoast.h" @@ -44,6 +46,8 @@ #include "storage/smgr.h" #include "utils/builtins.h" #include "utils/rel.h" +#include "utils/sampling.h" +#include "utils/spccache.h" static TM_Result heapam_tuple_lock(Relation relation, ItemPointer tid, Snapshot snapshot, TupleTableSlot *slot, @@ -1220,6 +1224,288 @@ heapam_scan_analyze_next_tuple(TableScanDesc scan, TransactionId OldestXmin, return false; } +/* + * Comparator for sorting rows[] array + */ +static int +compare_rows(const void *a, const void *b, void *arg) +{ + HeapTuple ha = *(const HeapTuple *) a; + HeapTuple hb = *(const HeapTuple *) b; + BlockNumber ba = ItemPointerGetBlockNumber(&ha->t_self); + OffsetNumber oa = ItemPointerGetOffsetNumber(&ha->t_self); + BlockNumber bb = ItemPointerGetBlockNumber(&hb->t_self); + OffsetNumber ob = ItemPointerGetOffsetNumber(&hb->t_self); + + if (ba < bb) + return -1; + if (ba > bb) + return 1; + if (oa < ob) + return -1; + if (oa > ob) + return 1; + return 0; +} + +static BufferAccessStrategy analyze_bstrategy; + +/* + * heapam_acquire_sample_rows -- acquire a random sample of rows from the table + * + * Selected rows are returned in the caller-allocated array rows[], which + * must have at least targrows entries. + * The actual number of rows selected is returned as the function result. + * We also estimate the total numbers of live and dead rows in the table, + * and return them into *totalrows and *totaldeadrows, respectively. + * + * The returned list of tuples is in order by physical position in the table. + * (We will rely on this later to derive correlation estimates.) + * + * As of May 2004 we use a new two-stage method: Stage one selects up + * to targrows random blocks (or all blocks, if there aren't so many). + * Stage two scans these blocks and uses the Vitter algorithm to create + * a random sample of targrows rows (or less, if there are less in the + * sample of blocks). The two stages are executed simultaneously: each + * block is processed as soon as stage one returns its number and while + * the rows are read stage two controls which ones are to be inserted + * into the sample. + * + * Although every row has an equal chance of ending up in the final + * sample, this sampling method is not perfect: not every possible + * sample has an equal chance of being selected. For large relations + * the number of different blocks represented by the sample tends to be + * too small. We can live with that for now. Improvements are welcome. + * + * An important property of this sampling method is that because we do + * look at a statistically unbiased set of blocks, we should get + * unbiased estimates of the average numbers of live and dead rows per + * block. The previous sampling method put too much credence in the row + * density near the start of the table. + */ +static int +heapam_acquire_sample_rows(Relation onerel, int elevel, + HeapTuple *rows, int targrows, + double *totalrows, double *totaldeadrows) +{ + int numrows = 0; /* # rows now in reservoir */ + double samplerows = 0; /* total # rows collected */ + double liverows = 0; /* # live rows seen */ + double deadrows = 0; /* # dead rows seen */ + double rowstoskip = -1; /* -1 means not set yet */ + uint32 randseed; /* Seed for block sampler(s) */ + BlockNumber totalblocks; + TransactionId OldestXmin; + BlockSamplerData bs; + ReservoirStateData rstate; + TupleTableSlot *slot; + TableScanDesc scan; + BlockNumber nblocks; + BlockNumber blksdone = 0; +#ifdef USE_PREFETCH + int prefetch_maximum = 0; /* blocks to prefetch if enabled */ + BlockSamplerData prefetch_bs; +#endif + + Assert(targrows > 0); + + totalblocks = RelationGetNumberOfBlocks(onerel); + + /* Need a cutoff xmin for HeapTupleSatisfiesVacuum */ + OldestXmin = GetOldestNonRemovableTransactionId(onerel); + + /* Prepare for sampling block numbers */ + randseed = pg_prng_uint32(&pg_global_prng_state); + nblocks = BlockSampler_Init(&bs, totalblocks, targrows, randseed); + +#ifdef USE_PREFETCH + prefetch_maximum = get_tablespace_maintenance_io_concurrency(onerel->rd_rel->reltablespace); + /* Create another BlockSampler, using the same seed, for prefetching */ + if (prefetch_maximum) + (void) BlockSampler_Init(&prefetch_bs, totalblocks, targrows, randseed); +#endif + + /* Report sampling block numbers */ + pgstat_progress_update_param(PROGRESS_ANALYZE_BLOCKS_TOTAL, + nblocks); + + /* Prepare for sampling rows */ + reservoir_init_selection_state(&rstate, targrows); + + scan = table_beginscan_analyze(onerel); + slot = table_slot_create(onerel, NULL); + +#ifdef USE_PREFETCH + + /* + * If we are doing prefetching, then go ahead and tell the kernel about + * the first set of pages we are going to want. This also moves our + * iterator out ahead of the main one being used, where we will keep it so + * that we're always pre-fetching out prefetch_maximum number of blocks + * ahead. + */ + if (prefetch_maximum) + { + for (int i = 0; i < prefetch_maximum; i++) + { + BlockNumber prefetch_block; + + if (!BlockSampler_HasMore(&prefetch_bs)) + break; + + prefetch_block = BlockSampler_Next(&prefetch_bs); + PrefetchBuffer(scan->rs_rd, MAIN_FORKNUM, prefetch_block); + } + } +#endif + + /* Outer loop over blocks to sample */ + while (BlockSampler_HasMore(&bs)) + { + bool block_accepted; + BlockNumber targblock = BlockSampler_Next(&bs); +#ifdef USE_PREFETCH + BlockNumber prefetch_targblock = InvalidBlockNumber; + + /* + * Make sure that every time the main BlockSampler is moved forward + * that our prefetch BlockSampler also gets moved forward, so that we + * always stay out ahead. + */ + if (prefetch_maximum && BlockSampler_HasMore(&prefetch_bs)) + prefetch_targblock = BlockSampler_Next(&prefetch_bs); +#endif + + vacuum_delay_point(); + + block_accepted = heapam_scan_analyze_next_block(scan, targblock, analyze_bstrategy); + +#ifdef USE_PREFETCH + + /* + * When pre-fetching, after we get a block, tell the kernel about the + * next one we will want, if there's any left. + * + * We want to do this even if the table_scan_analyze_next_block() call + * above decides against analyzing the block it picked. + */ + if (prefetch_maximum && prefetch_targblock != InvalidBlockNumber) + PrefetchBuffer(scan->rs_rd, MAIN_FORKNUM, prefetch_targblock); +#endif + + /* + * Don't analyze if table_scan_analyze_next_block() indicated this + * block is unsuitable for analyzing. + */ + if (!block_accepted) + continue; + + while (heapam_scan_analyze_next_tuple(scan, OldestXmin, &liverows, &deadrows, slot)) + { + /* + * The first targrows sample rows are simply copied into the + * reservoir. Then we start replacing tuples in the sample until + * we reach the end of the relation. This algorithm is from Jeff + * Vitter's paper (see full citation in utils/misc/sampling.c). It + * works by repeatedly computing the number of tuples to skip + * before selecting a tuple, which replaces a randomly chosen + * element of the reservoir (current set of tuples). At all times + * the reservoir is a true random sample of the tuples we've + * passed over so far, so when we fall off the end of the relation + * we're done. + */ + if (numrows < targrows) + rows[numrows++] = ExecCopySlotHeapTuple(slot); + else + { + /* + * t in Vitter's paper is the number of records already + * processed. If we need to compute a new S value, we must + * use the not-yet-incremented value of samplerows as t. + */ + if (rowstoskip < 0) + rowstoskip = reservoir_get_next_S(&rstate, samplerows, targrows); + + if (rowstoskip <= 0) + { + /* + * Found a suitable tuple, so save it, replacing one old + * tuple at random + */ + int k = (int) (targrows * sampler_random_fract(&rstate.randstate)); + + Assert(k >= 0 && k < targrows); + heap_freetuple(rows[k]); + rows[k] = ExecCopySlotHeapTuple(slot); + } + + rowstoskip -= 1; + } + + samplerows += 1; + } + + pgstat_progress_update_param(PROGRESS_ANALYZE_BLOCKS_DONE, + ++blksdone); + } + + ExecDropSingleTupleTableSlot(slot); + table_endscan(scan); + + /* + * If we didn't find as many tuples as we wanted then we're done. No sort + * is needed, since they're already in order. + * + * Otherwise we need to sort the collected tuples by position + * (itempointer). It's not worth worrying about corner cases where the + * tuples are already sorted. + */ + if (numrows == targrows) + qsort_interruptible(rows, numrows, sizeof(HeapTuple), + compare_rows, NULL); + + /* + * Estimate total numbers of live and dead rows in relation, extrapolating + * on the assumption that the average tuple density in pages we didn't + * scan is the same as in the pages we did scan. Since what we scanned is + * a random sample of the pages in the relation, this should be a good + * assumption. + */ + if (bs.m > 0) + { + *totalrows = floor((liverows / bs.m) * totalblocks + 0.5); + *totaldeadrows = floor((deadrows / bs.m) * totalblocks + 0.5); + } + else + { + *totalrows = 0.0; + *totaldeadrows = 0.0; + } + + /* + * Emit some interesting relation info + */ + ereport(elevel, + (errmsg("\"%s\": scanned %d of %u pages, " + "containing %.0f live rows and %.0f dead rows; " + "%d rows in sample, %.0f estimated total rows", + RelationGetRelationName(onerel), + bs.m, totalblocks, + liverows, deadrows, + numrows, *totalrows))); + + return numrows; +} + +static inline void +heapam_analyze(Relation relation, AcquireSampleRowsFunc *func, + BlockNumber *totalpages, BufferAccessStrategy bstrategy) +{ + *func = heapam_acquire_sample_rows; + *totalpages = RelationGetNumberOfBlocks(relation); + analyze_bstrategy = bstrategy; +} + static double heapam_index_build_range_scan(Relation heapRelation, Relation indexRelation, @@ -2637,10 +2923,9 @@ static const TableAmRoutine heapam_methods = { .relation_copy_data = heapam_relation_copy_data, .relation_copy_for_cluster = heapam_relation_copy_for_cluster, .relation_vacuum = heap_vacuum_rel, - .scan_analyze_next_block = heapam_scan_analyze_next_block, - .scan_analyze_next_tuple = heapam_scan_analyze_next_tuple, .index_build_range_scan = heapam_index_build_range_scan, .index_validate_scan = heapam_index_validate_scan, + .relation_analyze = heapam_analyze, .free_rd_amcache = NULL, .relation_size = table_block_relation_size, diff --git a/src/backend/access/table/tableamapi.c b/src/backend/access/table/tableamapi.c index ce637a5a5d9..55b8caeadf2 100644 --- a/src/backend/access/table/tableamapi.c +++ b/src/backend/access/table/tableamapi.c @@ -81,8 +81,6 @@ GetTableAmRoutine(Oid amhandler) Assert(routine->relation_copy_data != NULL); Assert(routine->relation_copy_for_cluster != NULL); Assert(routine->relation_vacuum != NULL); - Assert(routine->scan_analyze_next_block != NULL); - Assert(routine->scan_analyze_next_tuple != NULL); Assert(routine->index_build_range_scan != NULL); Assert(routine->index_validate_scan != NULL); diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c index 8a82af4a4ca..659f69ef270 100644 --- a/src/backend/commands/analyze.c +++ b/src/backend/commands/analyze.c @@ -87,10 +87,6 @@ static void compute_index_stats(Relation onerel, double totalrows, MemoryContext col_context); static VacAttrStats *examine_attribute(Relation onerel, int attnum, Node *index_expr); -static int acquire_sample_rows(Relation onerel, int elevel, - HeapTuple *rows, int targrows, - double *totalrows, double *totaldeadrows); -static int compare_rows(const void *a, const void *b, void *arg); static int acquire_inherited_sample_rows(Relation onerel, int elevel, HeapTuple *rows, int targrows, double *totalrows, double *totaldeadrows); @@ -190,10 +186,9 @@ analyze_rel(Oid relid, RangeVar *relation, if (onerel->rd_rel->relkind == RELKIND_RELATION || onerel->rd_rel->relkind == RELKIND_MATVIEW) { - /* Regular table, so we'll use the regular row acquisition function */ - acquirefunc = acquire_sample_rows; - /* Also get regular table's size */ - relpages = RelationGetNumberOfBlocks(onerel); + /* Use row acquisition function provided by table AM */ + table_relation_analyze(onerel, &acquirefunc, + &relpages, vac_strategy); } else if (onerel->rd_rel->relkind == RELKIND_FOREIGN_TABLE) { @@ -1102,277 +1097,6 @@ examine_attribute(Relation onerel, int attnum, Node *index_expr) return stats; } -/* - * acquire_sample_rows -- acquire a random sample of rows from the table - * - * Selected rows are returned in the caller-allocated array rows[], which - * must have at least targrows entries. - * The actual number of rows selected is returned as the function result. - * We also estimate the total numbers of live and dead rows in the table, - * and return them into *totalrows and *totaldeadrows, respectively. - * - * The returned list of tuples is in order by physical position in the table. - * (We will rely on this later to derive correlation estimates.) - * - * As of May 2004 we use a new two-stage method: Stage one selects up - * to targrows random blocks (or all blocks, if there aren't so many). - * Stage two scans these blocks and uses the Vitter algorithm to create - * a random sample of targrows rows (or less, if there are less in the - * sample of blocks). The two stages are executed simultaneously: each - * block is processed as soon as stage one returns its number and while - * the rows are read stage two controls which ones are to be inserted - * into the sample. - * - * Although every row has an equal chance of ending up in the final - * sample, this sampling method is not perfect: not every possible - * sample has an equal chance of being selected. For large relations - * the number of different blocks represented by the sample tends to be - * too small. We can live with that for now. Improvements are welcome. - * - * An important property of this sampling method is that because we do - * look at a statistically unbiased set of blocks, we should get - * unbiased estimates of the average numbers of live and dead rows per - * block. The previous sampling method put too much credence in the row - * density near the start of the table. - */ -static int -acquire_sample_rows(Relation onerel, int elevel, - HeapTuple *rows, int targrows, - double *totalrows, double *totaldeadrows) -{ - int numrows = 0; /* # rows now in reservoir */ - double samplerows = 0; /* total # rows collected */ - double liverows = 0; /* # live rows seen */ - double deadrows = 0; /* # dead rows seen */ - double rowstoskip = -1; /* -1 means not set yet */ - uint32 randseed; /* Seed for block sampler(s) */ - BlockNumber totalblocks; - TransactionId OldestXmin; - BlockSamplerData bs; - ReservoirStateData rstate; - TupleTableSlot *slot; - TableScanDesc scan; - BlockNumber nblocks; - BlockNumber blksdone = 0; -#ifdef USE_PREFETCH - int prefetch_maximum = 0; /* blocks to prefetch if enabled */ - BlockSamplerData prefetch_bs; -#endif - - Assert(targrows > 0); - - totalblocks = RelationGetNumberOfBlocks(onerel); - - /* Need a cutoff xmin for HeapTupleSatisfiesVacuum */ - OldestXmin = GetOldestNonRemovableTransactionId(onerel); - - /* Prepare for sampling block numbers */ - randseed = pg_prng_uint32(&pg_global_prng_state); - nblocks = BlockSampler_Init(&bs, totalblocks, targrows, randseed); - -#ifdef USE_PREFETCH - prefetch_maximum = get_tablespace_maintenance_io_concurrency(onerel->rd_rel->reltablespace); - /* Create another BlockSampler, using the same seed, for prefetching */ - if (prefetch_maximum) - (void) BlockSampler_Init(&prefetch_bs, totalblocks, targrows, randseed); -#endif - - /* Report sampling block numbers */ - pgstat_progress_update_param(PROGRESS_ANALYZE_BLOCKS_TOTAL, - nblocks); - - /* Prepare for sampling rows */ - reservoir_init_selection_state(&rstate, targrows); - - scan = table_beginscan_analyze(onerel); - slot = table_slot_create(onerel, NULL); - -#ifdef USE_PREFETCH - - /* - * If we are doing prefetching, then go ahead and tell the kernel about - * the first set of pages we are going to want. This also moves our - * iterator out ahead of the main one being used, where we will keep it so - * that we're always pre-fetching out prefetch_maximum number of blocks - * ahead. - */ - if (prefetch_maximum) - { - for (int i = 0; i < prefetch_maximum; i++) - { - BlockNumber prefetch_block; - - if (!BlockSampler_HasMore(&prefetch_bs)) - break; - - prefetch_block = BlockSampler_Next(&prefetch_bs); - PrefetchBuffer(scan->rs_rd, MAIN_FORKNUM, prefetch_block); - } - } -#endif - - /* Outer loop over blocks to sample */ - while (BlockSampler_HasMore(&bs)) - { - bool block_accepted; - BlockNumber targblock = BlockSampler_Next(&bs); -#ifdef USE_PREFETCH - BlockNumber prefetch_targblock = InvalidBlockNumber; - - /* - * Make sure that every time the main BlockSampler is moved forward - * that our prefetch BlockSampler also gets moved forward, so that we - * always stay out ahead. - */ - if (prefetch_maximum && BlockSampler_HasMore(&prefetch_bs)) - prefetch_targblock = BlockSampler_Next(&prefetch_bs); -#endif - - vacuum_delay_point(); - - block_accepted = table_scan_analyze_next_block(scan, targblock, vac_strategy); - -#ifdef USE_PREFETCH - - /* - * When pre-fetching, after we get a block, tell the kernel about the - * next one we will want, if there's any left. - * - * We want to do this even if the table_scan_analyze_next_block() call - * above decides against analyzing the block it picked. - */ - if (prefetch_maximum && prefetch_targblock != InvalidBlockNumber) - PrefetchBuffer(scan->rs_rd, MAIN_FORKNUM, prefetch_targblock); -#endif - - /* - * Don't analyze if table_scan_analyze_next_block() indicated this - * block is unsuitable for analyzing. - */ - if (!block_accepted) - continue; - - while (table_scan_analyze_next_tuple(scan, OldestXmin, &liverows, &deadrows, slot)) - { - /* - * The first targrows sample rows are simply copied into the - * reservoir. Then we start replacing tuples in the sample until - * we reach the end of the relation. This algorithm is from Jeff - * Vitter's paper (see full citation in utils/misc/sampling.c). It - * works by repeatedly computing the number of tuples to skip - * before selecting a tuple, which replaces a randomly chosen - * element of the reservoir (current set of tuples). At all times - * the reservoir is a true random sample of the tuples we've - * passed over so far, so when we fall off the end of the relation - * we're done. - */ - if (numrows < targrows) - rows[numrows++] = ExecCopySlotHeapTuple(slot); - else - { - /* - * t in Vitter's paper is the number of records already - * processed. If we need to compute a new S value, we must - * use the not-yet-incremented value of samplerows as t. - */ - if (rowstoskip < 0) - rowstoskip = reservoir_get_next_S(&rstate, samplerows, targrows); - - if (rowstoskip <= 0) - { - /* - * Found a suitable tuple, so save it, replacing one old - * tuple at random - */ - int k = (int) (targrows * sampler_random_fract(&rstate.randstate)); - - Assert(k >= 0 && k < targrows); - heap_freetuple(rows[k]); - rows[k] = ExecCopySlotHeapTuple(slot); - } - - rowstoskip -= 1; - } - - samplerows += 1; - } - - pgstat_progress_update_param(PROGRESS_ANALYZE_BLOCKS_DONE, - ++blksdone); - } - - ExecDropSingleTupleTableSlot(slot); - table_endscan(scan); - - /* - * If we didn't find as many tuples as we wanted then we're done. No sort - * is needed, since they're already in order. - * - * Otherwise we need to sort the collected tuples by position - * (itempointer). It's not worth worrying about corner cases where the - * tuples are already sorted. - */ - if (numrows == targrows) - qsort_interruptible(rows, numrows, sizeof(HeapTuple), - compare_rows, NULL); - - /* - * Estimate total numbers of live and dead rows in relation, extrapolating - * on the assumption that the average tuple density in pages we didn't - * scan is the same as in the pages we did scan. Since what we scanned is - * a random sample of the pages in the relation, this should be a good - * assumption. - */ - if (bs.m > 0) - { - *totalrows = floor((liverows / bs.m) * totalblocks + 0.5); - *totaldeadrows = floor((deadrows / bs.m) * totalblocks + 0.5); - } - else - { - *totalrows = 0.0; - *totaldeadrows = 0.0; - } - - /* - * Emit some interesting relation info - */ - ereport(elevel, - (errmsg("\"%s\": scanned %d of %u pages, " - "containing %.0f live rows and %.0f dead rows; " - "%d rows in sample, %.0f estimated total rows", - RelationGetRelationName(onerel), - bs.m, totalblocks, - liverows, deadrows, - numrows, *totalrows))); - - return numrows; -} - -/* - * Comparator for sorting rows[] array - */ -static int -compare_rows(const void *a, const void *b, void *arg) -{ - HeapTuple ha = *(const HeapTuple *) a; - HeapTuple hb = *(const HeapTuple *) b; - BlockNumber ba = ItemPointerGetBlockNumber(&ha->t_self); - OffsetNumber oa = ItemPointerGetOffsetNumber(&ha->t_self); - BlockNumber bb = ItemPointerGetBlockNumber(&hb->t_self); - OffsetNumber ob = ItemPointerGetOffsetNumber(&hb->t_self); - - if (ba < bb) - return -1; - if (ba > bb) - return 1; - if (oa < ob) - return -1; - if (oa > ob) - return 1; - return 0; -} - /* * acquire_inherited_sample_rows -- acquire sample rows from inheritance tree @@ -1462,9 +1186,9 @@ acquire_inherited_sample_rows(Relation onerel, int elevel, if (childrel->rd_rel->relkind == RELKIND_RELATION || childrel->rd_rel->relkind == RELKIND_MATVIEW) { - /* Regular table, so use the regular row acquisition function */ - acquirefunc = acquire_sample_rows; - relpages = RelationGetNumberOfBlocks(childrel); + /* Use row acquisition function provided by table AM */ + table_relation_analyze(childrel, &acquirefunc, + &relpages, vac_strategy); } else if (childrel->rd_rel->relkind == RELKIND_FOREIGN_TABLE) { diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h index f192e09313b..5f3c7f865ef 100644 --- a/src/include/access/tableam.h +++ b/src/include/access/tableam.h @@ -20,6 +20,7 @@ #include "access/relscan.h" #include "access/sdir.h" #include "access/xact.h" +#include "commands/vacuum.h" #include "executor/tuptable.h" #include "utils/rel.h" #include "utils/snapshot.h" @@ -658,41 +659,6 @@ typedef struct TableAmRoutine struct VacuumParams *params, BufferAccessStrategy bstrategy); - /* - * Prepare to analyze block `blockno` of `scan`. The scan has been started - * with table_beginscan_analyze(). See also - * table_scan_analyze_next_block(). - * - * The callback may acquire resources like locks that are held until - * table_scan_analyze_next_tuple() returns false. It e.g. can make sense - * to hold a lock until all tuples on a block have been analyzed by - * scan_analyze_next_tuple. - * - * The callback can return false if the block is not suitable for - * sampling, e.g. because it's a metapage that could never contain tuples. - * - * XXX: This obviously is primarily suited for block-based AMs. It's not - * clear what a good interface for non block based AMs would be, so there - * isn't one yet. - */ - bool (*scan_analyze_next_block) (TableScanDesc scan, - BlockNumber blockno, - BufferAccessStrategy bstrategy); - - /* - * See table_scan_analyze_next_tuple(). - * - * Not every AM might have a meaningful concept of dead rows, in which - * case it's OK to not increment *deadrows - but note that that may - * influence autovacuum scheduling (see comment for relation_vacuum - * callback). - */ - bool (*scan_analyze_next_tuple) (TableScanDesc scan, - TransactionId OldestXmin, - double *liverows, - double *deadrows, - TupleTableSlot *slot); - /* see table_index_build_range_scan for reference about parameters */ double (*index_build_range_scan) (Relation table_rel, Relation index_rel, @@ -713,6 +679,15 @@ typedef struct TableAmRoutine Snapshot snapshot, struct ValidateIndexState *state); + /* + * Provides row sampling callback for relation and number of relation + * pages. + */ + void (*relation_analyze) (Relation relation, + AcquireSampleRowsFunc *func, + BlockNumber *totalpages, + BufferAccessStrategy bstrategy); + /* ------------------------------------------------------------------------ * Miscellaneous functions. @@ -1744,42 +1719,6 @@ table_relation_vacuum(Relation rel, struct VacuumParams *params, rel->rd_tableam->relation_vacuum(rel, params, bstrategy); } -/* - * Prepare to analyze block `blockno` of `scan`. The scan needs to have been - * started with table_beginscan_analyze(). Note that this routine might - * acquire resources like locks that are held until - * table_scan_analyze_next_tuple() returns false. - * - * Returns false if block is unsuitable for sampling, true otherwise. - */ -static inline bool -table_scan_analyze_next_block(TableScanDesc scan, BlockNumber blockno, - BufferAccessStrategy bstrategy) -{ - return scan->rs_rd->rd_tableam->scan_analyze_next_block(scan, blockno, - bstrategy); -} - -/* - * Iterate over tuples in the block selected with - * table_scan_analyze_next_block() (which needs to have returned true, and - * this routine may not have returned false for the same block before). If a - * tuple that's suitable for sampling is found, true is returned and a tuple - * is stored in `slot`. - * - * *liverows and *deadrows are incremented according to the encountered - * tuples. - */ -static inline bool -table_scan_analyze_next_tuple(TableScanDesc scan, TransactionId OldestXmin, - double *liverows, double *deadrows, - TupleTableSlot *slot) -{ - return scan->rs_rd->rd_tableam->scan_analyze_next_tuple(scan, OldestXmin, - liverows, deadrows, - slot); -} - /* * table_index_build_scan - scan the table to find tuples to be indexed * @@ -1885,6 +1824,17 @@ table_index_validate_scan(Relation table_rel, state); } +/* + * Provides row sampling callback for relation and number of relation + * pages. + */ +static inline void +table_relation_analyze(Relation relation, AcquireSampleRowsFunc *func, + BlockNumber *totalpages, BufferAccessStrategy bstrategy) +{ + relation->rd_tableam->relation_analyze(relation, func, + totalpages, bstrategy); +} /* ---------------------------------------------------------------------------- * Miscellaneous functionality diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h index 1182a967427..d38ddc68b79 100644 --- a/src/include/commands/vacuum.h +++ b/src/include/commands/vacuum.h @@ -104,6 +104,11 @@ typedef struct ParallelVacuumState ParallelVacuumState; */ typedef struct VacAttrStats *VacAttrStatsP; +typedef int (*AcquireSampleRowsFunc) (Relation relation, int elevel, + HeapTuple *rows, int targrows, + double *totalrows, + double *totaldeadrows); + typedef Datum (*AnalyzeAttrFetchFunc) (VacAttrStatsP stats, int rownum, bool *isNull); diff --git a/src/include/foreign/fdwapi.h b/src/include/foreign/fdwapi.h index fcde3876b28..0968e0a01ec 100644 --- a/src/include/foreign/fdwapi.h +++ b/src/include/foreign/fdwapi.h @@ -13,6 +13,7 @@ #define FDWAPI_H #include "access/parallel.h" +#include "commands/vacuum.h" #include "nodes/execnodes.h" #include "nodes/pathnodes.h" @@ -148,11 +149,6 @@ typedef void (*ExplainForeignModify_function) (ModifyTableState *mtstate, typedef void (*ExplainDirectModify_function) (ForeignScanState *node, struct ExplainState *es); -typedef int (*AcquireSampleRowsFunc) (Relation relation, int elevel, - HeapTuple *rows, int targrows, - double *totalrows, - double *totaldeadrows); - typedef bool (*AnalyzeForeignTable_function) (Relation relation, AcquireSampleRowsFunc *func, BlockNumber *totalpages); -- 2.39.3 (Apple Git-145)