diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 8b81106fa23..2c35252dc06 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -30175,6 +30175,14 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset function is to maintain a consistent function signature to avoid errors when restoring statistics from previous versions. + + To match the behavior of and when updating relation statistics, + pg_restore_relation_stats() does not follow MVCC + transactional semantics (see ). New relation + statistics may be durable even if the transaction aborts, and the + changes are not isolated from other transactions. + Arguments are passed as pairs of argname and argvalue, where diff --git a/src/backend/statistics/relation_stats.c b/src/backend/statistics/relation_stats.c index e619d5cf5b1..264a224a4d8 100644 --- a/src/backend/statistics/relation_stats.c +++ b/src/backend/statistics/relation_stats.c @@ -20,6 +20,7 @@ #include "access/heapam.h" #include "catalog/indexing.h" #include "statistics/stat_utils.h" +#include "utils/fmgroids.h" #include "utils/fmgrprotos.h" #include "utils/syscache.h" @@ -50,25 +51,75 @@ static struct StatsArgInfo relarginfo[] = [NUM_RELATION_STATS_ARGS] = {0} }; -static bool relation_statistics_update(FunctionCallInfo fcinfo, int elevel); +static bool relation_statistics_update(FunctionCallInfo fcinfo, int elevel, + bool inplace); /* * Internal function for modifying statistics for a relation. */ static bool -relation_statistics_update(FunctionCallInfo fcinfo, int elevel) +relation_statistics_update(FunctionCallInfo fcinfo, int elevel, bool inplace) { 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; + int32 relpages = DEFAULT_RELPAGES; + bool update_relpages = false; + float reltuples = DEFAULT_RELTUPLES; + bool update_reltuples = false; + int32 relallvisible = DEFAULT_RELALLVISIBLE; + bool update_relallvisible = false; bool result = true; + if (!PG_ARGISNULL(RELPAGES_ARG)) + { + 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 + update_relpages = true; + } + + if (!PG_ARGISNULL(RELTUPLES_ARG)) + { + 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 + update_reltuples = true; + } + + if (!PG_ARGISNULL(RELALLVISIBLE_ARG)) + { + relallvisible = PG_GETARG_INT32(RELALLVISIBLE_ARG); + + if (relallvisible < 0) + { + ereport(elevel, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("relallvisible cannot be < 0"))); + result = false; + } + else + update_relallvisible = true; + } + stats_check_required_arg(fcinfo, relarginfo, RELATION_ARG); reloid = PG_GETARG_OID(RELATION_ARG); @@ -86,92 +137,99 @@ relation_statistics_update(FunctionCallInfo fcinfo, int elevel) */ crel = table_open(RelationRelationId, RowExclusiveLock); - tupdesc = RelationGetDescr(crel); - ctup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(reloid)); - if (!HeapTupleIsValid(ctup)) + if (inplace) { - ereport(elevel, - (errcode(ERRCODE_OBJECT_IN_USE), - errmsg("pg_class entry for relid %u not found", reloid))); - table_close(crel, RowExclusiveLock); - return false; + HeapTuple ctup = NULL; + ScanKeyData key[1]; + Form_pg_class pgcform; + void *inplace_state = NULL; + bool dirty = false; + + ScanKeyInit(&key[0], Anum_pg_class_oid, BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(reloid)); + systable_inplace_update_begin(crel, ClassOidIndexId, true, NULL, 1, key, + &ctup, &inplace_state); + if (!HeapTupleIsValid(ctup)) + elog(ERROR, "pg_class entry for relid %u vanished while updating statistics", + reloid); + pgcform = (Form_pg_class) GETSTRUCT(ctup); + + if (update_relpages && pgcform->relpages != relpages) + { + pgcform->relpages = relpages; + dirty = true; + } + if (update_reltuples && pgcform->reltuples != reltuples) + { + pgcform->reltuples = reltuples; + dirty = true; + } + if (update_relallvisible && pgcform->relallvisible != relallvisible) + { + pgcform->relallvisible = relallvisible; + dirty = true; + } + + if (dirty) + systable_inplace_update_finish(inplace_state, ctup); + else + systable_inplace_update_cancel(inplace_state); + + heap_freetuple(ctup); } - - pgcform = (Form_pg_class) GETSTRUCT(ctup); - - /* relpages */ - if (!PG_ARGISNULL(RELPAGES_ARG)) + else { - int32 relpages = PG_GETARG_INT32(RELPAGES_ARG); + TupleDesc tupdesc = RelationGetDescr(crel); + HeapTuple ctup; + Form_pg_class pgcform; + int replaces[3] = {0}; + Datum values[3] = {0}; + bool nulls[3] = {0}; + int nreplaces = 0; - /* - * 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) + ctup = SearchSysCache1(RELOID, ObjectIdGetDatum(reloid)); + if (!HeapTupleIsValid(ctup)) { ereport(elevel, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("relpages cannot be < -1"))); - result = false; + (errcode(ERRCODE_OBJECT_IN_USE), + errmsg("pg_class entry for relid %u not found", reloid))); + table_close(crel, RowExclusiveLock); + return false; } - else if (relpages != pgcform->relpages) + pgcform = (Form_pg_class) GETSTRUCT(ctup); + + if (update_relpages && relpages != pgcform->relpages) { - replaces[ncols] = Anum_pg_class_relpages; - values[ncols] = Int32GetDatum(relpages); - ncols++; + replaces[nreplaces] = Anum_pg_class_relpages; + values[nreplaces] = Int32GetDatum(relpages); + nreplaces++; } - } - if (!PG_ARGISNULL(RELTUPLES_ARG)) - { - float reltuples = PG_GETARG_FLOAT4(RELTUPLES_ARG); - - if (reltuples < -1.0) + if (update_reltuples && reltuples != pgcform->reltuples) { - ereport(elevel, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("reltuples cannot be < -1.0"))); - result = false; + replaces[nreplaces] = Anum_pg_class_reltuples; + values[nreplaces] = Float4GetDatum(reltuples); + nreplaces++; } - else if (reltuples != pgcform->reltuples) + + if (update_relallvisible && relallvisible != pgcform->relallvisible) { - replaces[ncols] = Anum_pg_class_reltuples; - values[ncols] = Float4GetDatum(reltuples); - ncols++; + replaces[nreplaces] = Anum_pg_class_relallvisible; + values[nreplaces] = Int32GetDatum(relallvisible); + nreplaces++; } - } - - if (!PG_ARGISNULL(RELALLVISIBLE_ARG)) - { - int32 relallvisible = PG_GETARG_INT32(RELALLVISIBLE_ARG); - - if (relallvisible < 0) + if (nreplaces > 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++; - } - } + HeapTuple newtup; - /* only update pg_class if there is a meaningful change */ - if (ncols > 0) - { - HeapTuple newtup; + newtup = heap_modify_tuple_by_cols(ctup, tupdesc, nreplaces, + replaces, values, nulls); + CatalogTupleUpdate(crel, &newtup->t_self, newtup); + heap_freetuple(newtup); + } - newtup = heap_modify_tuple_by_cols(ctup, tupdesc, ncols, replaces, values, - nulls); - CatalogTupleUpdate(crel, &newtup->t_self, newtup); - heap_freetuple(newtup); + ReleaseSysCache(ctup); } /* release the lock, consistent with vac_update_relstats() */ @@ -188,7 +246,7 @@ relation_statistics_update(FunctionCallInfo fcinfo, int elevel) Datum pg_set_relation_stats(PG_FUNCTION_ARGS) { - relation_statistics_update(fcinfo, ERROR); + relation_statistics_update(fcinfo, ERROR, false); PG_RETURN_VOID(); } @@ -212,7 +270,7 @@ pg_clear_relation_stats(PG_FUNCTION_ARGS) newfcinfo->args[3].value = DEFAULT_RELALLVISIBLE; newfcinfo->args[3].isnull = false; - relation_statistics_update(newfcinfo, ERROR); + relation_statistics_update(newfcinfo, ERROR, false); PG_RETURN_VOID(); } @@ -230,7 +288,7 @@ pg_restore_relation_stats(PG_FUNCTION_ARGS) relarginfo, WARNING)) result = false; - if (!relation_statistics_update(positional_fcinfo, WARNING)) + if (!relation_statistics_update(positional_fcinfo, WARNING, true)) result = false; PG_RETURN_BOOL(result); diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out index aab862c97c7..fb50da1cd83 100644 --- a/src/test/regress/expected/stats_import.out +++ b/src/test/regress/expected/stats_import.out @@ -105,6 +105,47 @@ WHERE oid = 'stats_import.test'::regclass; 18 | 401 | 5 (1 row) +-- test MVCC behavior: changes do not persist after abort (in contrast +-- to pg_restore_relation_stats(), which uses in-place updates). +BEGIN; +SELECT + pg_catalog.pg_set_relation_stats( + relation => 'stats_import.test'::regclass, + relpages => NULL::integer, + reltuples => 4000.0::real, + relallvisible => 4::integer); + pg_set_relation_stats +----------------------- + +(1 row) + +ABORT; +SELECT relpages, reltuples, relallvisible +FROM pg_class +WHERE oid = 'stats_import.test'::regclass; + relpages | reltuples | relallvisible +----------+-----------+--------------- + 18 | 401 | 5 +(1 row) + +BEGIN; +SELECT + pg_catalog.pg_clear_relation_stats( + 'stats_import.test'::regclass); + pg_clear_relation_stats +------------------------- + +(1 row) + +ABORT; +SELECT relpages, reltuples, relallvisible +FROM pg_class +WHERE oid = 'stats_import.test'::regclass; + relpages | reltuples | relallvisible +----------+-----------+--------------- + 18 | 401 | 5 +(1 row) + -- clear SELECT pg_catalog.pg_clear_relation_stats( @@ -705,6 +746,25 @@ WHERE oid = 'stats_import.test'::regclass; (1 row) -- ok: just relpages +SELECT pg_restore_relation_stats( + 'relation', 'stats_import.test'::regclass, + 'version', 150000::integer, + 'relpages', '15'::integer); + pg_restore_relation_stats +--------------------------- + t +(1 row) + +SELECT relpages, reltuples, relallvisible +FROM pg_class +WHERE oid = 'stats_import.test'::regclass; + relpages | reltuples | relallvisible +----------+-----------+--------------- + 15 | 400 | 4 +(1 row) + +-- test non-MVCC behavior: new value should persist after abort +BEGIN; SELECT pg_restore_relation_stats( 'relation', 'stats_import.test'::regclass, 'version', 150000::integer, @@ -714,6 +774,7 @@ SELECT pg_restore_relation_stats( t (1 row) +ABORT; SELECT relpages, reltuples, relallvisible FROM pg_class WHERE oid = 'stats_import.test'::regclass; diff --git a/src/test/regress/sql/stats_import.sql b/src/test/regress/sql/stats_import.sql index 31455b58c1d..d3058bf8f6b 100644 --- a/src/test/regress/sql/stats_import.sql +++ b/src/test/regress/sql/stats_import.sql @@ -76,6 +76,31 @@ SELECT relpages, reltuples, relallvisible FROM pg_class WHERE oid = 'stats_import.test'::regclass; +-- test MVCC behavior: changes do not persist after abort (in contrast +-- to pg_restore_relation_stats(), which uses in-place updates). +BEGIN; +SELECT + pg_catalog.pg_set_relation_stats( + relation => 'stats_import.test'::regclass, + relpages => NULL::integer, + reltuples => 4000.0::real, + relallvisible => 4::integer); +ABORT; + +SELECT relpages, reltuples, relallvisible +FROM pg_class +WHERE oid = 'stats_import.test'::regclass; + +BEGIN; +SELECT + pg_catalog.pg_clear_relation_stats( + 'stats_import.test'::regclass); +ABORT; + +SELECT relpages, reltuples, relallvisible +FROM pg_class +WHERE oid = 'stats_import.test'::regclass; + -- clear SELECT pg_catalog.pg_clear_relation_stats( @@ -565,10 +590,22 @@ FROM pg_class WHERE oid = 'stats_import.test'::regclass; -- ok: just relpages +SELECT pg_restore_relation_stats( + 'relation', 'stats_import.test'::regclass, + 'version', 150000::integer, + 'relpages', '15'::integer); + +SELECT relpages, reltuples, relallvisible +FROM pg_class +WHERE oid = 'stats_import.test'::regclass; + +-- test non-MVCC behavior: new value should persist after abort +BEGIN; SELECT pg_restore_relation_stats( 'relation', 'stats_import.test'::regclass, 'version', 150000::integer, 'relpages', '16'::integer); +ABORT; SELECT relpages, reltuples, relallvisible FROM pg_class