Coverage Report

Created: 2026-11-12 07:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/postgres/src/backend/statistics/relation_stats.c
Line
Count
Source
1
/*-------------------------------------------------------------------------
2
 * relation_stats.c
3
 *
4
 *    PostgreSQL relation statistics manipulation
5
 *
6
 * Code supporting the direct import of relation statistics, similar to
7
 * what is done by the ANALYZE command.
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/relation_stats.c
14
 *
15
 *-------------------------------------------------------------------------
16
 */
17
18
#include "postgres.h"
19
20
#include <math.h>
21
22
#include "access/heapam.h"
23
#include "catalog/indexing.h"
24
#include "catalog/namespace.h"
25
#include "nodes/makefuncs.h"
26
#include "statistics/statistics.h"
27
#include "statistics/stat_utils.h"
28
#include "utils/builtins.h"
29
#include "utils/fmgroids.h"
30
#include "utils/fmgrprotos.h"
31
#include "utils/lsyscache.h"
32
#include "utils/syscache.h"
33
34
35
/*
36
 * Positional argument numbers, names, and types for
37
 * relation_statistics_update().
38
 */
39
40
enum relation_stats_argnum
41
{
42
  RELSCHEMA_ARG = 0,
43
  RELNAME_ARG,
44
  RELPAGES_ARG,
45
  RELTUPLES_ARG,
46
  RELALLVISIBLE_ARG,
47
  RELALLFROZEN_ARG,
48
  NUM_RELATION_STATS_ARGS
49
};
50
51
static struct StatsArgInfo relarginfo[] =
52
{
53
  [RELSCHEMA_ARG] = {"schemaname", TEXTOID},
54
  [RELNAME_ARG] = {"relname", TEXTOID},
55
  [RELPAGES_ARG] = {"relpages", INT4OID},
56
  [RELTUPLES_ARG] = {"reltuples", FLOAT4OID},
57
  [RELALLVISIBLE_ARG] = {"relallvisible", INT4OID},
58
  [RELALLFROZEN_ARG] = {"relallfrozen", INT4OID},
59
  [NUM_RELATION_STATS_ARGS] = {0}
60
};
61
62
static bool relation_statistics_update(FunctionCallInfo fcinfo);
63
static bool relation_statistics_update_internal(Oid reloid,
64
                        FunctionCallInfo fcinfo);
65
66
/*
67
 * Internal function for modifying statistics for a relation.
68
 */
69
static bool
70
relation_statistics_update(FunctionCallInfo fcinfo)
71
0
{
72
0
  char     *nspname;
73
0
  char     *relname;
74
0
  Oid     reloid;
75
0
  Oid     locked_table = InvalidOid;
76
77
0
  stats_check_required_arg(fcinfo, relarginfo, RELSCHEMA_ARG);
78
0
  stats_check_required_arg(fcinfo, relarginfo, RELNAME_ARG);
79
80
0
  nspname = TextDatumGetCString(PG_GETARG_DATUM(RELSCHEMA_ARG));
81
0
  relname = TextDatumGetCString(PG_GETARG_DATUM(RELNAME_ARG));
82
83
0
  if (RecoveryInProgress())
84
0
    ereport(ERROR,
85
0
        (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
86
0
         errmsg("recovery is in progress"),
87
0
         errhint("Statistics cannot be modified during recovery.")));
88
89
0
  reloid = RangeVarGetRelidExtended(makeRangeVar(nspname, relname, -1),
90
0
                    ShareUpdateExclusiveLock, 0,
91
0
                    RangeVarCallbackForStats, &locked_table);
92
93
0
  return relation_statistics_update_internal(reloid, fcinfo);
94
0
}
95
96
/*
97
 * Workhorse function for relation_statistics_update.
98
 */
99
static bool
100
relation_statistics_update_internal(Oid reloid, FunctionCallInfo fcinfo)
101
0
{
102
0
  int32   relpages = 0;
103
0
  bool    update_relpages = false;
104
0
  float4    reltuples = 0;
105
0
  bool    update_reltuples = false;
106
0
  int32   relallvisible = 0;
107
0
  bool    update_relallvisible = false;
108
0
  int32   relallfrozen = 0;
109
0
  bool    update_relallfrozen = false;
110
0
  Relation  crel;
111
0
  HeapTuple ctup;
112
0
  Form_pg_class pgcform;
113
0
  int     replaces[4] = {0};
114
0
  Datum   values[4] = {0};
115
0
  bool    nulls[4] = {0};
116
0
  int     nreplaces = 0;
117
0
  bool    result = true;
118
119
0
  if (!PG_ARGISNULL(RELPAGES_ARG))
120
0
  {
121
0
    relpages = PG_GETARG_INT32(RELPAGES_ARG);
122
0
    update_relpages = true;
123
0
  }
124
125
0
  if (!PG_ARGISNULL(RELTUPLES_ARG))
126
0
  {
127
0
    reltuples = PG_GETARG_FLOAT4(RELTUPLES_ARG);
128
0
    if (isnan(reltuples) || isinf(reltuples))
129
0
    {
130
0
      ereport(WARNING,
131
0
          (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
132
0
           errmsg("argument \"%s\" must be a finite value", "reltuples")));
133
0
      result = false;
134
0
    }
135
0
    else if (reltuples < -1.0)
136
0
    {
137
0
      ereport(WARNING,
138
0
          (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
139
0
           errmsg("argument \"%s\" must not be less than -1.0", "reltuples")));
140
0
      result = false;
141
0
    }
142
0
    else
143
0
      update_reltuples = true;
144
0
  }
145
146
0
  if (!PG_ARGISNULL(RELALLVISIBLE_ARG))
147
0
  {
148
0
    relallvisible = PG_GETARG_INT32(RELALLVISIBLE_ARG);
149
0
    update_relallvisible = true;
150
0
  }
151
152
0
  if (!PG_ARGISNULL(RELALLFROZEN_ARG))
153
0
  {
154
0
    relallfrozen = PG_GETARG_INT32(RELALLFROZEN_ARG);
155
0
    update_relallfrozen = true;
156
0
  }
157
158
  /*
159
   * Take RowExclusiveLock on pg_class, consistent with
160
   * vac_update_relstats().
161
   */
162
0
  crel = table_open(RelationRelationId, RowExclusiveLock);
163
164
0
  ctup = SearchSysCache1(RELOID, ObjectIdGetDatum(reloid));
165
0
  if (!HeapTupleIsValid(ctup))
166
0
    elog(ERROR, "pg_class entry for relid %u not found", reloid);
167
168
0
  pgcform = (Form_pg_class) GETSTRUCT(ctup);
169
170
0
  if (update_relpages && relpages != pgcform->relpages)
171
0
  {
172
0
    replaces[nreplaces] = Anum_pg_class_relpages;
173
0
    values[nreplaces] = Int32GetDatum(relpages);
174
0
    nreplaces++;
175
0
  }
176
177
0
  if (update_reltuples && reltuples != pgcform->reltuples)
178
0
  {
179
0
    replaces[nreplaces] = Anum_pg_class_reltuples;
180
0
    values[nreplaces] = Float4GetDatum(reltuples);
181
0
    nreplaces++;
182
0
  }
183
184
0
  if (update_relallvisible && relallvisible != pgcform->relallvisible)
185
0
  {
186
0
    replaces[nreplaces] = Anum_pg_class_relallvisible;
187
0
    values[nreplaces] = Int32GetDatum(relallvisible);
188
0
    nreplaces++;
189
0
  }
190
191
0
  if (update_relallfrozen && relallfrozen != pgcform->relallfrozen)
192
0
  {
193
0
    replaces[nreplaces] = Anum_pg_class_relallfrozen;
194
0
    values[nreplaces] = Int32GetDatum(relallfrozen);
195
0
    nreplaces++;
196
0
  }
197
198
0
  if (nreplaces > 0)
199
0
  {
200
0
    TupleDesc tupdesc = RelationGetDescr(crel);
201
0
    HeapTuple newtup;
202
203
0
    newtup = heap_modify_tuple_by_cols(ctup, tupdesc, nreplaces,
204
0
                       replaces, values, nulls);
205
0
    CatalogTupleUpdate(crel, &newtup->t_self, newtup);
206
0
    heap_freetuple(newtup);
207
0
  }
208
209
0
  ReleaseSysCache(ctup);
210
211
  /* release the lock, consistent with vac_update_relstats() */
212
0
  table_close(crel, RowExclusiveLock);
213
214
0
  CommandCounterIncrement();
215
216
0
  return result;
217
0
}
218
219
/*
220
 * Clear statistics for a given pg_class entry; that is, set back to initial
221
 * stats for a newly-created table.
222
 */
223
Datum
224
pg_clear_relation_stats(PG_FUNCTION_ARGS)
225
0
{
226
0
  LOCAL_FCINFO(newfcinfo, 6);
227
228
0
  InitFunctionCallInfoData(*newfcinfo, NULL, 6, InvalidOid, NULL, NULL);
229
230
0
  newfcinfo->args[0].value = PG_GETARG_DATUM(0);
231
0
  newfcinfo->args[0].isnull = PG_ARGISNULL(0);
232
0
  newfcinfo->args[1].value = PG_GETARG_DATUM(1);
233
0
  newfcinfo->args[1].isnull = PG_ARGISNULL(1);
234
0
  newfcinfo->args[2].value = Int32GetDatum(0);
235
0
  newfcinfo->args[2].isnull = false;
236
0
  newfcinfo->args[3].value = Float4GetDatum(-1.0);
237
0
  newfcinfo->args[3].isnull = false;
238
0
  newfcinfo->args[4].value = Int32GetDatum(0);
239
0
  newfcinfo->args[4].isnull = false;
240
0
  newfcinfo->args[5].value = Int32GetDatum(0);
241
0
  newfcinfo->args[5].isnull = false;
242
243
0
  relation_statistics_update(newfcinfo);
244
0
  PG_RETURN_VOID();
245
0
}
246
247
Datum
248
pg_restore_relation_stats(PG_FUNCTION_ARGS)
249
0
{
250
0
  LOCAL_FCINFO(positional_fcinfo, NUM_RELATION_STATS_ARGS);
251
0
  bool    result = true;
252
253
0
  InitFunctionCallInfoData(*positional_fcinfo, NULL,
254
0
               NUM_RELATION_STATS_ARGS,
255
0
               InvalidOid, NULL, NULL);
256
257
0
  if (!stats_fill_fcinfo_from_arg_pairs(fcinfo, positional_fcinfo,
258
0
                      relarginfo))
259
0
    result = false;
260
261
0
  if (!relation_statistics_update(positional_fcinfo))
262
0
    result = false;
263
264
0
  PG_RETURN_BOOL(result);
265
0
}
266
267
/*
268
 * Import relation statistics from NullableDatum inputs for all statistical
269
 * values.
270
 *
271
 * For now, the 'version' argument is ignored. In the future it can be used
272
 * to interpret older statistics properly.
273
 */
274
bool
275
import_relation_statistics(Relation rel,
276
               const NullableDatum *version,
277
               const NullableDatum *relpages,
278
               const NullableDatum *reltuples,
279
               const NullableDatum *relallvisible,
280
               const NullableDatum *relallfrozen)
281
0
{
282
0
  LOCAL_FCINFO(newfcinfo, NUM_RELATION_STATS_ARGS);
283
284
0
  Assert(relpages);
285
0
  Assert(reltuples);
286
0
  Assert(relallvisible);
287
0
  Assert(relallfrozen);
288
289
0
  InitFunctionCallInfoData(*newfcinfo, NULL, NUM_RELATION_STATS_ARGS,
290
0
               InvalidOid, NULL, NULL);
291
292
0
  newfcinfo->args[RELSCHEMA_ARG].value =
293
0
    CStringGetTextDatum(get_namespace_name(RelationGetNamespace(rel)));
294
0
  newfcinfo->args[RELSCHEMA_ARG].isnull = false;
295
0
  newfcinfo->args[RELNAME_ARG].value =
296
0
    CStringGetTextDatum(RelationGetRelationName(rel));
297
0
  newfcinfo->args[RELNAME_ARG].isnull = false;
298
299
0
  newfcinfo->args[RELPAGES_ARG] = *relpages;
300
0
  newfcinfo->args[RELTUPLES_ARG] = *reltuples;
301
0
  newfcinfo->args[RELALLVISIBLE_ARG] = *relallvisible;
302
0
  newfcinfo->args[RELALLFROZEN_ARG] = *relallfrozen;
303
304
0
  return relation_statistics_update_internal(RelationGetRelid(rel),
305
0
                         newfcinfo);
306
0
}