postgresql/src/backend/commands/statscmds.c

902 lines
27 KiB
C

/*-------------------------------------------------------------------------
*
* statscmds.c
* Commands for creating and altering extended statistics objects
*
* Portions Copyright (c) 1996-2024, 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/relation.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/acl.h"
#include "utils/builtins.h"
#include "utils/inval.h"
#include "utils/lsyscache.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 (!object_ownercheck(RelationRelationId, 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, put the object in the same namespace as the relation, and cons
* up a name for it. (This can happen either via "CREATE STATISTICS ..."
* or via "CREATE TABLE ... (LIKE)".)
*/
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 (stmt->stat_types != NIL)
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 = (stxexprs != NIL);
/*
* 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_builtin(types, ntypes, CHAROID);
/* 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] = Int16GetDatum(-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 > MAX_STATISTICS_TARGET)
{
newtarget = MAX_STATISTICS_TARGET;
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 (!object_ownercheck(StatisticExtRelationId, 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] = Int16GetDatum(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;
Relation rel;
HeapTuple tup;
Form_pg_statistic_ext statext;
Oid relid;
/*
* 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;
/*
* Delete the pg_statistic_ext_data tuples holding the actual statistical
* data. There might be data with/without inheritance, so attempt deleting
* both. We lock the user table first, to prevent other processes (e.g.
* DROP STATISTICS) from removing the row concurrently.
*/
rel = table_open(relid, ShareUpdateExclusiveLock);
RemoveStatisticsDataById(statsOid, true);
RemoveStatisticsDataById(statsOid, false);
CacheInvalidateRelcacheByRelid(relid);
CatalogTupleDelete(relation, &tup->t_self);
ReleaseSysCache(tup);
/* Keep lock until the end of the transaction. */
table_close(rel, NoLock);
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;
}