LCOV - code coverage report
Current view: top level - src/backend/commands - statscmds.c (source / functions) Hit Total Coverage
Test: PostgreSQL 19devel Lines: 267 289 92.4 %
Date: 2025-11-20 16:17:43 Functions: 8 8 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*-------------------------------------------------------------------------
       2             :  *
       3             :  * statscmds.c
       4             :  *    Commands for creating and altering extended statistics objects
       5             :  *
       6             :  * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
       7             :  * Portions Copyright (c) 1994, Regents of the University of California
       8             :  *
       9             :  *
      10             :  * IDENTIFICATION
      11             :  *    src/backend/commands/statscmds.c
      12             :  *
      13             :  *-------------------------------------------------------------------------
      14             :  */
      15             : #include "postgres.h"
      16             : 
      17             : #include "access/htup_details.h"
      18             : #include "access/relation.h"
      19             : #include "access/table.h"
      20             : #include "catalog/catalog.h"
      21             : #include "catalog/dependency.h"
      22             : #include "catalog/indexing.h"
      23             : #include "catalog/namespace.h"
      24             : #include "catalog/objectaccess.h"
      25             : #include "catalog/pg_namespace.h"
      26             : #include "catalog/pg_statistic_ext.h"
      27             : #include "catalog/pg_statistic_ext_data.h"
      28             : #include "commands/comment.h"
      29             : #include "commands/defrem.h"
      30             : #include "miscadmin.h"
      31             : #include "nodes/nodeFuncs.h"
      32             : #include "optimizer/optimizer.h"
      33             : #include "statistics/statistics.h"
      34             : #include "utils/acl.h"
      35             : #include "utils/builtins.h"
      36             : #include "utils/inval.h"
      37             : #include "utils/lsyscache.h"
      38             : #include "utils/rel.h"
      39             : #include "utils/syscache.h"
      40             : #include "utils/typcache.h"
      41             : 
      42             : 
      43             : static char *ChooseExtendedStatisticName(const char *name1, const char *name2,
      44             :                                          const char *label, Oid namespaceid);
      45             : static char *ChooseExtendedStatisticNameAddition(List *exprs);
      46             : 
      47             : 
      48             : /* qsort comparator for the attnums in CreateStatistics */
      49             : static int
      50         688 : compare_int16(const void *a, const void *b)
      51             : {
      52         688 :     int         av = *(const int16 *) a;
      53         688 :     int         bv = *(const int16 *) b;
      54             : 
      55             :     /* this can't overflow if int is wider than int16 */
      56         688 :     return (av - bv);
      57             : }
      58             : 
      59             : /*
      60             :  *      CREATE STATISTICS
      61             :  */
      62             : ObjectAddress
      63         844 : CreateStatistics(CreateStatsStmt *stmt, bool check_rights)
      64             : {
      65             :     int16       attnums[STATS_MAX_DIMENSIONS];
      66         844 :     int         nattnums = 0;
      67             :     int         numcols;
      68             :     char       *namestr;
      69             :     NameData    stxname;
      70             :     Oid         statoid;
      71             :     Oid         namespaceId;
      72         844 :     Oid         stxowner = GetUserId();
      73             :     HeapTuple   htup;
      74             :     Datum       values[Natts_pg_statistic_ext];
      75             :     bool        nulls[Natts_pg_statistic_ext];
      76             :     int2vector *stxkeys;
      77         844 :     List       *stxexprs = NIL;
      78             :     Datum       exprsDatum;
      79             :     Relation    statrel;
      80         844 :     Relation    rel = NULL;
      81             :     Oid         relid;
      82             :     ObjectAddress parentobject,
      83             :                 myself;
      84             :     Datum       types[4];       /* one for each possible type of statistic */
      85             :     int         ntypes;
      86             :     ArrayType  *stxkind;
      87             :     bool        build_ndistinct;
      88             :     bool        build_dependencies;
      89             :     bool        build_mcv;
      90             :     bool        build_expressions;
      91         844 :     bool        requested_type = false;
      92             :     int         i;
      93             :     ListCell   *cell;
      94             :     ListCell   *cell2;
      95             : 
      96             :     Assert(IsA(stmt, CreateStatsStmt));
      97             : 
      98             :     /*
      99             :      * Examine the FROM clause.  Currently, we only allow it to be a single
     100             :      * simple table, but later we'll probably allow multiple tables and JOIN
     101             :      * syntax.  The grammar is already prepared for that, so we have to check
     102             :      * here that what we got is what we can support.
     103             :      */
     104         844 :     if (list_length(stmt->relations) != 1)
     105           0 :         ereport(ERROR,
     106             :                 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     107             :                  errmsg("only a single relation is allowed in CREATE STATISTICS")));
     108             : 
     109        1658 :     foreach(cell, stmt->relations)
     110             :     {
     111         844 :         Node       *rln = (Node *) lfirst(cell);
     112             : 
     113         844 :         if (!IsA(rln, RangeVar))
     114           0 :             ereport(ERROR,
     115             :                     (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     116             :                      errmsg("only a single relation is allowed in CREATE STATISTICS")));
     117             : 
     118             :         /*
     119             :          * CREATE STATISTICS will influence future execution plans but does
     120             :          * not interfere with currently executing plans.  So it should be
     121             :          * enough to take only ShareUpdateExclusiveLock on relation,
     122             :          * conflicting with ANALYZE and other DDL that sets statistical
     123             :          * information, but not with normal queries.
     124             :          */
     125         844 :         rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
     126             : 
     127             :         /* Restrict to allowed relation types */
     128         844 :         if (rel->rd_rel->relkind != RELKIND_RELATION &&
     129          60 :             rel->rd_rel->relkind != RELKIND_MATVIEW &&
     130          54 :             rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
     131          42 :             rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
     132          30 :             ereport(ERROR,
     133             :                     (errcode(ERRCODE_WRONG_OBJECT_TYPE),
     134             :                      errmsg("cannot define statistics for relation \"%s\"",
     135             :                             RelationGetRelationName(rel)),
     136             :                      errdetail_relkind_not_supported(rel->rd_rel->relkind)));
     137             : 
     138             :         /*
     139             :          * You must own the relation to create stats on it.
     140             :          *
     141             :          * NB: Concurrent changes could cause this function's lookup to find a
     142             :          * different relation than a previous lookup by the caller, so we must
     143             :          * perform this check even when check_rights == false.
     144             :          */
     145         814 :         if (!object_ownercheck(RelationRelationId, RelationGetRelid(rel), stxowner))
     146           0 :             aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(rel->rd_rel->relkind),
     147           0 :                            RelationGetRelationName(rel));
     148             : 
     149             :         /* Creating statistics on system catalogs is not allowed */
     150         814 :         if (!allowSystemTableMods && IsSystemRelation(rel))
     151           0 :             ereport(ERROR,
     152             :                     (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
     153             :                      errmsg("permission denied: \"%s\" is a system catalog",
     154             :                             RelationGetRelationName(rel))));
     155             :     }
     156             : 
     157             :     Assert(rel);
     158         814 :     relid = RelationGetRelid(rel);
     159             : 
     160             :     /*
     161             :      * If the node has a name, split it up and determine creation namespace.
     162             :      * If not, put the object in the same namespace as the relation, and cons
     163             :      * up a name for it.  (This can happen either via "CREATE STATISTICS ..."
     164             :      * or via "CREATE TABLE ... (LIKE)".)
     165             :      */
     166         814 :     if (stmt->defnames)
     167         700 :         namespaceId = QualifiedNameGetCreationNamespace(stmt->defnames,
     168             :                                                         &namestr);
     169             :     else
     170             :     {
     171         114 :         namespaceId = RelationGetNamespace(rel);
     172         114 :         namestr = ChooseExtendedStatisticName(RelationGetRelationName(rel),
     173         114 :                                               ChooseExtendedStatisticNameAddition(stmt->exprs),
     174             :                                               "stat",
     175             :                                               namespaceId);
     176             :     }
     177         814 :     namestrcpy(&stxname, namestr);
     178             : 
     179             :     /*
     180             :      * Check we have creation rights in target namespace.  Skip check if
     181             :      * caller doesn't want it.
     182             :      */
     183         814 :     if (check_rights)
     184             :     {
     185             :         AclResult   aclresult;
     186             : 
     187         740 :         aclresult = object_aclcheck(NamespaceRelationId, namespaceId,
     188             :                                     GetUserId(), ACL_CREATE);
     189         740 :         if (aclresult != ACLCHECK_OK)
     190          24 :             aclcheck_error(aclresult, OBJECT_SCHEMA,
     191          24 :                            get_namespace_name(namespaceId));
     192             :     }
     193             : 
     194             :     /*
     195             :      * Deal with the possibility that the statistics object already exists.
     196             :      */
     197         790 :     if (SearchSysCacheExists2(STATEXTNAMENSP,
     198             :                               CStringGetDatum(namestr),
     199             :                               ObjectIdGetDatum(namespaceId)))
     200             :     {
     201           6 :         if (stmt->if_not_exists)
     202             :         {
     203             :             /*
     204             :              * Since stats objects aren't members of extensions (see comments
     205             :              * below), no need for checkMembershipInCurrentExtension here.
     206             :              */
     207           6 :             ereport(NOTICE,
     208             :                     (errcode(ERRCODE_DUPLICATE_OBJECT),
     209             :                      errmsg("statistics object \"%s\" already exists, skipping",
     210             :                             namestr)));
     211           6 :             relation_close(rel, NoLock);
     212           6 :             return InvalidObjectAddress;
     213             :         }
     214             : 
     215           0 :         ereport(ERROR,
     216             :                 (errcode(ERRCODE_DUPLICATE_OBJECT),
     217             :                  errmsg("statistics object \"%s\" already exists", namestr)));
     218             :     }
     219             : 
     220             :     /*
     221             :      * Make sure no more than STATS_MAX_DIMENSIONS columns are used. There
     222             :      * might be duplicates and so on, but we'll deal with those later.
     223             :      */
     224         784 :     numcols = list_length(stmt->exprs);
     225         784 :     if (numcols > STATS_MAX_DIMENSIONS)
     226          18 :         ereport(ERROR,
     227             :                 (errcode(ERRCODE_TOO_MANY_COLUMNS),
     228             :                  errmsg("cannot have more than %d columns in statistics",
     229             :                         STATS_MAX_DIMENSIONS)));
     230             : 
     231             :     /*
     232             :      * Convert the expression list to a simple array of attnums, but also keep
     233             :      * a list of more complex expressions.  While at it, enforce some
     234             :      * constraints - we don't allow extended statistics on system attributes,
     235             :      * and we require the data type to have a less-than operator.
     236             :      *
     237             :      * There are many ways to "mask" a simple attribute reference as an
     238             :      * expression, for example "(a+0)" etc. We can't possibly detect all of
     239             :      * them, but we handle at least the simple case with the attribute in
     240             :      * parens. There'll always be a way around this, if the user is determined
     241             :      * (like the "(a+0)" example), but this makes it somewhat consistent with
     242             :      * how indexes treat attributes/expressions.
     243             :      */
     244        2414 :     foreach(cell, stmt->exprs)
     245             :     {
     246        1708 :         StatsElem  *selem = lfirst_node(StatsElem, cell);
     247             : 
     248        1708 :         if (selem->name)     /* column reference */
     249             :         {
     250             :             char       *attname;
     251             :             HeapTuple   atttuple;
     252             :             Form_pg_attribute attForm;
     253             :             TypeCacheEntry *type;
     254             : 
     255        1258 :             attname = selem->name;
     256             : 
     257        1258 :             atttuple = SearchSysCacheAttName(relid, attname);
     258        1258 :             if (!HeapTupleIsValid(atttuple))
     259           6 :                 ereport(ERROR,
     260             :                         (errcode(ERRCODE_UNDEFINED_COLUMN),
     261             :                          errmsg("column \"%s\" does not exist",
     262             :                                 attname)));
     263        1252 :             attForm = (Form_pg_attribute) GETSTRUCT(atttuple);
     264             : 
     265             :             /* Disallow use of system attributes in extended stats */
     266        1252 :             if (attForm->attnum <= 0)
     267          12 :                 ereport(ERROR,
     268             :                         (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     269             :                          errmsg("statistics creation on system columns is not supported")));
     270             : 
     271             :             /* Disallow use of virtual generated columns in extended stats */
     272        1240 :             if (attForm->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
     273          12 :                 ereport(ERROR,
     274             :                         (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     275             :                          errmsg("statistics creation on virtual generated columns is not supported")));
     276             : 
     277             :             /* Disallow data types without a less-than operator */
     278        1228 :             type = lookup_type_cache(attForm->atttypid, TYPECACHE_LT_OPR);
     279        1228 :             if (type->lt_opr == InvalidOid)
     280           6 :                 ereport(ERROR,
     281             :                         (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     282             :                          errmsg("column \"%s\" cannot be used in statistics because its type %s has no default btree operator class",
     283             :                                 attname, format_type_be(attForm->atttypid))));
     284             : 
     285        1222 :             attnums[nattnums] = attForm->attnum;
     286        1222 :             nattnums++;
     287        1222 :             ReleaseSysCache(atttuple);
     288             :         }
     289         450 :         else if (IsA(selem->expr, Var)) /* column reference in parens */
     290             :         {
     291          18 :             Var        *var = (Var *) selem->expr;
     292             :             TypeCacheEntry *type;
     293             : 
     294             :             /* Disallow use of system attributes in extended stats */
     295          18 :             if (var->varattno <= 0)
     296           6 :                 ereport(ERROR,
     297             :                         (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     298             :                          errmsg("statistics creation on system columns is not supported")));
     299             : 
     300             :             /* Disallow use of virtual generated columns in extended stats */
     301          12 :             if (get_attgenerated(relid, var->varattno) == ATTRIBUTE_GENERATED_VIRTUAL)
     302           6 :                 ereport(ERROR,
     303             :                         (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     304             :                          errmsg("statistics creation on virtual generated columns is not supported")));
     305             : 
     306             :             /* Disallow data types without a less-than operator */
     307           6 :             type = lookup_type_cache(var->vartype, TYPECACHE_LT_OPR);
     308           6 :             if (type->lt_opr == InvalidOid)
     309           0 :                 ereport(ERROR,
     310             :                         (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     311             :                          errmsg("column \"%s\" cannot be used in statistics because its type %s has no default btree operator class",
     312             :                                 get_attname(relid, var->varattno, false), format_type_be(var->vartype))));
     313             : 
     314           6 :             attnums[nattnums] = var->varattno;
     315           6 :             nattnums++;
     316             :         }
     317             :         else                    /* expression */
     318             :         {
     319         432 :             Node       *expr = selem->expr;
     320             :             Oid         atttype;
     321             :             TypeCacheEntry *type;
     322         432 :             Bitmapset  *attnums = NULL;
     323             :             int         k;
     324             : 
     325             :             Assert(expr != NULL);
     326             : 
     327         432 :             pull_varattnos(expr, 1, &attnums);
     328             : 
     329         432 :             k = -1;
     330         978 :             while ((k = bms_next_member(attnums, k)) >= 0)
     331             :             {
     332         558 :                 AttrNumber  attnum = k + FirstLowInvalidHeapAttributeNumber;
     333             : 
     334             :                 /* Disallow expressions referencing system attributes. */
     335         558 :                 if (attnum <= 0)
     336           6 :                     ereport(ERROR,
     337             :                             (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     338             :                              errmsg("statistics creation on system columns is not supported")));
     339             : 
     340             :                 /* Disallow use of virtual generated columns in extended stats */
     341         552 :                 if (get_attgenerated(relid, attnum) == ATTRIBUTE_GENERATED_VIRTUAL)
     342           6 :                     ereport(ERROR,
     343             :                             (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     344             :                              errmsg("statistics creation on virtual generated columns is not supported")));
     345             :             }
     346             : 
     347             :             /*
     348             :              * Disallow data types without a less-than operator.
     349             :              *
     350             :              * We ignore this for statistics on a single expression, in which
     351             :              * case we'll build the regular statistics only (and that code can
     352             :              * deal with such data types).
     353             :              */
     354         420 :             if (list_length(stmt->exprs) > 1)
     355             :             {
     356         304 :                 atttype = exprType(expr);
     357         304 :                 type = lookup_type_cache(atttype, TYPECACHE_LT_OPR);
     358         304 :                 if (type->lt_opr == InvalidOid)
     359           0 :                     ereport(ERROR,
     360             :                             (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     361             :                              errmsg("expression cannot be used in multivariate statistics because its type %s has no default btree operator class",
     362             :                                     format_type_be(atttype))));
     363             :             }
     364             : 
     365         420 :             stxexprs = lappend(stxexprs, expr);
     366             :         }
     367             :     }
     368             : 
     369             :     /*
     370             :      * Parse the statistics kinds.
     371             :      *
     372             :      * First check that if this is the case with a single expression, there
     373             :      * are no statistics kinds specified (we don't allow that for the simple
     374             :      * CREATE STATISTICS form).
     375             :      */
     376         706 :     if ((list_length(stmt->exprs) == 1) && (list_length(stxexprs) == 1))
     377             :     {
     378             :         /* statistics kinds not specified */
     379         116 :         if (stmt->stat_types != NIL)
     380           0 :             ereport(ERROR,
     381             :                     (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     382             :                      errmsg("when building statistics on a single expression, statistics kinds may not be specified")));
     383             :     }
     384             : 
     385             :     /* OK, let's check that we recognize the statistics kinds. */
     386         706 :     build_ndistinct = false;
     387         706 :     build_dependencies = false;
     388         706 :     build_mcv = false;
     389        1082 :     foreach(cell, stmt->stat_types)
     390             :     {
     391         382 :         char       *type = strVal(lfirst(cell));
     392             : 
     393         382 :         if (strcmp(type, "ndistinct") == 0)
     394             :         {
     395         110 :             build_ndistinct = true;
     396         110 :             requested_type = true;
     397             :         }
     398         272 :         else if (strcmp(type, "dependencies") == 0)
     399             :         {
     400          90 :             build_dependencies = true;
     401          90 :             requested_type = true;
     402             :         }
     403         182 :         else if (strcmp(type, "mcv") == 0)
     404             :         {
     405         176 :             build_mcv = true;
     406         176 :             requested_type = true;
     407             :         }
     408             :         else
     409           6 :             ereport(ERROR,
     410             :                     (errcode(ERRCODE_SYNTAX_ERROR),
     411             :                      errmsg("unrecognized statistics kind \"%s\"",
     412             :                             type)));
     413             :     }
     414             : 
     415             :     /*
     416             :      * If no statistic type was specified, build them all (but only when the
     417             :      * statistics is defined on more than one column/expression).
     418             :      */
     419         700 :     if ((!requested_type) && (numcols >= 2))
     420             :     {
     421         274 :         build_ndistinct = true;
     422         274 :         build_dependencies = true;
     423         274 :         build_mcv = true;
     424             :     }
     425             : 
     426             :     /*
     427             :      * When there are non-trivial expressions, build the expression stats
     428             :      * automatically. This allows calculating good estimates for stats that
     429             :      * consider per-clause estimates (e.g. functional dependencies).
     430             :      */
     431         700 :     build_expressions = (stxexprs != NIL);
     432             : 
     433             :     /*
     434             :      * Check that at least two columns were specified in the statement, or
     435             :      * that we're building statistics on a single expression.
     436             :      */
     437         700 :     if ((numcols < 2) && (list_length(stxexprs) != 1))
     438           6 :         ereport(ERROR,
     439             :                 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
     440             :                  errmsg("extended statistics require at least 2 columns")));
     441             : 
     442             :     /*
     443             :      * Sort the attnums, which makes detecting duplicates somewhat easier, and
     444             :      * it does not hurt (it does not matter for the contents, unlike for
     445             :      * indexes, for example).
     446             :      */
     447         694 :     qsort(attnums, nattnums, sizeof(int16), compare_int16);
     448             : 
     449             :     /*
     450             :      * Check for duplicates in the list of columns. The attnums are sorted so
     451             :      * just check consecutive elements.
     452             :      */
     453        1370 :     for (i = 1; i < nattnums; i++)
     454             :     {
     455         682 :         if (attnums[i] == attnums[i - 1])
     456           6 :             ereport(ERROR,
     457             :                     (errcode(ERRCODE_DUPLICATE_COLUMN),
     458             :                      errmsg("duplicate column name in statistics definition")));
     459             :     }
     460             : 
     461             :     /*
     462             :      * Check for duplicate expressions. We do two loops, counting the
     463             :      * occurrences of each expression. This is O(N^2) but we only allow small
     464             :      * number of expressions and it's not executed often.
     465             :      *
     466             :      * XXX We don't cross-check attributes and expressions, because it does
     467             :      * not seem worth it. In principle we could check that expressions don't
     468             :      * contain trivial attribute references like "(a)", but the reasoning is
     469             :      * similar to why we don't bother with extracting columns from
     470             :      * expressions. It's either expensive or very easy to defeat for
     471             :      * determined user, and there's no risk if we allow such statistics (the
     472             :      * statistics is useless, but harmless).
     473             :      */
     474        1096 :     foreach(cell, stxexprs)
     475             :     {
     476         414 :         Node       *expr1 = (Node *) lfirst(cell);
     477         414 :         int         cnt = 0;
     478             : 
     479        1294 :         foreach(cell2, stxexprs)
     480             :         {
     481         880 :             Node       *expr2 = (Node *) lfirst(cell2);
     482             : 
     483         880 :             if (equal(expr1, expr2))
     484         420 :                 cnt += 1;
     485             :         }
     486             : 
     487             :         /* every expression should find at least itself */
     488             :         Assert(cnt >= 1);
     489             : 
     490         414 :         if (cnt > 1)
     491           6 :             ereport(ERROR,
     492             :                     (errcode(ERRCODE_DUPLICATE_COLUMN),
     493             :                      errmsg("duplicate expression in statistics definition")));
     494             :     }
     495             : 
     496             :     /* Form an int2vector representation of the sorted column list */
     497         682 :     stxkeys = buildint2vector(attnums, nattnums);
     498             : 
     499             :     /* construct the char array of enabled statistic types */
     500         682 :     ntypes = 0;
     501         682 :     if (build_ndistinct)
     502         372 :         types[ntypes++] = CharGetDatum(STATS_EXT_NDISTINCT);
     503         682 :     if (build_dependencies)
     504         352 :         types[ntypes++] = CharGetDatum(STATS_EXT_DEPENDENCIES);
     505         682 :     if (build_mcv)
     506         438 :         types[ntypes++] = CharGetDatum(STATS_EXT_MCV);
     507         682 :     if (build_expressions)
     508         246 :         types[ntypes++] = CharGetDatum(STATS_EXT_EXPRESSIONS);
     509             :     Assert(ntypes > 0 && ntypes <= lengthof(types));
     510         682 :     stxkind = construct_array_builtin(types, ntypes, CHAROID);
     511             : 
     512             :     /* convert the expressions (if any) to a text datum */
     513         682 :     if (stxexprs != NIL)
     514             :     {
     515             :         char       *exprsString;
     516             : 
     517         246 :         exprsString = nodeToString(stxexprs);
     518         246 :         exprsDatum = CStringGetTextDatum(exprsString);
     519         246 :         pfree(exprsString);
     520             :     }
     521             :     else
     522         436 :         exprsDatum = (Datum) 0;
     523             : 
     524         682 :     statrel = table_open(StatisticExtRelationId, RowExclusiveLock);
     525             : 
     526             :     /*
     527             :      * Everything seems fine, so let's build the pg_statistic_ext tuple.
     528             :      */
     529         682 :     memset(values, 0, sizeof(values));
     530         682 :     memset(nulls, false, sizeof(nulls));
     531             : 
     532         682 :     statoid = GetNewOidWithIndex(statrel, StatisticExtOidIndexId,
     533             :                                  Anum_pg_statistic_ext_oid);
     534         682 :     values[Anum_pg_statistic_ext_oid - 1] = ObjectIdGetDatum(statoid);
     535         682 :     values[Anum_pg_statistic_ext_stxrelid - 1] = ObjectIdGetDatum(relid);
     536         682 :     values[Anum_pg_statistic_ext_stxname - 1] = NameGetDatum(&stxname);
     537         682 :     values[Anum_pg_statistic_ext_stxnamespace - 1] = ObjectIdGetDatum(namespaceId);
     538         682 :     values[Anum_pg_statistic_ext_stxowner - 1] = ObjectIdGetDatum(stxowner);
     539         682 :     values[Anum_pg_statistic_ext_stxkeys - 1] = PointerGetDatum(stxkeys);
     540         682 :     nulls[Anum_pg_statistic_ext_stxstattarget - 1] = true;
     541         682 :     values[Anum_pg_statistic_ext_stxkind - 1] = PointerGetDatum(stxkind);
     542             : 
     543         682 :     values[Anum_pg_statistic_ext_stxexprs - 1] = exprsDatum;
     544         682 :     if (exprsDatum == (Datum) 0)
     545         436 :         nulls[Anum_pg_statistic_ext_stxexprs - 1] = true;
     546             : 
     547             :     /* insert it into pg_statistic_ext */
     548         682 :     htup = heap_form_tuple(statrel->rd_att, values, nulls);
     549         682 :     CatalogTupleInsert(statrel, htup);
     550         682 :     heap_freetuple(htup);
     551             : 
     552         682 :     relation_close(statrel, RowExclusiveLock);
     553             : 
     554             :     /*
     555             :      * We used to create the pg_statistic_ext_data tuple too, but it's not
     556             :      * clear what value should the stxdinherit flag have (it depends on
     557             :      * whether the rel is partitioned, contains data, etc.)
     558             :      */
     559             : 
     560         682 :     InvokeObjectPostCreateHook(StatisticExtRelationId, statoid, 0);
     561             : 
     562             :     /*
     563             :      * Invalidate relcache so that others see the new statistics object.
     564             :      */
     565         682 :     CacheInvalidateRelcache(rel);
     566             : 
     567         682 :     relation_close(rel, NoLock);
     568             : 
     569             :     /*
     570             :      * Add an AUTO dependency on each column used in the stats, so that the
     571             :      * stats object goes away if any or all of them get dropped.
     572             :      */
     573         682 :     ObjectAddressSet(myself, StatisticExtRelationId, statoid);
     574             : 
     575             :     /* add dependencies for plain column references */
     576        1868 :     for (i = 0; i < nattnums; i++)
     577             :     {
     578        1186 :         ObjectAddressSubSet(parentobject, RelationRelationId, relid, attnums[i]);
     579        1186 :         recordDependencyOn(&myself, &parentobject, DEPENDENCY_AUTO);
     580             :     }
     581             : 
     582             :     /*
     583             :      * If there are no dependencies on a column, give the statistics object an
     584             :      * auto dependency on the whole table.  In most cases, this will be
     585             :      * redundant, but it might not be if the statistics expressions contain no
     586             :      * Vars (which might seem strange but possible). This is consistent with
     587             :      * what we do for indexes in index_create.
     588             :      *
     589             :      * XXX We intentionally don't consider the expressions before adding this
     590             :      * dependency, because recordDependencyOnSingleRelExpr may not create any
     591             :      * dependencies for whole-row Vars.
     592             :      */
     593         682 :     if (!nattnums)
     594             :     {
     595         172 :         ObjectAddressSet(parentobject, RelationRelationId, relid);
     596         172 :         recordDependencyOn(&myself, &parentobject, DEPENDENCY_AUTO);
     597             :     }
     598             : 
     599             :     /*
     600             :      * Store dependencies on anything mentioned in statistics expressions,
     601             :      * just like we do for index expressions.
     602             :      */
     603         682 :     if (stxexprs)
     604         246 :         recordDependencyOnSingleRelExpr(&myself,
     605             :                                         (Node *) stxexprs,
     606             :                                         relid,
     607             :                                         DEPENDENCY_NORMAL,
     608             :                                         DEPENDENCY_AUTO, false);
     609             : 
     610             :     /*
     611             :      * Also add dependencies on namespace and owner.  These are required
     612             :      * because the stats object might have a different namespace and/or owner
     613             :      * than the underlying table(s).
     614             :      */
     615         682 :     ObjectAddressSet(parentobject, NamespaceRelationId, namespaceId);
     616         682 :     recordDependencyOn(&myself, &parentobject, DEPENDENCY_NORMAL);
     617             : 
     618         682 :     recordDependencyOnOwner(StatisticExtRelationId, statoid, stxowner);
     619             : 
     620             :     /*
     621             :      * XXX probably there should be a recordDependencyOnCurrentExtension call
     622             :      * here too, but we'd have to add support for ALTER EXTENSION ADD/DROP
     623             :      * STATISTICS, which is more work than it seems worth.
     624             :      */
     625             : 
     626             :     /* Add any requested comment */
     627         682 :     if (stmt->stxcomment != NULL)
     628          36 :         CreateComments(statoid, StatisticExtRelationId, 0,
     629          36 :                        stmt->stxcomment);
     630             : 
     631             :     /* Return stats object's address */
     632         682 :     return myself;
     633             : }
     634             : 
     635             : /*
     636             :  *      ALTER STATISTICS
     637             :  */
     638             : ObjectAddress
     639          26 : AlterStatistics(AlterStatsStmt *stmt)
     640             : {
     641             :     Relation    rel;
     642             :     Oid         stxoid;
     643             :     HeapTuple   oldtup;
     644             :     HeapTuple   newtup;
     645             :     Datum       repl_val[Natts_pg_statistic_ext];
     646             :     bool        repl_null[Natts_pg_statistic_ext];
     647             :     bool        repl_repl[Natts_pg_statistic_ext];
     648             :     ObjectAddress address;
     649          26 :     int         newtarget = 0;
     650             :     bool        newtarget_default;
     651             : 
     652             :     /* -1 was used in previous versions for the default setting */
     653          26 :     if (stmt->stxstattarget && intVal(stmt->stxstattarget) != -1)
     654             :     {
     655          20 :         newtarget = intVal(stmt->stxstattarget);
     656          20 :         newtarget_default = false;
     657             :     }
     658             :     else
     659           6 :         newtarget_default = true;
     660             : 
     661          26 :     if (!newtarget_default)
     662             :     {
     663             :         /* Limit statistics target to a sane range */
     664          20 :         if (newtarget < 0)
     665             :         {
     666           0 :             ereport(ERROR,
     667             :                     (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     668             :                      errmsg("statistics target %d is too low",
     669             :                             newtarget)));
     670             :         }
     671          20 :         else if (newtarget > MAX_STATISTICS_TARGET)
     672             :         {
     673           0 :             newtarget = MAX_STATISTICS_TARGET;
     674           0 :             ereport(WARNING,
     675             :                     (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     676             :                      errmsg("lowering statistics target to %d",
     677             :                             newtarget)));
     678             :         }
     679             :     }
     680             : 
     681             :     /* lookup OID of the statistics object */
     682          26 :     stxoid = get_statistics_object_oid(stmt->defnames, stmt->missing_ok);
     683             : 
     684             :     /*
     685             :      * If we got here and the OID is not valid, it means the statistics object
     686             :      * does not exist, but the command specified IF EXISTS. So report this as
     687             :      * a simple NOTICE and we're done.
     688             :      */
     689          20 :     if (!OidIsValid(stxoid))
     690             :     {
     691             :         char       *schemaname;
     692             :         char       *statname;
     693             : 
     694             :         Assert(stmt->missing_ok);
     695             : 
     696           6 :         DeconstructQualifiedName(stmt->defnames, &schemaname, &statname);
     697             : 
     698           6 :         if (schemaname)
     699           0 :             ereport(NOTICE,
     700             :                     (errmsg("statistics object \"%s.%s\" does not exist, skipping",
     701             :                             schemaname, statname)));
     702             :         else
     703           6 :             ereport(NOTICE,
     704             :                     (errmsg("statistics object \"%s\" does not exist, skipping",
     705             :                             statname)));
     706             : 
     707           6 :         return InvalidObjectAddress;
     708             :     }
     709             : 
     710             :     /* Search pg_statistic_ext */
     711          14 :     rel = table_open(StatisticExtRelationId, RowExclusiveLock);
     712             : 
     713          14 :     oldtup = SearchSysCache1(STATEXTOID, ObjectIdGetDatum(stxoid));
     714          14 :     if (!HeapTupleIsValid(oldtup))
     715           0 :         elog(ERROR, "cache lookup failed for extended statistics object %u", stxoid);
     716             : 
     717             :     /* Must be owner of the existing statistics object */
     718          14 :     if (!object_ownercheck(StatisticExtRelationId, stxoid, GetUserId()))
     719           0 :         aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_STATISTIC_EXT,
     720           0 :                        NameListToString(stmt->defnames));
     721             : 
     722             :     /* Build new tuple. */
     723          14 :     memset(repl_val, 0, sizeof(repl_val));
     724          14 :     memset(repl_null, false, sizeof(repl_null));
     725          14 :     memset(repl_repl, false, sizeof(repl_repl));
     726             : 
     727             :     /* replace the stxstattarget column */
     728          14 :     repl_repl[Anum_pg_statistic_ext_stxstattarget - 1] = true;
     729          14 :     if (!newtarget_default)
     730           8 :         repl_val[Anum_pg_statistic_ext_stxstattarget - 1] = Int16GetDatum(newtarget);
     731             :     else
     732           6 :         repl_null[Anum_pg_statistic_ext_stxstattarget - 1] = true;
     733             : 
     734          14 :     newtup = heap_modify_tuple(oldtup, RelationGetDescr(rel),
     735             :                                repl_val, repl_null, repl_repl);
     736             : 
     737             :     /* Update system catalog. */
     738          14 :     CatalogTupleUpdate(rel, &newtup->t_self, newtup);
     739             : 
     740          14 :     InvokeObjectPostAlterHook(StatisticExtRelationId, stxoid, 0);
     741             : 
     742          14 :     ObjectAddressSet(address, StatisticExtRelationId, stxoid);
     743             : 
     744             :     /*
     745             :      * NOTE: because we only support altering the statistics target, not the
     746             :      * other fields, there is no need to update dependencies.
     747             :      */
     748             : 
     749          14 :     heap_freetuple(newtup);
     750          14 :     ReleaseSysCache(oldtup);
     751             : 
     752          14 :     table_close(rel, RowExclusiveLock);
     753             : 
     754          14 :     return address;
     755             : }
     756             : 
     757             : /*
     758             :  * Delete entry in pg_statistic_ext_data catalog. We don't know if the row
     759             :  * exists, so don't error out.
     760             :  */
     761             : void
     762        1670 : RemoveStatisticsDataById(Oid statsOid, bool inh)
     763             : {
     764             :     Relation    relation;
     765             :     HeapTuple   tup;
     766             : 
     767        1670 :     relation = table_open(StatisticExtDataRelationId, RowExclusiveLock);
     768             : 
     769        1670 :     tup = SearchSysCache2(STATEXTDATASTXOID, ObjectIdGetDatum(statsOid),
     770             :                           BoolGetDatum(inh));
     771             : 
     772             :     /* We don't know if the data row for inh value exists. */
     773        1670 :     if (HeapTupleIsValid(tup))
     774             :     {
     775         386 :         CatalogTupleDelete(relation, &tup->t_self);
     776             : 
     777         386 :         ReleaseSysCache(tup);
     778             :     }
     779             : 
     780        1670 :     table_close(relation, RowExclusiveLock);
     781        1670 : }
     782             : 
     783             : /*
     784             :  * Guts of statistics object deletion.
     785             :  */
     786             : void
     787         628 : RemoveStatisticsById(Oid statsOid)
     788             : {
     789             :     Relation    relation;
     790             :     Relation    rel;
     791             :     HeapTuple   tup;
     792             :     Form_pg_statistic_ext statext;
     793             :     Oid         relid;
     794             : 
     795             :     /*
     796             :      * Delete the pg_statistic_ext tuple.  Also send out a cache inval on the
     797             :      * associated table, so that dependent plans will be rebuilt.
     798             :      */
     799         628 :     relation = table_open(StatisticExtRelationId, RowExclusiveLock);
     800             : 
     801         628 :     tup = SearchSysCache1(STATEXTOID, ObjectIdGetDatum(statsOid));
     802             : 
     803         628 :     if (!HeapTupleIsValid(tup)) /* should not happen */
     804           0 :         elog(ERROR, "cache lookup failed for statistics object %u", statsOid);
     805             : 
     806         628 :     statext = (Form_pg_statistic_ext) GETSTRUCT(tup);
     807         628 :     relid = statext->stxrelid;
     808             : 
     809             :     /*
     810             :      * Delete the pg_statistic_ext_data tuples holding the actual statistical
     811             :      * data. There might be data with/without inheritance, so attempt deleting
     812             :      * both. We lock the user table first, to prevent other processes (e.g.
     813             :      * DROP STATISTICS) from removing the row concurrently.
     814             :      */
     815         628 :     rel = table_open(relid, ShareUpdateExclusiveLock);
     816             : 
     817         628 :     RemoveStatisticsDataById(statsOid, true);
     818         628 :     RemoveStatisticsDataById(statsOid, false);
     819             : 
     820         628 :     CacheInvalidateRelcacheByRelid(relid);
     821             : 
     822         628 :     CatalogTupleDelete(relation, &tup->t_self);
     823             : 
     824         628 :     ReleaseSysCache(tup);
     825             : 
     826             :     /* Keep lock until the end of the transaction. */
     827         628 :     table_close(rel, NoLock);
     828             : 
     829         628 :     table_close(relation, RowExclusiveLock);
     830         628 : }
     831             : 
     832             : /*
     833             :  * Select a nonconflicting name for a new statistics object.
     834             :  *
     835             :  * name1, name2, and label are used the same way as for makeObjectName(),
     836             :  * except that the label can't be NULL; digits will be appended to the label
     837             :  * if needed to create a name that is unique within the specified namespace.
     838             :  *
     839             :  * Returns a palloc'd string.
     840             :  *
     841             :  * Note: it is theoretically possible to get a collision anyway, if someone
     842             :  * else chooses the same name concurrently.  This is fairly unlikely to be
     843             :  * a problem in practice, especially if one is holding a share update
     844             :  * exclusive lock on the relation identified by name1.  However, if choosing
     845             :  * multiple names within a single command, you'd better create the new object
     846             :  * and do CommandCounterIncrement before choosing the next one!
     847             :  */
     848             : static char *
     849         114 : ChooseExtendedStatisticName(const char *name1, const char *name2,
     850             :                             const char *label, Oid namespaceid)
     851             : {
     852         114 :     int         pass = 0;
     853         114 :     char       *stxname = NULL;
     854             :     char        modlabel[NAMEDATALEN];
     855             : 
     856             :     /* try the unmodified label first */
     857         114 :     strlcpy(modlabel, label, sizeof(modlabel));
     858             : 
     859             :     for (;;)
     860          36 :     {
     861             :         Oid         existingstats;
     862             : 
     863         150 :         stxname = makeObjectName(name1, name2, modlabel);
     864             : 
     865         150 :         existingstats = GetSysCacheOid2(STATEXTNAMENSP, Anum_pg_statistic_ext_oid,
     866             :                                         PointerGetDatum(stxname),
     867             :                                         ObjectIdGetDatum(namespaceid));
     868         150 :         if (!OidIsValid(existingstats))
     869         114 :             break;
     870             : 
     871             :         /* found a conflict, so try a new name component */
     872          36 :         pfree(stxname);
     873          36 :         snprintf(modlabel, sizeof(modlabel), "%s%d", label, ++pass);
     874             :     }
     875             : 
     876         114 :     return stxname;
     877             : }
     878             : 
     879             : /*
     880             :  * Generate "name2" for a new statistics object given the list of column
     881             :  * names for it.  This will be passed to ChooseExtendedStatisticName along
     882             :  * with the parent table name and a suitable label.
     883             :  *
     884             :  * We know that less than NAMEDATALEN characters will actually be used,
     885             :  * so we can truncate the result once we've generated that many.
     886             :  *
     887             :  * XXX see also ChooseForeignKeyConstraintNameAddition and
     888             :  * ChooseIndexNameAddition.
     889             :  */
     890             : static char *
     891         114 : ChooseExtendedStatisticNameAddition(List *exprs)
     892             : {
     893             :     char        buf[NAMEDATALEN * 2];
     894         114 :     int         buflen = 0;
     895             :     ListCell   *lc;
     896             : 
     897         114 :     buf[0] = '\0';
     898         366 :     foreach(lc, exprs)
     899             :     {
     900         252 :         StatsElem  *selem = (StatsElem *) lfirst(lc);
     901             :         const char *name;
     902             : 
     903             :         /* It should be one of these, but just skip if it happens not to be */
     904         252 :         if (!IsA(selem, StatsElem))
     905           0 :             continue;
     906             : 
     907         252 :         name = selem->name;
     908             : 
     909         252 :         if (buflen > 0)
     910         138 :             buf[buflen++] = '_';    /* insert _ between names */
     911             : 
     912             :         /*
     913             :          * We use fixed 'expr' for expressions, which have empty column names.
     914             :          * For indexes this is handled in ChooseIndexColumnNames, but we have
     915             :          * no such function for stats and it does not seem worth adding. If a
     916             :          * better name is needed, the user can specify it explicitly.
     917             :          */
     918         252 :         if (!name)
     919          60 :             name = "expr";
     920             : 
     921             :         /*
     922             :          * At this point we have buflen <= NAMEDATALEN.  name should be less
     923             :          * than NAMEDATALEN already, but use strlcpy for paranoia.
     924             :          */
     925         252 :         strlcpy(buf + buflen, name, NAMEDATALEN);
     926         252 :         buflen += strlen(buf + buflen);
     927         252 :         if (buflen >= NAMEDATALEN)
     928           0 :             break;
     929             :     }
     930         114 :     return pstrdup(buf);
     931             : }
     932             : 
     933             : /*
     934             :  * StatisticsGetRelation: given a statistics object's OID, get the OID of
     935             :  * the relation it is defined on.  Uses the system cache.
     936             :  */
     937             : Oid
     938          74 : StatisticsGetRelation(Oid statId, bool missing_ok)
     939             : {
     940             :     HeapTuple   tuple;
     941             :     Form_pg_statistic_ext stx;
     942             :     Oid         result;
     943             : 
     944          74 :     tuple = SearchSysCache1(STATEXTOID, ObjectIdGetDatum(statId));
     945          74 :     if (!HeapTupleIsValid(tuple))
     946             :     {
     947           0 :         if (missing_ok)
     948           0 :             return InvalidOid;
     949           0 :         elog(ERROR, "cache lookup failed for statistics object %u", statId);
     950             :     }
     951          74 :     stx = (Form_pg_statistic_ext) GETSTRUCT(tuple);
     952             :     Assert(stx->oid == statId);
     953             : 
     954          74 :     result = stx->stxrelid;
     955          74 :     ReleaseSysCache(tuple);
     956          74 :     return result;
     957             : }

Generated by: LCOV version 1.16