Coverage Report

Created: 2026-08-13 07:12

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/postgres/src/backend/commands/statscmds.c
Line
Count
Source
1
/*-------------------------------------------------------------------------
2
 *
3
 * statscmds.c
4
 *    Commands for creating and altering extended statistics objects
5
 *
6
 * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
7
 * Portions Copyright (c) 1994, Regents of the University of California
8
 *
9
 *
10
 * IDENTIFICATION
11
 *    src/backend/commands/statscmds.c
12
 *
13
 *-------------------------------------------------------------------------
14
 */
15
#include "postgres.h"
16
17
#include "access/htup_details.h"
18
#include "access/relation.h"
19
#include "access/table.h"
20
#include "catalog/catalog.h"
21
#include "catalog/dependency.h"
22
#include "catalog/indexing.h"
23
#include "catalog/namespace.h"
24
#include "catalog/objectaccess.h"
25
#include "catalog/pg_namespace.h"
26
#include "catalog/pg_statistic_ext.h"
27
#include "catalog/pg_statistic_ext_data.h"
28
#include "commands/comment.h"
29
#include "commands/defrem.h"
30
#include "miscadmin.h"
31
#include "nodes/makefuncs.h"
32
#include "nodes/nodeFuncs.h"
33
#include "optimizer/optimizer.h"
34
#include "statistics/statistics.h"
35
#include "utils/acl.h"
36
#include "utils/builtins.h"
37
#include "utils/inval.h"
38
#include "utils/lsyscache.h"
39
#include "utils/rel.h"
40
#include "utils/syscache.h"
41
#include "utils/typcache.h"
42
43
44
static char *ChooseExtendedStatisticName(const char *name1, const char *name2,
45
                     const char *label, Oid namespaceid);
46
static char *ChooseExtendedStatisticNameAddition(List *exprs);
47
48
49
/* qsort comparator for the attnums in CreateStatistics */
50
static int
51
compare_int16(const void *a, const void *b)
52
0
{
53
0
  int     av = *(const int16 *) a;
54
0
  int     bv = *(const int16 *) b;
55
56
  /* this can't overflow if int is wider than int16 */
57
0
  return (av - bv);
58
0
}
59
60
/*
61
 *    CREATE STATISTICS
62
 *
63
 * relids is a list of OIDs of relations specified in the FROM clause, on which
64
 * the statistics object is defined. We identify the target by the passed-in
65
 * OID rather than re-resolving stmt->relations by name, so that we operate
66
 * on exactly the relation the caller looked up. Only a single relation is
67
 * supported for now.
68
 */
69
ObjectAddress
70
CreateStatistics(List *relids, CreateStatsStmt *stmt, bool check_rights)
71
0
{
72
0
  int16   attnums[STATS_MAX_DIMENSIONS];
73
0
  int     nattnums = 0;
74
0
  int     numcols;
75
0
  char     *namestr;
76
0
  NameData  stxname;
77
0
  Oid     statoid;
78
0
  Oid     namespaceId;
79
0
  Oid     stxowner = OidIsValid(stmt->owner) ? stmt->owner : GetUserId();
80
0
  HeapTuple htup;
81
0
  Datum   values[Natts_pg_statistic_ext];
82
0
  bool    nulls[Natts_pg_statistic_ext];
83
0
  int2vector *stxkeys;
84
0
  List     *stxexprs = NIL;
85
0
  Datum   exprsDatum;
86
0
  Relation  statrel;
87
0
  Relation  rel = NULL;
88
0
  Oid     relid = InvalidOid;
89
0
  ObjectAddress parentobject,
90
0
        myself;
91
0
  Datum   types[4];   /* one for each possible type of statistic */
92
0
  int     ntypes;
93
0
  ArrayType  *stxkind;
94
0
  bool    build_ndistinct;
95
0
  bool    build_dependencies;
96
0
  bool    build_mcv;
97
0
  bool    build_expressions;
98
0
  bool    requested_type = false;
99
0
  int     i;
100
0
  ListCell   *cell;
101
0
  ListCell   *cell2;
102
103
0
  Assert(IsA(stmt, CreateStatsStmt));
104
105
  /*
106
   * Currently, we only allow the FROM clause to be a single simple table,
107
   * but later we'll probably allow multiple tables and JOIN syntax.  The
108
   * grammar and the loop below are already prepared for that, but examining
109
   * the FROM clause is the caller's job, so all we do here is assert that
110
   * the caller rejected what we can't support.
111
   */
112
0
  Assert(list_length(relids) == 1);
113
114
0
  foreach(cell, relids)
115
0
  {
116
0
    relid = lfirst_oid(cell);
117
118
    /*
119
     * CREATE STATISTICS will influence future execution plans but does
120
     * not interfere with currently executing plans.  So it should be
121
     * enough to take only ShareUpdateExclusiveLock on relation,
122
     * conflicting with ANALYZE and other DDL that sets statistical
123
     * information, but not with normal queries.
124
     */
125
0
    rel = relation_open(relid, ShareUpdateExclusiveLock);
126
127
    /* Restrict to allowed relation types */
128
0
    if (rel->rd_rel->relkind != RELKIND_RELATION &&
129
0
      rel->rd_rel->relkind != RELKIND_MATVIEW &&
130
0
      rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
131
0
      rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
132
0
      ereport(ERROR,
133
0
          (errcode(ERRCODE_WRONG_OBJECT_TYPE),
134
0
           errmsg("cannot define statistics for relation \"%s\"",
135
0
              RelationGetRelationName(rel)),
136
0
           errdetail_relkind_not_supported(rel->rd_rel->relkind)));
137
138
    /*
139
     * You must own the relation to create stats on it. Skip check if
140
     * caller doesn't want it.
141
     */
142
0
    if (check_rights &&
143
0
      !object_ownercheck(RelationRelationId, RelationGetRelid(rel), stxowner))
144
0
      aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(rel->rd_rel->relkind),
145
0
               RelationGetRelationName(rel));
146
147
    /*
148
     * Conflict log tables are system-managed tables used internally for
149
     * logical replication conflict logging. Unlike user tables, they are
150
     * not expected to have complex query usage, so to keep things simple,
151
     * user-defined extended statistics are not required or supported at
152
     * present.
153
     */
154
0
    if (IsConflictLogTableClass(rel->rd_rel))
155
0
      ereport(ERROR,
156
0
          (errcode(ERRCODE_WRONG_OBJECT_TYPE),
157
0
           errmsg("cannot create statistics on conflict log table \"%s\"",
158
0
              RelationGetRelationName(rel)),
159
0
           errdetail("Conflict log tables are system-managed tables for logical replication conflicts.")));
160
161
    /* Creating statistics on system catalogs is not allowed */
162
0
    if (!allowSystemTableMods && IsSystemRelation(rel))
163
0
      ereport(ERROR,
164
0
          (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
165
0
           errmsg("permission denied: \"%s\" is a system catalog",
166
0
              RelationGetRelationName(rel))));
167
0
  }
168
169
0
  Assert(rel);
170
171
  /*
172
   * If the node has a name, split it up and determine creation namespace.
173
   * If not, put the object in the same namespace as the relation, and cons
174
   * up a name for it.  (This can happen either via "CREATE STATISTICS ..."
175
   * or via "CREATE TABLE ... (LIKE)".)
176
   */
177
0
  if (stmt->defnames)
178
0
    namespaceId = QualifiedNameGetCreationNamespace(stmt->defnames,
179
0
                            &namestr);
180
0
  else
181
0
  {
182
0
    namespaceId = RelationGetNamespace(rel);
183
0
    namestr = ChooseExtendedStatisticName(RelationGetRelationName(rel),
184
0
                        ChooseExtendedStatisticNameAddition(stmt->exprs),
185
0
                        "stat",
186
0
                        namespaceId);
187
0
  }
188
0
  namestrcpy(&stxname, namestr);
189
190
  /*
191
   * Check we have creation rights in target namespace.  Skip check if
192
   * caller doesn't want it.
193
   */
194
0
  if (check_rights)
195
0
  {
196
0
    AclResult aclresult;
197
198
0
    aclresult = object_aclcheck(NamespaceRelationId, namespaceId,
199
0
                  GetUserId(), ACL_CREATE);
200
0
    if (aclresult != ACLCHECK_OK)
201
0
      aclcheck_error(aclresult, OBJECT_SCHEMA,
202
0
               get_namespace_name(namespaceId));
203
0
  }
204
205
  /*
206
   * Deal with the possibility that the statistics object already exists.
207
   */
208
0
  if (SearchSysCacheExists2(STATEXTNAMENSP,
209
0
                CStringGetDatum(namestr),
210
0
                ObjectIdGetDatum(namespaceId)))
211
0
  {
212
0
    if (stmt->if_not_exists)
213
0
    {
214
      /*
215
       * Since stats objects aren't members of extensions (see comments
216
       * below), no need for checkMembershipInCurrentExtension here.
217
       */
218
0
      ereport(NOTICE,
219
0
          (errcode(ERRCODE_DUPLICATE_OBJECT),
220
0
           errmsg("statistics object \"%s\" already exists, skipping",
221
0
              namestr)));
222
0
      relation_close(rel, NoLock);
223
0
      return InvalidObjectAddress;
224
0
    }
225
226
0
    ereport(ERROR,
227
0
        (errcode(ERRCODE_DUPLICATE_OBJECT),
228
0
         errmsg("statistics object \"%s\" already exists", namestr)));
229
0
  }
230
231
  /*
232
   * Make sure no more than STATS_MAX_DIMENSIONS columns are used. There
233
   * might be duplicates and so on, but we'll deal with those later.
234
   */
235
0
  numcols = list_length(stmt->exprs);
236
0
  if (numcols > STATS_MAX_DIMENSIONS)
237
0
    ereport(ERROR,
238
0
        (errcode(ERRCODE_TOO_MANY_COLUMNS),
239
0
         errmsg("cannot have more than %d columns in statistics",
240
0
            STATS_MAX_DIMENSIONS)));
241
242
  /*
243
   * Convert the expression list to a simple array of attnums, but also keep
244
   * a list of more complex expressions.  While at it, enforce some
245
   * constraints - we don't allow extended statistics on system attributes,
246
   * and we require the data type to have a less-than operator, if we're
247
   * building multivariate statistics.
248
   *
249
   * There are many ways to "mask" a simple attribute reference as an
250
   * expression, for example "(a+0)" etc. We can't possibly detect all of
251
   * them, but we handle at least the simple case with the attribute in
252
   * parens. There'll always be a way around this, if the user is determined
253
   * (like the "(a+0)" example), but this makes it somewhat consistent with
254
   * how indexes treat attributes/expressions.
255
   */
256
0
  foreach(cell, stmt->exprs)
257
0
  {
258
0
    StatsElem  *selem = lfirst_node(StatsElem, cell);
259
260
0
    if (selem->name)   /* column reference */
261
0
    {
262
0
      char     *attname;
263
0
      HeapTuple atttuple;
264
0
      Form_pg_attribute attForm;
265
0
      TypeCacheEntry *type;
266
267
0
      attname = selem->name;
268
269
0
      atttuple = SearchSysCacheAttName(relid, attname);
270
0
      if (!HeapTupleIsValid(atttuple))
271
0
        ereport(ERROR,
272
0
            (errcode(ERRCODE_UNDEFINED_COLUMN),
273
0
             errmsg("column \"%s\" does not exist",
274
0
                attname)));
275
0
      attForm = (Form_pg_attribute) GETSTRUCT(atttuple);
276
277
      /* Disallow use of system attributes in extended stats */
278
0
      if (attForm->attnum <= 0)
279
0
        ereport(ERROR,
280
0
            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
281
0
             errmsg("statistics creation on system columns is not supported")));
282
283
      /*
284
       * Disallow data types without a less-than operator in
285
       * multivariate statistics.
286
       */
287
0
      if (numcols > 1)
288
0
      {
289
0
        type = lookup_type_cache(attForm->atttypid, TYPECACHE_LT_OPR);
290
0
        if (type->lt_opr == InvalidOid)
291
0
          ereport(ERROR,
292
0
              (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
293
0
               errmsg("cannot create multivariate statistics on column \"%s\"",
294
0
                  attname),
295
0
               errdetail("The type %s has no default btree operator class.",
296
0
                     format_type_be(attForm->atttypid))));
297
0
      }
298
299
      /* Treat virtual generated columns as expressions */
300
0
      if (attForm->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
301
0
      {
302
0
        Node     *expr;
303
304
0
        expr = (Node *) makeVar(1,
305
0
                    attForm->attnum,
306
0
                    attForm->atttypid,
307
0
                    attForm->atttypmod,
308
0
                    attForm->attcollation,
309
0
                    0);
310
0
        stxexprs = lappend(stxexprs, expr);
311
0
      }
312
0
      else
313
0
      {
314
0
        attnums[nattnums] = attForm->attnum;
315
0
        nattnums++;
316
0
      }
317
0
      ReleaseSysCache(atttuple);
318
0
    }
319
0
    else if (IsA(selem->expr, Var)) /* column reference in parens */
320
0
    {
321
0
      Var      *var = (Var *) selem->expr;
322
0
      TypeCacheEntry *type;
323
324
      /* Disallow use of system attributes in extended stats */
325
0
      if (var->varattno <= 0)
326
0
        ereport(ERROR,
327
0
            (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
328
0
             errmsg("statistics creation on system columns is not supported")));
329
330
      /*
331
       * Disallow data types without a less-than operator in
332
       * multivariate statistics.
333
       */
334
0
      if (numcols > 1)
335
0
      {
336
0
        type = lookup_type_cache(var->vartype, TYPECACHE_LT_OPR);
337
0
        if (type->lt_opr == InvalidOid)
338
0
          ereport(ERROR,
339
0
              (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
340
0
               errmsg("cannot create multivariate statistics on column \"%s\"",
341
0
                  get_attname(relid, var->varattno, false)),
342
0
               errdetail("The type %s has no default btree operator class.",
343
0
                     format_type_be(var->vartype))));
344
0
      }
345
346
      /* Treat virtual generated columns as expressions */
347
0
      if (get_attgenerated(relid, var->varattno) == ATTRIBUTE_GENERATED_VIRTUAL)
348
0
      {
349
0
        stxexprs = lappend(stxexprs, (Node *) var);
350
0
      }
351
0
      else
352
0
      {
353
0
        attnums[nattnums] = var->varattno;
354
0
        nattnums++;
355
0
      }
356
0
    }
357
0
    else          /* expression */
358
0
    {
359
0
      Node     *expr = selem->expr;
360
0
      Oid     atttype;
361
0
      TypeCacheEntry *type;
362
0
      Bitmapset  *attnums = NULL;
363
0
      int     k;
364
365
0
      Assert(expr != NULL);
366
367
0
      pull_varattnos(expr, 1, &attnums);
368
369
0
      k = -1;
370
0
      while ((k = bms_next_member(attnums, k)) >= 0)
371
0
      {
372
0
        AttrNumber  attnum = k + FirstLowInvalidHeapAttributeNumber;
373
374
        /* Disallow expressions referencing system attributes. */
375
0
        if (attnum <= 0)
376
0
          ereport(ERROR,
377
0
              (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
378
0
               errmsg("statistics creation on system columns is not supported")));
379
0
      }
380
381
      /*
382
       * Disallow data types without a less-than operator in
383
       * multivariate statistics.
384
       */
385
0
      if (numcols > 1)
386
0
      {
387
0
        atttype = exprType(expr);
388
0
        type = lookup_type_cache(atttype, TYPECACHE_LT_OPR);
389
0
        if (type->lt_opr == InvalidOid)
390
0
          ereport(ERROR,
391
0
              (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
392
0
               errmsg("cannot create multivariate statistics on this expression"),
393
0
               errdetail("The type %s has no default btree operator class.",
394
0
                     format_type_be(atttype))));
395
0
      }
396
397
0
      stxexprs = lappend(stxexprs, expr);
398
0
    }
399
0
  }
400
401
  /*
402
   * Check that at least two columns were specified in the statement, or
403
   * that we're building statistics on a single expression (or virtual
404
   * generated column).
405
   */
406
0
  if (numcols < 2 && list_length(stxexprs) != 1)
407
0
    ereport(ERROR,
408
0
        errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
409
0
        errmsg("cannot create extended statistics on a single non-virtual column"),
410
0
        errdetail("Univariate statistics are already built for each individual non-virtual table column."));
411
412
  /*
413
   * Parse the statistics kinds (not allowed when building univariate
414
   * statistics).
415
   */
416
0
  if (numcols == 1 && stmt->stat_types != NIL)
417
0
    ereport(ERROR,
418
0
        errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
419
0
        errmsg("cannot specify statistics kinds when building univariate statistics"));
420
421
0
  build_ndistinct = false;
422
0
  build_dependencies = false;
423
0
  build_mcv = false;
424
0
  foreach(cell, stmt->stat_types)
425
0
  {
426
0
    char     *type = strVal(lfirst(cell));
427
428
0
    if (strcmp(type, "ndistinct") == 0)
429
0
    {
430
0
      build_ndistinct = true;
431
0
      requested_type = true;
432
0
    }
433
0
    else if (strcmp(type, "dependencies") == 0)
434
0
    {
435
0
      build_dependencies = true;
436
0
      requested_type = true;
437
0
    }
438
0
    else if (strcmp(type, "mcv") == 0)
439
0
    {
440
0
      build_mcv = true;
441
0
      requested_type = true;
442
0
    }
443
0
    else
444
0
      ereport(ERROR,
445
0
          (errcode(ERRCODE_SYNTAX_ERROR),
446
0
           errmsg("unrecognized statistics kind \"%s\"",
447
0
              type)));
448
0
  }
449
450
  /*
451
   * If no statistic type was specified, build them all (but only when the
452
   * statistics is defined on more than one column/expression).
453
   */
454
0
  if ((!requested_type) && (numcols >= 2))
455
0
  {
456
0
    build_ndistinct = true;
457
0
    build_dependencies = true;
458
0
    build_mcv = true;
459
0
  }
460
461
  /*
462
   * When there are non-trivial expressions, build the expression stats
463
   * automatically. This allows calculating good estimates for stats that
464
   * consider per-clause estimates (e.g. functional dependencies).
465
   */
466
0
  build_expressions = (stxexprs != NIL);
467
468
  /*
469
   * Sort the attnums, which makes detecting duplicates somewhat easier, and
470
   * it does not hurt (it does not matter for the contents, unlike for
471
   * indexes, for example).
472
   */
473
0
  qsort(attnums, nattnums, sizeof(int16), compare_int16);
474
475
  /*
476
   * Check for duplicates in the list of columns. The attnums are sorted so
477
   * just check consecutive elements.
478
   */
479
0
  for (i = 1; i < nattnums; i++)
480
0
  {
481
0
    if (attnums[i] == attnums[i - 1])
482
0
      ereport(ERROR,
483
0
          (errcode(ERRCODE_DUPLICATE_COLUMN),
484
0
           errmsg("duplicate column name in statistics definition")));
485
0
  }
486
487
  /*
488
   * Check for duplicate expressions. We do two loops, counting the
489
   * occurrences of each expression. This is O(N^2) but we only allow small
490
   * number of expressions and it's not executed often.
491
   *
492
   * XXX We don't cross-check attributes and expressions, because it does
493
   * not seem worth it. In principle we could check that expressions don't
494
   * contain trivial attribute references like "(a)", but the reasoning is
495
   * similar to why we don't bother with extracting columns from
496
   * expressions. It's either expensive or very easy to defeat for
497
   * determined user, and there's no risk if we allow such statistics (the
498
   * statistics is useless, but harmless).
499
   */
500
0
  foreach(cell, stxexprs)
501
0
  {
502
0
    Node     *expr1 = (Node *) lfirst(cell);
503
0
    int     cnt = 0;
504
505
0
    foreach(cell2, stxexprs)
506
0
    {
507
0
      Node     *expr2 = (Node *) lfirst(cell2);
508
509
0
      if (equal(expr1, expr2))
510
0
        cnt += 1;
511
0
    }
512
513
    /* every expression should find at least itself */
514
0
    Assert(cnt >= 1);
515
516
0
    if (cnt > 1)
517
0
      ereport(ERROR,
518
0
          (errcode(ERRCODE_DUPLICATE_COLUMN),
519
0
           errmsg("duplicate expression in statistics definition")));
520
0
  }
521
522
  /* Form an int2vector representation of the sorted column list */
523
0
  stxkeys = buildint2vector(attnums, nattnums);
524
525
  /* construct the char array of enabled statistic types */
526
0
  ntypes = 0;
527
0
  if (build_ndistinct)
528
0
    types[ntypes++] = CharGetDatum(STATS_EXT_NDISTINCT);
529
0
  if (build_dependencies)
530
0
    types[ntypes++] = CharGetDatum(STATS_EXT_DEPENDENCIES);
531
0
  if (build_mcv)
532
0
    types[ntypes++] = CharGetDatum(STATS_EXT_MCV);
533
0
  if (build_expressions)
534
0
    types[ntypes++] = CharGetDatum(STATS_EXT_EXPRESSIONS);
535
0
  Assert(ntypes > 0 && ntypes <= lengthof(types));
536
0
  stxkind = construct_array_builtin(types, ntypes, CHAROID);
537
538
  /* convert the expressions (if any) to a text datum */
539
0
  if (stxexprs != NIL)
540
0
  {
541
0
    char     *exprsString;
542
543
0
    exprsString = nodeToString(stxexprs);
544
0
    exprsDatum = CStringGetTextDatum(exprsString);
545
0
    pfree(exprsString);
546
0
  }
547
0
  else
548
0
    exprsDatum = (Datum) 0;
549
550
0
  statrel = table_open(StatisticExtRelationId, RowExclusiveLock);
551
552
  /*
553
   * Everything seems fine, so let's build the pg_statistic_ext tuple.
554
   */
555
0
  memset(values, 0, sizeof(values));
556
0
  memset(nulls, false, sizeof(nulls));
557
558
0
  statoid = GetNewOidWithIndex(statrel, StatisticExtOidIndexId,
559
0
                 Anum_pg_statistic_ext_oid);
560
0
  values[Anum_pg_statistic_ext_oid - 1] = ObjectIdGetDatum(statoid);
561
0
  values[Anum_pg_statistic_ext_stxrelid - 1] = ObjectIdGetDatum(relid);
562
0
  values[Anum_pg_statistic_ext_stxname - 1] = NameGetDatum(&stxname);
563
0
  values[Anum_pg_statistic_ext_stxnamespace - 1] = ObjectIdGetDatum(namespaceId);
564
0
  values[Anum_pg_statistic_ext_stxowner - 1] = ObjectIdGetDatum(stxowner);
565
0
  values[Anum_pg_statistic_ext_stxkeys - 1] = PointerGetDatum(stxkeys);
566
0
  nulls[Anum_pg_statistic_ext_stxstattarget - 1] = true;
567
0
  values[Anum_pg_statistic_ext_stxkind - 1] = PointerGetDatum(stxkind);
568
569
0
  values[Anum_pg_statistic_ext_stxexprs - 1] = exprsDatum;
570
0
  if (exprsDatum == (Datum) 0)
571
0
    nulls[Anum_pg_statistic_ext_stxexprs - 1] = true;
572
573
  /* insert it into pg_statistic_ext */
574
0
  htup = heap_form_tuple(statrel->rd_att, values, nulls);
575
0
  CatalogTupleInsert(statrel, htup);
576
0
  heap_freetuple(htup);
577
578
0
  relation_close(statrel, RowExclusiveLock);
579
580
  /*
581
   * We used to create the pg_statistic_ext_data tuple too, but it's not
582
   * clear what value should the stxdinherit flag have (it depends on
583
   * whether the rel is partitioned, contains data, etc.)
584
   */
585
586
0
  InvokeObjectPostCreateHook(StatisticExtRelationId, statoid, 0);
587
588
  /*
589
   * Invalidate relcache so that others see the new statistics object.
590
   */
591
0
  CacheInvalidateRelcache(rel);
592
593
0
  relation_close(rel, NoLock);
594
595
  /*
596
   * Add an AUTO dependency on each column used in the stats, so that the
597
   * stats object goes away if any or all of them get dropped.
598
   */
599
0
  ObjectAddressSet(myself, StatisticExtRelationId, statoid);
600
601
  /* add dependencies for plain column references */
602
0
  for (i = 0; i < nattnums; i++)
603
0
  {
604
0
    ObjectAddressSubSet(parentobject, RelationRelationId, relid, attnums[i]);
605
0
    recordDependencyOn(&myself, &parentobject, DEPENDENCY_AUTO);
606
0
  }
607
608
  /*
609
   * If there are no dependencies on a column, give the statistics object an
610
   * auto dependency on the whole table.  In most cases, this will be
611
   * redundant, but it might not be if the statistics expressions contain no
612
   * Vars (which might seem strange but possible). This is consistent with
613
   * what we do for indexes in index_create.
614
   *
615
   * XXX We intentionally don't consider the expressions before adding this
616
   * dependency, because recordDependencyOnSingleRelExpr may not create any
617
   * dependencies for whole-row Vars.
618
   */
619
0
  if (!nattnums)
620
0
  {
621
0
    ObjectAddressSet(parentobject, RelationRelationId, relid);
622
0
    recordDependencyOn(&myself, &parentobject, DEPENDENCY_AUTO);
623
0
  }
624
625
  /*
626
   * Store dependencies on anything mentioned in statistics expressions,
627
   * just like we do for index expressions.
628
   */
629
0
  if (stxexprs)
630
0
  {
631
0
    if (check_rights)
632
0
      CheckUsageOnTypesInSingleRelExpr((Node *) stxexprs, relid, GetUserId());
633
634
0
    recordDependencyOnSingleRelExpr(&myself,
635
0
                    (Node *) stxexprs,
636
0
                    relid,
637
0
                    DEPENDENCY_NORMAL,
638
0
                    DEPENDENCY_AUTO, false);
639
0
  }
640
641
  /*
642
   * Also add dependencies on namespace and owner.  These are required
643
   * because the stats object might have a different namespace and/or owner
644
   * than the underlying table(s).
645
   */
646
0
  ObjectAddressSet(parentobject, NamespaceRelationId, namespaceId);
647
0
  recordDependencyOn(&myself, &parentobject, DEPENDENCY_NORMAL);
648
649
0
  recordDependencyOnOwner(StatisticExtRelationId, statoid, stxowner);
650
651
  /*
652
   * XXX probably there should be a recordDependencyOnCurrentExtension call
653
   * here too, but we'd have to add support for ALTER EXTENSION ADD/DROP
654
   * STATISTICS, which is more work than it seems worth.
655
   */
656
657
  /* Add any requested comment */
658
0
  if (stmt->stxcomment != NULL)
659
0
    CreateComments(statoid, StatisticExtRelationId, 0,
660
0
             stmt->stxcomment);
661
662
  /* Return stats object's address */
663
0
  return myself;
664
0
}
665
666
/*
667
 *    ALTER STATISTICS
668
 */
669
ObjectAddress
670
AlterStatistics(AlterStatsStmt *stmt)
671
0
{
672
0
  Relation  rel;
673
0
  Oid     stxoid;
674
0
  HeapTuple oldtup;
675
0
  HeapTuple newtup;
676
0
  Datum   repl_val[Natts_pg_statistic_ext];
677
0
  bool    repl_null[Natts_pg_statistic_ext];
678
0
  bool    repl_repl[Natts_pg_statistic_ext];
679
0
  ObjectAddress address;
680
0
  int     newtarget = 0;
681
0
  bool    newtarget_default;
682
683
  /* -1 was used in previous versions for the default setting */
684
0
  if (stmt->stxstattarget && intVal(stmt->stxstattarget) != -1)
685
0
  {
686
0
    newtarget = intVal(stmt->stxstattarget);
687
0
    newtarget_default = false;
688
0
  }
689
0
  else
690
0
    newtarget_default = true;
691
692
0
  if (!newtarget_default)
693
0
  {
694
    /* Limit statistics target to a sane range */
695
0
    if (newtarget < 0)
696
0
    {
697
0
      ereport(ERROR,
698
0
          (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
699
0
           errmsg("statistics target %d is too low",
700
0
              newtarget)));
701
0
    }
702
0
    else if (newtarget > MAX_STATISTICS_TARGET)
703
0
    {
704
0
      newtarget = MAX_STATISTICS_TARGET;
705
0
      ereport(WARNING,
706
0
          (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
707
0
           errmsg("lowering statistics target to %d",
708
0
              newtarget)));
709
0
    }
710
0
  }
711
712
  /* lookup OID of the statistics object */
713
0
  stxoid = get_statistics_object_oid(stmt->defnames, stmt->missing_ok);
714
715
  /*
716
   * If we got here and the OID is not valid, it means the statistics object
717
   * does not exist, but the command specified IF EXISTS. So report this as
718
   * a simple NOTICE and we're done.
719
   */
720
0
  if (!OidIsValid(stxoid))
721
0
  {
722
0
    char     *schemaname;
723
0
    char     *statname;
724
725
0
    Assert(stmt->missing_ok);
726
727
0
    DeconstructQualifiedName(stmt->defnames, &schemaname, &statname);
728
729
0
    if (schemaname)
730
0
      ereport(NOTICE,
731
0
          (errmsg("statistics object \"%s.%s\" does not exist, skipping",
732
0
              schemaname, statname)));
733
0
    else
734
0
      ereport(NOTICE,
735
0
          (errmsg("statistics object \"%s\" does not exist, skipping",
736
0
              statname)));
737
738
0
    return InvalidObjectAddress;
739
0
  }
740
741
  /* Search pg_statistic_ext */
742
0
  rel = table_open(StatisticExtRelationId, RowExclusiveLock);
743
744
0
  oldtup = SearchSysCache1(STATEXTOID, ObjectIdGetDatum(stxoid));
745
0
  if (!HeapTupleIsValid(oldtup))
746
0
    elog(ERROR, "cache lookup failed for extended statistics object %u", stxoid);
747
748
  /* Must be owner of the existing statistics object */
749
0
  if (!object_ownercheck(StatisticExtRelationId, stxoid, GetUserId()))
750
0
    aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_STATISTIC_EXT,
751
0
             NameListToString(stmt->defnames));
752
753
  /* Build new tuple. */
754
0
  memset(repl_val, 0, sizeof(repl_val));
755
0
  memset(repl_null, false, sizeof(repl_null));
756
0
  memset(repl_repl, false, sizeof(repl_repl));
757
758
  /* replace the stxstattarget column */
759
0
  repl_repl[Anum_pg_statistic_ext_stxstattarget - 1] = true;
760
0
  if (!newtarget_default)
761
0
    repl_val[Anum_pg_statistic_ext_stxstattarget - 1] = Int16GetDatum(newtarget);
762
0
  else
763
0
    repl_null[Anum_pg_statistic_ext_stxstattarget - 1] = true;
764
765
0
  newtup = heap_modify_tuple(oldtup, RelationGetDescr(rel),
766
0
                 repl_val, repl_null, repl_repl);
767
768
  /* Update system catalog. */
769
0
  CatalogTupleUpdate(rel, &newtup->t_self, newtup);
770
771
0
  InvokeObjectPostAlterHook(StatisticExtRelationId, stxoid, 0);
772
773
0
  ObjectAddressSet(address, StatisticExtRelationId, stxoid);
774
775
  /*
776
   * NOTE: because we only support altering the statistics target, not the
777
   * other fields, there is no need to update dependencies.
778
   */
779
780
0
  heap_freetuple(newtup);
781
0
  ReleaseSysCache(oldtup);
782
783
0
  table_close(rel, RowExclusiveLock);
784
785
0
  return address;
786
0
}
787
788
/*
789
 * Delete entry in pg_statistic_ext_data catalog. We don't know if the row
790
 * exists, so don't error out.
791
 */
792
void
793
RemoveStatisticsDataById(Oid statsOid, bool inh)
794
0
{
795
0
  Relation  relation;
796
0
  HeapTuple tup;
797
798
0
  relation = table_open(StatisticExtDataRelationId, RowExclusiveLock);
799
800
0
  tup = SearchSysCache2(STATEXTDATASTXOID, ObjectIdGetDatum(statsOid),
801
0
              BoolGetDatum(inh));
802
803
  /* We don't know if the data row for inh value exists. */
804
0
  if (HeapTupleIsValid(tup))
805
0
  {
806
0
    CatalogTupleDelete(relation, &tup->t_self);
807
808
0
    ReleaseSysCache(tup);
809
0
  }
810
811
0
  table_close(relation, RowExclusiveLock);
812
0
}
813
814
/*
815
 * Guts of statistics object deletion.
816
 */
817
void
818
RemoveStatisticsById(Oid statsOid)
819
0
{
820
0
  Relation  relation;
821
0
  Relation  rel;
822
0
  HeapTuple tup;
823
0
  Form_pg_statistic_ext statext;
824
0
  Oid     relid;
825
826
  /*
827
   * Delete the pg_statistic_ext tuple.  Also send out a cache inval on the
828
   * associated table, so that dependent plans will be rebuilt.
829
   */
830
0
  relation = table_open(StatisticExtRelationId, RowExclusiveLock);
831
832
0
  tup = SearchSysCache1(STATEXTOID, ObjectIdGetDatum(statsOid));
833
834
0
  if (!HeapTupleIsValid(tup)) /* should not happen */
835
0
    elog(ERROR, "cache lookup failed for statistics object %u", statsOid);
836
837
0
  statext = (Form_pg_statistic_ext) GETSTRUCT(tup);
838
0
  relid = statext->stxrelid;
839
840
  /*
841
   * Delete the pg_statistic_ext_data tuples holding the actual statistical
842
   * data. There might be data with/without inheritance, so attempt deleting
843
   * both. We lock the user table first, to prevent other processes (e.g.
844
   * DROP STATISTICS) from removing the row concurrently.
845
   */
846
0
  rel = table_open(relid, ShareUpdateExclusiveLock);
847
848
0
  RemoveStatisticsDataById(statsOid, true);
849
0
  RemoveStatisticsDataById(statsOid, false);
850
851
0
  CacheInvalidateRelcacheByRelid(relid);
852
853
0
  CatalogTupleDelete(relation, &tup->t_self);
854
855
0
  ReleaseSysCache(tup);
856
857
  /* Keep lock until the end of the transaction. */
858
0
  table_close(rel, NoLock);
859
860
0
  table_close(relation, RowExclusiveLock);
861
0
}
862
863
/*
864
 * Select a nonconflicting name for a new statistics object.
865
 *
866
 * name1, name2, and label are used the same way as for makeObjectName(),
867
 * except that the label can't be NULL; digits will be appended to the label
868
 * if needed to create a name that is unique within the specified namespace.
869
 *
870
 * Returns a palloc'd string.
871
 *
872
 * Note: it is theoretically possible to get a collision anyway, if someone
873
 * else chooses the same name concurrently.  This is fairly unlikely to be
874
 * a problem in practice, especially if one is holding a share update
875
 * exclusive lock on the relation identified by name1.  However, if choosing
876
 * multiple names within a single command, you'd better create the new object
877
 * and do CommandCounterIncrement before choosing the next one!
878
 */
879
static char *
880
ChooseExtendedStatisticName(const char *name1, const char *name2,
881
              const char *label, Oid namespaceid)
882
0
{
883
0
  int     pass = 0;
884
0
  char     *stxname = NULL;
885
0
  char    modlabel[NAMEDATALEN];
886
887
  /* try the unmodified label first */
888
0
  strlcpy(modlabel, label, sizeof(modlabel));
889
890
0
  for (;;)
891
0
  {
892
0
    Oid     existingstats;
893
894
0
    stxname = makeObjectName(name1, name2, modlabel);
895
896
0
    existingstats = GetSysCacheOid2(STATEXTNAMENSP, Anum_pg_statistic_ext_oid,
897
0
                    PointerGetDatum(stxname),
898
0
                    ObjectIdGetDatum(namespaceid));
899
0
    if (!OidIsValid(existingstats))
900
0
      break;
901
902
    /* found a conflict, so try a new name component */
903
0
    pfree(stxname);
904
0
    snprintf(modlabel, sizeof(modlabel), "%s%d", label, ++pass);
905
0
  }
906
907
0
  return stxname;
908
0
}
909
910
/*
911
 * Generate "name2" for a new statistics object given the list of column
912
 * names for it.  This will be passed to ChooseExtendedStatisticName along
913
 * with the parent table name and a suitable label.
914
 *
915
 * We know that less than NAMEDATALEN characters will actually be used,
916
 * so we can truncate the result once we've generated that many.
917
 *
918
 * XXX see also ChooseForeignKeyConstraintNameAddition and
919
 * ChooseIndexNameAddition.
920
 */
921
static char *
922
ChooseExtendedStatisticNameAddition(List *exprs)
923
0
{
924
0
  char    buf[NAMEDATALEN * 2];
925
0
  int     buflen = 0;
926
0
  ListCell   *lc;
927
928
0
  buf[0] = '\0';
929
0
  foreach(lc, exprs)
930
0
  {
931
0
    StatsElem  *selem = (StatsElem *) lfirst(lc);
932
0
    const char *name;
933
934
    /* It should be one of these, but just skip if it happens not to be */
935
0
    if (!IsA(selem, StatsElem))
936
0
      continue;
937
938
0
    name = selem->name;
939
940
0
    if (buflen > 0)
941
0
      buf[buflen++] = '_'; /* insert _ between names */
942
943
    /*
944
     * We use fixed 'expr' for expressions, which have empty column names.
945
     * For indexes this is handled in ChooseIndexColumnNames, but we have
946
     * no such function for stats and it does not seem worth adding. If a
947
     * better name is needed, the user can specify it explicitly.
948
     */
949
0
    if (!name)
950
0
      name = "expr";
951
952
    /*
953
     * At this point we have buflen <= NAMEDATALEN.  name should be less
954
     * than NAMEDATALEN already, but use strlcpy for paranoia.
955
     */
956
0
    strlcpy(buf + buflen, name, NAMEDATALEN);
957
0
    buflen += strlen(buf + buflen);
958
0
    if (buflen >= NAMEDATALEN)
959
0
      break;
960
0
  }
961
0
  return pstrdup(buf);
962
0
}
963
964
/*
965
 * StatisticsGetRelation: given a statistics object's OID, get the OID of
966
 * the relation it is defined on.  Uses the system cache.
967
 */
968
Oid
969
StatisticsGetRelation(Oid statId, bool missing_ok)
970
0
{
971
0
  HeapTuple tuple;
972
0
  Form_pg_statistic_ext stx;
973
0
  Oid     result;
974
975
0
  tuple = SearchSysCache1(STATEXTOID, ObjectIdGetDatum(statId));
976
0
  if (!HeapTupleIsValid(tuple))
977
0
  {
978
0
    if (missing_ok)
979
0
      return InvalidOid;
980
0
    elog(ERROR, "cache lookup failed for statistics object %u", statId);
981
0
  }
982
0
  stx = (Form_pg_statistic_ext) GETSTRUCT(tuple);
983
0
  Assert(stx->oid == statId);
984
985
0
  result = stx->stxrelid;
986
0
  ReleaseSysCache(tuple);
987
0
  return result;
988
0
}