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

Generated by: LCOV version 2.0-1