#include "postgres.h"
#include "access/relation.h"
+#include "catalog/index.h"
#include "catalog/pg_database.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "statistics/stat_utils.h"
+#include "storage/lmgr.h"
#include "utils/acl.h"
#include "utils/array.h"
#include "utils/builtins.h"
+#include "utils/lsyscache.h"
#include "utils/rel.h"
/*
void
stats_lock_check_privileges(Oid reloid)
{
- Relation rel = relation_open(reloid, ShareUpdateExclusiveLock);
- const char relkind = rel->rd_rel->relkind;
+ Relation table;
+ Oid table_oid = reloid;
+ Oid index_oid = InvalidOid;
+ LOCKMODE index_lockmode = NoLock;
- /* All of the types that can be used with ANALYZE, plus indexes */
- switch (relkind)
+ /*
+ * For indexes, we follow the locking behavior in do_analyze_rel() and
+ * check_inplace_rel_lock(), which is to lock the table first in
+ * ShareUpdateExclusive mode and then the index in AccessShare mode.
+ *
+ * Partitioned indexes are treated differently than normal indexes in
+ * check_inplace_rel_lock(), so we take a ShareUpdateExclusive lock on
+ * both the partitioned table and the partitioned index.
+ */
+ switch (get_rel_relkind(reloid))
{
- case RELKIND_RELATION:
case RELKIND_INDEX:
+ index_oid = reloid;
+ table_oid = IndexGetRelation(index_oid, false);
+ index_lockmode = AccessShareLock;
+ break;
+ case RELKIND_PARTITIONED_INDEX:
+ index_oid = reloid;
+ table_oid = IndexGetRelation(index_oid, false);
+ index_lockmode = ShareUpdateExclusiveLock;
+ break;
+ default:
+ break;
+ }
+
+ table = relation_open(table_oid, ShareUpdateExclusiveLock);
+
+ /* the relkinds that can be used with ANALYZE */
+ switch (table->rd_rel->relkind)
+ {
+ case RELKIND_RELATION:
case RELKIND_MATVIEW:
case RELKIND_FOREIGN_TABLE:
case RELKIND_PARTITIONED_TABLE:
- case RELKIND_PARTITIONED_INDEX:
break;
default:
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("cannot modify statistics for relation \"%s\"",
- RelationGetRelationName(rel)),
- errdetail_relkind_not_supported(rel->rd_rel->relkind)));
+ RelationGetRelationName(table)),
+ errdetail_relkind_not_supported(table->rd_rel->relkind)));
+ }
+
+ if (OidIsValid(index_oid))
+ {
+ Relation index;
+
+ Assert(index_lockmode != NoLock);
+ index = relation_open(index_oid, index_lockmode);
+
+ Assert(index->rd_index && index->rd_index->indrelid == table_oid);
+
+ /* retain lock on index */
+ relation_close(index, NoLock);
}
- if (rel->rd_rel->relisshared)
+ if (table->rd_rel->relisshared)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot modify statistics for shared relation")));
if (!object_ownercheck(DatabaseRelationId, MyDatabaseId, GetUserId()))
{
- AclResult aclresult = pg_class_aclcheck(RelationGetRelid(rel),
+ AclResult aclresult = pg_class_aclcheck(RelationGetRelid(table),
GetUserId(),
ACL_MAINTAIN);
if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult,
- get_relkind_objtype(rel->rd_rel->relkind),
- NameStr(rel->rd_rel->relname));
+ get_relkind_objtype(table->rd_rel->relkind),
+ NameStr(table->rd_rel->relname));
}
- relation_close(rel, NoLock);
+ /* retain lock on table */
+ relation_close(table, NoLock);
}
/*
17 | 400 | 4
(1 row)
+CREATE INDEX test_i ON stats_import.test(id);
+BEGIN;
+-- regular indexes have special case locking rules
+SELECT
+ pg_catalog.pg_set_relation_stats(
+ relation => 'stats_import.test_i'::regclass,
+ relpages => 18::integer);
+ pg_set_relation_stats
+-----------------------
+
+(1 row)
+
+SELECT mode FROM pg_locks
+WHERE relation = 'stats_import.test'::regclass AND
+ pid = pg_backend_pid() AND granted;
+ mode
+--------------------------
+ ShareUpdateExclusiveLock
+(1 row)
+
+SELECT mode FROM pg_locks
+WHERE relation = 'stats_import.test_i'::regclass AND
+ pid = pg_backend_pid() AND granted;
+ mode
+-----------------
+ AccessShareLock
+(1 row)
+
+COMMIT;
+SELECT
+ pg_catalog.pg_restore_relation_stats(
+ 'relation', 'stats_import.test_i'::regclass,
+ 'relpages', 19::integer );
+ pg_restore_relation_stats
+---------------------------
+ t
+(1 row)
+
-- positional arguments
SELECT
pg_catalog.pg_set_relation_stats(
PARTITION OF stats_import.part_parent
FOR VALUES FROM (0) TO (10)
WITH (autovacuum_enabled = false);
+CREATE INDEX part_parent_i ON stats_import.part_parent(i);
ANALYZE stats_import.part_parent;
SELECT relpages
FROM pg_class
-- although partitioned tables have no storage, setting relpages to a
-- positive value is still allowed
+SELECT
+ pg_catalog.pg_set_relation_stats(
+ relation => 'stats_import.part_parent_i'::regclass,
+ relpages => 2::integer);
+ pg_set_relation_stats
+-----------------------
+
+(1 row)
+
SELECT
pg_catalog.pg_set_relation_stats(
relation => 'stats_import.part_parent'::regclass,
(1 row)
+--
+-- Partitioned indexes aren't analyzed but it is possible to set
+-- stats. The locking rules are different from normal indexes due to
+-- the rules for in-place updates: both the partitioned table and the
+-- partitioned index are locked in ShareUpdateExclusive mode.
+--
+BEGIN;
+SELECT
+ pg_catalog.pg_set_relation_stats(
+ relation => 'stats_import.part_parent_i'::regclass,
+ relpages => 2::integer);
+ pg_set_relation_stats
+-----------------------
+
+(1 row)
+
+SELECT mode FROM pg_locks
+WHERE relation = 'stats_import.part_parent'::regclass AND
+ pid = pg_backend_pid() AND granted;
+ mode
+--------------------------
+ ShareUpdateExclusiveLock
+(1 row)
+
+SELECT mode FROM pg_locks
+WHERE relation = 'stats_import.part_parent_i'::regclass AND
+ pid = pg_backend_pid() AND granted;
+ mode
+--------------------------
+ ShareUpdateExclusiveLock
+(1 row)
+
+COMMIT;
+SELECT
+ pg_catalog.pg_restore_relation_stats(
+ 'relation', 'stats_import.part_parent_i'::regclass,
+ 'relpages', 2::integer);
+ pg_restore_relation_stats
+---------------------------
+ t
+(1 row)
+
-- nothing stops us from setting it to -1
SELECT
pg_catalog.pg_set_relation_stats(
UNION ALL
SELECT 4, 'four', NULL, int4range(0,100), NULL;
CREATE INDEX is_odd ON stats_import.test(((comp).a % 2 = 1));
+-- restoring stats on index
+SELECT * FROM pg_catalog.pg_restore_relation_stats(
+ 'relation', 'stats_import.is_odd'::regclass,
+ 'version', '180000'::integer,
+ 'relpages', '11'::integer,
+ 'reltuples', '10000'::real,
+ 'relallvisible', '0'::integer
+);
+ pg_restore_relation_stats
+---------------------------
+ t
+(1 row)
+
-- Generate statistics on table with data
ANALYZE stats_import.test;
CREATE TABLE stats_import.test_clone ( LIKE stats_import.test )
FROM pg_class
WHERE oid = 'stats_import.test'::regclass;
+CREATE INDEX test_i ON stats_import.test(id);
+
+BEGIN;
+-- regular indexes have special case locking rules
+SELECT
+ pg_catalog.pg_set_relation_stats(
+ relation => 'stats_import.test_i'::regclass,
+ relpages => 18::integer);
+
+SELECT mode FROM pg_locks
+WHERE relation = 'stats_import.test'::regclass AND
+ pid = pg_backend_pid() AND granted;
+
+SELECT mode FROM pg_locks
+WHERE relation = 'stats_import.test_i'::regclass AND
+ pid = pg_backend_pid() AND granted;
+
+COMMIT;
+
+SELECT
+ pg_catalog.pg_restore_relation_stats(
+ 'relation', 'stats_import.test_i'::regclass,
+ 'relpages', 19::integer );
+
-- positional arguments
SELECT
pg_catalog.pg_set_relation_stats(
FOR VALUES FROM (0) TO (10)
WITH (autovacuum_enabled = false);
+CREATE INDEX part_parent_i ON stats_import.part_parent(i);
+
ANALYZE stats_import.part_parent;
SELECT relpages
-- although partitioned tables have no storage, setting relpages to a
-- positive value is still allowed
+SELECT
+ pg_catalog.pg_set_relation_stats(
+ relation => 'stats_import.part_parent_i'::regclass,
+ relpages => 2::integer);
+
SELECT
pg_catalog.pg_set_relation_stats(
relation => 'stats_import.part_parent'::regclass,
relpages => 2::integer);
+--
+-- Partitioned indexes aren't analyzed but it is possible to set
+-- stats. The locking rules are different from normal indexes due to
+-- the rules for in-place updates: both the partitioned table and the
+-- partitioned index are locked in ShareUpdateExclusive mode.
+--
+BEGIN;
+
+SELECT
+ pg_catalog.pg_set_relation_stats(
+ relation => 'stats_import.part_parent_i'::regclass,
+ relpages => 2::integer);
+
+SELECT mode FROM pg_locks
+WHERE relation = 'stats_import.part_parent'::regclass AND
+ pid = pg_backend_pid() AND granted;
+
+SELECT mode FROM pg_locks
+WHERE relation = 'stats_import.part_parent_i'::regclass AND
+ pid = pg_backend_pid() AND granted;
+
+COMMIT;
+
+SELECT
+ pg_catalog.pg_restore_relation_stats(
+ 'relation', 'stats_import.part_parent_i'::regclass,
+ 'relpages', 2::integer);
+
-- nothing stops us from setting it to -1
SELECT
pg_catalog.pg_set_relation_stats(
CREATE INDEX is_odd ON stats_import.test(((comp).a % 2 = 1));
+-- restoring stats on index
+SELECT * FROM pg_catalog.pg_restore_relation_stats(
+ 'relation', 'stats_import.is_odd'::regclass,
+ 'version', '180000'::integer,
+ 'relpages', '11'::integer,
+ 'reltuples', '10000'::real,
+ 'relallvisible', '0'::integer
+);
+
-- Generate statistics on table with data
ANALYZE stats_import.test;