LCOV - code coverage report
Current view: top level - src/backend/statistics - stat_utils.c (source / functions) Hit Total Coverage
Test: PostgreSQL 19devel Lines: 211 236 89.4 %
Date: 2026-01-07 10:17:45 Functions: 13 13 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-2026, 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_collation.h"
      25             : #include "catalog/pg_database.h"
      26             : #include "catalog/pg_statistic.h"
      27             : #include "funcapi.h"
      28             : #include "miscadmin.h"
      29             : #include "nodes/nodeFuncs.h"
      30             : #include "statistics/stat_utils.h"
      31             : #include "storage/lmgr.h"
      32             : #include "utils/acl.h"
      33             : #include "utils/array.h"
      34             : #include "utils/builtins.h"
      35             : #include "utils/lsyscache.h"
      36             : #include "utils/rel.h"
      37             : #include "utils/syscache.h"
      38             : 
      39             : /* Default values assigned to new pg_statistic tuples. */
      40             : #define DEFAULT_STATATT_NULL_FRAC      Float4GetDatum(0.0)  /* stanullfrac */
      41             : #define DEFAULT_STATATT_AVG_WIDTH      Int32GetDatum(0) /* stawidth, same as
      42             :                                                          * unknown */
      43             : #define DEFAULT_STATATT_N_DISTINCT     Float4GetDatum(0.0)  /* stadistinct, same as
      44             :                                                              * unknown */
      45             : 
      46             : static Node *statatt_get_index_expr(Relation rel, int attnum);
      47             : 
      48             : /*
      49             :  * Ensure that a given argument is not null.
      50             :  */
      51             : void
      52        8690 : stats_check_required_arg(FunctionCallInfo fcinfo,
      53             :                          struct StatsArgInfo *arginfo,
      54             :                          int argnum)
      55             : {
      56        8690 :     if (PG_ARGISNULL(argnum))
      57          48 :         ereport(ERROR,
      58             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
      59             :                  errmsg("argument \"%s\" must not be null",
      60             :                         arginfo[argnum].argname)));
      61        8642 : }
      62             : 
      63             : /*
      64             :  * Check that argument is either NULL or a one dimensional array with no
      65             :  * NULLs.
      66             :  *
      67             :  * If a problem is found, emit a WARNING, and return false. Otherwise return
      68             :  * true.
      69             :  */
      70             : bool
      71        4110 : stats_check_arg_array(FunctionCallInfo fcinfo,
      72             :                       struct StatsArgInfo *arginfo,
      73             :                       int argnum)
      74             : {
      75             :     ArrayType  *arr;
      76             : 
      77        4110 :     if (PG_ARGISNULL(argnum))
      78        3374 :         return true;
      79             : 
      80         736 :     arr = DatumGetArrayTypeP(PG_GETARG_DATUM(argnum));
      81             : 
      82         736 :     if (ARR_NDIM(arr) != 1)
      83             :     {
      84           0 :         ereport(WARNING,
      85             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
      86             :                  errmsg("argument \"%s\" must not be a multidimensional array",
      87             :                         arginfo[argnum].argname)));
      88           0 :         return false;
      89             :     }
      90             : 
      91         736 :     if (array_contains_nulls(arr))
      92             :     {
      93           6 :         ereport(WARNING,
      94             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
      95             :                  errmsg("argument \"%s\" array must not contain null values",
      96             :                         arginfo[argnum].argname)));
      97           6 :         return false;
      98             :     }
      99             : 
     100         730 :     return true;
     101             : }
     102             : 
     103             : /*
     104             :  * Enforce parameter pairs that must be specified together (or not at all) for
     105             :  * a particular stakind, such as most_common_vals and most_common_freqs for
     106             :  * STATISTIC_KIND_MCV.
     107             :  *
     108             :  * If a problem is found, emit a WARNING, and return false. Otherwise return
     109             :  * true.
     110             :  */
     111             : bool
     112        4110 : stats_check_arg_pair(FunctionCallInfo fcinfo,
     113             :                      struct StatsArgInfo *arginfo,
     114             :                      int argnum1, int argnum2)
     115             : {
     116        4110 :     if (PG_ARGISNULL(argnum1) && PG_ARGISNULL(argnum2))
     117        3358 :         return true;
     118             : 
     119         752 :     if (PG_ARGISNULL(argnum1) || PG_ARGISNULL(argnum2))
     120             :     {
     121          42 :         int         nullarg = PG_ARGISNULL(argnum1) ? argnum1 : argnum2;
     122          42 :         int         otherarg = PG_ARGISNULL(argnum1) ? argnum2 : argnum1;
     123             : 
     124          42 :         ereport(WARNING,
     125             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     126             :                  errmsg("argument \"%s\" must be specified when argument \"%s\" is specified",
     127             :                         arginfo[nullarg].argname,
     128             :                         arginfo[otherarg].argname)));
     129             : 
     130          42 :         return false;
     131             :     }
     132             : 
     133         710 :     return true;
     134             : }
     135             : 
     136             : /*
     137             :  * A role has privileges to set statistics on the relation if any of the
     138             :  * following are true:
     139             :  *   - the role owns the current database and the relation is not shared
     140             :  *   - the role has the MAINTAIN privilege on the relation
     141             :  */
     142             : void
     143        3616 : RangeVarCallbackForStats(const RangeVar *relation,
     144             :                          Oid relId, Oid oldRelId, void *arg)
     145             : {
     146        3616 :     Oid        *locked_oid = (Oid *) arg;
     147        3616 :     Oid         table_oid = relId;
     148             :     HeapTuple   tuple;
     149             :     Form_pg_class form;
     150             :     char        relkind;
     151             : 
     152             :     /*
     153             :      * If we previously locked some other index's heap, and the name we're
     154             :      * looking up no longer refers to that relation, release the now-useless
     155             :      * lock.
     156             :      */
     157        3616 :     if (relId != oldRelId && OidIsValid(*locked_oid))
     158             :     {
     159           0 :         UnlockRelationOid(*locked_oid, ShareUpdateExclusiveLock);
     160           0 :         *locked_oid = InvalidOid;
     161             :     }
     162             : 
     163             :     /* If the relation does not exist, there's nothing more to do. */
     164        3616 :     if (!OidIsValid(relId))
     165          12 :         return;
     166             : 
     167             :     /* If the relation does exist, check whether it's an index. */
     168        3604 :     relkind = get_rel_relkind(relId);
     169        3604 :     if (relkind == RELKIND_INDEX ||
     170             :         relkind == RELKIND_PARTITIONED_INDEX)
     171         600 :         table_oid = IndexGetRelation(relId, false);
     172             : 
     173             :     /*
     174             :      * If retrying yields the same OID, there are a couple of extremely
     175             :      * unlikely scenarios we need to handle.
     176             :      */
     177        3604 :     if (relId == oldRelId)
     178             :     {
     179             :         /*
     180             :          * If a previous lookup found an index, but the current lookup did
     181             :          * not, the index was dropped and the OID was reused for something
     182             :          * else between lookups.  In theory, we could simply drop our lock on
     183             :          * the index's parent table and proceed, but in the interest of
     184             :          * avoiding complexity, we just error.
     185             :          */
     186           4 :         if (table_oid == relId && OidIsValid(*locked_oid))
     187           0 :             ereport(ERROR,
     188             :                     (errcode(ERRCODE_UNDEFINED_OBJECT),
     189             :                      errmsg("index \"%s\" was concurrently dropped",
     190             :                             relation->relname)));
     191             : 
     192             :         /*
     193             :          * If the current lookup found an index but a previous lookup either
     194             :          * did not find an index or found one with a different parent
     195             :          * relation, the relation was dropped and the OID was reused for an
     196             :          * index between lookups.  RangeVarGetRelidExtended() will have
     197             :          * already locked the index at this point, so we can't just lock the
     198             :          * newly discovered parent table OID without risking deadlock.  As
     199             :          * above, we just error in this case.
     200             :          */
     201           4 :         if (table_oid != relId && table_oid != *locked_oid)
     202           0 :             ereport(ERROR,
     203             :                     (errcode(ERRCODE_UNDEFINED_OBJECT),
     204             :                      errmsg("index \"%s\" was concurrently created",
     205             :                             relation->relname)));
     206             :     }
     207             : 
     208        3604 :     tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(table_oid));
     209        3604 :     if (!HeapTupleIsValid(tuple))
     210           0 :         elog(ERROR, "cache lookup failed for OID %u", table_oid);
     211        3604 :     form = (Form_pg_class) GETSTRUCT(tuple);
     212             : 
     213             :     /* the relkinds that can be used with ANALYZE */
     214        3604 :     switch (form->relkind)
     215             :     {
     216        3586 :         case RELKIND_RELATION:
     217             :         case RELKIND_MATVIEW:
     218             :         case RELKIND_FOREIGN_TABLE:
     219             :         case RELKIND_PARTITIONED_TABLE:
     220        3586 :             break;
     221          18 :         default:
     222          18 :             ereport(ERROR,
     223             :                     (errcode(ERRCODE_WRONG_OBJECT_TYPE),
     224             :                      errmsg("cannot modify statistics for relation \"%s\"",
     225             :                             NameStr(form->relname)),
     226             :                      errdetail_relkind_not_supported(form->relkind)));
     227             :     }
     228             : 
     229        3586 :     if (form->relisshared)
     230           0 :         ereport(ERROR,
     231             :                 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     232             :                  errmsg("cannot modify statistics for shared relation")));
     233             : 
     234             :     /* Check permissions */
     235        3586 :     if (!object_ownercheck(DatabaseRelationId, MyDatabaseId, GetUserId()))
     236             :     {
     237           0 :         AclResult   aclresult = pg_class_aclcheck(table_oid,
     238             :                                                   GetUserId(),
     239             :                                                   ACL_MAINTAIN);
     240             : 
     241           0 :         if (aclresult != ACLCHECK_OK)
     242           0 :             aclcheck_error(aclresult,
     243           0 :                            get_relkind_objtype(form->relkind),
     244           0 :                            NameStr(form->relname));
     245             :     }
     246             : 
     247        3586 :     ReleaseSysCache(tuple);
     248             : 
     249             :     /* Lock heap before index to avoid deadlock. */
     250        3586 :     if (relId != oldRelId && table_oid != relId)
     251             :     {
     252         600 :         LockRelationOid(table_oid, ShareUpdateExclusiveLock);
     253         600 :         *locked_oid = table_oid;
     254             :     }
     255             : }
     256             : 
     257             : 
     258             : /*
     259             :  * Find the argument number for the given argument name, returning -1 if not
     260             :  * found.
     261             :  */
     262             : static int
     263       25818 : get_arg_by_name(const char *argname, struct StatsArgInfo *arginfo)
     264             : {
     265             :     int         argnum;
     266             : 
     267      124068 :     for (argnum = 0; arginfo[argnum].argname != NULL; argnum++)
     268      124056 :         if (pg_strcasecmp(argname, arginfo[argnum].argname) == 0)
     269       25806 :             return argnum;
     270             : 
     271          12 :     ereport(WARNING,
     272             :             (errmsg("unrecognized argument name: \"%s\"", argname)));
     273             : 
     274          12 :     return -1;
     275             : }
     276             : 
     277             : /*
     278             :  * Ensure that a given argument matched the expected type.
     279             :  */
     280             : static bool
     281       25806 : stats_check_arg_type(const char *argname, Oid argtype, Oid expectedtype)
     282             : {
     283       25806 :     if (argtype != expectedtype)
     284             :     {
     285          24 :         ereport(WARNING,
     286             :                 (errmsg("argument \"%s\" has type %s, expected type %s",
     287             :                         argname, format_type_be(argtype),
     288             :                         format_type_be(expectedtype))));
     289          24 :         return false;
     290             :     }
     291             : 
     292       25782 :     return true;
     293             : }
     294             : 
     295             : /*
     296             :  * Check if attribute of an index is an expression, then retrieve the
     297             :  * expression if is it the case.
     298             :  *
     299             :  * If the attnum specified is known to be an expression, then we must
     300             :  * walk the list attributes up to the specified attnum to get the right
     301             :  * expression.
     302             :  */
     303             : static Node *
     304        1370 : statatt_get_index_expr(Relation rel, int attnum)
     305             : {
     306             :     List       *index_exprs;
     307             :     ListCell   *indexpr_item;
     308             : 
     309             :     /* relation is not an index */
     310        1370 :     if (rel->rd_rel->relkind != RELKIND_INDEX &&
     311        1356 :         rel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
     312        1356 :         return NULL;
     313             : 
     314          14 :     index_exprs = RelationGetIndexExpressions(rel);
     315             : 
     316             :     /* index has no expressions to give */
     317          14 :     if (index_exprs == NIL)
     318           0 :         return NULL;
     319             : 
     320             :     /*
     321             :      * The index's attnum points directly to a relation attnum, hence it is
     322             :      * not an expression attribute.
     323             :      */
     324          14 :     if (rel->rd_index->indkey.values[attnum - 1] != 0)
     325           0 :         return NULL;
     326             : 
     327          14 :     indexpr_item = list_head(rel->rd_indexprs);
     328             : 
     329          14 :     for (int i = 0; i < attnum - 1; i++)
     330           0 :         if (rel->rd_index->indkey.values[i] == 0)
     331           0 :             indexpr_item = lnext(rel->rd_indexprs, indexpr_item);
     332             : 
     333          14 :     if (indexpr_item == NULL)   /* shouldn't happen */
     334           0 :         elog(ERROR, "too few entries in indexprs list");
     335             : 
     336          14 :     return (Node *) lfirst(indexpr_item);
     337             : }
     338             : 
     339             : /*
     340             :  * Translate variadic argument pairs from 'pairs_fcinfo' into a
     341             :  * 'positional_fcinfo' appropriate for calling relation_statistics_update() or
     342             :  * attribute_statistics_update() with positional arguments.
     343             :  *
     344             :  * Caller should have already initialized positional_fcinfo with a size
     345             :  * appropriate for calling the intended positional function, and arginfo
     346             :  * should also match the intended positional function.
     347             :  */
     348             : bool
     349        3642 : stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
     350             :                                  FunctionCallInfo positional_fcinfo,
     351             :                                  struct StatsArgInfo *arginfo)
     352             : {
     353             :     Datum      *args;
     354             :     bool       *argnulls;
     355             :     Oid        *types;
     356             :     int         nargs;
     357        3642 :     bool        result = true;
     358             : 
     359             :     /* clear positional args */
     360       42726 :     for (int i = 0; arginfo[i].argname != NULL; i++)
     361             :     {
     362       39084 :         positional_fcinfo->args[i].value = (Datum) 0;
     363       39084 :         positional_fcinfo->args[i].isnull = true;
     364             :     }
     365             : 
     366        3642 :     nargs = extract_variadic_args(pairs_fcinfo, 0, true,
     367             :                                   &args, &types, &argnulls);
     368             : 
     369        3642 :     if (nargs % 2 != 0)
     370           6 :         ereport(ERROR,
     371             :                 errmsg("variadic arguments must be name/value pairs"),
     372             :                 errhint("Provide an even number of variadic arguments that can be divided into pairs."));
     373             : 
     374             :     /*
     375             :      * For each argument name/value pair, find corresponding positional
     376             :      * argument for the argument name, and assign the argument value to
     377             :      * positional_fcinfo.
     378             :      */
     379       33060 :     for (int i = 0; i < nargs; i += 2)
     380             :     {
     381             :         int         argnum;
     382             :         char       *argname;
     383             : 
     384       29430 :         if (argnulls[i])
     385           6 :             ereport(ERROR,
     386             :                     (errmsg("name at variadic position %d is null", i + 1)));
     387             : 
     388       29424 :         if (types[i] != TEXTOID)
     389           0 :             ereport(ERROR,
     390             :                     (errmsg("name at variadic position %d has type %s, expected type %s",
     391             :                             i + 1, format_type_be(types[i]),
     392             :                             format_type_be(TEXTOID))));
     393             : 
     394       29424 :         if (argnulls[i + 1])
     395         276 :             continue;
     396             : 
     397       29148 :         argname = TextDatumGetCString(args[i]);
     398             : 
     399             :         /*
     400             :          * The 'version' argument is a special case, not handled by arginfo
     401             :          * because it's not a valid positional argument.
     402             :          *
     403             :          * For now, 'version' is accepted but ignored. In the future it can be
     404             :          * used to interpret older statistics properly.
     405             :          */
     406       29148 :         if (pg_strcasecmp(argname, "version") == 0)
     407        3330 :             continue;
     408             : 
     409       25818 :         argnum = get_arg_by_name(argname, arginfo);
     410             : 
     411       51624 :         if (argnum < 0 || !stats_check_arg_type(argname, types[i + 1],
     412       25806 :                                                 arginfo[argnum].argtype))
     413             :         {
     414          36 :             result = false;
     415          36 :             continue;
     416             :         }
     417             : 
     418       25782 :         positional_fcinfo->args[argnum].value = args[i + 1];
     419       25782 :         positional_fcinfo->args[argnum].isnull = false;
     420             :     }
     421             : 
     422        3630 :     return result;
     423             : }
     424             : 
     425             : /*
     426             :  * Derive type information from a relation attribute.
     427             :  *
     428             :  * This is needed for setting most slot statistics for all data types.
     429             :  *
     430             :  * This duplicates the logic in examine_attribute() but it will not skip the
     431             :  * attribute if the attstattarget is 0.
     432             :  *
     433             :  * This information, retrieved from pg_attribute and pg_type with some
     434             :  * specific handling for index expressions, is a prerequisite to calling
     435             :  * any of the other statatt_*() functions.
     436             :  */
     437             : void
     438        1370 : statatt_get_type(Oid reloid, AttrNumber attnum,
     439             :                  Oid *atttypid, int32 *atttypmod,
     440             :                  char *atttyptype, Oid *atttypcoll,
     441             :                  Oid *eq_opr, Oid *lt_opr)
     442             : {
     443        1370 :     Relation    rel = relation_open(reloid, AccessShareLock);
     444             :     Form_pg_attribute attr;
     445             :     HeapTuple   atup;
     446             :     Node       *expr;
     447             :     TypeCacheEntry *typcache;
     448             : 
     449        1370 :     atup = SearchSysCache2(ATTNUM, ObjectIdGetDatum(reloid),
     450             :                            Int16GetDatum(attnum));
     451             : 
     452             :     /* Attribute not found */
     453        1370 :     if (!HeapTupleIsValid(atup))
     454           0 :         ereport(ERROR,
     455             :                 (errcode(ERRCODE_UNDEFINED_COLUMN),
     456             :                  errmsg("column %d of relation \"%s\" does not exist",
     457             :                         attnum, RelationGetRelationName(rel))));
     458             : 
     459        1370 :     attr = (Form_pg_attribute) GETSTRUCT(atup);
     460             : 
     461        1370 :     if (attr->attisdropped)
     462           0 :         ereport(ERROR,
     463             :                 (errcode(ERRCODE_UNDEFINED_COLUMN),
     464             :                  errmsg("column %d of relation \"%s\" does not exist",
     465             :                         attnum, RelationGetRelationName(rel))));
     466             : 
     467        1370 :     expr = statatt_get_index_expr(rel, attr->attnum);
     468             : 
     469             :     /*
     470             :      * When analyzing an expression index, believe the expression tree's type
     471             :      * not the column datatype --- the latter might be the opckeytype storage
     472             :      * type of the opclass, which is not interesting for our purposes.  This
     473             :      * mimics the behavior of examine_attribute().
     474             :      */
     475        1370 :     if (expr == NULL)
     476             :     {
     477        1356 :         *atttypid = attr->atttypid;
     478        1356 :         *atttypmod = attr->atttypmod;
     479        1356 :         *atttypcoll = attr->attcollation;
     480             :     }
     481             :     else
     482             :     {
     483          14 :         *atttypid = exprType(expr);
     484          14 :         *atttypmod = exprTypmod(expr);
     485             : 
     486          14 :         if (OidIsValid(attr->attcollation))
     487           0 :             *atttypcoll = attr->attcollation;
     488             :         else
     489          14 :             *atttypcoll = exprCollation(expr);
     490             :     }
     491        1370 :     ReleaseSysCache(atup);
     492             : 
     493             :     /*
     494             :      * If it's a multirange, step down to the range type, as is done by
     495             :      * multirange_typanalyze().
     496             :      */
     497        1370 :     if (type_is_multirange(*atttypid))
     498           2 :         *atttypid = get_multirange_range(*atttypid);
     499             : 
     500             :     /* finds the right operators even if atttypid is a domain */
     501        1370 :     typcache = lookup_type_cache(*atttypid, TYPECACHE_LT_OPR | TYPECACHE_EQ_OPR);
     502        1370 :     *atttyptype = typcache->typtype;
     503        1370 :     *eq_opr = typcache->eq_opr;
     504        1370 :     *lt_opr = typcache->lt_opr;
     505             : 
     506             :     /*
     507             :      * Special case: collation for tsvector is DEFAULT_COLLATION_OID. See
     508             :      * compute_tsvector_stats().
     509             :      */
     510        1370 :     if (*atttypid == TSVECTOROID)
     511           2 :         *atttypcoll = DEFAULT_COLLATION_OID;
     512             : 
     513        1370 :     relation_close(rel, NoLock);
     514        1370 : }
     515             : 
     516             : /*
     517             :  * Derive element type information from the attribute type.  This information
     518             :  * is needed when the given type is one that contains elements of other types.
     519             :  *
     520             :  * The atttypid and atttyptype should be derived from a previous call to
     521             :  * statatt_get_type().
     522             :  */
     523             : bool
     524          52 : statatt_get_elem_type(Oid atttypid, char atttyptype,
     525             :                       Oid *elemtypid, Oid *elem_eq_opr)
     526             : {
     527             :     TypeCacheEntry *elemtypcache;
     528             : 
     529          52 :     if (atttypid == TSVECTOROID)
     530             :     {
     531             :         /*
     532             :          * Special case: element type for tsvector is text. See
     533             :          * compute_tsvector_stats().
     534             :          */
     535           2 :         *elemtypid = TEXTOID;
     536             :     }
     537             :     else
     538             :     {
     539             :         /* find underlying element type through any domain */
     540          50 :         *elemtypid = get_base_element_type(atttypid);
     541             :     }
     542             : 
     543          52 :     if (!OidIsValid(*elemtypid))
     544          18 :         return false;
     545             : 
     546             :     /* finds the right operator even if elemtypid is a domain */
     547          34 :     elemtypcache = lookup_type_cache(*elemtypid, TYPECACHE_EQ_OPR);
     548          34 :     if (!OidIsValid(elemtypcache->eq_opr))
     549           0 :         return false;
     550             : 
     551          34 :     *elem_eq_opr = elemtypcache->eq_opr;
     552             : 
     553          34 :     return true;
     554             : }
     555             : 
     556             : /*
     557             :  * Build an array with element type elemtypid from a text datum, used as
     558             :  * value of an attribute in a tuple to-be-inserted into pg_statistic.
     559             :  *
     560             :  * The typid and typmod should be derived from a previous call to
     561             :  * statatt_get_type().
     562             :  *
     563             :  * If an error is encountered, capture it and throw a WARNING, with "ok" set
     564             :  * to false.  If the resulting array contains NULLs, raise a WARNING and
     565             :  * set "ok" to false.  When the operation succeeds, set "ok" to true.
     566             :  */
     567             : Datum
     568        1326 : statatt_build_stavalues(const char *staname, FmgrInfo *array_in, Datum d, Oid typid,
     569             :                         int32 typmod, bool *ok)
     570             : {
     571        1326 :     LOCAL_FCINFO(fcinfo, 8);
     572             :     char       *s;
     573             :     Datum       result;
     574        1326 :     ErrorSaveContext escontext = {T_ErrorSaveContext};
     575             : 
     576        1326 :     escontext.details_wanted = true;
     577             : 
     578        1326 :     s = TextDatumGetCString(d);
     579             : 
     580        1326 :     InitFunctionCallInfoData(*fcinfo, array_in, 3, InvalidOid,
     581             :                              (Node *) &escontext, NULL);
     582             : 
     583        1326 :     fcinfo->args[0].value = CStringGetDatum(s);
     584        1326 :     fcinfo->args[0].isnull = false;
     585        1326 :     fcinfo->args[1].value = ObjectIdGetDatum(typid);
     586        1326 :     fcinfo->args[1].isnull = false;
     587        1326 :     fcinfo->args[2].value = Int32GetDatum(typmod);
     588        1326 :     fcinfo->args[2].isnull = false;
     589             : 
     590        1326 :     result = FunctionCallInvoke(fcinfo);
     591             : 
     592        1326 :     pfree(s);
     593             : 
     594        1326 :     if (escontext.error_occurred)
     595             :     {
     596           6 :         escontext.error_data->elevel = WARNING;
     597           6 :         ThrowErrorData(escontext.error_data);
     598           6 :         *ok = false;
     599           6 :         return (Datum) 0;
     600             :     }
     601             : 
     602        1320 :     if (array_contains_nulls(DatumGetArrayTypeP(result)))
     603             :     {
     604           6 :         ereport(WARNING,
     605             :                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     606             :                  errmsg("\"%s\" array must not contain null values", staname)));
     607           6 :         *ok = false;
     608           6 :         return (Datum) 0;
     609             :     }
     610             : 
     611        1314 :     *ok = true;
     612             : 
     613        1314 :     return result;
     614             : }
     615             : 
     616             : /*
     617             :  * Find and update the slot of a stakind, or use the first empty slot.
     618             :  *
     619             :  * Core statistics types expect the stakind value to be one of the
     620             :  * STATISTIC_KIND_* constants defined in pg_statistic.h, but types defined
     621             :  * by extensions are not restricted to those values.
     622             :  *
     623             :  * In the case of core statistics, the required staop is determined by the
     624             :  * stakind given and will either be a hardcoded oid, or the eq/lt operator
     625             :  * derived from statatt_get_type().  Likewise, types defined by extensions
     626             :  * have no such restriction.
     627             :  *
     628             :  * The stacoll value should be either the atttypcoll derived from
     629             :  * statatt_get_type(), or a hardcoded value required by that particular
     630             :  * stakind.
     631             :  *
     632             :  * The value/null pairs for stanumbers and stavalues should be calculated
     633             :  * based on the stakind, using statatt_build_stavalues() or constructed arrays.
     634             :  */
     635             : void
     636        2492 : statatt_set_slot(Datum *values, bool *nulls, bool *replaces,
     637             :                  int16 stakind, Oid staop, Oid stacoll,
     638             :                  Datum stanumbers, bool stanumbers_isnull,
     639             :                  Datum stavalues, bool stavalues_isnull)
     640             : {
     641             :     int         slotidx;
     642        2492 :     int         first_empty = -1;
     643             :     AttrNumber  stakind_attnum;
     644             :     AttrNumber  staop_attnum;
     645             :     AttrNumber  stacoll_attnum;
     646             : 
     647             :     /* find existing slot with given stakind */
     648       14952 :     for (slotidx = 0; slotidx < STATISTIC_NUM_SLOTS; slotidx++)
     649             :     {
     650       12460 :         stakind_attnum = Anum_pg_statistic_stakind1 - 1 + slotidx;
     651             : 
     652       16406 :         if (first_empty < 0 &&
     653        3946 :             DatumGetInt16(values[stakind_attnum]) == 0)
     654        2492 :             first_empty = slotidx;
     655       12460 :         if (DatumGetInt16(values[stakind_attnum]) == stakind)
     656           0 :             break;
     657             :     }
     658             : 
     659        2492 :     if (slotidx >= STATISTIC_NUM_SLOTS && first_empty >= 0)
     660        2492 :         slotidx = first_empty;
     661             : 
     662        2492 :     if (slotidx >= STATISTIC_NUM_SLOTS)
     663           0 :         ereport(ERROR,
     664             :                 (errmsg("maximum number of statistics slots exceeded: %d",
     665             :                         slotidx + 1)));
     666             : 
     667        2492 :     stakind_attnum = Anum_pg_statistic_stakind1 - 1 + slotidx;
     668        2492 :     staop_attnum = Anum_pg_statistic_staop1 - 1 + slotidx;
     669        2492 :     stacoll_attnum = Anum_pg_statistic_stacoll1 - 1 + slotidx;
     670             : 
     671        2492 :     if (DatumGetInt16(values[stakind_attnum]) != stakind)
     672             :     {
     673        2492 :         values[stakind_attnum] = Int16GetDatum(stakind);
     674        2492 :         replaces[stakind_attnum] = true;
     675             :     }
     676        2492 :     if (DatumGetObjectId(values[staop_attnum]) != staop)
     677             :     {
     678        2474 :         values[staop_attnum] = ObjectIdGetDatum(staop);
     679        2474 :         replaces[staop_attnum] = true;
     680             :     }
     681        2492 :     if (DatumGetObjectId(values[stacoll_attnum]) != stacoll)
     682             :     {
     683         644 :         values[stacoll_attnum] = ObjectIdGetDatum(stacoll);
     684         644 :         replaces[stacoll_attnum] = true;
     685             :     }
     686        2492 :     if (!stanumbers_isnull)
     687             :     {
     688        1864 :         values[Anum_pg_statistic_stanumbers1 - 1 + slotidx] = stanumbers;
     689        1864 :         nulls[Anum_pg_statistic_stanumbers1 - 1 + slotidx] = false;
     690        1864 :         replaces[Anum_pg_statistic_stanumbers1 - 1 + slotidx] = true;
     691             :     }
     692        2492 :     if (!stavalues_isnull)
     693             :     {
     694        1314 :         values[Anum_pg_statistic_stavalues1 - 1 + slotidx] = stavalues;
     695        1314 :         nulls[Anum_pg_statistic_stavalues1 - 1 + slotidx] = false;
     696        1314 :         replaces[Anum_pg_statistic_stavalues1 - 1 + slotidx] = true;
     697             :     }
     698        2492 : }
     699             : 
     700             : /*
     701             :  * Initialize values and nulls for a new pg_statistic tuple.
     702             :  *
     703             :  * The caller is responsible for allocating the arrays where the results are
     704             :  * stored, which should be of size Natts_pg_statistic.
     705             :  *
     706             :  * When using this routine for a tuple inserted into pg_statistic, reloid,
     707             :  * attnum and inherited flags should all be set.
     708             :  *
     709             :  * When using this routine for a tuple that is an element of a stxdexpr
     710             :  * array inserted into pg_statistic_ext_data, reloid, attnum and inherited
     711             :  * should be respectively set to InvalidOid, InvalidAttrNumber and false.
     712             :  */
     713             : void
     714        1244 : statatt_init_empty_tuple(Oid reloid, int16 attnum, bool inherited,
     715             :                          Datum *values, bool *nulls, bool *replaces)
     716             : {
     717        1244 :     memset(nulls, true, sizeof(bool) * Natts_pg_statistic);
     718        1244 :     memset(replaces, true, sizeof(bool) * Natts_pg_statistic);
     719             : 
     720             :     /* This must initialize non-NULL attributes */
     721        1244 :     values[Anum_pg_statistic_starelid - 1] = ObjectIdGetDatum(reloid);
     722        1244 :     nulls[Anum_pg_statistic_starelid - 1] = false;
     723        1244 :     values[Anum_pg_statistic_staattnum - 1] = Int16GetDatum(attnum);
     724        1244 :     nulls[Anum_pg_statistic_staattnum - 1] = false;
     725        1244 :     values[Anum_pg_statistic_stainherit - 1] = BoolGetDatum(inherited);
     726        1244 :     nulls[Anum_pg_statistic_stainherit - 1] = false;
     727             : 
     728        1244 :     values[Anum_pg_statistic_stanullfrac - 1] = DEFAULT_STATATT_NULL_FRAC;
     729        1244 :     nulls[Anum_pg_statistic_stanullfrac - 1] = false;
     730        1244 :     values[Anum_pg_statistic_stawidth - 1] = DEFAULT_STATATT_AVG_WIDTH;
     731        1244 :     nulls[Anum_pg_statistic_stawidth - 1] = false;
     732        1244 :     values[Anum_pg_statistic_stadistinct - 1] = DEFAULT_STATATT_N_DISTINCT;
     733        1244 :     nulls[Anum_pg_statistic_stadistinct - 1] = false;
     734             : 
     735             :     /* initialize stakind, staop, and stacoll slots */
     736        7464 :     for (int slotnum = 0; slotnum < STATISTIC_NUM_SLOTS; slotnum++)
     737             :     {
     738        6220 :         values[Anum_pg_statistic_stakind1 + slotnum - 1] = (Datum) 0;
     739        6220 :         nulls[Anum_pg_statistic_stakind1 + slotnum - 1] = false;
     740        6220 :         values[Anum_pg_statistic_staop1 + slotnum - 1] = ObjectIdGetDatum(InvalidOid);
     741        6220 :         nulls[Anum_pg_statistic_staop1 + slotnum - 1] = false;
     742        6220 :         values[Anum_pg_statistic_stacoll1 + slotnum - 1] = ObjectIdGetDatum(InvalidOid);
     743        6220 :         nulls[Anum_pg_statistic_stacoll1 + slotnum - 1] = false;
     744             :     }
     745        1244 : }

Generated by: LCOV version 1.16