LCOV - code coverage report
Current view: top level - src/backend/statistics - stat_utils.c (source / functions) Hit Total Coverage
Test: PostgreSQL 18devel Lines: 71 79 89.9 %
Date: 2024-11-21 08:14:44 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-2024, 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/pg_database.h"
      21             : #include "funcapi.h"
      22             : #include "miscadmin.h"
      23             : #include "statistics/stat_utils.h"
      24             : #include "utils/acl.h"
      25             : #include "utils/array.h"
      26             : #include "utils/builtins.h"
      27             : #include "utils/rel.h"
      28             : 
      29             : /*
      30             :  * Ensure that a given argument is not null.
      31             :  */
      32             : void
      33        1350 : stats_check_required_arg(FunctionCallInfo fcinfo,
      34             :                          struct StatsArgInfo *arginfo,
      35             :                          int argnum)
      36             : {
      37        1350 :     if (PG_ARGISNULL(argnum))
      38          36 :         ereport(ERROR,
      39             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
      40             :                  errmsg("\"%s\" cannot be NULL",
      41             :                         arginfo[argnum].argname)));
      42        1314 : }
      43             : 
      44             : /*
      45             :  * Check that argument is either NULL or a one dimensional array with no
      46             :  * NULLs.
      47             :  *
      48             :  * If a problem is found, emit at elevel, and return false. Otherwise return
      49             :  * true.
      50             :  */
      51             : bool
      52         954 : stats_check_arg_array(FunctionCallInfo fcinfo,
      53             :                       struct StatsArgInfo *arginfo,
      54             :                       int argnum, int elevel)
      55             : {
      56             :     ArrayType  *arr;
      57             : 
      58         954 :     if (PG_ARGISNULL(argnum))
      59         804 :         return true;
      60             : 
      61         150 :     arr = DatumGetArrayTypeP(PG_GETARG_DATUM(argnum));
      62             : 
      63         150 :     if (ARR_NDIM(arr) != 1)
      64             :     {
      65           0 :         ereport(elevel,
      66             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
      67             :                  errmsg("\"%s\" cannot be a multidimensional array",
      68             :                         arginfo[argnum].argname)));
      69           0 :         return false;
      70             :     }
      71             : 
      72         150 :     if (array_contains_nulls(arr))
      73             :     {
      74          12 :         ereport(elevel,
      75             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
      76             :                  errmsg("\"%s\" array cannot contain NULL values",
      77             :                         arginfo[argnum].argname)));
      78           6 :         return false;
      79             :     }
      80             : 
      81         138 :     return true;
      82             : }
      83             : 
      84             : /*
      85             :  * Enforce parameter pairs that must be specified together (or not at all) for
      86             :  * a particular stakind, such as most_common_vals and most_common_freqs for
      87             :  * STATISTIC_KIND_MCV.
      88             :  *
      89             :  * If a problem is found, emit at elevel, and return false. Otherwise return
      90             :  * true.
      91             :  */
      92             : bool
      93         900 : stats_check_arg_pair(FunctionCallInfo fcinfo,
      94             :                      struct StatsArgInfo *arginfo,
      95             :                      int argnum1, int argnum2, int elevel)
      96             : {
      97         900 :     if (PG_ARGISNULL(argnum1) && PG_ARGISNULL(argnum2))
      98         696 :         return true;
      99             : 
     100         204 :     if (PG_ARGISNULL(argnum1) || PG_ARGISNULL(argnum2))
     101             :     {
     102          66 :         int         nullarg = PG_ARGISNULL(argnum1) ? argnum1 : argnum2;
     103          66 :         int         otherarg = PG_ARGISNULL(argnum1) ? argnum2 : argnum1;
     104             : 
     105          66 :         ereport(elevel,
     106             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     107             :                  errmsg("\"%s\" must be specified when \"%s\" is specified",
     108             :                         arginfo[nullarg].argname,
     109             :                         arginfo[otherarg].argname)));
     110             : 
     111          30 :         return false;
     112             :     }
     113             : 
     114         138 :     return true;
     115             : }
     116             : 
     117             : /*
     118             :  * Lock relation in ShareUpdateExclusive mode, check privileges, and close the
     119             :  * relation (but retain the lock).
     120             :  *
     121             :  * A role has privileges to set statistics on the relation if any of the
     122             :  * following are true:
     123             :  *   - the role owns the current database and the relation is not shared
     124             :  *   - the role has the MAINTAIN privilege on the relation
     125             :  */
     126             : void
     127         540 : stats_lock_check_privileges(Oid reloid)
     128             : {
     129         540 :     Relation    rel = relation_open(reloid, ShareUpdateExclusiveLock);
     130         504 :     const char  relkind = rel->rd_rel->relkind;
     131             : 
     132             :     /* All of the types that can be used with ANALYZE, plus indexes */
     133         504 :     switch (relkind)
     134             :     {
     135         492 :         case RELKIND_RELATION:
     136             :         case RELKIND_INDEX:
     137             :         case RELKIND_MATVIEW:
     138             :         case RELKIND_FOREIGN_TABLE:
     139             :         case RELKIND_PARTITIONED_TABLE:
     140             :         case RELKIND_PARTITIONED_INDEX:
     141         492 :             break;
     142          12 :         default:
     143          12 :             ereport(ERROR,
     144             :                     (errcode(ERRCODE_WRONG_OBJECT_TYPE),
     145             :                      errmsg("cannot modify statistics for relation \"%s\"",
     146             :                             RelationGetRelationName(rel)),
     147             :                      errdetail_relkind_not_supported(rel->rd_rel->relkind)));
     148             :     }
     149             : 
     150         492 :     if (rel->rd_rel->relisshared)
     151           0 :         ereport(ERROR,
     152             :                 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     153             :                  errmsg("cannot modify statistics for shared relation")));
     154             : 
     155         492 :     if (!object_ownercheck(DatabaseRelationId, MyDatabaseId, GetUserId()))
     156             :     {
     157           0 :         AclResult   aclresult = pg_class_aclcheck(RelationGetRelid(rel),
     158             :                                                   GetUserId(),
     159             :                                                   ACL_MAINTAIN);
     160             : 
     161           0 :         if (aclresult != ACLCHECK_OK)
     162           0 :             aclcheck_error(aclresult,
     163           0 :                            get_relkind_objtype(rel->rd_rel->relkind),
     164           0 :                            NameStr(rel->rd_rel->relname));
     165             :     }
     166             : 
     167         492 :     relation_close(rel, NoLock);
     168         492 : }
     169             : 
     170             : /*
     171             :  * Find the argument number for the given argument name, returning -1 if not
     172             :  * found.
     173             :  */
     174             : static int
     175        1440 : get_arg_by_name(const char *argname, struct StatsArgInfo *arginfo, int elevel)
     176             : {
     177             :     int         argnum;
     178             : 
     179        7026 :     for (argnum = 0; arginfo[argnum].argname != NULL; argnum++)
     180        7014 :         if (pg_strcasecmp(argname, arginfo[argnum].argname) == 0)
     181        1428 :             return argnum;
     182             : 
     183          12 :     ereport(elevel,
     184             :             (errmsg("unrecognized argument name: \"%s\"", argname)));
     185             : 
     186          12 :     return -1;
     187             : }
     188             : 
     189             : /*
     190             :  * Ensure that a given argument matched the expected type.
     191             :  */
     192             : static bool
     193        1428 : stats_check_arg_type(const char *argname, Oid argtype, Oid expectedtype, int elevel)
     194             : {
     195        1428 :     if (argtype != expectedtype)
     196             :     {
     197          12 :         ereport(elevel,
     198             :                 (errmsg("argument \"%s\" has type \"%s\", expected type \"%s\"",
     199             :                         argname, format_type_be(argtype),
     200             :                         format_type_be(expectedtype))));
     201          12 :         return false;
     202             :     }
     203             : 
     204        1416 :     return true;
     205             : }
     206             : 
     207             : /*
     208             :  * Translate variadic argument pairs from 'pairs_fcinfo' into a
     209             :  * 'positional_fcinfo' appropriate for calling relation_statistics_update() or
     210             :  * attribute_statistics_update() with positional arguments.
     211             :  *
     212             :  * Caller should have already initialized positional_fcinfo with a size
     213             :  * appropriate for calling the intended positional function, and arginfo
     214             :  * should also match the intended positional function.
     215             :  */
     216             : bool
     217         234 : stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
     218             :                                  FunctionCallInfo positional_fcinfo,
     219             :                                  struct StatsArgInfo *arginfo,
     220             :                                  int elevel)
     221             : {
     222             :     Datum      *args;
     223             :     bool       *argnulls;
     224             :     Oid        *types;
     225             :     int         nargs;
     226         234 :     bool        result = true;
     227             : 
     228             :     /* clear positional args */
     229        3258 :     for (int i = 0; arginfo[i].argname != NULL; i++)
     230             :     {
     231        3024 :         positional_fcinfo->args[i].value = (Datum) 0;
     232        3024 :         positional_fcinfo->args[i].isnull = true;
     233             :     }
     234             : 
     235         234 :     nargs = extract_variadic_args(pairs_fcinfo, 0, true,
     236             :                                   &args, &types, &argnulls);
     237             : 
     238         234 :     if (nargs % 2 != 0)
     239           6 :         ereport(ERROR,
     240             :                 errmsg("variadic arguments must be name/value pairs"),
     241             :                 errhint("Provide an even number of variadic arguments that can be divided into pairs."));
     242             : 
     243             :     /*
     244             :      * For each argument name/value pair, find corresponding positional
     245             :      * argument for the argument name, and assign the argument value to
     246             :      * postitional_fcinfo.
     247             :      */
     248        2178 :     for (int i = 0; i < nargs; i += 2)
     249             :     {
     250             :         int         argnum;
     251             :         char       *argname;
     252             : 
     253        1962 :         if (argnulls[i])
     254           6 :             ereport(ERROR,
     255             :                     (errmsg("name at variadic position %d is NULL", i + 1)));
     256             : 
     257        1956 :         if (types[i] != TEXTOID)
     258           6 :             ereport(ERROR,
     259             :                     (errmsg("name at variadic position %d has type \"%s\", expected type \"%s\"",
     260             :                             i + 1, format_type_be(types[i]),
     261             :                             format_type_be(TEXTOID))));
     262             : 
     263        1950 :         if (argnulls[i + 1])
     264         282 :             continue;
     265             : 
     266        1668 :         argname = TextDatumGetCString(args[i]);
     267             : 
     268             :         /*
     269             :          * The 'version' argument is a special case, not handled by arginfo
     270             :          * because it's not a valid positional argument.
     271             :          *
     272             :          * For now, 'version' is accepted but ignored. In the future it can be
     273             :          * used to interpret older statistics properly.
     274             :          */
     275        1668 :         if (pg_strcasecmp(argname, "version") == 0)
     276         228 :             continue;
     277             : 
     278        1440 :         argnum = get_arg_by_name(argname, arginfo, elevel);
     279             : 
     280        2868 :         if (argnum < 0 || !stats_check_arg_type(argname, types[i + 1],
     281        1428 :                                                 arginfo[argnum].argtype,
     282             :                                                 elevel))
     283             :         {
     284          24 :             result = false;
     285          24 :             continue;
     286             :         }
     287             : 
     288        1416 :         positional_fcinfo->args[argnum].value = args[i + 1];
     289        1416 :         positional_fcinfo->args[argnum].isnull = false;
     290             :     }
     291             : 
     292         216 :     return result;
     293             : }

Generated by: LCOV version 1.14