LCOV - code coverage report
Current view: top level - src/backend/statistics - stat_utils.c (source / functions) Hit Total Coverage
Test: PostgreSQL 18devel Lines: 89 97 91.8 %
Date: 2025-02-22 07:14:56 Functions: 7 7 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*-------------------------------------------------------------------------
       2             :  * stat_utils.c
       3             :  *
       4             :  *    PostgreSQL statistics manipulation utilities.
       5             :  *
       6             :  * Code supporting the direct manipulation of statistics.
       7             :  *
       8             :  * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
       9             :  * Portions Copyright (c) 1994, Regents of the University of California
      10             :  *
      11             :  * IDENTIFICATION
      12             :  *       src/backend/statistics/stat_utils.c
      13             :  *
      14             :  *-------------------------------------------------------------------------
      15             :  */
      16             : 
      17             : #include "postgres.h"
      18             : 
      19             : #include "access/relation.h"
      20             : #include "catalog/index.h"
      21             : #include "catalog/pg_database.h"
      22             : #include "funcapi.h"
      23             : #include "miscadmin.h"
      24             : #include "statistics/stat_utils.h"
      25             : #include "storage/lmgr.h"
      26             : #include "utils/acl.h"
      27             : #include "utils/array.h"
      28             : #include "utils/builtins.h"
      29             : #include "utils/lsyscache.h"
      30             : #include "utils/rel.h"
      31             : 
      32             : /*
      33             :  * Ensure that a given argument is not null.
      34             :  */
      35             : void
      36        7034 : stats_check_required_arg(FunctionCallInfo fcinfo,
      37             :                          struct StatsArgInfo *arginfo,
      38             :                          int argnum)
      39             : {
      40        7034 :     if (PG_ARGISNULL(argnum))
      41          36 :         ereport(ERROR,
      42             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
      43             :                  errmsg("\"%s\" cannot be NULL",
      44             :                         arginfo[argnum].argname)));
      45        6998 : }
      46             : 
      47             : /*
      48             :  * Check that argument is either NULL or a one dimensional array with no
      49             :  * NULLs.
      50             :  *
      51             :  * If a problem is found, emit at elevel, and return false. Otherwise return
      52             :  * true.
      53             :  */
      54             : bool
      55        4506 : stats_check_arg_array(FunctionCallInfo fcinfo,
      56             :                       struct StatsArgInfo *arginfo,
      57             :                       int argnum, int elevel)
      58             : {
      59             :     ArrayType  *arr;
      60             : 
      61        4506 :     if (PG_ARGISNULL(argnum))
      62        3706 :         return true;
      63             : 
      64         800 :     arr = DatumGetArrayTypeP(PG_GETARG_DATUM(argnum));
      65             : 
      66         800 :     if (ARR_NDIM(arr) != 1)
      67             :     {
      68           0 :         ereport(elevel,
      69             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
      70             :                  errmsg("\"%s\" cannot be a multidimensional array",
      71             :                         arginfo[argnum].argname)));
      72           0 :         return false;
      73             :     }
      74             : 
      75         800 :     if (array_contains_nulls(arr))
      76             :     {
      77          12 :         ereport(elevel,
      78             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
      79             :                  errmsg("\"%s\" array cannot contain NULL values",
      80             :                         arginfo[argnum].argname)));
      81           6 :         return false;
      82             :     }
      83             : 
      84         788 :     return true;
      85             : }
      86             : 
      87             : /*
      88             :  * Enforce parameter pairs that must be specified together (or not at all) for
      89             :  * a particular stakind, such as most_common_vals and most_common_freqs for
      90             :  * STATISTIC_KIND_MCV.
      91             :  *
      92             :  * If a problem is found, emit at elevel, and return false. Otherwise return
      93             :  * true.
      94             :  */
      95             : bool
      96        4452 : stats_check_arg_pair(FunctionCallInfo fcinfo,
      97             :                      struct StatsArgInfo *arginfo,
      98             :                      int argnum1, int argnum2, int elevel)
      99             : {
     100        4452 :     if (PG_ARGISNULL(argnum1) && PG_ARGISNULL(argnum2))
     101        3606 :         return true;
     102             : 
     103         846 :     if (PG_ARGISNULL(argnum1) || PG_ARGISNULL(argnum2))
     104             :     {
     105          66 :         int         nullarg = PG_ARGISNULL(argnum1) ? argnum1 : argnum2;
     106          66 :         int         otherarg = PG_ARGISNULL(argnum1) ? argnum2 : argnum1;
     107             : 
     108          66 :         ereport(elevel,
     109             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     110             :                  errmsg("\"%s\" must be specified when \"%s\" is specified",
     111             :                         arginfo[nullarg].argname,
     112             :                         arginfo[otherarg].argname)));
     113             : 
     114          30 :         return false;
     115             :     }
     116             : 
     117         780 :     return true;
     118             : }
     119             : 
     120             : /*
     121             :  * Lock relation in ShareUpdateExclusive mode, check privileges, and close the
     122             :  * relation (but retain the lock).
     123             :  *
     124             :  * A role has privileges to set statistics on the relation if any of the
     125             :  * following are true:
     126             :  *   - the role owns the current database and the relation is not shared
     127             :  *   - the role has the MAINTAIN privilege on the relation
     128             :  */
     129             : void
     130        3844 : stats_lock_check_privileges(Oid reloid)
     131             : {
     132             :     Relation    table;
     133        3844 :     Oid         table_oid = reloid;
     134        3844 :     Oid         index_oid = InvalidOid;
     135        3844 :     LOCKMODE    index_lockmode = NoLock;
     136             : 
     137             :     /*
     138             :      * For indexes, we follow the locking behavior in do_analyze_rel() and
     139             :      * check_inplace_rel_lock(), which is to lock the table first in
     140             :      * ShareUpdateExclusive mode and then the index in AccessShare mode.
     141             :      *
     142             :      * Partitioned indexes are treated differently than normal indexes in
     143             :      * check_inplace_rel_lock(), so we take a ShareUpdateExclusive lock on
     144             :      * both the partitioned table and the partitioned index.
     145             :      */
     146        3844 :     switch (get_rel_relkind(reloid))
     147             :     {
     148         580 :         case RELKIND_INDEX:
     149         580 :             index_oid = reloid;
     150         580 :             table_oid = IndexGetRelation(index_oid, false);
     151         580 :             index_lockmode = AccessShareLock;
     152         580 :             break;
     153          72 :         case RELKIND_PARTITIONED_INDEX:
     154          72 :             index_oid = reloid;
     155          72 :             table_oid = IndexGetRelation(index_oid, false);
     156          72 :             index_lockmode = ShareUpdateExclusiveLock;
     157          72 :             break;
     158        3192 :         default:
     159        3192 :             break;
     160             :     }
     161             : 
     162        3844 :     table = relation_open(table_oid, ShareUpdateExclusiveLock);
     163             : 
     164             :     /* the relkinds that can be used with ANALYZE */
     165        3808 :     switch (table->rd_rel->relkind)
     166             :     {
     167        3796 :         case RELKIND_RELATION:
     168             :         case RELKIND_MATVIEW:
     169             :         case RELKIND_FOREIGN_TABLE:
     170             :         case RELKIND_PARTITIONED_TABLE:
     171        3796 :             break;
     172          12 :         default:
     173          12 :             ereport(ERROR,
     174             :                     (errcode(ERRCODE_WRONG_OBJECT_TYPE),
     175             :                      errmsg("cannot modify statistics for relation \"%s\"",
     176             :                             RelationGetRelationName(table)),
     177             :                      errdetail_relkind_not_supported(table->rd_rel->relkind)));
     178             :     }
     179             : 
     180        3796 :     if (OidIsValid(index_oid))
     181             :     {
     182             :         Relation    index;
     183             : 
     184             :         Assert(index_lockmode != NoLock);
     185         652 :         index = relation_open(index_oid, index_lockmode);
     186             : 
     187             :         Assert(index->rd_index && index->rd_index->indrelid == table_oid);
     188             : 
     189             :         /* retain lock on index */
     190         652 :         relation_close(index, NoLock);
     191             :     }
     192             : 
     193        3796 :     if (table->rd_rel->relisshared)
     194           0 :         ereport(ERROR,
     195             :                 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     196             :                  errmsg("cannot modify statistics for shared relation")));
     197             : 
     198        3796 :     if (!object_ownercheck(DatabaseRelationId, MyDatabaseId, GetUserId()))
     199             :     {
     200           0 :         AclResult   aclresult = pg_class_aclcheck(RelationGetRelid(table),
     201             :                                                   GetUserId(),
     202             :                                                   ACL_MAINTAIN);
     203             : 
     204           0 :         if (aclresult != ACLCHECK_OK)
     205           0 :             aclcheck_error(aclresult,
     206           0 :                            get_relkind_objtype(table->rd_rel->relkind),
     207           0 :                            NameStr(table->rd_rel->relname));
     208             :     }
     209             : 
     210             :     /* retain lock on table */
     211        3796 :     relation_close(table, NoLock);
     212        3796 : }
     213             : 
     214             : /*
     215             :  * Find the argument number for the given argument name, returning -1 if not
     216             :  * found.
     217             :  */
     218             : static int
     219       19826 : get_arg_by_name(const char *argname, struct StatsArgInfo *arginfo, int elevel)
     220             : {
     221             :     int         argnum;
     222             : 
     223       79104 :     for (argnum = 0; arginfo[argnum].argname != NULL; argnum++)
     224       79092 :         if (pg_strcasecmp(argname, arginfo[argnum].argname) == 0)
     225       19814 :             return argnum;
     226             : 
     227          12 :     ereport(elevel,
     228             :             (errmsg("unrecognized argument name: \"%s\"", argname)));
     229             : 
     230          12 :     return -1;
     231             : }
     232             : 
     233             : /*
     234             :  * Ensure that a given argument matched the expected type.
     235             :  */
     236             : static bool
     237       19814 : stats_check_arg_type(const char *argname, Oid argtype, Oid expectedtype, int elevel)
     238             : {
     239       19814 :     if (argtype != expectedtype)
     240             :     {
     241          12 :         ereport(elevel,
     242             :                 (errmsg("argument \"%s\" has type \"%s\", expected type \"%s\"",
     243             :                         argname, format_type_be(argtype),
     244             :                         format_type_be(expectedtype))));
     245          12 :         return false;
     246             :     }
     247             : 
     248       19802 :     return true;
     249             : }
     250             : 
     251             : /*
     252             :  * Translate variadic argument pairs from 'pairs_fcinfo' into a
     253             :  * 'positional_fcinfo' appropriate for calling relation_statistics_update() or
     254             :  * attribute_statistics_update() with positional arguments.
     255             :  *
     256             :  * Caller should have already initialized positional_fcinfo with a size
     257             :  * appropriate for calling the intended positional function, and arginfo
     258             :  * should also match the intended positional function.
     259             :  */
     260             : bool
     261        3496 : stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
     262             :                                  FunctionCallInfo positional_fcinfo,
     263             :                                  struct StatsArgInfo *arginfo,
     264             :                                  int elevel)
     265             : {
     266             :     Datum      *args;
     267             :     bool       *argnulls;
     268             :     Oid        *types;
     269             :     int         nargs;
     270        3496 :     bool        result = true;
     271             : 
     272             :     /* clear positional args */
     273       33776 :     for (int i = 0; arginfo[i].argname != NULL; i++)
     274             :     {
     275       30280 :         positional_fcinfo->args[i].value = (Datum) 0;
     276       30280 :         positional_fcinfo->args[i].isnull = true;
     277             :     }
     278             : 
     279        3496 :     nargs = extract_variadic_args(pairs_fcinfo, 0, true,
     280             :                                   &args, &types, &argnulls);
     281             : 
     282        3496 :     if (nargs % 2 != 0)
     283           6 :         ereport(ERROR,
     284             :                 errmsg("variadic arguments must be name/value pairs"),
     285             :                 errhint("Provide an even number of variadic arguments that can be divided into pairs."));
     286             : 
     287             :     /*
     288             :      * For each argument name/value pair, find corresponding positional
     289             :      * argument for the argument name, and assign the argument value to
     290             :      * positional_fcinfo.
     291             :      */
     292       27076 :     for (int i = 0; i < nargs; i += 2)
     293             :     {
     294             :         int         argnum;
     295             :         char       *argname;
     296             : 
     297       23598 :         if (argnulls[i])
     298           6 :             ereport(ERROR,
     299             :                     (errmsg("name at variadic position %d is NULL", i + 1)));
     300             : 
     301       23592 :         if (types[i] != TEXTOID)
     302           6 :             ereport(ERROR,
     303             :                     (errmsg("name at variadic position %d has type \"%s\", expected type \"%s\"",
     304             :                             i + 1, format_type_be(types[i]),
     305             :                             format_type_be(TEXTOID))));
     306             : 
     307       23586 :         if (argnulls[i + 1])
     308         282 :             continue;
     309             : 
     310       23304 :         argname = TextDatumGetCString(args[i]);
     311             : 
     312             :         /*
     313             :          * The 'version' argument is a special case, not handled by arginfo
     314             :          * because it's not a valid positional argument.
     315             :          *
     316             :          * For now, 'version' is accepted but ignored. In the future it can be
     317             :          * used to interpret older statistics properly.
     318             :          */
     319       23304 :         if (pg_strcasecmp(argname, "version") == 0)
     320        3478 :             continue;
     321             : 
     322       19826 :         argnum = get_arg_by_name(argname, arginfo, elevel);
     323             : 
     324       39640 :         if (argnum < 0 || !stats_check_arg_type(argname, types[i + 1],
     325       19814 :                                                 arginfo[argnum].argtype,
     326             :                                                 elevel))
     327             :         {
     328          24 :             result = false;
     329          24 :             continue;
     330             :         }
     331             : 
     332       19802 :         positional_fcinfo->args[argnum].value = args[i + 1];
     333       19802 :         positional_fcinfo->args[argnum].isnull = false;
     334             :     }
     335             : 
     336        3478 :     return result;
     337             : }

Generated by: LCOV version 1.14