LCOV - code coverage report
Current view: top level - src/backend/statistics - extended_stats_funcs.c (source / functions) Hit Total Coverage
Test: PostgreSQL 19devel Lines: 277 291 95.2 %
Date: 2026-02-01 08:17:44 Functions: 9 9 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*-------------------------------------------------------------------------
       2             :  *
       3             :  * extended_stats_funcs.c
       4             :  *    Functions for manipulating extended statistics.
       5             :  *
       6             :  * This file includes the set of facilities required to support the direct
       7             :  * manipulations of extended statistics objects.
       8             :  *
       9             :  * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
      10             :  * Portions Copyright (c) 1994, Regents of the University of California
      11             :  *
      12             :  * IDENTIFICATION
      13             :  *    src/backend/statistics/extended_stats_funcs.c
      14             :  *
      15             :  *-------------------------------------------------------------------------
      16             :  */
      17             : #include "postgres.h"
      18             : 
      19             : #include "access/heapam.h"
      20             : #include "catalog/indexing.h"
      21             : #include "catalog/namespace.h"
      22             : #include "catalog/pg_database.h"
      23             : #include "catalog/pg_statistic_ext.h"
      24             : #include "catalog/pg_statistic_ext_data.h"
      25             : #include "miscadmin.h"
      26             : #include "nodes/makefuncs.h"
      27             : #include "nodes/nodeFuncs.h"
      28             : #include "optimizer/optimizer.h"
      29             : #include "statistics/extended_stats_internal.h"
      30             : #include "statistics/stat_utils.h"
      31             : #include "utils/acl.h"
      32             : #include "utils/array.h"
      33             : #include "utils/builtins.h"
      34             : #include "utils/fmgroids.h"
      35             : #include "utils/lsyscache.h"
      36             : #include "utils/syscache.h"
      37             : 
      38             : 
      39             : /*
      40             :  * Index of the arguments for the SQL functions.
      41             :  */
      42             : enum extended_stats_argnum
      43             : {
      44             :     RELSCHEMA_ARG = 0,
      45             :     RELNAME_ARG,
      46             :     STATSCHEMA_ARG,
      47             :     STATNAME_ARG,
      48             :     INHERITED_ARG,
      49             :     NDISTINCT_ARG,
      50             :     DEPENDENCIES_ARG,
      51             :     MOST_COMMON_VALS_ARG,
      52             :     MOST_COMMON_FREQS_ARG,
      53             :     MOST_COMMON_BASE_FREQS_ARG,
      54             :     NUM_EXTENDED_STATS_ARGS,
      55             : };
      56             : 
      57             : /*
      58             :  * The argument names and type OIDs of the arguments for the SQL
      59             :  * functions.
      60             :  */
      61             : static struct StatsArgInfo extarginfo[] =
      62             : {
      63             :     [RELSCHEMA_ARG] = {"schemaname", TEXTOID},
      64             :     [RELNAME_ARG] = {"relname", TEXTOID},
      65             :     [STATSCHEMA_ARG] = {"statistics_schemaname", TEXTOID},
      66             :     [STATNAME_ARG] = {"statistics_name", TEXTOID},
      67             :     [INHERITED_ARG] = {"inherited", BOOLOID},
      68             :     [NDISTINCT_ARG] = {"n_distinct", PG_NDISTINCTOID},
      69             :     [DEPENDENCIES_ARG] = {"dependencies", PG_DEPENDENCIESOID},
      70             :     [MOST_COMMON_VALS_ARG] = {"most_common_vals", TEXTARRAYOID},
      71             :     [MOST_COMMON_FREQS_ARG] = {"most_common_freqs", FLOAT8ARRAYOID},
      72             :     [MOST_COMMON_BASE_FREQS_ARG] = {"most_common_base_freqs", FLOAT8ARRAYOID},
      73             :     [NUM_EXTENDED_STATS_ARGS] = {0},
      74             : };
      75             : 
      76             : static bool extended_statistics_update(FunctionCallInfo fcinfo);
      77             : 
      78             : static HeapTuple get_pg_statistic_ext(Relation pg_stext, Oid nspoid,
      79             :                                       const char *stxname);
      80             : static bool delete_pg_statistic_ext_data(Oid stxoid, bool inherited);
      81             : 
      82             : /*
      83             :  * Track the extended statistics kinds expected for a pg_statistic_ext
      84             :  * tuple.
      85             :  */
      86             : typedef struct
      87             : {
      88             :     bool        ndistinct;
      89             :     bool        dependencies;
      90             :     bool        mcv;
      91             :     bool        expressions;
      92             : } StakindFlags;
      93             : 
      94             : static void expand_stxkind(HeapTuple tup, StakindFlags *enabled);
      95             : static void upsert_pg_statistic_ext_data(const Datum *values,
      96             :                                          const bool *nulls,
      97             :                                          const bool *replaces);
      98             : 
      99             : static bool check_mcvlist_array(const ArrayType *arr, int argindex,
     100             :                                 int required_ndims, int mcv_length);
     101             : static Datum import_mcv(const ArrayType *mcv_arr,
     102             :                         const ArrayType *freqs_arr,
     103             :                         const ArrayType *base_freqs_arr,
     104             :                         Oid *atttypids, int32 *atttypmods,
     105             :                         Oid *atttypcolls, int numattrs,
     106             :                         bool *ok);
     107             : 
     108             : 
     109             : /*
     110             :  * Fetch a pg_statistic_ext row by name and namespace OID.
     111             :  */
     112             : static HeapTuple
     113         188 : get_pg_statistic_ext(Relation pg_stext, Oid nspoid, const char *stxname)
     114             : {
     115             :     ScanKeyData key[2];
     116             :     SysScanDesc scan;
     117             :     HeapTuple   tup;
     118         188 :     Oid         stxoid = InvalidOid;
     119             : 
     120         188 :     ScanKeyInit(&key[0],
     121             :                 Anum_pg_statistic_ext_stxname,
     122             :                 BTEqualStrategyNumber,
     123             :                 F_NAMEEQ,
     124             :                 CStringGetDatum(stxname));
     125         188 :     ScanKeyInit(&key[1],
     126             :                 Anum_pg_statistic_ext_stxnamespace,
     127             :                 BTEqualStrategyNumber,
     128             :                 F_OIDEQ,
     129             :                 ObjectIdGetDatum(nspoid));
     130             : 
     131             :     /*
     132             :      * Try to find matching pg_statistic_ext row.
     133             :      */
     134         188 :     scan = systable_beginscan(pg_stext,
     135             :                               StatisticExtNameIndexId,
     136             :                               true,
     137             :                               NULL,
     138             :                               2,
     139             :                               key);
     140             : 
     141             :     /* Lookup is based on a unique index, so we get either 0 or 1 tuple. */
     142         188 :     tup = systable_getnext(scan);
     143             : 
     144         188 :     if (HeapTupleIsValid(tup))
     145         176 :         stxoid = ((Form_pg_statistic_ext) GETSTRUCT(tup))->oid;
     146             : 
     147         188 :     systable_endscan(scan);
     148             : 
     149         188 :     if (!OidIsValid(stxoid))
     150          12 :         return NULL;
     151             : 
     152         176 :     return SearchSysCacheCopy1(STATEXTOID, ObjectIdGetDatum(stxoid));
     153             : }
     154             : 
     155             : /*
     156             :  * Decode the stxkind column so that we know which stats types to expect,
     157             :  * returning a StakindFlags set depending on the stats kinds expected by
     158             :  * a pg_statistic_ext tuple.
     159             :  */
     160             : static void
     161         146 : expand_stxkind(HeapTuple tup, StakindFlags *enabled)
     162             : {
     163             :     Datum       datum;
     164             :     ArrayType  *arr;
     165             :     char       *kinds;
     166             : 
     167         146 :     datum = SysCacheGetAttrNotNull(STATEXTOID,
     168             :                                    tup,
     169             :                                    Anum_pg_statistic_ext_stxkind);
     170         146 :     arr = DatumGetArrayTypeP(datum);
     171         146 :     if (ARR_NDIM(arr) != 1 || ARR_HASNULL(arr) || ARR_ELEMTYPE(arr) != CHAROID)
     172           0 :         elog(ERROR, "stxkind is not a one-dimension char array");
     173             : 
     174         146 :     kinds = (char *) ARR_DATA_PTR(arr);
     175             : 
     176         378 :     for (int i = 0; i < ARR_DIMS(arr)[0]; i++)
     177             :     {
     178         232 :         switch (kinds[i])
     179             :         {
     180          48 :             case STATS_EXT_NDISTINCT:
     181          48 :                 enabled->ndistinct = true;
     182          48 :                 break;
     183          56 :             case STATS_EXT_DEPENDENCIES:
     184          56 :                 enabled->dependencies = true;
     185          56 :                 break;
     186          78 :             case STATS_EXT_MCV:
     187          78 :                 enabled->mcv = true;
     188          78 :                 break;
     189          50 :             case STATS_EXT_EXPRESSIONS:
     190          50 :                 enabled->expressions = true;
     191          50 :                 break;
     192           0 :             default:
     193           0 :                 elog(ERROR, "incorrect stxkind %c found", kinds[i]);
     194             :                 break;
     195             :         }
     196             :     }
     197         146 : }
     198             : 
     199             : /*
     200             :  * Perform the actual storage of a pg_statistic_ext_data tuple.
     201             :  */
     202             : static void
     203         146 : upsert_pg_statistic_ext_data(const Datum *values, const bool *nulls,
     204             :                              const bool *replaces)
     205             : {
     206             :     Relation    pg_stextdata;
     207             :     HeapTuple   stxdtup;
     208             :     HeapTuple   newtup;
     209             : 
     210         146 :     pg_stextdata = table_open(StatisticExtDataRelationId, RowExclusiveLock);
     211             : 
     212         146 :     stxdtup = SearchSysCache2(STATEXTDATASTXOID,
     213             :                               values[Anum_pg_statistic_ext_data_stxoid - 1],
     214         146 :                               values[Anum_pg_statistic_ext_data_stxdinherit - 1]);
     215             : 
     216         146 :     if (HeapTupleIsValid(stxdtup))
     217             :     {
     218         126 :         newtup = heap_modify_tuple(stxdtup,
     219             :                                    RelationGetDescr(pg_stextdata),
     220             :                                    values,
     221             :                                    nulls,
     222             :                                    replaces);
     223         126 :         CatalogTupleUpdate(pg_stextdata, &newtup->t_self, newtup);
     224         126 :         ReleaseSysCache(stxdtup);
     225             :     }
     226             :     else
     227             :     {
     228          20 :         newtup = heap_form_tuple(RelationGetDescr(pg_stextdata), values, nulls);
     229          20 :         CatalogTupleInsert(pg_stextdata, newtup);
     230             :     }
     231             : 
     232         146 :     heap_freetuple(newtup);
     233             : 
     234         146 :     CommandCounterIncrement();
     235             : 
     236         146 :     table_close(pg_stextdata, RowExclusiveLock);
     237         146 : }
     238             : 
     239             : /*
     240             :  * Insert or update an extended statistics object.
     241             :  *
     242             :  * Major errors, such as the table not existing or permission errors, are
     243             :  * reported as ERRORs.  There are a couple of paths that generate a WARNING,
     244             :  * like when the statistics object or its schema do not exist, a conversion
     245             :  * failure on one statistic kind, or when other statistic kinds may still
     246             :  * be updated.
     247             :  */
     248             : static bool
     249         212 : extended_statistics_update(FunctionCallInfo fcinfo)
     250             : {
     251             :     char       *relnspname;
     252             :     char       *relname;
     253             :     Oid         nspoid;
     254             :     char       *nspname;
     255             :     char       *stxname;
     256             :     bool        inherited;
     257         212 :     Relation    pg_stext = NULL;
     258         212 :     HeapTuple   tup = NULL;
     259             : 
     260         212 :     StakindFlags enabled = {false, false, false, false};
     261         212 :     StakindFlags has = {false, false, false, false};
     262             : 
     263             :     Form_pg_statistic_ext stxform;
     264             : 
     265         212 :     Datum       values[Natts_pg_statistic_ext_data] = {0};
     266         212 :     bool        nulls[Natts_pg_statistic_ext_data] = {0};
     267         212 :     bool        replaces[Natts_pg_statistic_ext_data] = {0};
     268         212 :     bool        success = true;
     269             :     Datum       exprdatum;
     270             :     bool        isnull;
     271         212 :     List       *exprs = NIL;
     272         212 :     int         numattnums = 0;
     273         212 :     int         numexprs = 0;
     274         212 :     int         numattrs = 0;
     275             : 
     276             :     /* arrays of type info, if we need them */
     277         212 :     Oid        *atttypids = NULL;
     278         212 :     int32      *atttypmods = NULL;
     279         212 :     Oid        *atttypcolls = NULL;
     280             :     Oid         relid;
     281         212 :     Oid         locked_table = InvalidOid;
     282             : 
     283             :     /*
     284             :      * Fill out the StakindFlags "has" structure based on which parameters
     285             :      * were provided to the function.
     286             :      *
     287             :      * The MCV stats composite value is an array of record type, but this is
     288             :      * externally represented as three arrays that must be interleaved into
     289             :      * the array of records (pg_stats_ext stores four arrays,
     290             :      * most_common_val_nulls is built from the contents of most_common_vals).
     291             :      * Therefore, none of the three array values is meaningful unless the
     292             :      * other two are also present and in sync in terms of array length.
     293             :      */
     294         496 :     has.mcv = (!PG_ARGISNULL(MOST_COMMON_VALS_ARG) &&
     295         278 :                !PG_ARGISNULL(MOST_COMMON_FREQS_ARG) &&
     296          66 :                !PG_ARGISNULL(MOST_COMMON_BASE_FREQS_ARG));
     297         212 :     has.ndistinct = !PG_ARGISNULL(NDISTINCT_ARG);
     298         212 :     has.dependencies = !PG_ARGISNULL(DEPENDENCIES_ARG);
     299             : 
     300         212 :     if (RecoveryInProgress())
     301             :     {
     302           0 :         ereport(WARNING,
     303             :                 errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
     304             :                 errmsg("recovery is in progress"),
     305             :                 errhint("Statistics cannot be modified during recovery."));
     306           0 :         return false;
     307             :     }
     308             : 
     309             :     /* relation arguments */
     310         212 :     stats_check_required_arg(fcinfo, extarginfo, RELSCHEMA_ARG);
     311         206 :     relnspname = TextDatumGetCString(PG_GETARG_DATUM(RELSCHEMA_ARG));
     312         206 :     stats_check_required_arg(fcinfo, extarginfo, RELNAME_ARG);
     313         200 :     relname = TextDatumGetCString(PG_GETARG_DATUM(RELNAME_ARG));
     314             : 
     315             :     /* extended statistics arguments */
     316         200 :     stats_check_required_arg(fcinfo, extarginfo, STATSCHEMA_ARG);
     317         194 :     nspname = TextDatumGetCString(PG_GETARG_DATUM(STATSCHEMA_ARG));
     318         194 :     stats_check_required_arg(fcinfo, extarginfo, STATNAME_ARG);
     319         188 :     stxname = TextDatumGetCString(PG_GETARG_DATUM(STATNAME_ARG));
     320         188 :     stats_check_required_arg(fcinfo, extarginfo, INHERITED_ARG);
     321         182 :     inherited = PG_GETARG_BOOL(INHERITED_ARG);
     322             : 
     323             :     /*
     324             :      * First open the relation where we expect to find the statistics.  This
     325             :      * is similar to relation and attribute statistics, so as ACL checks are
     326             :      * done before any locks are taken, even before any attempts related to
     327             :      * the extended stats object.
     328             :      */
     329         182 :     relid = RangeVarGetRelidExtended(makeRangeVar(relnspname, relname, -1),
     330             :                                      ShareUpdateExclusiveLock, 0,
     331             :                                      RangeVarCallbackForStats, &locked_table);
     332             : 
     333         164 :     nspoid = get_namespace_oid(nspname, true);
     334         164 :     if (nspoid == InvalidOid)
     335             :     {
     336           6 :         ereport(WARNING,
     337             :                 errcode(ERRCODE_UNDEFINED_OBJECT),
     338             :                 errmsg("could not find schema \"%s\"", nspname));
     339           6 :         success = false;
     340           6 :         goto cleanup;
     341             :     }
     342             : 
     343         158 :     pg_stext = table_open(StatisticExtRelationId, RowExclusiveLock);
     344         158 :     tup = get_pg_statistic_ext(pg_stext, nspoid, stxname);
     345             : 
     346         158 :     if (!HeapTupleIsValid(tup))
     347             :     {
     348           6 :         ereport(WARNING,
     349             :                 errcode(ERRCODE_UNDEFINED_OBJECT),
     350             :                 errmsg("could not find extended statistics object \"%s\".\"%s\"",
     351             :                        quote_identifier(nspname),
     352             :                        quote_identifier(stxname)));
     353           6 :         success = false;
     354           6 :         goto cleanup;
     355             :     }
     356             : 
     357         152 :     stxform = (Form_pg_statistic_ext) GETSTRUCT(tup);
     358             : 
     359             :     /*
     360             :      * The relation tracked by the stats object has to match with the relation
     361             :      * we have already locked.
     362             :      */
     363         152 :     if (stxform->stxrelid != relid)
     364             :     {
     365           6 :         ereport(WARNING,
     366             :                 errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     367             :                 errmsg("could not restore extended statistics object \"%s\".\"%s\": incorrect relation \"%s\".\"%s\" specified",
     368             :                        quote_identifier(nspname),
     369             :                        quote_identifier(stxname),
     370             :                        quote_identifier(relnspname),
     371             :                        quote_identifier(relname)));
     372             : 
     373           6 :         success = false;
     374           6 :         goto cleanup;
     375             :     }
     376             : 
     377             :     /* Find out what extended statistics kinds we should expect. */
     378         146 :     expand_stxkind(tup, &enabled);
     379         146 :     numattnums = stxform->stxkeys.dim1;
     380             : 
     381             :     /* decode expression (if any) */
     382         146 :     exprdatum = SysCacheGetAttr(STATEXTOID,
     383             :                                 tup,
     384             :                                 Anum_pg_statistic_ext_stxexprs,
     385             :                                 &isnull);
     386         146 :     if (!isnull)
     387             :     {
     388             :         char       *s;
     389             : 
     390          50 :         s = TextDatumGetCString(exprdatum);
     391          50 :         exprs = (List *) stringToNode(s);
     392          50 :         pfree(s);
     393             : 
     394             :         /*
     395             :          * Run the expressions through eval_const_expressions().  This is not
     396             :          * just an optimization, but is necessary, because the planner will be
     397             :          * comparing them to similarly-processed qual clauses, and may fail to
     398             :          * detect valid matches without this.
     399             :          *
     400             :          * We must not use canonicalize_qual(), however, since these are not
     401             :          * qual expressions.
     402             :          */
     403          50 :         exprs = (List *) eval_const_expressions(NULL, (Node *) exprs);
     404             : 
     405             :         /* May as well fix opfuncids too */
     406          50 :         fix_opfuncids((Node *) exprs);
     407             : 
     408             :         /* Compute the number of expression, for input validation. */
     409          50 :         numexprs = list_length(exprs);
     410             :     }
     411             : 
     412         146 :     numattrs = numattnums + numexprs;
     413             : 
     414             :     /*
     415             :      * If the object cannot support ndistinct, we should not have data for it.
     416             :      */
     417         146 :     if (has.ndistinct && !enabled.ndistinct)
     418             :     {
     419           6 :         ereport(WARNING,
     420             :                 errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     421             :                 errmsg("cannot specify parameter \"%s\"",
     422             :                        extarginfo[NDISTINCT_ARG].argname),
     423             :                 errhint("Extended statistics object \"%s\".\"%s\" does not support statistics of this type.",
     424             :                         quote_identifier(nspname),
     425             :                         quote_identifier(stxname)));
     426             : 
     427           6 :         has.ndistinct = false;
     428           6 :         success = false;
     429             :     }
     430             : 
     431             :     /*
     432             :      * If the object cannot support dependencies, we should not have data for
     433             :      * it.
     434             :      */
     435         146 :     if (has.dependencies && !enabled.dependencies)
     436             :     {
     437           6 :         ereport(WARNING,
     438             :                 errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     439             :                 errmsg("cannot specify parameter \"%s\"",
     440             :                        extarginfo[DEPENDENCIES_ARG].argname),
     441             :                 errhint("Extended statistics object \"%s\".\"%s\" does not support statistics of this type.",
     442             :                         quote_identifier(nspname),
     443             :                         quote_identifier(stxname)));
     444           6 :         has.dependencies = false;
     445           6 :         success = false;
     446             :     }
     447             : 
     448             :     /*
     449             :      * If the object cannot hold an MCV value, but any of the MCV parameters
     450             :      * are set, then issue a WARNING and ensure that we do not try to load MCV
     451             :      * stats later.  In pg_stats_ext, most_common_val_nulls, most_common_freqs
     452             :      * and most_common_base_freqs are NULL if most_common_vals is NULL.
     453             :      */
     454         146 :     if (!enabled.mcv)
     455             :     {
     456          68 :         if (!PG_ARGISNULL(MOST_COMMON_VALS_ARG) ||
     457          62 :             !PG_ARGISNULL(MOST_COMMON_FREQS_ARG) ||
     458          62 :             !PG_ARGISNULL(MOST_COMMON_BASE_FREQS_ARG))
     459             :         {
     460           6 :             ereport(WARNING,
     461             :                     errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     462             :                     errmsg("cannot specify parameters \"%s\", \"%s\" or \"%s\"",
     463             :                            extarginfo[MOST_COMMON_VALS_ARG].argname,
     464             :                            extarginfo[MOST_COMMON_FREQS_ARG].argname,
     465             :                            extarginfo[MOST_COMMON_BASE_FREQS_ARG].argname),
     466             :                     errhint("Extended statistics object \"%s\".\"%s\" does not support statistics of this type.",
     467             :                             quote_identifier(nspname),
     468             :                             quote_identifier(stxname)));
     469             : 
     470           6 :             has.mcv = false;
     471           6 :             success = false;
     472             :         }
     473             :     }
     474          78 :     else if (!has.mcv)
     475             :     {
     476             :         /*
     477             :          * If we do not have all of the MCV arrays set while the extended
     478             :          * statistics object expects something, something is wrong.  This
     479             :          * issues a WARNING if a partial input has been provided.
     480             :          */
     481          24 :         if (!PG_ARGISNULL(MOST_COMMON_VALS_ARG) ||
     482          12 :             !PG_ARGISNULL(MOST_COMMON_FREQS_ARG) ||
     483           6 :             !PG_ARGISNULL(MOST_COMMON_BASE_FREQS_ARG))
     484             :         {
     485          18 :             ereport(WARNING,
     486             :                     errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     487             :                     errmsg("could not use \"%s\", \"%s\" and \"%s\": missing one or more parameters",
     488             :                            extarginfo[MOST_COMMON_VALS_ARG].argname,
     489             :                            extarginfo[MOST_COMMON_FREQS_ARG].argname,
     490             :                            extarginfo[MOST_COMMON_BASE_FREQS_ARG].argname));
     491          18 :             success = false;
     492             :         }
     493             :     }
     494             : 
     495             :     /*
     496             :      * Either of these statistic types requires that we supply a semi-filled
     497             :      * VacAttrStatP array.
     498             :      *
     499             :      * It is not possible to use the existing lookup_var_attr_stats() and
     500             :      * examine_attribute() because these functions will skip attributes where
     501             :      * attstattarget is 0, and we may have statistics data to import for those
     502             :      * attributes.
     503             :      */
     504         146 :     if (has.mcv)
     505             :     {
     506          54 :         atttypids = palloc0_array(Oid, numattrs);
     507          54 :         atttypmods = palloc0_array(int32, numattrs);
     508          54 :         atttypcolls = palloc0_array(Oid, numattrs);
     509             : 
     510             :         /*
     511             :          * The leading stxkeys are attribute numbers up through numattnums.
     512             :          * These keys must be in ascending AttNumber order, but we do not rely
     513             :          * on that.
     514             :          */
     515         156 :         for (int i = 0; i < numattnums; i++)
     516             :         {
     517         102 :             AttrNumber  attnum = stxform->stxkeys.values[i];
     518         102 :             HeapTuple   atup = SearchSysCache2(ATTNUM,
     519             :                                                ObjectIdGetDatum(relid),
     520             :                                                Int16GetDatum(attnum));
     521             : 
     522             :             Form_pg_attribute attr;
     523             : 
     524             :             /* Attribute not found */
     525         102 :             if (!HeapTupleIsValid(atup))
     526           0 :                 elog(ERROR, "stxkeys references nonexistent attnum %d", attnum);
     527             : 
     528         102 :             attr = (Form_pg_attribute) GETSTRUCT(atup);
     529             : 
     530         102 :             if (attr->attisdropped)
     531           0 :                 elog(ERROR, "stxkeys references dropped attnum %d", attnum);
     532             : 
     533         102 :             atttypids[i] = attr->atttypid;
     534         102 :             atttypmods[i] = attr->atttypmod;
     535         102 :             atttypcolls[i] = attr->attcollation;
     536         102 :             ReleaseSysCache(atup);
     537             :         }
     538             : 
     539             :         /*
     540             :          * After all the positive number attnums in stxkeys come the negative
     541             :          * numbers (if any) which represent expressions in the order that they
     542             :          * appear in stxdexprs.  Because the expressions are always
     543             :          * monotonically decreasing from -1, there is no point in looking at
     544             :          * the values in stxkeys, it's enough to know how many of them there
     545             :          * are.
     546             :          */
     547          84 :         for (int i = numattnums; i < numattrs; i++)
     548             :         {
     549          30 :             Node       *expr = list_nth(exprs, i - numattnums);
     550             : 
     551          30 :             atttypids[i] = exprType(expr);
     552          30 :             atttypmods[i] = exprTypmod(expr);
     553          30 :             atttypcolls[i] = exprCollation(expr);
     554             :         }
     555             :     }
     556             : 
     557             :     /*
     558             :      * Populate the pg_statistic_ext_data result tuple.
     559             :      */
     560             : 
     561             :     /* Primary Key: cannot be NULL or replaced. */
     562         146 :     values[Anum_pg_statistic_ext_data_stxoid - 1] = ObjectIdGetDatum(stxform->oid);
     563         146 :     nulls[Anum_pg_statistic_ext_data_stxoid - 1] = false;
     564         146 :     values[Anum_pg_statistic_ext_data_stxdinherit - 1] = BoolGetDatum(inherited);
     565         146 :     nulls[Anum_pg_statistic_ext_data_stxdinherit - 1] = false;
     566             : 
     567             :     /* All unspecified parameters will be left unmodified */
     568         146 :     nulls[Anum_pg_statistic_ext_data_stxdndistinct - 1] = true;
     569         146 :     nulls[Anum_pg_statistic_ext_data_stxddependencies - 1] = true;
     570         146 :     nulls[Anum_pg_statistic_ext_data_stxdmcv - 1] = true;
     571         146 :     nulls[Anum_pg_statistic_ext_data_stxdexpr - 1] = true;
     572             : 
     573             :     /*
     574             :      * For each stats kind, deserialize the data at hand and perform a round
     575             :      * of validation.  The resulting tuple is filled with a set of updated
     576             :      * values.
     577             :      */
     578             : 
     579         146 :     if (has.ndistinct)
     580             :     {
     581          42 :         Datum       ndistinct_datum = PG_GETARG_DATUM(NDISTINCT_ARG);
     582          42 :         bytea      *data = DatumGetByteaPP(ndistinct_datum);
     583          42 :         MVNDistinct *ndistinct = statext_ndistinct_deserialize(data);
     584             : 
     585          42 :         if (statext_ndistinct_validate(ndistinct, &stxform->stxkeys,
     586             :                                        numexprs, WARNING))
     587             :         {
     588          30 :             values[Anum_pg_statistic_ext_data_stxdndistinct - 1] = ndistinct_datum;
     589          30 :             nulls[Anum_pg_statistic_ext_data_stxdndistinct - 1] = false;
     590          30 :             replaces[Anum_pg_statistic_ext_data_stxdndistinct - 1] = true;
     591             :         }
     592             :         else
     593          12 :             success = false;
     594             : 
     595          42 :         statext_ndistinct_free(ndistinct);
     596             :     }
     597             : 
     598         146 :     if (has.dependencies)
     599             :     {
     600          38 :         Datum       dependencies_datum = PG_GETARG_DATUM(DEPENDENCIES_ARG);
     601          38 :         bytea      *data = DatumGetByteaPP(dependencies_datum);
     602          38 :         MVDependencies *dependencies = statext_dependencies_deserialize(data);
     603             : 
     604          38 :         if (statext_dependencies_validate(dependencies, &stxform->stxkeys,
     605             :                                           numexprs, WARNING))
     606             :         {
     607          26 :             values[Anum_pg_statistic_ext_data_stxddependencies - 1] = dependencies_datum;
     608          26 :             nulls[Anum_pg_statistic_ext_data_stxddependencies - 1] = false;
     609          26 :             replaces[Anum_pg_statistic_ext_data_stxddependencies - 1] = true;
     610             :         }
     611             :         else
     612          12 :             success = false;
     613             : 
     614          38 :         statext_dependencies_free(dependencies);
     615             :     }
     616             : 
     617         146 :     if (has.mcv)
     618             :     {
     619             :         Datum       datum;
     620          54 :         bool        val_ok = false;
     621             : 
     622          54 :         datum = import_mcv(PG_GETARG_ARRAYTYPE_P(MOST_COMMON_VALS_ARG),
     623          54 :                            PG_GETARG_ARRAYTYPE_P(MOST_COMMON_FREQS_ARG),
     624          54 :                            PG_GETARG_ARRAYTYPE_P(MOST_COMMON_BASE_FREQS_ARG),
     625             :                            atttypids, atttypmods, atttypcolls, numattrs,
     626             :                            &val_ok);
     627             : 
     628          54 :         if (val_ok)
     629             :         {
     630             :             Assert(datum != (Datum) 0);
     631          30 :             values[Anum_pg_statistic_ext_data_stxdmcv - 1] = datum;
     632          30 :             nulls[Anum_pg_statistic_ext_data_stxdmcv - 1] = false;
     633          30 :             replaces[Anum_pg_statistic_ext_data_stxdmcv - 1] = true;
     634             :         }
     635             :         else
     636          24 :             success = false;
     637             :     }
     638             : 
     639         146 :     upsert_pg_statistic_ext_data(values, nulls, replaces);
     640             : 
     641         164 : cleanup:
     642         164 :     if (HeapTupleIsValid(tup))
     643         152 :         heap_freetuple(tup);
     644         164 :     if (pg_stext != NULL)
     645         158 :         table_close(pg_stext, RowExclusiveLock);
     646         164 :     if (atttypids != NULL)
     647          54 :         pfree(atttypids);
     648         164 :     if (atttypmods != NULL)
     649          54 :         pfree(atttypmods);
     650         164 :     if (atttypcolls != NULL)
     651          54 :         pfree(atttypcolls);
     652         164 :     return success;
     653             : }
     654             : 
     655             : /*
     656             :  * Consistency checks to ensure that other mcvlist arrays are in alignment
     657             :  * with the mcv array.
     658             :  */
     659             : static bool
     660          78 : check_mcvlist_array(const ArrayType *arr, int argindex, int required_ndims,
     661             :                     int mcv_length)
     662             : {
     663          78 :     if (ARR_NDIM(arr) != required_ndims)
     664             :     {
     665           0 :         ereport(WARNING,
     666             :                 errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     667             :                 errmsg("could not parse array \"%s\": incorrect number of dimensions (%d required)",
     668             :                        extarginfo[argindex].argname, required_ndims));
     669           0 :         return false;
     670             :     }
     671             : 
     672          78 :     if (array_contains_nulls(arr))
     673             :     {
     674           0 :         ereport(WARNING,
     675             :                 errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     676             :                 errmsg("could not parse array \"%s\": NULL value found",
     677             :                        extarginfo[argindex].argname));
     678           0 :         return false;
     679             :     }
     680             : 
     681          78 :     if (ARR_DIMS(arr)[0] != mcv_length)
     682             :     {
     683          12 :         ereport(WARNING,
     684             :                 errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     685             :                 errmsg("could not parse array \"%s\": incorrect number of elements (same as \"%s\" required)",
     686             :                        extarginfo[argindex].argname,
     687             :                        extarginfo[MOST_COMMON_VALS_ARG].argname));
     688          12 :         return false;
     689             :     }
     690             : 
     691          66 :     return true;
     692             : }
     693             : 
     694             : /*
     695             :  * Create the stxdmcv datum from the equal-sized arrays of most common values,
     696             :  * their null flags, and the frequency and base frequency associated with
     697             :  * each value.
     698             :  */
     699             : static Datum
     700          54 : import_mcv(const ArrayType *mcv_arr, const ArrayType *freqs_arr,
     701             :            const ArrayType *base_freqs_arr, Oid *atttypids, int32 *atttypmods,
     702             :            Oid *atttypcolls, int numattrs, bool *ok)
     703             : {
     704             :     int         nitems;
     705             :     Datum      *mcv_elems;
     706             :     bool       *mcv_nulls;
     707             :     int         check_nummcv;
     708          54 :     Datum       mcv = (Datum) 0;
     709             : 
     710          54 :     *ok = false;
     711             : 
     712             :     /*
     713             :      * mcv_arr is an array of arrays.  Each inner array must have the same
     714             :      * number of elements "numattrs".
     715             :      */
     716          54 :     if (ARR_NDIM(mcv_arr) != 2)
     717             :     {
     718           6 :         ereport(WARNING,
     719             :                 errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     720             :                 errmsg("could not parse array \"%s\": incorrect number of dimensions (%d required)",
     721             :                        extarginfo[MOST_COMMON_VALS_ARG].argname, 2));
     722           6 :         goto mcv_error;
     723             :     }
     724             : 
     725          48 :     if (ARR_DIMS(mcv_arr)[1] != numattrs)
     726             :     {
     727           6 :         ereport(WARNING,
     728             :                 errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     729             :                 errmsg("could not parse array \"%s\": found %d attributes but expected %d",
     730             :                        extarginfo[MOST_COMMON_VALS_ARG].argname,
     731             :                        ARR_DIMS(mcv_arr)[1], numattrs));
     732           6 :         goto mcv_error;
     733             :     }
     734             : 
     735             :     /*
     736             :      * "most_common_freqs" and "most_common_base_freqs" arrays must be of the
     737             :      * same length, one-dimension and cannot contain NULLs.  We use mcv_arr as
     738             :      * the reference array for determining their length.
     739             :      */
     740          42 :     nitems = ARR_DIMS(mcv_arr)[0];
     741          42 :     if (!check_mcvlist_array(freqs_arr, MOST_COMMON_FREQS_ARG, 1, nitems) ||
     742          36 :         !check_mcvlist_array(base_freqs_arr, MOST_COMMON_BASE_FREQS_ARG, 1, nitems))
     743             :     {
     744             :         /* inconsistent input arrays found */
     745          12 :         goto mcv_error;
     746             :     }
     747             : 
     748             :     /*
     749             :      * This part builds the contents for "most_common_val_nulls", based on the
     750             :      * values from "most_common_vals".
     751             :      */
     752          30 :     deconstruct_array_builtin(mcv_arr, TEXTOID, &mcv_elems,
     753             :                               &mcv_nulls, &check_nummcv);
     754             : 
     755          30 :     mcv = statext_mcv_import(WARNING, numattrs,
     756             :                              atttypids, atttypmods, atttypcolls,
     757             :                              nitems, mcv_elems, mcv_nulls,
     758          30 :                              (float8 *) ARR_DATA_PTR(freqs_arr),
     759          30 :                              (float8 *) ARR_DATA_PTR(base_freqs_arr));
     760             : 
     761          30 :     *ok = (mcv != (Datum) 0);
     762             : 
     763          54 : mcv_error:
     764          54 :     return mcv;
     765             : }
     766             : 
     767             : /*
     768             :  * Remove an existing pg_statistic_ext_data row for a given pg_statistic_ext
     769             :  * row and "inherited" pair.
     770             :  */
     771             : static bool
     772          18 : delete_pg_statistic_ext_data(Oid stxoid, bool inherited)
     773             : {
     774          18 :     Relation    sed = table_open(StatisticExtDataRelationId, RowExclusiveLock);
     775             :     HeapTuple   oldtup;
     776          18 :     bool        result = false;
     777             : 
     778             :     /* Is there already a pg_statistic tuple for this attribute? */
     779          18 :     oldtup = SearchSysCache2(STATEXTDATASTXOID,
     780             :                              ObjectIdGetDatum(stxoid),
     781             :                              BoolGetDatum(inherited));
     782             : 
     783          18 :     if (HeapTupleIsValid(oldtup))
     784             :     {
     785          12 :         CatalogTupleDelete(sed, &oldtup->t_self);
     786          12 :         ReleaseSysCache(oldtup);
     787          12 :         result = true;
     788             :     }
     789             : 
     790          18 :     table_close(sed, RowExclusiveLock);
     791             : 
     792          18 :     CommandCounterIncrement();
     793             : 
     794          18 :     return result;
     795             : }
     796             : 
     797             : /*
     798             :  * Restore (insert or replace) statistics for the given statistics object.
     799             :  *
     800             :  * This function accepts variadic arguments in key-value pairs, which are
     801             :  * given to stats_fill_fcinfo_from_arg_pairs to be mapped into positional
     802             :  * arguments.
     803             :  */
     804             : Datum
     805         212 : pg_restore_extended_stats(PG_FUNCTION_ARGS)
     806             : {
     807         212 :     LOCAL_FCINFO(positional_fcinfo, NUM_EXTENDED_STATS_ARGS);
     808         212 :     bool        result = true;
     809             : 
     810         212 :     InitFunctionCallInfoData(*positional_fcinfo, NULL, NUM_EXTENDED_STATS_ARGS,
     811             :                              InvalidOid, NULL, NULL);
     812             : 
     813         212 :     if (!stats_fill_fcinfo_from_arg_pairs(fcinfo, positional_fcinfo, extarginfo))
     814           0 :         result = false;
     815             : 
     816         212 :     if (!extended_statistics_update(positional_fcinfo))
     817         102 :         result = false;
     818             : 
     819         164 :     PG_RETURN_BOOL(result);
     820             : }
     821             : 
     822             : /*
     823             :  * Delete statistics for the given statistics object.
     824             :  */
     825             : Datum
     826          84 : pg_clear_extended_stats(PG_FUNCTION_ARGS)
     827             : {
     828             :     char       *relnspname;
     829             :     char       *relname;
     830             :     char       *nspname;
     831             :     Oid         nspoid;
     832             :     Oid         relid;
     833             :     char       *stxname;
     834             :     bool        inherited;
     835             :     Relation    pg_stext;
     836             :     HeapTuple   tup;
     837             :     Form_pg_statistic_ext stxform;
     838          84 :     Oid         locked_table = InvalidOid;
     839             : 
     840             :     /* relation arguments */
     841          84 :     stats_check_required_arg(fcinfo, extarginfo, RELSCHEMA_ARG);
     842          78 :     relnspname = TextDatumGetCString(PG_GETARG_DATUM(RELSCHEMA_ARG));
     843          78 :     stats_check_required_arg(fcinfo, extarginfo, RELNAME_ARG);
     844          72 :     relname = TextDatumGetCString(PG_GETARG_DATUM(RELNAME_ARG));
     845             : 
     846             :     /* extended statistics arguments */
     847          72 :     stats_check_required_arg(fcinfo, extarginfo, STATSCHEMA_ARG);
     848          66 :     nspname = TextDatumGetCString(PG_GETARG_DATUM(STATSCHEMA_ARG));
     849          66 :     stats_check_required_arg(fcinfo, extarginfo, STATNAME_ARG);
     850          60 :     stxname = TextDatumGetCString(PG_GETARG_DATUM(STATNAME_ARG));
     851          60 :     stats_check_required_arg(fcinfo, extarginfo, INHERITED_ARG);
     852          54 :     inherited = PG_GETARG_BOOL(INHERITED_ARG);
     853             : 
     854          54 :     if (RecoveryInProgress())
     855             :     {
     856           0 :         ereport(WARNING,
     857             :                 errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
     858             :                 errmsg("recovery is in progress"),
     859             :                 errhint("Statistics cannot be modified during recovery."));
     860           0 :         PG_RETURN_VOID();
     861             :     }
     862             : 
     863             :     /*
     864             :      * First open the relation where we expect to find the statistics.  This
     865             :      * is similar to relation and attribute statistics, so as ACL checks are
     866             :      * done before any locks are taken, even before any attempts related to
     867             :      * the extended stats object.
     868             :      */
     869          54 :     relid = RangeVarGetRelidExtended(makeRangeVar(relnspname, relname, -1),
     870             :                                      ShareUpdateExclusiveLock, 0,
     871             :                                      RangeVarCallbackForStats, &locked_table);
     872             : 
     873             :     /* Now check if the namespace of the stats object exists. */
     874          36 :     nspoid = get_namespace_oid(nspname, true);
     875          36 :     if (nspoid == InvalidOid)
     876             :     {
     877           6 :         ereport(WARNING,
     878             :                 errcode(ERRCODE_UNDEFINED_OBJECT),
     879             :                 errmsg("could not find schema \"%s\"", nspname));
     880           6 :         PG_RETURN_VOID();
     881             :     }
     882             : 
     883          30 :     pg_stext = table_open(StatisticExtRelationId, RowExclusiveLock);
     884          30 :     tup = get_pg_statistic_ext(pg_stext, nspoid, stxname);
     885             : 
     886          30 :     if (!HeapTupleIsValid(tup))
     887             :     {
     888           6 :         table_close(pg_stext, RowExclusiveLock);
     889           6 :         ereport(WARNING,
     890             :                 errcode(ERRCODE_UNDEFINED_OBJECT),
     891             :                 errmsg("could not find extended statistics object \"%s\".\"%s\"",
     892             :                        nspname, stxname));
     893           6 :         PG_RETURN_VOID();
     894             :     }
     895             : 
     896          24 :     stxform = (Form_pg_statistic_ext) GETSTRUCT(tup);
     897             : 
     898             :     /*
     899             :      * This should be consistent, based on the lock taken on the table when we
     900             :      * started.
     901             :      */
     902          24 :     if (stxform->stxrelid != relid)
     903             :     {
     904           6 :         table_close(pg_stext, RowExclusiveLock);
     905           6 :         ereport(WARNING,
     906             :                 errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     907             :                 errmsg("could not clear extended statistics object \"%s\".\"%s\": incorrect relation \"%s\".\"%s\" specified",
     908             :                        get_namespace_name(nspoid), stxname,
     909             :                        relnspname, relname));
     910           6 :         PG_RETURN_VOID();
     911             :     }
     912             : 
     913          18 :     delete_pg_statistic_ext_data(stxform->oid, inherited);
     914          18 :     heap_freetuple(tup);
     915             : 
     916          18 :     table_close(pg_stext, RowExclusiveLock);
     917             : 
     918          18 :     PG_RETURN_VOID();
     919             : }

Generated by: LCOV version 1.16