LCOV - code coverage report
Current view: top level - src/backend/statistics - stat_utils.c (source / functions) Hit Total Coverage
Test: PostgreSQL 19devel Lines: 84 98 85.7 %
Date: 2025-10-24 00:17:25 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/htup_details.h"
      20             : #include "access/relation.h"
      21             : #include "catalog/index.h"
      22             : #include "catalog/namespace.h"
      23             : #include "catalog/pg_class.h"
      24             : #include "catalog/pg_database.h"
      25             : #include "funcapi.h"
      26             : #include "miscadmin.h"
      27             : #include "statistics/stat_utils.h"
      28             : #include "storage/lmgr.h"
      29             : #include "utils/acl.h"
      30             : #include "utils/array.h"
      31             : #include "utils/builtins.h"
      32             : #include "utils/lsyscache.h"
      33             : #include "utils/rel.h"
      34             : #include "utils/syscache.h"
      35             : 
      36             : /*
      37             :  * Ensure that a given argument is not null.
      38             :  */
      39             : void
      40        8646 : stats_check_required_arg(FunctionCallInfo fcinfo,
      41             :                          struct StatsArgInfo *arginfo,
      42             :                          int argnum)
      43             : {
      44        8646 :     if (PG_ARGISNULL(argnum))
      45          48 :         ereport(ERROR,
      46             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
      47             :                  errmsg("argument \"%s\" must not be null",
      48             :                         arginfo[argnum].argname)));
      49        8598 : }
      50             : 
      51             : /*
      52             :  * Check that argument is either NULL or a one dimensional array with no
      53             :  * NULLs.
      54             :  *
      55             :  * If a problem is found, emit a WARNING, and return false. Otherwise return
      56             :  * true.
      57             :  */
      58             : bool
      59        4110 : stats_check_arg_array(FunctionCallInfo fcinfo,
      60             :                       struct StatsArgInfo *arginfo,
      61             :                       int argnum)
      62             : {
      63             :     ArrayType  *arr;
      64             : 
      65        4110 :     if (PG_ARGISNULL(argnum))
      66        3382 :         return true;
      67             : 
      68         728 :     arr = DatumGetArrayTypeP(PG_GETARG_DATUM(argnum));
      69             : 
      70         728 :     if (ARR_NDIM(arr) != 1)
      71             :     {
      72           0 :         ereport(WARNING,
      73             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
      74             :                  errmsg("argument \"%s\" must not be a multidimensional array",
      75             :                         arginfo[argnum].argname)));
      76           0 :         return false;
      77             :     }
      78             : 
      79         728 :     if (array_contains_nulls(arr))
      80             :     {
      81           6 :         ereport(WARNING,
      82             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
      83             :                  errmsg("argument \"%s\" array must not contain null values",
      84             :                         arginfo[argnum].argname)));
      85           6 :         return false;
      86             :     }
      87             : 
      88         722 :     return true;
      89             : }
      90             : 
      91             : /*
      92             :  * Enforce parameter pairs that must be specified together (or not at all) for
      93             :  * a particular stakind, such as most_common_vals and most_common_freqs for
      94             :  * STATISTIC_KIND_MCV.
      95             :  *
      96             :  * If a problem is found, emit a WARNING, and return false. Otherwise return
      97             :  * true.
      98             :  */
      99             : bool
     100        4110 : stats_check_arg_pair(FunctionCallInfo fcinfo,
     101             :                      struct StatsArgInfo *arginfo,
     102             :                      int argnum1, int argnum2)
     103             : {
     104        4110 :     if (PG_ARGISNULL(argnum1) && PG_ARGISNULL(argnum2))
     105        3366 :         return true;
     106             : 
     107         744 :     if (PG_ARGISNULL(argnum1) || PG_ARGISNULL(argnum2))
     108             :     {
     109          42 :         int         nullarg = PG_ARGISNULL(argnum1) ? argnum1 : argnum2;
     110          42 :         int         otherarg = PG_ARGISNULL(argnum1) ? argnum2 : argnum1;
     111             : 
     112          42 :         ereport(WARNING,
     113             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     114             :                  errmsg("argument \"%s\" must be specified when argument \"%s\" is specified",
     115             :                         arginfo[nullarg].argname,
     116             :                         arginfo[otherarg].argname)));
     117             : 
     118          42 :         return false;
     119             :     }
     120             : 
     121         702 :     return true;
     122             : }
     123             : 
     124             : /*
     125             :  * A role has privileges to set statistics on the relation if any of the
     126             :  * following are true:
     127             :  *   - the role owns the current database and the relation is not shared
     128             :  *   - the role has the MAINTAIN privilege on the relation
     129             :  */
     130             : void
     131        3594 : RangeVarCallbackForStats(const RangeVar *relation,
     132             :                          Oid relId, Oid oldRelId, void *arg)
     133             : {
     134        3594 :     Oid        *locked_oid = (Oid *) arg;
     135        3594 :     Oid         table_oid = relId;
     136             :     HeapTuple   tuple;
     137             :     Form_pg_class form;
     138             :     char        relkind;
     139             : 
     140             :     /*
     141             :      * If we previously locked some other index's heap, and the name we're
     142             :      * looking up no longer refers to that relation, release the now-useless
     143             :      * lock.
     144             :      */
     145        3594 :     if (relId != oldRelId && OidIsValid(*locked_oid))
     146             :     {
     147           0 :         UnlockRelationOid(*locked_oid, ShareUpdateExclusiveLock);
     148           0 :         *locked_oid = InvalidOid;
     149             :     }
     150             : 
     151             :     /* If the relation does not exist, there's nothing more to do. */
     152        3594 :     if (!OidIsValid(relId))
     153          12 :         return;
     154             : 
     155             :     /* If the relation does exist, check whether it's an index. */
     156        3582 :     relkind = get_rel_relkind(relId);
     157        3582 :     if (relkind == RELKIND_INDEX ||
     158             :         relkind == RELKIND_PARTITIONED_INDEX)
     159         590 :         table_oid = IndexGetRelation(relId, false);
     160             : 
     161             :     /*
     162             :      * If retrying yields the same OID, there are a couple of extremely
     163             :      * unlikely scenarios we need to handle.
     164             :      */
     165        3582 :     if (relId == oldRelId)
     166             :     {
     167             :         /*
     168             :          * If a previous lookup found an index, but the current lookup did
     169             :          * not, the index was dropped and the OID was reused for something
     170             :          * else between lookups.  In theory, we could simply drop our lock on
     171             :          * the index's parent table and proceed, but in the interest of
     172             :          * avoiding complexity, we just error.
     173             :          */
     174           4 :         if (table_oid == relId && OidIsValid(*locked_oid))
     175           0 :             ereport(ERROR,
     176             :                     (errcode(ERRCODE_UNDEFINED_OBJECT),
     177             :                      errmsg("index \"%s\" was concurrently dropped",
     178             :                             relation->relname)));
     179             : 
     180             :         /*
     181             :          * If the current lookup found an index but a previous lookup either
     182             :          * did not find an index or found one with a different parent
     183             :          * relation, the relation was dropped and the OID was reused for an
     184             :          * index between lookups.  RangeVarGetRelidExtended() will have
     185             :          * already locked the index at this point, so we can't just lock the
     186             :          * newly discovered parent table OID without risking deadlock.  As
     187             :          * above, we just error in this case.
     188             :          */
     189           4 :         if (table_oid != relId && table_oid != *locked_oid)
     190           0 :             ereport(ERROR,
     191             :                     (errcode(ERRCODE_UNDEFINED_OBJECT),
     192             :                      errmsg("index \"%s\" was concurrently created",
     193             :                             relation->relname)));
     194             :     }
     195             : 
     196        3582 :     tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(table_oid));
     197        3582 :     if (!HeapTupleIsValid(tuple))
     198           0 :         elog(ERROR, "cache lookup failed for OID %u", table_oid);
     199        3582 :     form = (Form_pg_class) GETSTRUCT(tuple);
     200             : 
     201             :     /* the relkinds that can be used with ANALYZE */
     202        3582 :     switch (form->relkind)
     203             :     {
     204        3564 :         case RELKIND_RELATION:
     205             :         case RELKIND_MATVIEW:
     206             :         case RELKIND_FOREIGN_TABLE:
     207             :         case RELKIND_PARTITIONED_TABLE:
     208        3564 :             break;
     209          18 :         default:
     210          18 :             ereport(ERROR,
     211             :                     (errcode(ERRCODE_WRONG_OBJECT_TYPE),
     212             :                      errmsg("cannot modify statistics for relation \"%s\"",
     213             :                             NameStr(form->relname)),
     214             :                      errdetail_relkind_not_supported(form->relkind)));
     215             :     }
     216             : 
     217        3564 :     if (form->relisshared)
     218           0 :         ereport(ERROR,
     219             :                 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     220             :                  errmsg("cannot modify statistics for shared relation")));
     221             : 
     222             :     /* Check permissions */
     223        3564 :     if (!object_ownercheck(DatabaseRelationId, MyDatabaseId, GetUserId()))
     224             :     {
     225           0 :         AclResult   aclresult = pg_class_aclcheck(table_oid,
     226             :                                                   GetUserId(),
     227             :                                                   ACL_MAINTAIN);
     228             : 
     229           0 :         if (aclresult != ACLCHECK_OK)
     230           0 :             aclcheck_error(aclresult,
     231           0 :                            get_relkind_objtype(form->relkind),
     232           0 :                            NameStr(form->relname));
     233             :     }
     234             : 
     235        3564 :     ReleaseSysCache(tuple);
     236             : 
     237             :     /* Lock heap before index to avoid deadlock. */
     238        3564 :     if (relId != oldRelId && table_oid != relId)
     239             :     {
     240         588 :         LockRelationOid(table_oid, ShareUpdateExclusiveLock);
     241         588 :         *locked_oid = table_oid;
     242             :     }
     243             : }
     244             : 
     245             : 
     246             : /*
     247             :  * Find the argument number for the given argument name, returning -1 if not
     248             :  * found.
     249             :  */
     250             : static int
     251       25670 : get_arg_by_name(const char *argname, struct StatsArgInfo *arginfo)
     252             : {
     253             :     int         argnum;
     254             : 
     255      123454 :     for (argnum = 0; arginfo[argnum].argname != NULL; argnum++)
     256      123442 :         if (pg_strcasecmp(argname, arginfo[argnum].argname) == 0)
     257       25658 :             return argnum;
     258             : 
     259          12 :     ereport(WARNING,
     260             :             (errmsg("unrecognized argument name: \"%s\"", argname)));
     261             : 
     262          12 :     return -1;
     263             : }
     264             : 
     265             : /*
     266             :  * Ensure that a given argument matched the expected type.
     267             :  */
     268             : static bool
     269       25658 : stats_check_arg_type(const char *argname, Oid argtype, Oid expectedtype)
     270             : {
     271       25658 :     if (argtype != expectedtype)
     272             :     {
     273          24 :         ereport(WARNING,
     274             :                 (errmsg("argument \"%s\" has type %s, expected type %s",
     275             :                         argname, format_type_be(argtype),
     276             :                         format_type_be(expectedtype))));
     277          24 :         return false;
     278             :     }
     279             : 
     280       25634 :     return true;
     281             : }
     282             : 
     283             : /*
     284             :  * Translate variadic argument pairs from 'pairs_fcinfo' into a
     285             :  * 'positional_fcinfo' appropriate for calling relation_statistics_update() or
     286             :  * attribute_statistics_update() with positional arguments.
     287             :  *
     288             :  * Caller should have already initialized positional_fcinfo with a size
     289             :  * appropriate for calling the intended positional function, and arginfo
     290             :  * should also match the intended positional function.
     291             :  */
     292             : bool
     293        3620 : stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
     294             :                                  FunctionCallInfo positional_fcinfo,
     295             :                                  struct StatsArgInfo *arginfo)
     296             : {
     297             :     Datum      *args;
     298             :     bool       *argnulls;
     299             :     Oid        *types;
     300             :     int         nargs;
     301        3620 :     bool        result = true;
     302             : 
     303             :     /* clear positional args */
     304       42572 :     for (int i = 0; arginfo[i].argname != NULL; i++)
     305             :     {
     306       38952 :         positional_fcinfo->args[i].value = (Datum) 0;
     307       38952 :         positional_fcinfo->args[i].isnull = true;
     308             :     }
     309             : 
     310        3620 :     nargs = extract_variadic_args(pairs_fcinfo, 0, true,
     311             :                                   &args, &types, &argnulls);
     312             : 
     313        3620 :     if (nargs % 2 != 0)
     314           6 :         ereport(ERROR,
     315             :                 errmsg("variadic arguments must be name/value pairs"),
     316             :                 errhint("Provide an even number of variadic arguments that can be divided into pairs."));
     317             : 
     318             :     /*
     319             :      * For each argument name/value pair, find corresponding positional
     320             :      * argument for the argument name, and assign the argument value to
     321             :      * positional_fcinfo.
     322             :      */
     323       32868 :     for (int i = 0; i < nargs; i += 2)
     324             :     {
     325             :         int         argnum;
     326             :         char       *argname;
     327             : 
     328       29260 :         if (argnulls[i])
     329           6 :             ereport(ERROR,
     330             :                     (errmsg("name at variadic position %d is null", i + 1)));
     331             : 
     332       29254 :         if (types[i] != TEXTOID)
     333           0 :             ereport(ERROR,
     334             :                     (errmsg("name at variadic position %d has type %s, expected type %s",
     335             :                             i + 1, format_type_be(types[i]),
     336             :                             format_type_be(TEXTOID))));
     337             : 
     338       29254 :         if (argnulls[i + 1])
     339         276 :             continue;
     340             : 
     341       28978 :         argname = TextDatumGetCString(args[i]);
     342             : 
     343             :         /*
     344             :          * The 'version' argument is a special case, not handled by arginfo
     345             :          * because it's not a valid positional argument.
     346             :          *
     347             :          * For now, 'version' is accepted but ignored. In the future it can be
     348             :          * used to interpret older statistics properly.
     349             :          */
     350       28978 :         if (pg_strcasecmp(argname, "version") == 0)
     351        3308 :             continue;
     352             : 
     353       25670 :         argnum = get_arg_by_name(argname, arginfo);
     354             : 
     355       51328 :         if (argnum < 0 || !stats_check_arg_type(argname, types[i + 1],
     356       25658 :                                                 arginfo[argnum].argtype))
     357             :         {
     358          36 :             result = false;
     359          36 :             continue;
     360             :         }
     361             : 
     362       25634 :         positional_fcinfo->args[argnum].value = args[i + 1];
     363       25634 :         positional_fcinfo->args[argnum].isnull = false;
     364             :     }
     365             : 
     366        3608 :     return result;
     367             : }

Generated by: LCOV version 1.16