#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;