901 lines
27 KiB
C
901 lines
27 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* statscmds.c
|
|
* Commands for creating and altering extended statistics objects
|
|
*
|
|
* Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
*
|
|
* IDENTIFICATION
|
|
* src/backend/commands/statscmds.c
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
#include "postgres.h"
|
|
|
|
#include "access/heapam.h"
|
|
#include "access/relation.h"
|
|
#include "access/relscan.h"
|
|
#include "access/table.h"
|
|
#include "catalog/catalog.h"
|
|
#include "catalog/dependency.h"
|
|
#include "catalog/indexing.h"
|
|
#include "catalog/namespace.h"
|
|
#include "catalog/objectaccess.h"
|
|
#include "catalog/pg_namespace.h"
|
|
#include "catalog/pg_statistic_ext.h"
|
|
#include "catalog/pg_statistic_ext_data.h"
|
|
#include "commands/comment.h"
|
|
#include "commands/defrem.h"
|
|
#include "miscadmin.h"
|
|
#include "nodes/nodeFuncs.h"
|
|
#include "optimizer/optimizer.h"
|
|
#include "statistics/statistics.h"
|
|
#include "utils/builtins.h"
|
|
#include "utils/lsyscache.h"
|
|
#include "utils/fmgroids.h"
|
|
#include "utils/inval.h"
|
|
#include "utils/memutils.h"
|
|
#include "utils/rel.h"
|
|
#include "utils/syscache.h"
|
|
#include "utils/typcache.h"
|
|
|
|
|
|
static char *ChooseExtendedStatisticName(const char *name1, const char *name2,
|
|
const char *label, Oid namespaceid);
|
|
static char *ChooseExtendedStatisticNameAddition(List *exprs);
|
|
|
|
|
|
/* qsort comparator for the attnums in CreateStatistics */
|
|
static int
|
|
compare_int16(const void *a, const void *b)
|
|
{
|
|
int av = *(const int16 *) a;
|
|
int bv = *(const int16 *) b;
|
|
|
|
/* this can't overflow if int is wider than int16 */
|
|
return (av - bv);
|
|
}
|
|
|
|
/*
|
|
* CREATE STATISTICS
|
|
*/
|
|
ObjectAddress
|
|
CreateStatistics(CreateStatsStmt *stmt)
|
|
{
|
|
int16 attnums[STATS_MAX_DIMENSIONS];
|
|
int nattnums = 0;
|
|
int numcols;
|
|
char *namestr;
|
|
NameData stxname;
|
|
Oid statoid;
|
|
Oid namespaceId;
|
|
Oid stxowner = GetUserId();
|
|
HeapTuple htup;
|
|
Datum values[Natts_pg_statistic_ext];
|
|
bool nulls[Natts_pg_statistic_ext];
|
|
Datum datavalues[Natts_pg_statistic_ext_data];
|
|
bool datanulls[Natts_pg_statistic_ext_data];
|
|
int2vector *stxkeys;
|
|
List *stxexprs = NIL;
|
|
Datum exprsDatum;
|
|
Relation statrel;
|
|
Relation datarel;
|
|
Relation rel = NULL;
|
|
Oid relid;
|
|
ObjectAddress parentobject,
|
|
myself;
|
|
Datum types[4]; /* one for each possible type of statistic */
|
|
int ntypes;
|
|
ArrayType *stxkind;
|
|
bool build_ndistinct;
|
|
bool build_dependencies;
|
|
bool build_mcv;
|
|
bool build_expressions;
|
|
bool requested_type = false;
|
|
int i;
|
|
ListCell *cell;
|
|
ListCell *cell2;
|
|
|
|
Assert(IsA(stmt, CreateStatsStmt));
|
|
|
|
/*
|
|
* Examine the FROM clause. Currently, we only allow it to be a single
|
|
* simple table, but later we'll probably allow multiple tables and JOIN
|
|
* syntax. The grammar is already prepared for that, so we have to check
|
|
* here that what we got is what we can support.
|
|
*/
|
|
if (list_length(stmt->relations) != 1)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("only a single relation is allowed in CREATE STATISTICS")));
|
|
|
|
foreach(cell, stmt->relations)
|
|
{
|
|
Node *rln = (Node *) lfirst(cell);
|
|
|
|
if (!IsA(rln, RangeVar))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("only a single relation is allowed in CREATE STATISTICS")));
|
|
|
|
/*
|
|
* CREATE STATISTICS will influence future execution plans but does
|
|
* not interfere with currently executing plans. So it should be
|
|
* enough to take only ShareUpdateExclusiveLock on relation,
|
|
* conflicting with ANALYZE and other DDL that sets statistical
|
|
* information, but not with normal queries.
|
|
*/
|
|
rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
|
|
|
|
/* Restrict to allowed relation types */
|
|
if (rel->rd_rel->relkind != RELKIND_RELATION &&
|
|
rel->rd_rel->relkind != RELKIND_MATVIEW &&
|
|
rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
|
|
rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
|
errmsg("relation \"%s\" is not a table, foreign table, or materialized view",
|
|
RelationGetRelationName(rel))));
|
|
|
|
/* You must own the relation to create stats on it */
|
|
if (!pg_class_ownercheck(RelationGetRelid(rel), stxowner))
|
|
aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(rel->rd_rel->relkind),
|
|
RelationGetRelationName(rel));
|
|
|
|
/* Creating statistics on system catalogs is not allowed */
|
|
if (!allowSystemTableMods && IsSystemRelation(rel))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
|
|
errmsg("permission denied: \"%s\" is a system catalog",
|
|
RelationGetRelationName(rel))));
|
|
}
|
|
|
|
Assert(rel);
|
|
relid = RelationGetRelid(rel);
|
|
|
|
/*
|
|
* If the node has a name, split it up and determine creation namespace.
|
|
* If not (a possibility not considered by the grammar, but one which can
|
|
* occur via the "CREATE TABLE ... (LIKE)" command), then we put the
|
|
* object in the same namespace as the relation, and cons up a name for
|
|
* it.
|
|
*/
|
|
if (stmt->defnames)
|
|
namespaceId = QualifiedNameGetCreationNamespace(stmt->defnames,
|
|
&namestr);
|
|
else
|
|
{
|
|
namespaceId = RelationGetNamespace(rel);
|
|
namestr = ChooseExtendedStatisticName(RelationGetRelationName(rel),
|
|
ChooseExtendedStatisticNameAddition(stmt->exprs),
|
|
"stat",
|
|
namespaceId);
|
|
}
|
|
namestrcpy(&stxname, namestr);
|
|
|
|
/*
|
|
* Deal with the possibility that the statistics object already exists.
|
|
*/
|
|
if (SearchSysCacheExists2(STATEXTNAMENSP,
|
|
CStringGetDatum(namestr),
|
|
ObjectIdGetDatum(namespaceId)))
|
|
{
|
|
if (stmt->if_not_exists)
|
|
{
|
|
/*
|
|
* Since stats objects aren't members of extensions (see comments
|
|
* below), no need for checkMembershipInCurrentExtension here.
|
|
*/
|
|
ereport(NOTICE,
|
|
(errcode(ERRCODE_DUPLICATE_OBJECT),
|
|
errmsg("statistics object \"%s\" already exists, skipping",
|
|
namestr)));
|
|
relation_close(rel, NoLock);
|
|
return InvalidObjectAddress;
|
|
}
|
|
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_DUPLICATE_OBJECT),
|
|
errmsg("statistics object \"%s\" already exists", namestr)));
|
|
}
|
|
|
|
/*
|
|
* Make sure no more than STATS_MAX_DIMENSIONS columns are used. There
|
|
* might be duplicates and so on, but we'll deal with those later.
|
|
*/
|
|
numcols = list_length(stmt->exprs);
|
|
if (numcols > STATS_MAX_DIMENSIONS)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_TOO_MANY_COLUMNS),
|
|
errmsg("cannot have more than %d columns in statistics",
|
|
STATS_MAX_DIMENSIONS)));
|
|
|
|
/*
|
|
* Convert the expression list to a simple array of attnums, but also keep
|
|
* a list of more complex expressions. While at it, enforce some
|
|
* constraints - we don't allow extended statistics on system attributes,
|
|
* and we require the data type to have less-than operator.
|
|
*
|
|
* There are many ways how to "mask" a simple attribute refenrece as an
|
|
* expression, for example "(a+0)" etc. We can't possibly detect all of
|
|
* them, but we handle at least the simple case with attribute in parens.
|
|
* There'll always be a way around this, if the user is determined (like
|
|
* the "(a+0)" example), but this makes it somewhat consistent with how
|
|
* indexes treat attributes/expressions.
|
|
*/
|
|
foreach(cell, stmt->exprs)
|
|
{
|
|
StatsElem *selem = lfirst_node(StatsElem, cell);
|
|
|
|
if (selem->name) /* column reference */
|
|
{
|
|
char *attname;
|
|
HeapTuple atttuple;
|
|
Form_pg_attribute attForm;
|
|
TypeCacheEntry *type;
|
|
|
|
attname = selem->name;
|
|
|
|
atttuple = SearchSysCacheAttName(relid, attname);
|
|
if (!HeapTupleIsValid(atttuple))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_COLUMN),
|
|
errmsg("column \"%s\" does not exist",
|
|
attname)));
|
|
attForm = (Form_pg_attribute) GETSTRUCT(atttuple);
|
|
|
|
/* Disallow use of system attributes in extended stats */
|
|
if (attForm->attnum <= 0)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("statistics creation on system columns is not supported")));
|
|
|
|
/* Disallow data types without a less-than operator */
|
|
type = lookup_type_cache(attForm->atttypid, TYPECACHE_LT_OPR);
|
|
if (type->lt_opr == InvalidOid)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("column \"%s\" cannot be used in statistics because its type %s has no default btree operator class",
|
|
attname, format_type_be(attForm->atttypid))));
|
|
|
|
attnums[nattnums] = attForm->attnum;
|
|
nattnums++;
|
|
ReleaseSysCache(atttuple);
|
|
}
|
|
else if (IsA(selem->expr, Var)) /* column reference in parens */
|
|
{
|
|
Var *var = (Var *) selem->expr;
|
|
TypeCacheEntry *type;
|
|
|
|
/* Disallow use of system attributes in extended stats */
|
|
if (var->varattno <= 0)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("statistics creation on system columns is not supported")));
|
|
|
|
/* Disallow data types without a less-than operator */
|
|
type = lookup_type_cache(var->vartype, TYPECACHE_LT_OPR);
|
|
if (type->lt_opr == InvalidOid)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("column \"%s\" cannot be used in statistics because its type %s has no default btree operator class",
|
|
get_attname(relid, var->varattno, false), format_type_be(var->vartype))));
|
|
|
|
attnums[nattnums] = var->varattno;
|
|
nattnums++;
|
|
}
|
|
else /* expression */
|
|
{
|
|
Node *expr = selem->expr;
|
|
Oid atttype;
|
|
TypeCacheEntry *type;
|
|
Bitmapset *attnums = NULL;
|
|
int k;
|
|
|
|
Assert(expr != NULL);
|
|
|
|
/* Disallow expressions referencing system attributes. */
|
|
pull_varattnos(expr, 1, &attnums);
|
|
|
|
k = -1;
|
|
while ((k = bms_next_member(attnums, k)) >= 0)
|
|
{
|
|
AttrNumber attnum = k + FirstLowInvalidHeapAttributeNumber;
|
|
if (attnum <= 0)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("statistics creation on system columns is not supported")));
|
|
}
|
|
|
|
/*
|
|
* Disallow data types without a less-than operator.
|
|
*
|
|
* We ignore this for statistics on a single expression, in which
|
|
* case we'll build the regular statistics only (and that code can
|
|
* deal with such data types).
|
|
*/
|
|
if (list_length(stmt->exprs) > 1)
|
|
{
|
|
atttype = exprType(expr);
|
|
type = lookup_type_cache(atttype, TYPECACHE_LT_OPR);
|
|
if (type->lt_opr == InvalidOid)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("expression cannot be used in multivariate statistics because its type %s has no default btree operator class",
|
|
format_type_be(atttype))));
|
|
}
|
|
|
|
stxexprs = lappend(stxexprs, expr);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Parse the statistics kinds.
|
|
*
|
|
* First check that if this is the case with a single expression, there
|
|
* are no statistics kinds specified (we don't allow that for the simple
|
|
* CREATE STATISTICS form).
|
|
*/
|
|
if ((list_length(stmt->exprs) == 1) && (list_length(stxexprs) == 1))
|
|
{
|
|
/* statistics kinds not specified */
|
|
if (list_length(stmt->stat_types) > 0)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("when building statistics on a single expression, statistics kinds may not be specified")));
|
|
}
|
|
|
|
/* OK, let's check that we recognize the statistics kinds. */
|
|
build_ndistinct = false;
|
|
build_dependencies = false;
|
|
build_mcv = false;
|
|
foreach(cell, stmt->stat_types)
|
|
{
|
|
char *type = strVal((Value *) lfirst(cell));
|
|
|
|
if (strcmp(type, "ndistinct") == 0)
|
|
{
|
|
build_ndistinct = true;
|
|
requested_type = true;
|
|
}
|
|
else if (strcmp(type, "dependencies") == 0)
|
|
{
|
|
build_dependencies = true;
|
|
requested_type = true;
|
|
}
|
|
else if (strcmp(type, "mcv") == 0)
|
|
{
|
|
build_mcv = true;
|
|
requested_type = true;
|
|
}
|
|
else
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
errmsg("unrecognized statistics kind \"%s\"",
|
|
type)));
|
|
}
|
|
|
|
/*
|
|
* If no statistic type was specified, build them all (but only when the
|
|
* statistics is defined on more than one column/expression).
|
|
*/
|
|
if ((!requested_type) && (numcols >= 2))
|
|
{
|
|
build_ndistinct = true;
|
|
build_dependencies = true;
|
|
build_mcv = true;
|
|
}
|
|
|
|
/*
|
|
* When there are non-trivial expressions, build the expression stats
|
|
* automatically. This allows calculating good estimates for stats that
|
|
* consider per-clause estimates (e.g. functional dependencies).
|
|
*/
|
|
build_expressions = (list_length(stxexprs) > 0);
|
|
|
|
/*
|
|
* Check that at least two columns were specified in the statement, or
|
|
* that we're building statistics on a single expression.
|
|
*/
|
|
if ((numcols < 2) && (list_length(stxexprs) != 1))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
|
errmsg("extended statistics require at least 2 columns")));
|
|
|
|
/*
|
|
* Sort the attnums, which makes detecting duplicates somewhat easier, and
|
|
* it does not hurt (it does not matter for the contents, unlike for
|
|
* indexes, for example).
|
|
*/
|
|
qsort(attnums, nattnums, sizeof(int16), compare_int16);
|
|
|
|
/*
|
|
* Check for duplicates in the list of columns. The attnums are sorted so
|
|
* just check consecutive elements.
|
|
*/
|
|
for (i = 1; i < nattnums; i++)
|
|
{
|
|
if (attnums[i] == attnums[i - 1])
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_DUPLICATE_COLUMN),
|
|
errmsg("duplicate column name in statistics definition")));
|
|
}
|
|
|
|
/*
|
|
* Check for duplicate expressions. We do two loops, counting the
|
|
* occurrences of each expression. This is O(N^2) but we only allow small
|
|
* number of expressions and it's not executed often.
|
|
*
|
|
* XXX We don't cross-check attributes and expressions, because it does
|
|
* not seem worth it. In principle we could check that expressions don't
|
|
* contain trivial attribute references like "(a)", but the reasoning is
|
|
* similar to why we don't bother with extracting columns from
|
|
* expressions. It's either expensive or very easy to defeat for
|
|
* determined user, and there's no risk if we allow such statistics (the
|
|
* statistics is useless, but harmless).
|
|
*/
|
|
foreach(cell, stxexprs)
|
|
{
|
|
Node *expr1 = (Node *) lfirst(cell);
|
|
int cnt = 0;
|
|
|
|
foreach(cell2, stxexprs)
|
|
{
|
|
Node *expr2 = (Node *) lfirst(cell2);
|
|
|
|
if (equal(expr1, expr2))
|
|
cnt += 1;
|
|
}
|
|
|
|
/* every expression should find at least itself */
|
|
Assert(cnt >= 1);
|
|
|
|
if (cnt > 1)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_DUPLICATE_COLUMN),
|
|
errmsg("duplicate expression in statistics definition")));
|
|
}
|
|
|
|
/* Form an int2vector representation of the sorted column list */
|
|
stxkeys = buildint2vector(attnums, nattnums);
|
|
|
|
/* construct the char array of enabled statistic types */
|
|
ntypes = 0;
|
|
if (build_ndistinct)
|
|
types[ntypes++] = CharGetDatum(STATS_EXT_NDISTINCT);
|
|
if (build_dependencies)
|
|
types[ntypes++] = CharGetDatum(STATS_EXT_DEPENDENCIES);
|
|
if (build_mcv)
|
|
types[ntypes++] = CharGetDatum(STATS_EXT_MCV);
|
|
if (build_expressions)
|
|
types[ntypes++] = CharGetDatum(STATS_EXT_EXPRESSIONS);
|
|
Assert(ntypes > 0 && ntypes <= lengthof(types));
|
|
stxkind = construct_array(types, ntypes, CHAROID, 1, true, TYPALIGN_CHAR);
|
|
|
|
/* convert the expressions (if any) to a text datum */
|
|
if (stxexprs != NIL)
|
|
{
|
|
char *exprsString;
|
|
|
|
exprsString = nodeToString(stxexprs);
|
|
exprsDatum = CStringGetTextDatum(exprsString);
|
|
pfree(exprsString);
|
|
}
|
|
else
|
|
exprsDatum = (Datum) 0;
|
|
|
|
statrel = table_open(StatisticExtRelationId, RowExclusiveLock);
|
|
|
|
/*
|
|
* Everything seems fine, so let's build the pg_statistic_ext tuple.
|
|
*/
|
|
memset(values, 0, sizeof(values));
|
|
memset(nulls, false, sizeof(nulls));
|
|
|
|
statoid = GetNewOidWithIndex(statrel, StatisticExtOidIndexId,
|
|
Anum_pg_statistic_ext_oid);
|
|
values[Anum_pg_statistic_ext_oid - 1] = ObjectIdGetDatum(statoid);
|
|
values[Anum_pg_statistic_ext_stxrelid - 1] = ObjectIdGetDatum(relid);
|
|
values[Anum_pg_statistic_ext_stxname - 1] = NameGetDatum(&stxname);
|
|
values[Anum_pg_statistic_ext_stxnamespace - 1] = ObjectIdGetDatum(namespaceId);
|
|
values[Anum_pg_statistic_ext_stxstattarget - 1] = Int32GetDatum(-1);
|
|
values[Anum_pg_statistic_ext_stxowner - 1] = ObjectIdGetDatum(stxowner);
|
|
values[Anum_pg_statistic_ext_stxkeys - 1] = PointerGetDatum(stxkeys);
|
|
values[Anum_pg_statistic_ext_stxkind - 1] = PointerGetDatum(stxkind);
|
|
|
|
values[Anum_pg_statistic_ext_stxexprs - 1] = exprsDatum;
|
|
if (exprsDatum == (Datum) 0)
|
|
nulls[Anum_pg_statistic_ext_stxexprs - 1] = true;
|
|
|
|
/* insert it into pg_statistic_ext */
|
|
htup = heap_form_tuple(statrel->rd_att, values, nulls);
|
|
CatalogTupleInsert(statrel, htup);
|
|
heap_freetuple(htup);
|
|
|
|
relation_close(statrel, RowExclusiveLock);
|
|
|
|
/*
|
|
* Also build the pg_statistic_ext_data tuple, to hold the actual
|
|
* statistics data.
|
|
*/
|
|
datarel = table_open(StatisticExtDataRelationId, RowExclusiveLock);
|
|
|
|
memset(datavalues, 0, sizeof(datavalues));
|
|
memset(datanulls, false, sizeof(datanulls));
|
|
|
|
datavalues[Anum_pg_statistic_ext_data_stxoid - 1] = ObjectIdGetDatum(statoid);
|
|
|
|
/* no statistics built yet */
|
|
datanulls[Anum_pg_statistic_ext_data_stxdndistinct - 1] = true;
|
|
datanulls[Anum_pg_statistic_ext_data_stxddependencies - 1] = true;
|
|
datanulls[Anum_pg_statistic_ext_data_stxdmcv - 1] = true;
|
|
datanulls[Anum_pg_statistic_ext_data_stxdexpr - 1] = true;
|
|
|
|
/* insert it into pg_statistic_ext_data */
|
|
htup = heap_form_tuple(datarel->rd_att, datavalues, datanulls);
|
|
CatalogTupleInsert(datarel, htup);
|
|
heap_freetuple(htup);
|
|
|
|
relation_close(datarel, RowExclusiveLock);
|
|
|
|
InvokeObjectPostCreateHook(StatisticExtRelationId, statoid, 0);
|
|
|
|
/*
|
|
* Invalidate relcache so that others see the new statistics object.
|
|
*/
|
|
CacheInvalidateRelcache(rel);
|
|
|
|
relation_close(rel, NoLock);
|
|
|
|
/*
|
|
* Add an AUTO dependency on each column used in the stats, so that the
|
|
* stats object goes away if any or all of them get dropped.
|
|
*/
|
|
ObjectAddressSet(myself, StatisticExtRelationId, statoid);
|
|
|
|
/* add dependencies for plain column references */
|
|
for (i = 0; i < nattnums; i++)
|
|
{
|
|
ObjectAddressSubSet(parentobject, RelationRelationId, relid, attnums[i]);
|
|
recordDependencyOn(&myself, &parentobject, DEPENDENCY_AUTO);
|
|
}
|
|
|
|
/*
|
|
* If there are no dependencies on a column, give the statistics object an
|
|
* auto dependency on the whole table. In most cases, this will be
|
|
* redundant, but it might not be if the statistics expressions contain no
|
|
* Vars (which might seem strange but possible). This is consistent with
|
|
* what we do for indexes in index_create.
|
|
*
|
|
* XXX We intentionally don't consider the expressions before adding this
|
|
* dependency, because recordDependencyOnSingleRelExpr may not create any
|
|
* dependencies for whole-row Vars.
|
|
*/
|
|
if (!nattnums)
|
|
{
|
|
ObjectAddressSet(parentobject, RelationRelationId, relid);
|
|
recordDependencyOn(&myself, &parentobject, DEPENDENCY_AUTO);
|
|
}
|
|
|
|
/*
|
|
* Store dependencies on anything mentioned in statistics expressions,
|
|
* just like we do for index expressions.
|
|
*/
|
|
if (stxexprs)
|
|
recordDependencyOnSingleRelExpr(&myself,
|
|
(Node *) stxexprs,
|
|
relid,
|
|
DEPENDENCY_NORMAL,
|
|
DEPENDENCY_AUTO, false);
|
|
|
|
/*
|
|
* Also add dependencies on namespace and owner. These are required
|
|
* because the stats object might have a different namespace and/or owner
|
|
* than the underlying table(s).
|
|
*/
|
|
ObjectAddressSet(parentobject, NamespaceRelationId, namespaceId);
|
|
recordDependencyOn(&myself, &parentobject, DEPENDENCY_NORMAL);
|
|
|
|
recordDependencyOnOwner(StatisticExtRelationId, statoid, stxowner);
|
|
|
|
/*
|
|
* XXX probably there should be a recordDependencyOnCurrentExtension call
|
|
* here too, but we'd have to add support for ALTER EXTENSION ADD/DROP
|
|
* STATISTICS, which is more work than it seems worth.
|
|
*/
|
|
|
|
/* Add any requested comment */
|
|
if (stmt->stxcomment != NULL)
|
|
CreateComments(statoid, StatisticExtRelationId, 0,
|
|
stmt->stxcomment);
|
|
|
|
/* Return stats object's address */
|
|
return myself;
|
|
}
|
|
|
|
/*
|
|
* ALTER STATISTICS
|
|
*/
|
|
ObjectAddress
|
|
AlterStatistics(AlterStatsStmt *stmt)
|
|
{
|
|
Relation rel;
|
|
Oid stxoid;
|
|
HeapTuple oldtup;
|
|
HeapTuple newtup;
|
|
Datum repl_val[Natts_pg_statistic_ext];
|
|
bool repl_null[Natts_pg_statistic_ext];
|
|
bool repl_repl[Natts_pg_statistic_ext];
|
|
ObjectAddress address;
|
|
int newtarget = stmt->stxstattarget;
|
|
|
|
/* Limit statistics target to a sane range */
|
|
if (newtarget < -1)
|
|
{
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("statistics target %d is too low",
|
|
newtarget)));
|
|
}
|
|
else if (newtarget > 10000)
|
|
{
|
|
newtarget = 10000;
|
|
ereport(WARNING,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("lowering statistics target to %d",
|
|
newtarget)));
|
|
}
|
|
|
|
/* lookup OID of the statistics object */
|
|
stxoid = get_statistics_object_oid(stmt->defnames, stmt->missing_ok);
|
|
|
|
/*
|
|
* If we got here and the OID is not valid, it means the statistics object
|
|
* does not exist, but the command specified IF EXISTS. So report this as
|
|
* a simple NOTICE and we're done.
|
|
*/
|
|
if (!OidIsValid(stxoid))
|
|
{
|
|
char *schemaname;
|
|
char *statname;
|
|
|
|
Assert(stmt->missing_ok);
|
|
|
|
DeconstructQualifiedName(stmt->defnames, &schemaname, &statname);
|
|
|
|
if (schemaname)
|
|
ereport(NOTICE,
|
|
(errmsg("statistics object \"%s.%s\" does not exist, skipping",
|
|
schemaname, statname)));
|
|
else
|
|
ereport(NOTICE,
|
|
(errmsg("statistics object \"%s\" does not exist, skipping",
|
|
statname)));
|
|
|
|
return InvalidObjectAddress;
|
|
}
|
|
|
|
/* Search pg_statistic_ext */
|
|
rel = table_open(StatisticExtRelationId, RowExclusiveLock);
|
|
|
|
oldtup = SearchSysCache1(STATEXTOID, ObjectIdGetDatum(stxoid));
|
|
if (!HeapTupleIsValid(oldtup))
|
|
elog(ERROR, "cache lookup failed for extended statistics object %u", stxoid);
|
|
|
|
/* Must be owner of the existing statistics object */
|
|
if (!pg_statistics_object_ownercheck(stxoid, GetUserId()))
|
|
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_STATISTIC_EXT,
|
|
NameListToString(stmt->defnames));
|
|
|
|
/* Build new tuple. */
|
|
memset(repl_val, 0, sizeof(repl_val));
|
|
memset(repl_null, false, sizeof(repl_null));
|
|
memset(repl_repl, false, sizeof(repl_repl));
|
|
|
|
/* replace the stxstattarget column */
|
|
repl_repl[Anum_pg_statistic_ext_stxstattarget - 1] = true;
|
|
repl_val[Anum_pg_statistic_ext_stxstattarget - 1] = Int32GetDatum(newtarget);
|
|
|
|
newtup = heap_modify_tuple(oldtup, RelationGetDescr(rel),
|
|
repl_val, repl_null, repl_repl);
|
|
|
|
/* Update system catalog. */
|
|
CatalogTupleUpdate(rel, &newtup->t_self, newtup);
|
|
|
|
InvokeObjectPostAlterHook(StatisticExtRelationId, stxoid, 0);
|
|
|
|
ObjectAddressSet(address, StatisticExtRelationId, stxoid);
|
|
|
|
/*
|
|
* NOTE: because we only support altering the statistics target, not the
|
|
* other fields, there is no need to update dependencies.
|
|
*/
|
|
|
|
heap_freetuple(newtup);
|
|
ReleaseSysCache(oldtup);
|
|
|
|
table_close(rel, RowExclusiveLock);
|
|
|
|
return address;
|
|
}
|
|
|
|
/*
|
|
* Guts of statistics object deletion.
|
|
*/
|
|
void
|
|
RemoveStatisticsById(Oid statsOid)
|
|
{
|
|
Relation relation;
|
|
HeapTuple tup;
|
|
Form_pg_statistic_ext statext;
|
|
Oid relid;
|
|
|
|
/*
|
|
* First delete the pg_statistic_ext_data tuple holding the actual
|
|
* statistical data.
|
|
*/
|
|
relation = table_open(StatisticExtDataRelationId, RowExclusiveLock);
|
|
|
|
tup = SearchSysCache1(STATEXTDATASTXOID, ObjectIdGetDatum(statsOid));
|
|
|
|
if (!HeapTupleIsValid(tup)) /* should not happen */
|
|
elog(ERROR, "cache lookup failed for statistics data %u", statsOid);
|
|
|
|
CatalogTupleDelete(relation, &tup->t_self);
|
|
|
|
ReleaseSysCache(tup);
|
|
|
|
table_close(relation, RowExclusiveLock);
|
|
|
|
/*
|
|
* Delete the pg_statistic_ext tuple. Also send out a cache inval on the
|
|
* associated table, so that dependent plans will be rebuilt.
|
|
*/
|
|
relation = table_open(StatisticExtRelationId, RowExclusiveLock);
|
|
|
|
tup = SearchSysCache1(STATEXTOID, ObjectIdGetDatum(statsOid));
|
|
|
|
if (!HeapTupleIsValid(tup)) /* should not happen */
|
|
elog(ERROR, "cache lookup failed for statistics object %u", statsOid);
|
|
|
|
statext = (Form_pg_statistic_ext) GETSTRUCT(tup);
|
|
relid = statext->stxrelid;
|
|
|
|
CacheInvalidateRelcacheByRelid(relid);
|
|
|
|
CatalogTupleDelete(relation, &tup->t_self);
|
|
|
|
ReleaseSysCache(tup);
|
|
|
|
table_close(relation, RowExclusiveLock);
|
|
}
|
|
|
|
/*
|
|
* Select a nonconflicting name for a new statistics object.
|
|
*
|
|
* name1, name2, and label are used the same way as for makeObjectName(),
|
|
* except that the label can't be NULL; digits will be appended to the label
|
|
* if needed to create a name that is unique within the specified namespace.
|
|
*
|
|
* Returns a palloc'd string.
|
|
*
|
|
* Note: it is theoretically possible to get a collision anyway, if someone
|
|
* else chooses the same name concurrently. This is fairly unlikely to be
|
|
* a problem in practice, especially if one is holding a share update
|
|
* exclusive lock on the relation identified by name1. However, if choosing
|
|
* multiple names within a single command, you'd better create the new object
|
|
* and do CommandCounterIncrement before choosing the next one!
|
|
*/
|
|
static char *
|
|
ChooseExtendedStatisticName(const char *name1, const char *name2,
|
|
const char *label, Oid namespaceid)
|
|
{
|
|
int pass = 0;
|
|
char *stxname = NULL;
|
|
char modlabel[NAMEDATALEN];
|
|
|
|
/* try the unmodified label first */
|
|
strlcpy(modlabel, label, sizeof(modlabel));
|
|
|
|
for (;;)
|
|
{
|
|
Oid existingstats;
|
|
|
|
stxname = makeObjectName(name1, name2, modlabel);
|
|
|
|
existingstats = GetSysCacheOid2(STATEXTNAMENSP, Anum_pg_statistic_ext_oid,
|
|
PointerGetDatum(stxname),
|
|
ObjectIdGetDatum(namespaceid));
|
|
if (!OidIsValid(existingstats))
|
|
break;
|
|
|
|
/* found a conflict, so try a new name component */
|
|
pfree(stxname);
|
|
snprintf(modlabel, sizeof(modlabel), "%s%d", label, ++pass);
|
|
}
|
|
|
|
return stxname;
|
|
}
|
|
|
|
/*
|
|
* Generate "name2" for a new statistics object given the list of column
|
|
* names for it. This will be passed to ChooseExtendedStatisticName along
|
|
* with the parent table name and a suitable label.
|
|
*
|
|
* We know that less than NAMEDATALEN characters will actually be used,
|
|
* so we can truncate the result once we've generated that many.
|
|
*
|
|
* XXX see also ChooseForeignKeyConstraintNameAddition and
|
|
* ChooseIndexNameAddition.
|
|
*/
|
|
static char *
|
|
ChooseExtendedStatisticNameAddition(List *exprs)
|
|
{
|
|
char buf[NAMEDATALEN * 2];
|
|
int buflen = 0;
|
|
ListCell *lc;
|
|
|
|
buf[0] = '\0';
|
|
foreach(lc, exprs)
|
|
{
|
|
StatsElem *selem = (StatsElem *) lfirst(lc);
|
|
const char *name;
|
|
|
|
/* It should be one of these, but just skip if it happens not to be */
|
|
if (!IsA(selem, StatsElem))
|
|
continue;
|
|
|
|
name = selem->name;
|
|
|
|
if (buflen > 0)
|
|
buf[buflen++] = '_'; /* insert _ between names */
|
|
|
|
/*
|
|
* We use fixed 'expr' for expressions, which have empty column names.
|
|
* For indexes this is handled in ChooseIndexColumnNames, but we have
|
|
* no such function for stats and it does not seem worth adding. If a
|
|
* better name is needed, the user can specify it explicitly.
|
|
*/
|
|
if (!name)
|
|
name = "expr";
|
|
|
|
/*
|
|
* At this point we have buflen <= NAMEDATALEN. name should be less
|
|
* than NAMEDATALEN already, but use strlcpy for paranoia.
|
|
*/
|
|
strlcpy(buf + buflen, name, NAMEDATALEN);
|
|
buflen += strlen(buf + buflen);
|
|
if (buflen >= NAMEDATALEN)
|
|
break;
|
|
}
|
|
return pstrdup(buf);
|
|
}
|
|
|
|
/*
|
|
* StatisticsGetRelation: given a statistics object's OID, get the OID of
|
|
* the relation it is defined on. Uses the system cache.
|
|
*/
|
|
Oid
|
|
StatisticsGetRelation(Oid statId, bool missing_ok)
|
|
{
|
|
HeapTuple tuple;
|
|
Form_pg_statistic_ext stx;
|
|
Oid result;
|
|
|
|
tuple = SearchSysCache1(STATEXTOID, ObjectIdGetDatum(statId));
|
|
if (!HeapTupleIsValid(tuple))
|
|
{
|
|
if (missing_ok)
|
|
return InvalidOid;
|
|
elog(ERROR, "cache lookup failed for statistics object %u", statId);
|
|
}
|
|
stx = (Form_pg_statistic_ext) GETSTRUCT(tuple);
|
|
Assert(stx->oid == statId);
|
|
|
|
result = stx->stxrelid;
|
|
ReleaseSysCache(tuple);
|
|
return result;
|
|
}
|