0006-Generalize-relation-analyze-in-table-AM-interface-v2.patch
application/octet-stream
Filename: 0006-Generalize-relation-analyze-in-table-AM-interface-v2.patch
Type: application/octet-stream
Part: 8
Message:
Re: Table AM Interface Enhancements
Patch
Format: format-patch
Series: patch v2-0006
Subject: Generalize relation analyze in table AM interface
| File | + | − |
|---|---|---|
| src/backend/access/heap/heapam_handler.c | 284 | 2 |
| src/backend/access/table/tableamapi.c | 0 | 2 |
| src/backend/commands/analyze.c | 6 | 282 |
| src/include/access/tableam.h | 21 | 71 |
| src/include/commands/vacuum.h | 5 | 0 |
| src/include/foreign/fdwapi.h | 1 | 5 |
From 5423c41f1400b0267e1c4437a7143153ccdc90d7 Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@postgresql.org>
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 | 286 +++++++++++++++++++++-
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, 317 insertions(+), 362 deletions(-)
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 6abfe36dec7..66ac541ed21 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -44,6 +44,7 @@
#include "storage/smgr.h"
#include "utils/builtins.h"
#include "utils/rel.h"
+#include "utils/sampling.h"
static TM_Result heapam_tuple_lock(Relation relation, ItemPointer tid,
Snapshot snapshot, TupleTableSlot *slot,
@@ -1220,6 +1221,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 +2920,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 2c43ef3f60e..b9210ea4fcb 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"
@@ -654,41 +655,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,
@@ -709,6 +675,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.
@@ -1740,42 +1715,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
*
@@ -1881,6 +1820,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)