mirror of
https://github.com/postgres/postgres.git
synced 2025-05-20 00:03:14 -04:00
Allow defining extended statistics on expressions, not just just on simple column references. With this commit, expressions are supported by all existing extended statistics kinds, improving the same types of estimates. A simple example may look like this: CREATE TABLE t (a int); CREATE STATISTICS s ON mod(a,10), mod(a,20) FROM t; ANALYZE t; The collected statistics are useful e.g. to estimate queries with those expressions in WHERE or GROUP BY clauses: SELECT * FROM t WHERE mod(a,10) = 0 AND mod(a,20) = 0; SELECT 1 FROM t GROUP BY mod(a,10), mod(a,20); This introduces new internal statistics kind 'e' (expressions) which is built automatically when the statistics object definition includes any expressions. This represents single-expression statistics, as if there was an expression index (but without the index maintenance overhead). The statistics is stored in pg_statistics_ext_data as an array of composite types, which is possible thanks to 79f6a942bd. CREATE STATISTICS allows building statistics on a single expression, in which case in which case it's not possible to specify statistics kinds. A new system view pg_stats_ext_exprs can be used to display expression statistics, similarly to pg_stats and pg_stats_ext views. ALTER TABLE ... ALTER COLUMN ... TYPE now treats indexes the same way it treats indexes, i.e. it drops and recreates the statistics. This means all statistics are reset, and we no longer try to preserve at least the functional dependencies. This should not be a major issue in practice, as the functional dependencies actually rely on per-column statistics, which were always reset anyway. Author: Tomas Vondra Reviewed-by: Justin Pryzby, Dean Rasheed, Zhihong Yu Discussion: https://postgr.es/m/ad7891d2-e90c-b446-9fe2-7419143847d7%40enterprisedb.com
866 lines
26 KiB
C
866 lines
26 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/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)
|
|
{
|
|
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.
|
|
*
|
|
* XXX We do only the bare minimum to separate simple attribute and
|
|
* complex expressions - for example "(a)" will be treated as a complex
|
|
* expression. No matter how elaborate the check is, there'll always be a
|
|
* way around it, if the user is determined (consider e.g. "(a+0)"), so
|
|
* it's not worth protecting against it.
|
|
*/
|
|
foreach(cell, stmt->exprs)
|
|
{
|
|
Node *expr = (Node *) lfirst(cell);
|
|
StatsElem *selem;
|
|
HeapTuple atttuple;
|
|
Form_pg_attribute attForm;
|
|
TypeCacheEntry *type;
|
|
|
|
/*
|
|
* We should not get anything else than StatsElem, given the grammar.
|
|
* But let's keep it as a safety.
|
|
*/
|
|
if (!IsA(expr, StatsElem))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("only simple column references and expressions are allowed in CREATE STATISTICS")));
|
|
|
|
selem = (StatsElem *) expr;
|
|
|
|
if (selem->name) /* column reference */
|
|
{
|
|
char *attname;
|
|
|
|
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 /* expression */
|
|
{
|
|
Node *expr = selem->expr;
|
|
Oid atttype;
|
|
|
|
Assert(expr != NULL);
|
|
|
|
/*
|
|
* 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 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, true);
|
|
|
|
/*
|
|
* 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 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));
|
|
|
|
/* 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.
|
|
*
|
|
* 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 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's relation OID, get the OID of
|
|
* the relation it is an statistics 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;
|
|
}
|