LCOV - code coverage report
Current view: top level - src/backend/commands - statscmds.c (source / functions) Hit Total Coverage
Test: PostgreSQL 13beta1 Lines: 206 230 89.6 %
Date: 2020-06-01 09:07:10 Functions: 7 7 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*-------------------------------------------------------------------------
       2             :  *
       3             :  * statscmds.c
       4             :  *    Commands for creating and altering extended statistics objects
       5             :  *
       6             :  * Portions Copyright (c) 1996-2020, 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/heapam.h"
      18             : #include "access/relation.h"
      19             : #include "access/relscan.h"
      20             : #include "access/table.h"
      21             : #include "catalog/catalog.h"
      22             : #include "catalog/dependency.h"
      23             : #include "catalog/indexing.h"
      24             : #include "catalog/namespace.h"
      25             : #include "catalog/objectaccess.h"
      26             : #include "catalog/pg_namespace.h"
      27             : #include "catalog/pg_statistic_ext.h"
      28             : #include "catalog/pg_statistic_ext_data.h"
      29             : #include "commands/comment.h"
      30             : #include "commands/defrem.h"
      31             : #include "miscadmin.h"
      32             : #include "statistics/statistics.h"
      33             : #include "utils/builtins.h"
      34             : #include "utils/fmgroids.h"
      35             : #include "utils/inval.h"
      36             : #include "utils/memutils.h"
      37             : #include "utils/rel.h"
      38             : #include "utils/syscache.h"
      39             : #include "utils/typcache.h"
      40             : 
      41             : 
      42             : static char *ChooseExtendedStatisticName(const char *name1, const char *name2,
      43             :                                          const char *label, Oid namespaceid);
      44             : static char *ChooseExtendedStatisticNameAddition(List *exprs);
      45             : 
      46             : 
      47             : /* qsort comparator for the attnums in CreateStatistics */
      48             : static int
      49         212 : compare_int16(const void *a, const void *b)
      50             : {
      51         212 :     int         av = *(const int16 *) a;
      52         212 :     int         bv = *(const int16 *) b;
      53             : 
      54             :     /* this can't overflow if int is wider than int16 */
      55         212 :     return (av - bv);
      56             : }
      57             : 
      58             : /*
      59             :  *      CREATE STATISTICS
      60             :  */
      61             : ObjectAddress
      62         196 : CreateStatistics(CreateStatsStmt *stmt)
      63             : {
      64             :     int16       attnums[STATS_MAX_DIMENSIONS];
      65         196 :     int         numcols = 0;
      66             :     char       *namestr;
      67             :     NameData    stxname;
      68             :     Oid         statoid;
      69             :     Oid         namespaceId;
      70         196 :     Oid         stxowner = GetUserId();
      71             :     HeapTuple   htup;
      72             :     Datum       values[Natts_pg_statistic_ext];
      73             :     bool        nulls[Natts_pg_statistic_ext];
      74             :     Datum       datavalues[Natts_pg_statistic_ext_data];
      75             :     bool        datanulls[Natts_pg_statistic_ext_data];
      76             :     int2vector *stxkeys;
      77             :     Relation    statrel;
      78             :     Relation    datarel;
      79         196 :     Relation    rel = NULL;
      80             :     Oid         relid;
      81             :     ObjectAddress parentobject,
      82             :                 myself;
      83             :     Datum       types[3];       /* one for each possible type of statistic */
      84             :     int         ntypes;
      85             :     ArrayType  *stxkind;
      86             :     bool        build_ndistinct;
      87             :     bool        build_dependencies;
      88             :     bool        build_mcv;
      89         196 :     bool        requested_type = false;
      90             :     int         i;
      91             :     ListCell   *cell;
      92             : 
      93             :     Assert(IsA(stmt, CreateStatsStmt));
      94             : 
      95             :     /*
      96             :      * Examine the FROM clause.  Currently, we only allow it to be a single
      97             :      * simple table, but later we'll probably allow multiple tables and JOIN
      98             :      * syntax.  The grammar is already prepared for that, so we have to check
      99             :      * here that what we got is what we can support.
     100             :      */
     101         196 :     if (list_length(stmt->relations) != 1)
     102           0 :         ereport(ERROR,
     103             :                 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     104             :                  errmsg("only a single relation is allowed in CREATE STATISTICS")));
     105             : 
     106         368 :     foreach(cell, stmt->relations)
     107             :     {
     108         196 :         Node       *rln = (Node *) lfirst(cell);
     109             : 
     110         196 :         if (!IsA(rln, RangeVar))
     111           0 :             ereport(ERROR,
     112             :                     (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     113             :                      errmsg("only a single relation is allowed in CREATE STATISTICS")));
     114             : 
     115             :         /*
     116             :          * CREATE STATISTICS will influence future execution plans but does
     117             :          * not interfere with currently executing plans.  So it should be
     118             :          * enough to take only ShareUpdateExclusiveLock on relation,
     119             :          * conflicting with ANALYZE and other DDL that sets statistical
     120             :          * information, but not with normal queries.
     121             :          */
     122         196 :         rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
     123             : 
     124             :         /* Restrict to allowed relation types */
     125         192 :         if (rel->rd_rel->relkind != RELKIND_RELATION &&
     126          32 :             rel->rd_rel->relkind != RELKIND_MATVIEW &&
     127          28 :             rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
     128          24 :             rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
     129          20 :             ereport(ERROR,
     130             :                     (errcode(ERRCODE_WRONG_OBJECT_TYPE),
     131             :                      errmsg("relation \"%s\" is not a table, foreign table, or materialized view",
     132             :                             RelationGetRelationName(rel))));
     133             : 
     134             :         /* You must own the relation to create stats on it */
     135         172 :         if (!pg_class_ownercheck(RelationGetRelid(rel), stxowner))
     136           0 :             aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(rel->rd_rel->relkind),
     137           0 :                            RelationGetRelationName(rel));
     138             :     }
     139             : 
     140             :     Assert(rel);
     141         172 :     relid = RelationGetRelid(rel);
     142             : 
     143             :     /*
     144             :      * If the node has a name, split it up and determine creation namespace.
     145             :      * If not (a possibility not considered by the grammar, but one which can
     146             :      * occur via the "CREATE TABLE ... (LIKE)" command), then we put the
     147             :      * object in the same namespace as the relation, and cons up a name for
     148             :      * it.
     149             :      */
     150         172 :     if (stmt->defnames)
     151         168 :         namespaceId = QualifiedNameGetCreationNamespace(stmt->defnames,
     152             :                                                         &namestr);
     153             :     else
     154             :     {
     155           4 :         namespaceId = RelationGetNamespace(rel);
     156           4 :         namestr = ChooseExtendedStatisticName(RelationGetRelationName(rel),
     157           4 :                                               ChooseExtendedStatisticNameAddition(stmt->exprs),
     158             :                                               "stat",
     159             :                                               namespaceId);
     160             :     }
     161         172 :     namestrcpy(&stxname, namestr);
     162             : 
     163             :     /*
     164             :      * Deal with the possibility that the statistics object already exists.
     165             :      */
     166         172 :     if (SearchSysCacheExists2(STATEXTNAMENSP,
     167             :                               CStringGetDatum(namestr),
     168             :                               ObjectIdGetDatum(namespaceId)))
     169             :     {
     170           4 :         if (stmt->if_not_exists)
     171             :         {
     172           4 :             ereport(NOTICE,
     173             :                     (errcode(ERRCODE_DUPLICATE_OBJECT),
     174             :                      errmsg("statistics object \"%s\" already exists, skipping",
     175             :                             namestr)));
     176           4 :             relation_close(rel, NoLock);
     177           4 :             return InvalidObjectAddress;
     178             :         }
     179             : 
     180           0 :         ereport(ERROR,
     181             :                 (errcode(ERRCODE_DUPLICATE_OBJECT),
     182             :                  errmsg("statistics object \"%s\" already exists", namestr)));
     183             :     }
     184             : 
     185             :     /*
     186             :      * Currently, we only allow simple column references in the expression
     187             :      * list.  That will change someday, and again the grammar already supports
     188             :      * it so we have to enforce restrictions here.  For now, we can convert
     189             :      * the expression list to a simple array of attnums.  While at it, enforce
     190             :      * some constraints.
     191             :      */
     192         536 :     foreach(cell, stmt->exprs)
     193             :     {
     194         380 :         Node       *expr = (Node *) lfirst(cell);
     195             :         ColumnRef  *cref;
     196             :         char       *attname;
     197             :         HeapTuple   atttuple;
     198             :         Form_pg_attribute attForm;
     199             :         TypeCacheEntry *type;
     200             : 
     201         380 :         if (!IsA(expr, ColumnRef))
     202           8 :             ereport(ERROR,
     203             :                     (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     204             :                      errmsg("only simple column references are allowed in CREATE STATISTICS")));
     205         372 :         cref = (ColumnRef *) expr;
     206             : 
     207         372 :         if (list_length(cref->fields) != 1)
     208           0 :             ereport(ERROR,
     209             :                     (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     210             :                      errmsg("only simple column references are allowed in CREATE STATISTICS")));
     211         372 :         attname = strVal((Value *) linitial(cref->fields));
     212             : 
     213         372 :         atttuple = SearchSysCacheAttName(relid, attname);
     214         372 :         if (!HeapTupleIsValid(atttuple))
     215           4 :             ereport(ERROR,
     216             :                     (errcode(ERRCODE_UNDEFINED_COLUMN),
     217             :                      errmsg("column \"%s\" does not exist",
     218             :                             attname)));
     219         368 :         attForm = (Form_pg_attribute) GETSTRUCT(atttuple);
     220             : 
     221             :         /* Disallow use of system attributes in extended stats */
     222         368 :         if (attForm->attnum <= 0)
     223           0 :             ereport(ERROR,
     224             :                     (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     225             :                      errmsg("statistics creation on system columns is not supported")));
     226             : 
     227             :         /* Disallow data types without a less-than operator */
     228         368 :         type = lookup_type_cache(attForm->atttypid, TYPECACHE_LT_OPR);
     229         368 :         if (type->lt_opr == InvalidOid)
     230           0 :             ereport(ERROR,
     231             :                     (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     232             :                      errmsg("column \"%s\" cannot be used in statistics because its type %s has no default btree operator class",
     233             :                             attname, format_type_be(attForm->atttypid))));
     234             : 
     235             :         /* Make sure no more than STATS_MAX_DIMENSIONS columns are used */
     236         368 :         if (numcols >= STATS_MAX_DIMENSIONS)
     237           0 :             ereport(ERROR,
     238             :                     (errcode(ERRCODE_TOO_MANY_COLUMNS),
     239             :                      errmsg("cannot have more than %d columns in statistics",
     240             :                             STATS_MAX_DIMENSIONS)));
     241             : 
     242         368 :         attnums[numcols] = attForm->attnum;
     243         368 :         numcols++;
     244         368 :         ReleaseSysCache(atttuple);
     245             :     }
     246             : 
     247             :     /*
     248             :      * Check that at least two columns were specified in the statement. The
     249             :      * upper bound was already checked in the loop above.
     250             :      */
     251         156 :     if (numcols < 2)
     252           0 :         ereport(ERROR,
     253             :                 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
     254             :                  errmsg("extended statistics require at least 2 columns")));
     255             : 
     256             :     /*
     257             :      * Sort the attnums, which makes detecting duplicates somewhat easier, and
     258             :      * it does not hurt (it does not affect the efficiency, unlike for
     259             :      * indexes, for example).
     260             :      */
     261         156 :     qsort(attnums, numcols, sizeof(int16), compare_int16);
     262             : 
     263             :     /*
     264             :      * Check for duplicates in the list of columns. The attnums are sorted so
     265             :      * just check consecutive elements.
     266             :      */
     267         360 :     for (i = 1; i < numcols; i++)
     268             :     {
     269         208 :         if (attnums[i] == attnums[i - 1])
     270           4 :             ereport(ERROR,
     271             :                     (errcode(ERRCODE_DUPLICATE_COLUMN),
     272             :                      errmsg("duplicate column name in statistics definition")));
     273             :     }
     274             : 
     275             :     /* Form an int2vector representation of the sorted column list */
     276         152 :     stxkeys = buildint2vector(attnums, numcols);
     277             : 
     278             :     /*
     279             :      * Parse the statistics kinds.
     280             :      */
     281         152 :     build_ndistinct = false;
     282         152 :     build_dependencies = false;
     283         152 :     build_mcv = false;
     284         230 :     foreach(cell, stmt->stat_types)
     285             :     {
     286          82 :         char       *type = strVal((Value *) lfirst(cell));
     287             : 
     288          82 :         if (strcmp(type, "ndistinct") == 0)
     289             :         {
     290           6 :             build_ndistinct = true;
     291           6 :             requested_type = true;
     292             :         }
     293          76 :         else if (strcmp(type, "dependencies") == 0)
     294             :         {
     295          22 :             build_dependencies = true;
     296          22 :             requested_type = true;
     297             :         }
     298          54 :         else if (strcmp(type, "mcv") == 0)
     299             :         {
     300          50 :             build_mcv = true;
     301          50 :             requested_type = true;
     302             :         }
     303             :         else
     304           4 :             ereport(ERROR,
     305             :                     (errcode(ERRCODE_SYNTAX_ERROR),
     306             :                      errmsg("unrecognized statistics kind \"%s\"",
     307             :                             type)));
     308             :     }
     309             :     /* If no statistic type was specified, build them all. */
     310         148 :     if (!requested_type)
     311             :     {
     312          78 :         build_ndistinct = true;
     313          78 :         build_dependencies = true;
     314          78 :         build_mcv = true;
     315             :     }
     316             : 
     317             :     /* construct the char array of enabled statistic types */
     318         148 :     ntypes = 0;
     319         148 :     if (build_ndistinct)
     320          84 :         types[ntypes++] = CharGetDatum(STATS_EXT_NDISTINCT);
     321         148 :     if (build_dependencies)
     322         100 :         types[ntypes++] = CharGetDatum(STATS_EXT_DEPENDENCIES);
     323         148 :     if (build_mcv)
     324         128 :         types[ntypes++] = CharGetDatum(STATS_EXT_MCV);
     325             :     Assert(ntypes > 0 && ntypes <= lengthof(types));
     326         148 :     stxkind = construct_array(types, ntypes, CHAROID, 1, true, TYPALIGN_CHAR);
     327             : 
     328         148 :     statrel = table_open(StatisticExtRelationId, RowExclusiveLock);
     329             : 
     330             :     /*
     331             :      * Everything seems fine, so let's build the pg_statistic_ext tuple.
     332             :      */
     333         148 :     memset(values, 0, sizeof(values));
     334         148 :     memset(nulls, false, sizeof(nulls));
     335             : 
     336         148 :     statoid = GetNewOidWithIndex(statrel, StatisticExtOidIndexId,
     337             :                                  Anum_pg_statistic_ext_oid);
     338         148 :     values[Anum_pg_statistic_ext_oid - 1] = ObjectIdGetDatum(statoid);
     339         148 :     values[Anum_pg_statistic_ext_stxrelid - 1] = ObjectIdGetDatum(relid);
     340         148 :     values[Anum_pg_statistic_ext_stxname - 1] = NameGetDatum(&stxname);
     341         148 :     values[Anum_pg_statistic_ext_stxnamespace - 1] = ObjectIdGetDatum(namespaceId);
     342         148 :     values[Anum_pg_statistic_ext_stxstattarget - 1] = Int32GetDatum(-1);
     343         148 :     values[Anum_pg_statistic_ext_stxowner - 1] = ObjectIdGetDatum(stxowner);
     344         148 :     values[Anum_pg_statistic_ext_stxkeys - 1] = PointerGetDatum(stxkeys);
     345         148 :     values[Anum_pg_statistic_ext_stxkind - 1] = PointerGetDatum(stxkind);
     346             : 
     347             :     /* insert it into pg_statistic_ext */
     348         148 :     htup = heap_form_tuple(statrel->rd_att, values, nulls);
     349         148 :     CatalogTupleInsert(statrel, htup);
     350         148 :     heap_freetuple(htup);
     351             : 
     352         148 :     relation_close(statrel, RowExclusiveLock);
     353             : 
     354             :     /*
     355             :      * Also build the pg_statistic_ext_data tuple, to hold the actual
     356             :      * statistics data.
     357             :      */
     358         148 :     datarel = table_open(StatisticExtDataRelationId, RowExclusiveLock);
     359             : 
     360         148 :     memset(datavalues, 0, sizeof(datavalues));
     361         148 :     memset(datanulls, false, sizeof(datanulls));
     362             : 
     363         148 :     datavalues[Anum_pg_statistic_ext_data_stxoid - 1] = ObjectIdGetDatum(statoid);
     364             : 
     365             :     /* no statistics built yet */
     366         148 :     datanulls[Anum_pg_statistic_ext_data_stxdndistinct - 1] = true;
     367         148 :     datanulls[Anum_pg_statistic_ext_data_stxddependencies - 1] = true;
     368         148 :     datanulls[Anum_pg_statistic_ext_data_stxdmcv - 1] = true;
     369             : 
     370             :     /* insert it into pg_statistic_ext_data */
     371         148 :     htup = heap_form_tuple(datarel->rd_att, datavalues, datanulls);
     372         148 :     CatalogTupleInsert(datarel, htup);
     373         148 :     heap_freetuple(htup);
     374             : 
     375         148 :     relation_close(datarel, RowExclusiveLock);
     376             : 
     377         148 :     InvokeObjectPostCreateHook(StatisticExtRelationId, statoid, 0);
     378             : 
     379             :     /*
     380             :      * Invalidate relcache so that others see the new statistics object.
     381             :      */
     382         148 :     CacheInvalidateRelcache(rel);
     383             : 
     384         148 :     relation_close(rel, NoLock);
     385             : 
     386             :     /*
     387             :      * Add an AUTO dependency on each column used in the stats, so that the
     388             :      * stats object goes away if any or all of them get dropped.
     389             :      */
     390         148 :     ObjectAddressSet(myself, StatisticExtRelationId, statoid);
     391             : 
     392         496 :     for (i = 0; i < numcols; i++)
     393             :     {
     394         348 :         ObjectAddressSubSet(parentobject, RelationRelationId, relid, attnums[i]);
     395         348 :         recordDependencyOn(&myself, &parentobject, DEPENDENCY_AUTO);
     396             :     }
     397             : 
     398             :     /*
     399             :      * Also add dependencies on namespace and owner.  These are required
     400             :      * because the stats object might have a different namespace and/or owner
     401             :      * than the underlying table(s).
     402             :      */
     403         148 :     ObjectAddressSet(parentobject, NamespaceRelationId, namespaceId);
     404         148 :     recordDependencyOn(&myself, &parentobject, DEPENDENCY_NORMAL);
     405             : 
     406         148 :     recordDependencyOnOwner(StatisticExtRelationId, statoid, stxowner);
     407             : 
     408             :     /*
     409             :      * XXX probably there should be a recordDependencyOnCurrentExtension call
     410             :      * here too, but we'd have to add support for ALTER EXTENSION ADD/DROP
     411             :      * STATISTICS, which is more work than it seems worth.
     412             :      */
     413             : 
     414             :     /* Add any requested comment */
     415         148 :     if (stmt->stxcomment != NULL)
     416           4 :         CreateComments(statoid, StatisticExtRelationId, 0,
     417           4 :                        stmt->stxcomment);
     418             : 
     419             :     /* Return stats object's address */
     420         148 :     return myself;
     421             : }
     422             : 
     423             : /*
     424             :  *      ALTER STATISTICS
     425             :  */
     426             : ObjectAddress
     427          18 : AlterStatistics(AlterStatsStmt *stmt)
     428             : {
     429             :     Relation    rel;
     430             :     Oid         stxoid;
     431             :     HeapTuple   oldtup;
     432             :     HeapTuple   newtup;
     433             :     Datum       repl_val[Natts_pg_statistic_ext];
     434             :     bool        repl_null[Natts_pg_statistic_ext];
     435             :     bool        repl_repl[Natts_pg_statistic_ext];
     436             :     ObjectAddress address;
     437          18 :     int         newtarget = stmt->stxstattarget;
     438             : 
     439             :     /* Limit statistics target to a sane range */
     440          18 :     if (newtarget < -1)
     441             :     {
     442           0 :         ereport(ERROR,
     443             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     444             :                  errmsg("statistics target %d is too low",
     445             :                         newtarget)));
     446             :     }
     447          18 :     else if (newtarget > 10000)
     448             :     {
     449           0 :         newtarget = 10000;
     450           0 :         ereport(WARNING,
     451             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     452             :                  errmsg("lowering statistics target to %d",
     453             :                         newtarget)));
     454             :     }
     455             : 
     456             :     /* lookup OID of the statistics object */
     457          18 :     stxoid = get_statistics_object_oid(stmt->defnames, stmt->missing_ok);
     458             : 
     459             :     /*
     460             :      * If we got here and the OID is not valid, it means the statistics does
     461             :      * not exist, but the command specified IF EXISTS. So report this as a
     462             :      * simple NOTICE and we're done.
     463             :      */
     464          14 :     if (!OidIsValid(stxoid))
     465             :     {
     466             :         char       *schemaname;
     467             :         char       *statname;
     468             : 
     469             :         Assert(stmt->missing_ok);
     470             : 
     471           4 :         DeconstructQualifiedName(stmt->defnames, &schemaname, &statname);
     472             : 
     473           4 :         if (schemaname)
     474           0 :             ereport(NOTICE,
     475             :                     (errmsg("statistics object \"%s.%s\" does not exist, skipping",
     476             :                             schemaname, statname)));
     477             :         else
     478           4 :             ereport(NOTICE,
     479             :                     (errmsg("statistics object \"%s\" does not exist, skipping",
     480             :                             statname)));
     481             : 
     482           4 :         return InvalidObjectAddress;
     483             :     }
     484             : 
     485             :     /* Search pg_statistic_ext */
     486          10 :     rel = table_open(StatisticExtRelationId, RowExclusiveLock);
     487             : 
     488          10 :     oldtup = SearchSysCache1(STATEXTOID, ObjectIdGetDatum(stxoid));
     489             : 
     490             :     /* Must be owner of the existing statistics object */
     491          10 :     if (!pg_statistics_object_ownercheck(stxoid, GetUserId()))
     492           0 :         aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_STATISTIC_EXT,
     493           0 :                        NameListToString(stmt->defnames));
     494             : 
     495             :     /* Build new tuple. */
     496          10 :     memset(repl_val, 0, sizeof(repl_val));
     497          10 :     memset(repl_null, false, sizeof(repl_null));
     498          10 :     memset(repl_repl, false, sizeof(repl_repl));
     499             : 
     500             :     /* replace the stxstattarget column */
     501          10 :     repl_repl[Anum_pg_statistic_ext_stxstattarget - 1] = true;
     502          10 :     repl_val[Anum_pg_statistic_ext_stxstattarget - 1] = Int32GetDatum(newtarget);
     503             : 
     504          10 :     newtup = heap_modify_tuple(oldtup, RelationGetDescr(rel),
     505             :                                repl_val, repl_null, repl_repl);
     506             : 
     507             :     /* Update system catalog. */
     508          10 :     CatalogTupleUpdate(rel, &newtup->t_self, newtup);
     509             : 
     510          10 :     InvokeObjectPostAlterHook(StatisticExtRelationId, stxoid, 0);
     511             : 
     512          10 :     ObjectAddressSet(address, StatisticExtRelationId, stxoid);
     513             : 
     514             :     /*
     515             :      * NOTE: because we only support altering the statistics target, not the
     516             :      * other fields, there is no need to update dependencies.
     517             :      */
     518             : 
     519          10 :     heap_freetuple(newtup);
     520          10 :     ReleaseSysCache(oldtup);
     521             : 
     522          10 :     table_close(rel, RowExclusiveLock);
     523             : 
     524          10 :     return address;
     525             : }
     526             : 
     527             : /*
     528             :  * Guts of statistics object deletion.
     529             :  */
     530             : void
     531         120 : RemoveStatisticsById(Oid statsOid)
     532             : {
     533             :     Relation    relation;
     534             :     HeapTuple   tup;
     535             :     Form_pg_statistic_ext statext;
     536             :     Oid         relid;
     537             : 
     538             :     /*
     539             :      * First delete the pg_statistic_ext_data tuple holding the actual
     540             :      * statistical data.
     541             :      */
     542         120 :     relation = table_open(StatisticExtDataRelationId, RowExclusiveLock);
     543             : 
     544         120 :     tup = SearchSysCache1(STATEXTDATASTXOID, ObjectIdGetDatum(statsOid));
     545             : 
     546         120 :     if (!HeapTupleIsValid(tup)) /* should not happen */
     547           0 :         elog(ERROR, "cache lookup failed for statistics data %u", statsOid);
     548             : 
     549         120 :     CatalogTupleDelete(relation, &tup->t_self);
     550             : 
     551         120 :     ReleaseSysCache(tup);
     552             : 
     553         120 :     table_close(relation, RowExclusiveLock);
     554             : 
     555             :     /*
     556             :      * Delete the pg_statistic_ext tuple.  Also send out a cache inval on the
     557             :      * associated table, so that dependent plans will be rebuilt.
     558             :      */
     559         120 :     relation = table_open(StatisticExtRelationId, RowExclusiveLock);
     560             : 
     561         120 :     tup = SearchSysCache1(STATEXTOID, ObjectIdGetDatum(statsOid));
     562             : 
     563         120 :     if (!HeapTupleIsValid(tup)) /* should not happen */
     564           0 :         elog(ERROR, "cache lookup failed for statistics object %u", statsOid);
     565             : 
     566         120 :     statext = (Form_pg_statistic_ext) GETSTRUCT(tup);
     567         120 :     relid = statext->stxrelid;
     568             : 
     569         120 :     CacheInvalidateRelcacheByRelid(relid);
     570             : 
     571         120 :     CatalogTupleDelete(relation, &tup->t_self);
     572             : 
     573         120 :     ReleaseSysCache(tup);
     574             : 
     575         120 :     table_close(relation, RowExclusiveLock);
     576         120 : }
     577             : 
     578             : /*
     579             :  * Update a statistics object for ALTER COLUMN TYPE on a source column.
     580             :  *
     581             :  * This could throw an error if the type change can't be supported.
     582             :  * If it can be supported, but the stats must be recomputed, a likely choice
     583             :  * would be to set the relevant column(s) of the pg_statistic_ext_data tuple
     584             :  * to null until the next ANALYZE.  (Note that the type change hasn't actually
     585             :  * happened yet, so one option that's *not* on the table is to recompute
     586             :  * immediately.)
     587             :  *
     588             :  * For both ndistinct and functional-dependencies stats, the on-disk
     589             :  * representation is independent of the source column data types, and it is
     590             :  * plausible to assume that the old statistic values will still be good for
     591             :  * the new column contents.  (Obviously, if the ALTER COLUMN TYPE has a USING
     592             :  * expression that substantially alters the semantic meaning of the column
     593             :  * values, this assumption could fail.  But that seems like a corner case
     594             :  * that doesn't justify zapping the stats in common cases.)
     595             :  *
     596             :  * For MCV lists that's not the case, as those statistics store the datums
     597             :  * internally. In this case we simply reset the statistics value to NULL.
     598             :  *
     599             :  * Note that "type change" includes collation change, which means we can rely
     600             :  * on the MCV list being consistent with the collation info in pg_attribute
     601             :  * during estimation.
     602             :  */
     603             : void
     604           8 : UpdateStatisticsForTypeChange(Oid statsOid, Oid relationOid, int attnum,
     605             :                               Oid oldColumnType, Oid newColumnType)
     606             : {
     607             :     HeapTuple   stup,
     608             :                 oldtup;
     609             : 
     610             :     Relation    rel;
     611             : 
     612             :     Datum       values[Natts_pg_statistic_ext_data];
     613             :     bool        nulls[Natts_pg_statistic_ext_data];
     614             :     bool        replaces[Natts_pg_statistic_ext_data];
     615             : 
     616           8 :     oldtup = SearchSysCache1(STATEXTDATASTXOID, ObjectIdGetDatum(statsOid));
     617           8 :     if (!HeapTupleIsValid(oldtup))
     618           0 :         elog(ERROR, "cache lookup failed for statistics object %u", statsOid);
     619             : 
     620             :     /*
     621             :      * When none of the defined statistics types contain datum values from the
     622             :      * table's columns then there's no need to reset the stats. Functional
     623             :      * dependencies and ndistinct stats should still hold true.
     624             :      */
     625           8 :     if (!statext_is_kind_built(oldtup, STATS_EXT_MCV))
     626             :     {
     627           4 :         ReleaseSysCache(oldtup);
     628           4 :         return;
     629             :     }
     630             : 
     631             :     /*
     632             :      * OK, we need to reset some statistics. So let's build the new tuple,
     633             :      * replacing the affected statistics types with NULL.
     634             :      */
     635           4 :     memset(nulls, 0, Natts_pg_statistic_ext_data * sizeof(bool));
     636           4 :     memset(replaces, 0, Natts_pg_statistic_ext_data * sizeof(bool));
     637           4 :     memset(values, 0, Natts_pg_statistic_ext_data * sizeof(Datum));
     638             : 
     639           4 :     replaces[Anum_pg_statistic_ext_data_stxdmcv - 1] = true;
     640           4 :     nulls[Anum_pg_statistic_ext_data_stxdmcv - 1] = true;
     641             : 
     642           4 :     rel = table_open(StatisticExtDataRelationId, RowExclusiveLock);
     643             : 
     644             :     /* replace the old tuple */
     645           4 :     stup = heap_modify_tuple(oldtup,
     646             :                              RelationGetDescr(rel),
     647             :                              values,
     648             :                              nulls,
     649             :                              replaces);
     650             : 
     651           4 :     ReleaseSysCache(oldtup);
     652           4 :     CatalogTupleUpdate(rel, &stup->t_self, stup);
     653             : 
     654           4 :     heap_freetuple(stup);
     655             : 
     656           4 :     table_close(rel, RowExclusiveLock);
     657             : }
     658             : 
     659             : /*
     660             :  * Select a nonconflicting name for a new statistics.
     661             :  *
     662             :  * name1, name2, and label are used the same way as for makeObjectName(),
     663             :  * except that the label can't be NULL; digits will be appended to the label
     664             :  * if needed to create a name that is unique within the specified namespace.
     665             :  *
     666             :  * Returns a palloc'd string.
     667             :  *
     668             :  * Note: it is theoretically possible to get a collision anyway, if someone
     669             :  * else chooses the same name concurrently.  This is fairly unlikely to be
     670             :  * a problem in practice, especially if one is holding a share update
     671             :  * exclusive lock on the relation identified by name1.  However, if choosing
     672             :  * multiple names within a single command, you'd better create the new object
     673             :  * and do CommandCounterIncrement before choosing the next one!
     674             :  */
     675             : static char *
     676           4 : ChooseExtendedStatisticName(const char *name1, const char *name2,
     677             :                             const char *label, Oid namespaceid)
     678             : {
     679           4 :     int         pass = 0;
     680           4 :     char       *stxname = NULL;
     681             :     char        modlabel[NAMEDATALEN];
     682             : 
     683             :     /* try the unmodified label first */
     684           4 :     StrNCpy(modlabel, label, sizeof(modlabel));
     685             : 
     686             :     for (;;)
     687           0 :     {
     688             :         Oid         existingstats;
     689             : 
     690           4 :         stxname = makeObjectName(name1, name2, modlabel);
     691             : 
     692           4 :         existingstats = GetSysCacheOid2(STATEXTNAMENSP, Anum_pg_statistic_ext_oid,
     693             :                                         PointerGetDatum(stxname),
     694             :                                         ObjectIdGetDatum(namespaceid));
     695           4 :         if (!OidIsValid(existingstats))
     696           4 :             break;
     697             : 
     698             :         /* found a conflict, so try a new name component */
     699           0 :         pfree(stxname);
     700           0 :         snprintf(modlabel, sizeof(modlabel), "%s%d", label, ++pass);
     701             :     }
     702             : 
     703           4 :     return stxname;
     704             : }
     705             : 
     706             : /*
     707             :  * Generate "name2" for a new statistics given the list of column names for it
     708             :  * This will be passed to ChooseExtendedStatisticName along with the parent
     709             :  * table name and a suitable label.
     710             :  *
     711             :  * We know that less than NAMEDATALEN characters will actually be used,
     712             :  * so we can truncate the result once we've generated that many.
     713             :  *
     714             :  * XXX see also ChooseForeignKeyConstraintNameAddition and
     715             :  * ChooseIndexNameAddition.
     716             :  */
     717             : static char *
     718           4 : ChooseExtendedStatisticNameAddition(List *exprs)
     719             : {
     720             :     char        buf[NAMEDATALEN * 2];
     721           4 :     int         buflen = 0;
     722             :     ListCell   *lc;
     723             : 
     724           4 :     buf[0] = '\0';
     725          12 :     foreach(lc, exprs)
     726             :     {
     727           8 :         ColumnRef  *cref = (ColumnRef *) lfirst(lc);
     728             :         const char *name;
     729             : 
     730             :         /* It should be one of these, but just skip if it happens not to be */
     731           8 :         if (!IsA(cref, ColumnRef))
     732           0 :             continue;
     733             : 
     734           8 :         name = strVal((Value *) linitial(cref->fields));
     735             : 
     736           8 :         if (buflen > 0)
     737           4 :             buf[buflen++] = '_';    /* insert _ between names */
     738             : 
     739             :         /*
     740             :          * At this point we have buflen <= NAMEDATALEN.  name should be less
     741             :          * than NAMEDATALEN already, but use strlcpy for paranoia.
     742             :          */
     743           8 :         strlcpy(buf + buflen, name, NAMEDATALEN);
     744           8 :         buflen += strlen(buf + buflen);
     745           8 :         if (buflen >= NAMEDATALEN)
     746           0 :             break;
     747             :     }
     748           4 :     return pstrdup(buf);
     749             : }

Generated by: LCOV version 1.13