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

Generated by: LCOV version 1.16