mirror of
https://github.com/postgres/postgres.git
synced 2025-07-03 20:02:46 +03:00
Redesign tablesample method API, and do extensive code review.
The original implementation of TABLESAMPLE modeled the tablesample method API on index access methods, which wasn't a good choice because, without specialized DDL commands, there's no way to build an extension that can implement a TSM. (Raw inserts into system catalogs are not an acceptable thing to do, because we can't undo them during DROP EXTENSION, nor will pg_upgrade behave sanely.) Instead adopt an API more like procedural language handlers or foreign data wrappers, wherein the only SQL-level support object needed is a single handler function identified by having a special return type. This lets us get rid of the supporting catalog altogether, so that no custom DDL support is needed for the feature. Adjust the API so that it can support non-constant tablesample arguments (the original coding assumed we could evaluate the argument expressions at ExecInitSampleScan time, which is undesirable even if it weren't outright unsafe), and discourage sampling methods from looking at invisible tuples. Make sure that the BERNOULLI and SYSTEM methods are genuinely repeatable within and across queries, as required by the SQL standard, and deal more honestly with methods that can't support that requirement. Make a full code-review pass over the tablesample additions, and fix assorted bugs, omissions, infelicities, and cosmetic issues (such as failure to put the added code stanzas in a consistent ordering). Improve EXPLAIN's output of tablesample plans, too. Back-patch to 9.5 so that we don't have to support the original API in production.
This commit is contained in:
@ -18,6 +18,7 @@
|
||||
#include <math.h>
|
||||
|
||||
#include "access/sysattr.h"
|
||||
#include "access/tsmapi.h"
|
||||
#include "catalog/pg_class.h"
|
||||
#include "catalog/pg_operator.h"
|
||||
#include "foreign/fdwapi.h"
|
||||
@ -390,7 +391,7 @@ set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
|
||||
}
|
||||
else if (rte->tablesample != NULL)
|
||||
{
|
||||
/* Build sample scan on relation */
|
||||
/* Sampled relation */
|
||||
set_tablesample_rel_pathlist(root, rel, rte);
|
||||
}
|
||||
else
|
||||
@ -480,11 +481,40 @@ set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
|
||||
|
||||
/*
|
||||
* set_tablesample_rel_size
|
||||
* Set size estimates for a sampled relation.
|
||||
* Set size estimates for a sampled relation
|
||||
*/
|
||||
static void
|
||||
set_tablesample_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
|
||||
{
|
||||
TableSampleClause *tsc = rte->tablesample;
|
||||
TsmRoutine *tsm;
|
||||
BlockNumber pages;
|
||||
double tuples;
|
||||
|
||||
/*
|
||||
* Test any partial indexes of rel for applicability. We must do this
|
||||
* first since partial unique indexes can affect size estimates.
|
||||
*/
|
||||
check_partial_indexes(root, rel);
|
||||
|
||||
/*
|
||||
* Call the sampling method's estimation function to estimate the number
|
||||
* of pages it will read and the number of tuples it will return. (Note:
|
||||
* we assume the function returns sane values.)
|
||||
*/
|
||||
tsm = GetTsmRoutine(tsc->tsmhandler);
|
||||
tsm->SampleScanGetSampleSize(root, rel, tsc->args,
|
||||
&pages, &tuples);
|
||||
|
||||
/*
|
||||
* For the moment, because we will only consider a SampleScan path for the
|
||||
* rel, it's okay to just overwrite the pages and tuples estimates for the
|
||||
* whole relation. If we ever consider multiple path types for sampled
|
||||
* rels, we'll need more complication.
|
||||
*/
|
||||
rel->pages = pages;
|
||||
rel->tuples = tuples;
|
||||
|
||||
/* Mark rel with estimated output rows, width, etc */
|
||||
set_baserel_size_estimates(root, rel);
|
||||
}
|
||||
@ -492,8 +522,6 @@ set_tablesample_rel_size(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
|
||||
/*
|
||||
* set_tablesample_rel_pathlist
|
||||
* Build access paths for a sampled relation
|
||||
*
|
||||
* There is only one possible path - sampling scan
|
||||
*/
|
||||
static void
|
||||
set_tablesample_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
|
||||
@ -502,15 +530,41 @@ set_tablesample_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *
|
||||
Path *path;
|
||||
|
||||
/*
|
||||
* We don't support pushing join clauses into the quals of a seqscan, but
|
||||
* it could still have required parameterization due to LATERAL refs in
|
||||
* its tlist.
|
||||
* We don't support pushing join clauses into the quals of a samplescan,
|
||||
* but it could still have required parameterization due to LATERAL refs
|
||||
* in its tlist or TABLESAMPLE arguments.
|
||||
*/
|
||||
required_outer = rel->lateral_relids;
|
||||
|
||||
/* We only do sample scan if it was requested */
|
||||
/* Consider sampled scan */
|
||||
path = create_samplescan_path(root, rel, required_outer);
|
||||
rel->pathlist = list_make1(path);
|
||||
|
||||
/*
|
||||
* If the sampling method does not support repeatable scans, we must avoid
|
||||
* plans that would scan the rel multiple times. Ideally, we'd simply
|
||||
* avoid putting the rel on the inside of a nestloop join; but adding such
|
||||
* a consideration to the planner seems like a great deal of complication
|
||||
* to support an uncommon usage of second-rate sampling methods. Instead,
|
||||
* if there is a risk that the query might perform an unsafe join, just
|
||||
* wrap the SampleScan in a Materialize node. We can check for joins by
|
||||
* counting the membership of all_baserels (note that this correctly
|
||||
* counts inheritance trees as single rels). If we're inside a subquery,
|
||||
* we can't easily check whether a join might occur in the outer query, so
|
||||
* just assume one is possible.
|
||||
*
|
||||
* GetTsmRoutine is relatively expensive compared to the other tests here,
|
||||
* so check repeatable_across_scans last, even though that's a bit odd.
|
||||
*/
|
||||
if ((root->query_level > 1 ||
|
||||
bms_membership(root->all_baserels) != BMS_SINGLETON) &&
|
||||
!(GetTsmRoutine(rte->tablesample->tsmhandler)->repeatable_across_scans))
|
||||
{
|
||||
path = (Path *) create_material_path(rel, path);
|
||||
}
|
||||
|
||||
add_path(rel, path);
|
||||
|
||||
/* For the moment, at least, there are no other paths to consider */
|
||||
}
|
||||
|
||||
/*
|
||||
@ -2450,7 +2504,33 @@ print_path(PlannerInfo *root, Path *path, int indent)
|
||||
switch (nodeTag(path))
|
||||
{
|
||||
case T_Path:
|
||||
ptype = "SeqScan";
|
||||
switch (path->pathtype)
|
||||
{
|
||||
case T_SeqScan:
|
||||
ptype = "SeqScan";
|
||||
break;
|
||||
case T_SampleScan:
|
||||
ptype = "SampleScan";
|
||||
break;
|
||||
case T_SubqueryScan:
|
||||
ptype = "SubqueryScan";
|
||||
break;
|
||||
case T_FunctionScan:
|
||||
ptype = "FunctionScan";
|
||||
break;
|
||||
case T_ValuesScan:
|
||||
ptype = "ValuesScan";
|
||||
break;
|
||||
case T_CteScan:
|
||||
ptype = "CteScan";
|
||||
break;
|
||||
case T_WorkTableScan:
|
||||
ptype = "WorkTableScan";
|
||||
break;
|
||||
default:
|
||||
ptype = "???Path";
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case T_IndexPath:
|
||||
ptype = "IdxScan";
|
||||
|
Reference in New Issue
Block a user