1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-02 09:02:37 +03:00
Files
postgres/src/backend/commands/statscmds.c
Tom Lane cc7e0feba5 In extensions, don't replace objects not belonging to the extension.
Previously, if an extension script did CREATE OR REPLACE and there was
an existing object not belonging to the extension, it would overwrite
the object and adopt it into the extension.  This is problematic, first
because the overwrite is probably unintentional, and second because we
didn't change the object's ownership.  Thus a hostile user could create
an object in advance of an expected CREATE EXTENSION command, and would
then have ownership rights on an extension object, which could be
modified for trojan-horse-type attacks.

Hence, forbid CREATE OR REPLACE of an existing object unless it already
belongs to the extension.  (Note that we've always forbidden replacing
an object that belongs to some other extension; only the behavior for
previously-free-standing objects changes here.)

For the same reason, also fail CREATE IF NOT EXISTS when there is
an existing object that doesn't belong to the extension.

Our thanks to Sven Klemm for reporting this problem.

Security: CVE-2022-2625
2022-08-08 11:12:31 -04:00

899 lines
27 KiB
C

/*-------------------------------------------------------------------------
*
* statscmds.c
* Commands for creating and altering extended statistics objects
*
* Portions Copyright (c) 1996-2022, 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];
int2vector *stxkeys;
List *stxexprs = NIL;
Datum exprsDatum;
Relation statrel;
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("cannot define statistics for relation \"%s\"",
RelationGetRelationName(rel)),
errdetail_relkind_not_supported(rel->rd_rel->relkind)));
/* 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 a less-than operator.
*
* There are many ways to "mask" a simple attribute reference 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 the 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(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);
/*
* We used to create the pg_statistic_ext_data tuple too, but it's not
* clear what value should the stxdinherit flag have (it depends on
* whether the rel is partitioned, contains data, etc.)
*/
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;
}
/*
* Delete entry in pg_statistic_ext_data catalog. We don't know if the row
* exists, so don't error out.
*/
void
RemoveStatisticsDataById(Oid statsOid, bool inh)
{
Relation relation;
HeapTuple tup;
relation = table_open(StatisticExtDataRelationId, RowExclusiveLock);
tup = SearchSysCache2(STATEXTDATASTXOID, ObjectIdGetDatum(statsOid),
BoolGetDatum(inh));
/* We don't know if the data row for inh value exists. */
if (HeapTupleIsValid(tup))
{
CatalogTupleDelete(relation, &tup->t_self);
ReleaseSysCache(tup);
}
table_close(relation, RowExclusiveLock);
}
/*
* 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 tuples holding the actual
* statistical data. There might be data with/without inheritance, so
* attempt deleting both.
*/
RemoveStatisticsDataById(statsOid, true);
RemoveStatisticsDataById(statsOid, false);
/*
* 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;
}