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

Generated by: LCOV version 2.0-1