blob: abd687a3551f1225055ea9c9cbd650cc7a474836 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*-------------------------------------------------------------------------
*
* plancat.c
* routines for accessing the system catalogs
*
*
* Portions Copyright (c) 2005-2008, Greenplum inc.
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/util/plancat.c,v 1.127 2006/10/04 00:29:55 momjian Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include <math.h>
#include "access/genam.h"
#include "access/heapam.h"
#include "access/parquetsegfiles.h"
#include "catalog/catquery.h"
#include "catalog/gp_policy.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_exttable.h"
#include "catalog/indexing.h"
#include "catalog/pg_type.h"
#include "cdb/cdbanalyze.h"
#include "commands/tablecmds.h"
#include "nodes/makefuncs.h"
#include "optimizer/clauses.h"
#include "optimizer/plancat.h"
#include "optimizer/predtest.h"
#include "optimizer/prep.h"
#include "parser/parse_expr.h"
#include "parser/parse_relation.h"
#include "parser/parsetree.h"
#include "rewrite/rewriteManip.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/relcache.h"
#include "utils/syscache.h"
#include "utils/array.h"
#include "utils/fmgroids.h"
#include "utils/builtins.h"
#include "utils/uri.h"
#include "catalog/catalog.h"
#include "cdb/cdbvars.h"
#include "miscadmin.h"
#include "cdb/cdbdisp.h"
#include "cdb/cdbrelsize.h"
#include "mb/pg_wchar.h"
#include "commands/vacuum.h"
/* GUC parameter */
static List *get_relation_constraints(PlannerInfo *root,
Oid relationObjectId, RelOptInfo *rel,
bool include_notnull);
static void
estimate_tuple_width(Relation rel,
int32 *attr_widths,
int32 *bytes_per_tuple,
double *tuples_per_page);
static void
cdb_estimate_rel_size(RelOptInfo *relOptInfo,
Relation baserel,
Relation rel,
int32 *attr_widths,
BlockNumber *pages,
double *tuples,
bool *default_stats_used);
static void
cdb_default_stats_warning_for_index(Oid reloid, Oid indexoid);
extern BlockNumber RelationGuessNumberOfBlocks(double totalbytes);
/*
* get_relation_info -
* Retrieves catalog information for a given relation.
*
* Given the Oid of the relation, return the following info into fields
* of the RelOptInfo struct:
*
* min_attr lowest valid AttrNumber
* max_attr highest valid AttrNumber
* indexlist list of IndexOptInfos for relation's indexes
* pages number of pages
* tuples number of tuples
*
* Also, initialize the attr_needed[] and attr_widths[] arrays. In most
* cases these are left as zeroes, but sometimes we need to compute attr
* widths here, and we may as well cache the results for costsize.c.
*
* If inhparent is true, all we need to do is set up the attr arrays:
* the RelOptInfo actually represents the appendrel formed by an inheritance
* tree, and so the parent rel's physical size and index information isn't
* important for it.
*/
void
get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
RelOptInfo *rel)
{
Index varno = rel->relid;
Relation relation;
bool hasindex;
List *indexinfos = NIL;
bool needs_longlock;
/*
* We need not lock the relation since it was already locked, either by
* the rewriter or when expand_inherited_rtentry() added it to the query's
* rangetable.
*/
relation = heap_open(relationObjectId, NoLock);
needs_longlock = rel_needs_long_lock(relationObjectId);
rel->min_attr = FirstLowInvalidHeapAttributeNumber + 1;
rel->max_attr = RelationGetNumberOfAttributes(relation);
Assert(rel->max_attr >= rel->min_attr);
rel->attr_needed = (Relids *)
palloc0((rel->max_attr - rel->min_attr + 1) * sizeof(Relids));
rel->attr_widths = (int32 *)
palloc0((rel->max_attr - rel->min_attr + 1) * sizeof(int32));
/*
* CDB: Get partitioning key info for distributed relation.
*/
rel->cdbpolicy = RelationGetPartitioningKey(relation);
/*
* Estimate relation size --- unless it's an inheritance parent, in which
* case the size will be computed later in set_append_rel_pathlist, and we
* must leave it zero for now to avoid bollixing the total_table_pages
* calculation.
*/
if (!inhparent)
{
cdb_estimate_rel_size
(
rel,
relation,
relation,
rel->attr_widths - rel->min_attr,
&rel->pages,
&rel->tuples,
&rel->cdb_default_stats_used
);
}
/*
* Make list of indexes. Ignore indexes on system catalogs if told to.
* Don't bother with indexes for an inheritance parent, either.
*/
if (inhparent ||
(IgnoreSystemIndexes && IsSystemClass(relation->rd_rel)))
hasindex = false;
else
hasindex = relation->rd_rel->relhasindex;
if (hasindex)
{
List *indexoidlist;
ListCell *l;
LOCKMODE lmode;
/* Warn if indexed table needs ANALYZE. */
if (rel->cdb_default_stats_used)
cdb_default_stats_warning_for_table(relation->rd_id);
indexoidlist = RelationGetIndexList(relation);
/*
* For each index, we get the same type of lock that the executor will
* need, and do not release it. This saves a couple of trips to the
* shared lock manager while not creating any real loss of
* concurrency, because no schema changes could be happening on the
* index while we hold lock on the parent rel, and neither lock type
* blocks any other kind of index operation.
*/
if (rel->relid == root->parse->resultRelation)
lmode = RowExclusiveLock;
else
lmode = AccessShareLock;
foreach(l, indexoidlist)
{
Oid indexoid = lfirst_oid(l);
Relation indexRelation;
Form_pg_index index;
IndexOptInfo *info;
int ncolumns;
int i;
int16 amorderstrategy;
/*
* Extract info from the relation descriptor for the index.
*/
indexRelation = index_open(indexoid, lmode);
index = indexRelation->rd_index;
/*
* Ignore invalid indexes, since they can't safely be used for
* queries. Note that this is OK because the data structure we
* are constructing is only used by the planner --- the executor
* still needs to insert into "invalid" indexes!
*/
if (!index->indisvalid)
{
index_close(indexRelation, NoLock);
continue;
}
info = makeNode(IndexOptInfo);
info->indexoid = index->indexrelid;
info->rel = rel;
info->ncolumns = ncolumns = index->indnatts;
/*
* Need to make classlist and ordering arrays large enough to put
* a terminating 0 at the end of each one.
*/
info->indexkeys = (int *) palloc(sizeof(int) * ncolumns);
info->classlist = (Oid *) palloc0(sizeof(Oid) * (ncolumns + 1));
info->ordering = (Oid *) palloc0(sizeof(Oid) * (ncolumns + 1));
for (i = 0; i < ncolumns; i++)
{
info->classlist[i] = indexRelation->rd_indclass->values[i];
info->indexkeys[i] = index->indkey.values[i];
}
info->relam = indexRelation->rd_rel->relam;
info->amcostestimate = indexRelation->rd_am->amcostestimate;
info->amoptionalkey = indexRelation->rd_am->amoptionalkey;
/*
* Fetch the ordering operators associated with the index, if any.
*/
amorderstrategy = indexRelation->rd_am->amorderstrategy;
if (amorderstrategy != 0)
{
int oprindex = amorderstrategy - 1;
for (i = 0; i < ncolumns; i++)
{
info->ordering[i] = indexRelation->rd_operator[oprindex];
oprindex += indexRelation->rd_am->amstrategies;
}
}
/*
* Fetch the index expressions and predicate, if any. We must
* modify the copies we obtain from the relcache to have the
* correct varno for the parent relation, so that they match up
* correctly against qual clauses.
*/
info->indexprs = RelationGetIndexExpressions(indexRelation);
info->indpred = RelationGetIndexPredicate(indexRelation);
if (info->indexprs && varno != 1)
ChangeVarNodes((Node *) info->indexprs, 1, varno, 0);
if (info->indpred && varno != 1)
ChangeVarNodes((Node *) info->indpred, 1, varno, 0);
info->predOK = false; /* set later in indxpath.c */
info->unique = index->indisunique;
/*
* Estimate the index size. If it's not a partial index, we lock
* the number-of-tuples estimate to equal the parent table; if it
* is partial then we have to use the same methods as we would for
* a table, except we can be sure that the index is not larger
* than the table.
*/
cdb_estimate_rel_size(rel,
relation,
indexRelation,
NULL,
&info->pages,
&info->tuples,
&info->cdb_default_stats_used);
if (!info->indpred ||
info->tuples > rel->tuples)
info->tuples = rel->tuples;
if (info->cdb_default_stats_used &&
!rel->cdb_default_stats_used)
cdb_default_stats_warning_for_index(relation->rd_id, indexoid);
index_close(indexRelation, needs_longlock ? NoLock : lmode);
indexinfos = lcons(info, indexinfos);
}
list_free(indexoidlist);
}
rel->indexlist = indexinfos;
heap_close(relation, NoLock);
}
/*
* Update RelOptInfo to include the external specifications (file URI list
* and data format) from the pg_exttable catalog.
*/
void
get_external_relation_info(Oid relationObjectId, RelOptInfo *rel)
{
Relation pg_class_rel;
ExtTableEntry* extentry;
/*
* Get partitioning key info for distributed relation.
*/
pg_class_rel = heap_open(relationObjectId, NoLock);
rel->cdbpolicy = RelationGetPartitioningKey(pg_class_rel);
heap_close(pg_class_rel, NoLock);
/*
* Get the pg_exttable fields for this table
*/
extentry = GetExtTableEntry(relationObjectId);
rel->locationlist = extentry->locations;
rel->execcommand = extentry->command;
rel->fmttype = extentry->fmtcode;
rel->fmtopts = extentry->fmtopts;
rel->rejectlimit = extentry->rejectlimit;
rel->rejectlimittype = extentry->rejectlimittype;
rel->fmterrtbl = extentry->fmterrtbl;
rel->ext_encoding = extentry->encoding;
rel->writable = extentry->iswritable;
/* web tables are non rescannable. others are */
rel->isrescannable = !extentry->isweb;
}
/*
* cdb_estimate_rel_size - estimate # pages and # tuples in a table or index
*
* If attr_widths isn't NULL, it points to the zero-index entry of the
* relation's attr_width[] cache; we fill this in if we have need to compute
* the attribute widths for estimation purposes.
*/
void
cdb_estimate_rel_size(RelOptInfo *relOptInfo,
Relation baserel,
Relation rel,
int32 *attr_widths,
BlockNumber *pages,
double *tuples,
bool *default_stats_used)
{
BlockNumber relpages;
double reltuples;
double density;
int32 tuple_width;
BlockNumber curpages = 0;
int64 size = 0;
StringInfoData location;
initStringInfo(&location);
*default_stats_used = false;
/* Rel not distributed? RelationGetNumberOfBlocks can get actual #pages. */
if (!relOptInfo->cdbpolicy ||
relOptInfo->cdbpolicy->ptype != POLICYTYPE_PARTITIONED)
{
estimate_rel_size(rel, attr_widths, pages, tuples);
return;
}
/* coerce values in pg_class to more desirable types */
relpages = (BlockNumber) rel->rd_rel->relpages;
reltuples = (double) rel->rd_rel->reltuples;
/*
* Asking the QE for the size of the relation is a bit expensive.
* Do we want to do it all the time? Or only for tables that have never had analyze run?
*/
if (relpages > 0)
{
/*
* Let's trust the values we had from analyze, even though they might be out of date.
*
* NOTE: external tables are created with estimated larger than zero values. therefore
* we will get here too even though we can never analyze them.
*/
curpages = relpages;
}
else /* relpages is 0 and this is a regular table or an external table */
{
/*
* Let's ask the QEs for the size of the relation.
* In the future, it would be better to send the command to only one QE.
*
* NOTE: External tables should always have >0 values in pg_class
* (created this way). Therefore we should never get here. However, as
* a security measure (if values in pg_class were somehow changed) we
* plug in our 1K pages 1M tuples estimate here as well, and skip
* cdbRelSize as we can't calculate ext table size.
*/
if(!RelationIsExternal(rel))
{
size = cdbRelSize(rel);
}
else
{
/*
* Estimate a default of 1000 pages - see comment above.
* NOTE: if you change this look at AddNewRelationTuple in heap.c).
*/
size = 1000 * BLCKSZ;
}
if (size < 0)
{
curpages = 100;
*default_stats_used = true;
}
else
{
curpages = size / BLCKSZ; /* average blocks per primary segment DB */
}
if (curpages == 0 && size > 0)
curpages = 1;
}
/* report estimated # pages */
*pages = curpages;
/* quick exit if rel is clearly empty */
if (curpages == 0)
{
*tuples = 0;
return;
}
/*
* If it's an index, discount the metapage. This is a kluge
* because it assumes more than it ought to about index contents;
* it's reasonably OK for btrees but a bit suspect otherwise.
*/
if (rel->rd_rel->relkind == RELKIND_INDEX &&
relpages > 0)
{
curpages--;
relpages--;
}
/* estimate number of tuples from previous tuple density (as of last analyze) */
if (relpages > 0)
density = reltuples / (double) relpages;
else
{
/*
* When we have no data because the relation was truncated,
* estimate tuples per page from attribute datatypes.
* (CDB: The code that was here has been moved into the
* estimate_tuple_width function below.)
*/
estimate_tuple_width(rel, attr_widths, &tuple_width, &density);
}
*tuples = ceil(density * curpages);
elog(DEBUG2,"cdb_estimate_rel_size estimated %g tuples and %d pages",*tuples,(int)*pages);
} /* cdb_estimate_rel_size */
/*
* estimate_rel_size - estimate # pages and # tuples in a table or index
*
* If attr_widths isn't NULL, it points to the zero-index entry of the
* relation's attr_width[] cache; we fill this in if we have need to compute
* the attribute widths for estimation purposes.
*/
void
estimate_rel_size(Relation rel, int32 *attr_widths,
BlockNumber *pages, double *tuples)
{
BlockNumber curpages;
BlockNumber relpages;
double reltuples;
double density;
int32 tuple_width;
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
case RELKIND_INDEX:
case RELKIND_TOASTVALUE:
case RELKIND_AOSEGMENTS:
case RELKIND_AOBLOCKDIR:
/* skip external tables */
if(RelationIsExternal(rel))
break;
if(RelationIsAoRows(rel))
{
/* MPP-7629 */
/*
* relation should not be distributed table
*/
FileSegTotals *fstotal = GetSegFilesTotals(rel, SnapshotNow);
Assert(fstotal);
curpages = RelationGuessNumberOfBlocks((double)fstotal->totalbytes);
pfree(fstotal);
}
else if(RelationIsParquet(rel))
{
/* MPP-7629 */
/*
* relation should not be distributed table
*/
ParquetFileSegTotals *fstotal = GetParquetSegFilesTotals(rel, SnapshotNow);
Assert(fstotal);
curpages = RelationGuessNumberOfBlocks((double)fstotal->totalbytes);
pfree(fstotal);
}
else
{
/* it has storage, ok to call the smgr */
curpages = RelationGetNumberOfBlocks(rel);
}
/*
* HACK: if the relation has never yet been vacuumed, use a
* minimum estimate of 10 pages. This emulates a desirable aspect
* of pre-8.0 behavior, which is that we wouldn't assume a newly
* created relation is really small, which saves us from making
* really bad plans during initial data loading. (The plans are
* not wrong when they are made, but if they are cached and used
* again after the table has grown a lot, they are bad.) It would
* be better to force replanning if the table size has changed a
* lot since the plan was made ... but we don't currently have any
* infrastructure for redoing cached plans at all, so we have to
* kluge things here instead.
*
* We approximate "never vacuumed" by "has relpages = 0", which
* means this will also fire on genuinely empty relations. Not
* great, but fortunately that's a seldom-seen case in the real
* world, and it shouldn't degrade the quality of the plan too
* much anyway to err in this direction.
*/
if (curpages < 10 && rel->rd_rel->relpages == 0)
curpages = 10;
/* report estimated # pages */
*pages = curpages;
/* quick exit if rel is clearly empty */
if (curpages == 0)
{
*tuples = 0;
break;
}
/* coerce values in pg_class to more desirable types */
relpages = (BlockNumber) rel->rd_rel->relpages;
reltuples = (double) rel->rd_rel->reltuples;
/*
* If it's an index, discount the metapage. This is a kluge
* because it assumes more than it ought to about index contents;
* it's reasonably OK for btrees but a bit suspect otherwise.
*/
if (rel->rd_rel->relkind == RELKIND_INDEX &&
relpages > 0)
{
curpages--;
relpages--;
}
/* estimate number of tuples from previous tuple density */
if (relpages > 0)
density = reltuples / (double) relpages;
else
{
/*
* When we have no data because the relation was truncated,
* estimate tuples per page from attribute datatypes.
* (CDB: The code that was here has been moved into the
* estimate_tuple_width function below.)
*/
estimate_tuple_width(rel, attr_widths, &tuple_width, &density);
}
*tuples = ceil(density * curpages);
break;
case RELKIND_SEQUENCE:
/* Sequences always have a known size */
*pages = 1;
*tuples = 1;
break;
default:
/* else it has no disk storage; probably shouldn't get here? */
*pages = 0;
*tuples = 0;
break;
}
}
/*
* estimate_tuple_width
*
* CDB: Pulled this code out of estimate_rel_size below to make a
* separate function.
*/
void
estimate_tuple_width(Relation rel,
int32 *attr_widths,
int32 *bytes_per_tuple,
double *tuples_per_page)
{
/*
* Estimate tuple width from attribute datatypes. We assume
* here that the pages are completely full, which is OK for
* tables (since they've presumably not been VACUUMed yet) but
* is probably an overestimate for indexes. Fortunately
* get_relation_info() can clamp the overestimate to the
* parent table's size.
*
* Note: this code intentionally disregards alignment
* considerations, because (a) that would be gilding the lily
* considering how crude the estimate is, and (b) it creates
* platform dependencies in the default plans which are kind
* of a headache for regression testing.
*/
double density;
int32 tuple_width = 0;
int i;
for (i = 1; i <= RelationGetNumberOfAttributes(rel); i++)
{
Form_pg_attribute att = rel->rd_att->attrs[i - 1];
int32 item_width;
if (att->attisdropped)
continue;
/* This should match set_rel_width() in costsize.c */
item_width = get_attavgwidth(RelationGetRelid(rel), i);
if (item_width <= 0)
{
item_width = get_typavgwidth(att->atttypid,
att->atttypmod);
Assert(item_width > 0);
}
if (attr_widths != NULL)
attr_widths[i] = item_width;
tuple_width += item_width;
}
tuple_width += sizeof(HeapTupleHeaderData);
tuple_width += sizeof(ItemPointerData);
/* note: integer division is intentional here */
density = (BLCKSZ - sizeof(PageHeaderData)) / tuple_width;
*bytes_per_tuple = tuple_width;
*tuples_per_page = Max(1.0, density);
} /* estimate_tuple_width */
/*
* get_relation_constraints
*
* Retrieve the CHECK constraint expressions of the given relation.
*
* Returns a List (possibly empty) of constraint expressions. Each one
* has been canonicalized, and its Vars are changed to have the varno
* indicated by rel->relid. This allows the expressions to be easily
* compared to expressions taken from WHERE.
*
* If include_notnull is true, "col IS NOT NULL" expressions are generated
* and added to the result for each column that's marked attnotnull.
*
* Note: at present this is invoked at most once per relation per planner
* run, and in many cases it won't be invoked at all, so there seems no
* point in caching the data in RelOptInfo.
*/
static List *
get_relation_constraints(PlannerInfo *root,
Oid relationObjectId, RelOptInfo *rel,
bool include_notnull)
{
List *result = NIL;
Index varno = rel->relid;
Relation relation;
TupleConstr *constr;
/*
* We assume the relation has already been safely locked.
*/
relation = heap_open(relationObjectId, NoLock);
constr = relation->rd_att->constr;
if (constr != NULL)
{
int num_check = constr->num_check;
int i;
for (i = 0; i < num_check; i++)
{
Node *cexpr;
cexpr = stringToNode(constr->check[i].ccbin);
/*
* Run each expression through const-simplification and
* canonicalization. This is not just an optimization, but is
* necessary, because we will be comparing it to
* similarly-processed qual clauses, and may fail to detect valid
* matches without this. This must match the processing done to
* qual clauses in preprocess_expression()! (We can skip the
* stuff involving subqueries, however, since we don't allow any
* in check constraints.)
*/
cexpr = eval_const_expressions(root, cexpr);
cexpr = (Node *) canonicalize_qual((Expr *) cexpr);
/*
* Also mark any coercion format fields as "don't care", so that
* we can match to both explicit and implicit coercions.
*/
set_coercionform_dontcare(cexpr);
/* Fix Vars to have the desired varno */
if (varno != 1)
ChangeVarNodes(cexpr, 1, varno, 0);
/*
* Finally, convert to implicit-AND format (that is, a List) and
* append the resulting item(s) to our output list.
*/
result = list_concat(result,
make_ands_implicit((Expr *) cexpr));
}
/* Add NOT NULL constraints in expression form, if requested */
if (include_notnull && constr->has_not_null)
{
int natts = relation->rd_att->natts;
for (i = 1; i <= natts; i++)
{
Form_pg_attribute att = relation->rd_att->attrs[i - 1];
if (att->attnotnull && !att->attisdropped)
{
NullTest *ntest = makeNode(NullTest);
ntest->arg = (Expr *) makeVar(varno,
i,
att->atttypid,
att->atttypmod,
0);
ntest->nulltesttype = IS_NOT_NULL;
result = lappend(result, ntest);
}
}
}
}
heap_close(relation, NoLock);
return result;
}
/*
* relation_excluded_by_constraints
*
* Detect whether the relation need not be scanned because it has either
* self-inconsistent restrictions, or restrictions inconsistent with the
* relation's CHECK constraints.
*/
bool
relation_excluded_by_constraints(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
{
List *safe_restrictions;
List *constraint_pred;
List *safe_constraints;
ListCell *lc;
/* Skip the test if constraint exclusion is disabled */
if (!root->config->constraint_exclusion)
return false;
/*
* Check for self-contradictory restriction clauses. We dare not make
* deductions with non-immutable functions, but any immutable clauses that
* are self-contradictory allow us to conclude the scan is unnecessary.
*
* Note: strip off RestrictInfo because predicate_refuted_by() isn't
* expecting to see any in its predicate argument.
*/
safe_restrictions = NIL;
foreach(lc, rel->baserestrictinfo)
{
RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
if (!contain_mutable_functions((Node *) rinfo->clause))
safe_restrictions = lappend(safe_restrictions, rinfo->clause);
}
if (predicate_refuted_by(safe_restrictions, safe_restrictions))
return true;
/* Only plain relations have constraints */
if (rte->rtekind != RTE_RELATION || rte->inh)
return false;
/*
* OK to fetch the constraint expressions. Include "col IS NOT NULL"
* expressions for attnotnull columns, in case we can refute those.
*/
constraint_pred = get_relation_constraints(root, rte->relid, rel, true);
/*
* We do not currently enforce that CHECK constraints contain only
* immutable functions, so it's necessary to check here. We daren't draw
* conclusions from plan-time evaluation of non-immutable functions. Since
* they're ANDed, we can just ignore any mutable constraints in the list,
* and reason about the rest.
*/
safe_constraints = NIL;
foreach(lc, constraint_pred)
{
Node *pred = (Node *) lfirst(lc);
if (!contain_mutable_functions(pred))
safe_constraints = lappend(safe_constraints, pred);
}
/*
* The constraints are effectively ANDed together, so we can just try to
* refute the entire collection at once. This may allow us to make proofs
* that would fail if we took them individually.
*
* Note: we use rel->baserestrictinfo, not safe_restrictions as might seem
* an obvious optimization. Some of the clauses might be OR clauses that
* have volatile and nonvolatile subclauses, and it's OK to make
* deductions with the nonvolatile parts.
*/
if (predicate_refuted_by(safe_constraints, rel->baserestrictinfo))
return true;
return false;
}
/*
* build_physical_tlist
*
* Build a targetlist consisting of exactly the relation's user attributes,
* in order. The executor can special-case such tlists to avoid a projection
* step at runtime, so we use such tlists preferentially for scan nodes.
*
* Exception: if there are any dropped columns, we punt and return NIL.
* Ideally we would like to handle the dropped-column case too. However this
* creates problems for ExecTypeFromTL, which may be asked to build a tupdesc
* for a tlist that includes vars of no-longer-existent types. In theory we
* could dig out the required info from the pg_attribute entries of the
* relation, but that data is not readily available to ExecTypeFromTL.
* For now, we don't apply the physical-tlist optimization when there are
* dropped cols.
*
* We also support building a "physical" tlist for subqueries, functions,
* and values lists, since the same optimization can occur in SubqueryScan,
* FunctionScan, and ValuesScan nodes.
*/
List *
build_physical_tlist(PlannerInfo *root, RelOptInfo *rel)
{
List *tlist = NIL;
Index varno = rel->relid;
RangeTblEntry *rte = rt_fetch(varno, root->parse->rtable);
Relation relation;
Query *subquery;
Var *var;
ListCell *l;
int attrno,
numattrs;
List *colvars;
switch (rte->rtekind)
{
case RTE_RELATION:
/* Assume we already have adequate lock */
relation = heap_open(rte->relid, NoLock);
numattrs = RelationGetNumberOfAttributes(relation);
for (attrno = 1; attrno <= numattrs; attrno++)
{
Form_pg_attribute att_tup = relation->rd_att->attrs[attrno - 1];
if (att_tup->attisdropped)
{
/* found a dropped col, so punt */
tlist = NIL;
break;
}
var = makeVar(varno,
attrno,
att_tup->atttypid,
att_tup->atttypmod,
0);
tlist = lappend(tlist,
makeTargetEntry((Expr *) var,
attrno,
NULL,
false));
}
heap_close(relation, NoLock);
break;
case RTE_SUBQUERY:
subquery = rte->subquery;
if ( subquery->querySource == QSRC_PLANNER )
{
/* MPP: punt since parse tree correspondence in doubt */
tlist = NIL;
break;
}
foreach(l, subquery->targetList)
{
TargetEntry *tle = (TargetEntry *) lfirst(l);
/*
* A resjunk column of the subquery can be reflected as
* resjunk in the physical tlist; we need not punt.
*/
var = makeVar(varno,
tle->resno,
exprType((Node *) tle->expr),
exprTypmod((Node *) tle->expr),
0);
tlist = lappend(tlist,
makeTargetEntry((Expr *) var,
tle->resno,
NULL,
tle->resjunk));
}
break;
case RTE_CTE:
case RTE_FUNCTION:
case RTE_TABLEFUNCTION:
expandRTE(rte, varno, 0, -1, true /* include dropped */ ,
NULL, &colvars);
foreach(l, colvars)
{
var = (Var *) lfirst(l);
/*
* A non-Var in expandRTE's output means a dropped column;
* must punt.
*/
if (!IsA(var, Var))
{
tlist = NIL;
break;
}
tlist = lappend(tlist,
makeTargetEntry((Expr *) var,
var->varattno,
NULL,
false));
}
break;
case RTE_VALUES:
expandRTE(rte, varno, 0, -1, false /* dropped not applicable */ ,
NULL, &colvars);
foreach(l, colvars)
{
var = (Var *) lfirst(l);
tlist = lappend(tlist,
makeTargetEntry((Expr *) var,
var->varattno,
NULL,
false));
}
break;
default:
/* caller error */
elog(ERROR, "unsupported RTE kind %d in build_physical_tlist",
(int) rte->rtekind);
break;
}
return tlist;
}
/*
* restriction_selectivity
*
* Returns the selectivity of a specified restriction operator clause.
* This code executes registered procedures stored in the
* operator relation, by calling the function manager.
*
* See clause_selectivity() for the meaning of the additional parameters.
*/
Selectivity
restriction_selectivity(PlannerInfo *root,
Oid operator,
List *args,
int varRelid)
{
RegProcedure oprrest = get_oprrest(operator);
float8 result;
/*
* if the oprrest procedure is missing for whatever reason, use a
* selectivity of 0.5
*/
if (!oprrest)
return (Selectivity) 0.5;
result = DatumGetFloat8(OidFunctionCall4(oprrest,
PointerGetDatum(root),
ObjectIdGetDatum(operator),
PointerGetDatum(args),
Int32GetDatum(varRelid)));
if (result < 0.0 || result > 1.0)
elog(ERROR, "invalid restriction selectivity: %f", result);
return (Selectivity) result;
}
/*
* join_selectivity
*
* Returns the selectivity of a specified join operator clause.
* This code executes registered procedures stored in the
* operator relation, by calling the function manager.
*/
Selectivity
join_selectivity(PlannerInfo *root,
Oid operator,
List *args,
JoinType jointype)
{
RegProcedure oprjoin = get_oprjoin(operator);
float8 result;
/*
* if the oprjoin procedure is missing for whatever reason, use a
* selectivity of 0.5
*/
if (!oprjoin)
return (Selectivity) 0.5;
result = DatumGetFloat8(OidFunctionCall4(oprjoin,
PointerGetDatum(root),
ObjectIdGetDatum(operator),
PointerGetDatum(args),
Int16GetDatum(jointype)));
if (result < 0.0 || result > 1.0)
elog(ERROR, "invalid join selectivity: %f", result);
return (Selectivity) result;
}
/*
* find_inheritance_children
*
* Returns a list containing the OIDs of all relations which
* inherit *directly* from the relation with OID 'inhparent'.
*
* XXX might be a good idea to create an index on pg_inherits' inhparent
* field, so that we can use an indexscan instead of sequential scan here.
* However, in typical databases pg_inherits won't have enough entries to
* justify an indexscan...
*/
List *
find_inheritance_children(Oid inhparent)
{
List *list = NIL;
HeapTuple inheritsTuple;
Oid inhrelid;
cqContext *pcqCtx;
/*
* Can skip the scan if pg_class shows the relation has never had a
* subclass.
*/
if (!has_subclass_fast(inhparent))
return NIL;
pcqCtx = caql_beginscan(
NULL,
cql("SELECT * FROM pg_inherits "
" WHERE inhparent = :1 ",
ObjectIdGetDatum(inhparent)));
while (HeapTupleIsValid(inheritsTuple = caql_getnext(pcqCtx)))
{
inhrelid = ((Form_pg_inherits) GETSTRUCT(inheritsTuple))->inhrelid;
list = lappend_oid(list, inhrelid);
}
caql_endscan(pcqCtx);
return list;
}
/*
* has_unique_index
*
* Detect whether there is a unique index on the specified attribute
* of the specified relation, thus allowing us to conclude that all
* the (non-null) values of the attribute are distinct.
*/
bool
has_unique_index(RelOptInfo *rel, AttrNumber attno)
{
ListCell *ilist;
foreach(ilist, rel->indexlist)
{
IndexOptInfo *index = (IndexOptInfo *) lfirst(ilist);
/*
* Note: ignore partial indexes, since they don't allow us to conclude
* that all attr values are distinct. We don't take any interest in
* expressional indexes either. Also, a multicolumn unique index
* doesn't allow us to conclude that just the specified attr is
* unique.
*/
if (index->unique &&
index->ncolumns == 1 &&
index->indexkeys[0] == attno &&
index->indpred == NIL)
return true;
}
return false;
}
/*
* cdb_default_stats_warning_needed
*/
static bool
cdb_default_stats_warning_needed(Oid reloid)
{
Relation relation;
bool warn = true;
/* Find relcache entry. */
relation = relation_open(reloid, NoLock);
/* Keep quiet if temporary or system table. */
if (relation->rd_istemp ||
IsSystemClass(relation->rd_rel))
warn = false;
/* Warn at most once during lifetime of relcache entry. */
else if (relation->rd_cdbDefaultStatsWarningIssued)
warn = false;
/* Caller will issue warning. Set flag so warning won't be repeated. */
else
relation->rd_cdbDefaultStatsWarningIssued = true;
/* Close rel. Don't disturb the lock. */
relation_close(relation, NoLock);
return warn;
} /* cdb_default_stats_warning_needed */
/*
* cdb_default_stats_warning_for_index
*/
void
cdb_default_stats_warning_for_index(Oid reloid, Oid indexoid)
{
char *relname;
char *indexname;
/* Warn at most once during lifetime of relcache entry. Skip if temp. */
if (!cdb_default_stats_warning_needed(indexoid))
return;
/* Get name from catalog, not from relcache, in case it has been renamed. */
relname = get_rel_name(reloid);
indexname = get_rel_name(indexoid);
ereport(NOTICE,
(errmsg("Query planner will use default statistics for index \"%s\" "
"on table \"%s\"",
indexname ? indexname : "??",
relname ? relname : "??"),
errhint("To cache a sample of the table's actual statistics for "
"optimization, use the ANALYZE or VACUUM ANALYZE command.")
));
if (relname)
pfree(relname);
if (indexname)
pfree(indexname);
} /* cdb_default_stats_warning_for_index */
/*
* cdb_default_stats_warning_for_table
*/
void
cdb_default_stats_warning_for_table(Oid reloid)
{
char *relname;
/* Warn at most once during lifetime of relcache entry. Skip if temp. */
if (!cdb_default_stats_warning_needed(reloid))
return;
/* Get name from catalog, not from relcache, in case name has changed. */
relname = get_rel_name(reloid);
ereport(NOTICE,
(errmsg("Query planner will use default statistics for table \"%s\"",
relname ? relname : "??"),
errhint("To cache a sample of the table's actual statistics for "
"optimization, use the ANALYZE or VACUUM ANALYZE command.")
));
if (relname)
pfree(relname);
} /* cdb_default_stats_warning_for_table */