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

Generated by: LCOV version 1.14