postgresql/src/backend/statistics/relation_stats.c
Fujii Masao c166454496 Improve error message for database object stats manipulation functions.
Previously, database object statistics manipulation functions like
pg_set_relation_stats() reported unclear error and hint messages
when executed during recovery. These messages were "internal",
making it difficult for users to understand the issue:

  ERROR:  cannot acquire lock mode ShareUpdateExclusiveLock on database objects while recovery is in progress
  HINT:  Only RowExclusiveLock or less can be acquired on database objects during recovery.

This commit updates the error handling so that, if these functions
are called during recovery, they produce clearer messages:

  ERROR:  recovery is in progress
  HINT:  Statistics cannot be modified during recovery.

The related documentation has also been updated to explicitly
clarify that these functions are not available during recovery.

Author: Fujii Masao
Reviewed-by: Heikki Linnakangas, Maxim Orlov
Discussion: https://postgr.es/m/6d313829-5f56-4a28-ae4b-bd01bf1ae791@oss.nttdata.com
2024-11-20 02:00:50 +09:00

237 lines
5.7 KiB
C

/*-------------------------------------------------------------------------
* relation_stats.c
*
* PostgreSQL relation statistics manipulation
*
* Code supporting the direct import of relation statistics, similar to
* what is done by the ANALYZE command.
*
* Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* src/backend/statistics/relation_stats.c
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/heapam.h"
#include "catalog/indexing.h"
#include "statistics/stat_utils.h"
#include "utils/fmgrprotos.h"
#include "utils/syscache.h"
#define DEFAULT_RELPAGES Int32GetDatum(0)
#define DEFAULT_RELTUPLES Float4GetDatum(-1.0)
#define DEFAULT_RELALLVISIBLE Int32GetDatum(0)
/*
* Positional argument numbers, names, and types for
* relation_statistics_update().
*/
enum relation_stats_argnum
{
RELATION_ARG = 0,
RELPAGES_ARG,
RELTUPLES_ARG,
RELALLVISIBLE_ARG,
NUM_RELATION_STATS_ARGS
};
static struct StatsArgInfo relarginfo[] =
{
[RELATION_ARG] = {"relation", REGCLASSOID},
[RELPAGES_ARG] = {"relpages", INT4OID},
[RELTUPLES_ARG] = {"reltuples", FLOAT4OID},
[RELALLVISIBLE_ARG] = {"relallvisible", INT4OID},
[NUM_RELATION_STATS_ARGS] = {0}
};
static bool relation_statistics_update(FunctionCallInfo fcinfo, int elevel);
/*
* Internal function for modifying statistics for a relation.
*/
static bool
relation_statistics_update(FunctionCallInfo fcinfo, int elevel)
{
Oid reloid;
Relation crel;
HeapTuple ctup;
Form_pg_class pgcform;
int replaces[3] = {0};
Datum values[3] = {0};
bool nulls[3] = {0};
int ncols = 0;
TupleDesc tupdesc;
bool result = true;
stats_check_required_arg(fcinfo, relarginfo, RELATION_ARG);
reloid = PG_GETARG_OID(RELATION_ARG);
if (RecoveryInProgress())
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("recovery is in progress"),
errhint("Statistics cannot be modified during recovery.")));
stats_lock_check_privileges(reloid);
/*
* Take RowExclusiveLock on pg_class, consistent with
* vac_update_relstats().
*/
crel = table_open(RelationRelationId, RowExclusiveLock);
tupdesc = RelationGetDescr(crel);
ctup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(reloid));
if (!HeapTupleIsValid(ctup))
{
ereport(elevel,
(errcode(ERRCODE_OBJECT_IN_USE),
errmsg("pg_class entry for relid %u not found", reloid)));
table_close(crel, RowExclusiveLock);
return false;
}
pgcform = (Form_pg_class) GETSTRUCT(ctup);
/* relpages */
if (!PG_ARGISNULL(RELPAGES_ARG))
{
int32 relpages = PG_GETARG_INT32(RELPAGES_ARG);
/*
* Partitioned tables may have relpages=-1. Note: for relations with
* no storage, relpages=-1 is not used consistently, but must be
* supported here.
*/
if (relpages < -1)
{
ereport(elevel,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("relpages cannot be < -1")));
result = false;
}
else if (relpages != pgcform->relpages)
{
replaces[ncols] = Anum_pg_class_relpages;
values[ncols] = Int32GetDatum(relpages);
ncols++;
}
}
if (!PG_ARGISNULL(RELTUPLES_ARG))
{
float reltuples = PG_GETARG_FLOAT4(RELTUPLES_ARG);
if (reltuples < -1.0)
{
ereport(elevel,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("reltuples cannot be < -1.0")));
result = false;
}
else if (reltuples != pgcform->reltuples)
{
replaces[ncols] = Anum_pg_class_reltuples;
values[ncols] = Float4GetDatum(reltuples);
ncols++;
}
}
if (!PG_ARGISNULL(RELALLVISIBLE_ARG))
{
int32 relallvisible = PG_GETARG_INT32(RELALLVISIBLE_ARG);
if (relallvisible < 0)
{
ereport(elevel,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("relallvisible cannot be < 0")));
result = false;
}
else if (relallvisible != pgcform->relallvisible)
{
replaces[ncols] = Anum_pg_class_relallvisible;
values[ncols] = Int32GetDatum(relallvisible);
ncols++;
}
}
/* only update pg_class if there is a meaningful change */
if (ncols > 0)
{
HeapTuple newtup;
newtup = heap_modify_tuple_by_cols(ctup, tupdesc, ncols, replaces, values,
nulls);
CatalogTupleUpdate(crel, &newtup->t_self, newtup);
heap_freetuple(newtup);
}
/* release the lock, consistent with vac_update_relstats() */
table_close(crel, RowExclusiveLock);
CommandCounterIncrement();
return result;
}
/*
* Set statistics for a given pg_class entry.
*/
Datum
pg_set_relation_stats(PG_FUNCTION_ARGS)
{
relation_statistics_update(fcinfo, ERROR);
PG_RETURN_VOID();
}
/*
* Clear statistics for a given pg_class entry; that is, set back to initial
* stats for a newly-created table.
*/
Datum
pg_clear_relation_stats(PG_FUNCTION_ARGS)
{
LOCAL_FCINFO(newfcinfo, 4);
InitFunctionCallInfoData(*newfcinfo, NULL, 4, InvalidOid, NULL, NULL);
newfcinfo->args[0].value = PG_GETARG_OID(0);
newfcinfo->args[0].isnull = PG_ARGISNULL(0);
newfcinfo->args[1].value = DEFAULT_RELPAGES;
newfcinfo->args[1].isnull = false;
newfcinfo->args[2].value = DEFAULT_RELTUPLES;
newfcinfo->args[2].isnull = false;
newfcinfo->args[3].value = DEFAULT_RELALLVISIBLE;
newfcinfo->args[3].isnull = false;
relation_statistics_update(newfcinfo, ERROR);
PG_RETURN_VOID();
}
Datum
pg_restore_relation_stats(PG_FUNCTION_ARGS)
{
LOCAL_FCINFO(positional_fcinfo, NUM_RELATION_STATS_ARGS);
bool result = true;
InitFunctionCallInfoData(*positional_fcinfo, NULL,
NUM_RELATION_STATS_ARGS,
InvalidOid, NULL, NULL);
if (!stats_fill_fcinfo_from_arg_pairs(fcinfo, positional_fcinfo,
relarginfo, WARNING))
result = false;
if (!relation_statistics_update(positional_fcinfo, WARNING))
result = false;
PG_RETURN_BOOL(result);
}