diff --git a/src/test/Makefile b/src/test/Makefile
index 69ef074d75..a2a01105f3 100644
--- a/src/test/Makefile
+++ b/src/test/Makefile
@@ -18,6 +18,10 @@ ifeq ($(with_icu),yes)
 SUBDIRS += icu
 endif
 
+ifeq ($(with_zstd),yes)
+SUBDIRS += zstd
+endif
+
 # Test suites that are not safe by default but can be run if selected
 # by the user via the whitespace-separated list in variable
 # PG_TEST_EXTRA:
diff --git a/src/test/README b/src/test/README
index afdc767651..b44d0c21d9 100644
--- a/src/test/README
+++ b/src/test/README
@@ -48,3 +48,6 @@ ssl/
 
 subscription/
   Tests for logical replication
+
+zstd/
+  Tests for page compression with zstd
diff --git a/src/test/recovery/t/001_stream_rep.pl b/src/test/recovery/t/001_stream_rep.pl
index 86864098f9..87fd3a3030 100644
--- a/src/test/recovery/t/001_stream_rep.pl
+++ b/src/test/recovery/t/001_stream_rep.pl
@@ -46,6 +46,9 @@ $node_standby_2->start;
 $node_primary->safe_psql('postgres',
 	"CREATE TABLE tab_int AS SELECT generate_series(1,1002) AS a");
 
+$node_primary->safe_psql('postgres',
+	"CREATE TABLE tab_int_compressed WITH(compresstype=pglz)AS SELECT generate_series(1,1002) AS a");
+
 # Wait for standbys to catch up
 my $primary_lsn = $node_primary->lsn('write');
 $node_primary->wait_for_catchup($node_standby_1, 'replay', $primary_lsn);
@@ -56,6 +59,11 @@ my $result =
 print "standby 1: $result\n";
 is($result, qq(1002), 'check streamed content on standby 1');
 
+my $result =
+  $node_standby_1->safe_psql('postgres', "SELECT count(*) FROM tab_int_compressed");
+print "standby 1: $result\n";
+is($result, qq(1002), 'check streamed content for compressed table on standby 1');
+
 $result =
   $node_standby_2->safe_psql('postgres', "SELECT count(*) FROM tab_int");
 print "standby 2: $result\n";
@@ -78,6 +86,11 @@ $result = $node_standby_2->safe_psql('postgres', "SELECT * FROM seq1");
 print "standby 2: $result\n";
 is($result, qq(33|0|t), 'check streamed sequence content on standby 2');
 
+$result =
+  $node_standby_2->safe_psql('postgres', "SELECT count(*) FROM tab_int_compressed");
+print "standby 2: $result\n";
+is($result, qq(1002), 'check streamed content for compressed table on standby 2');
+
 # Check that only READ-only queries can run on standbys
 is($node_standby_1->psql('postgres', 'INSERT INTO tab_int VALUES (1)'),
 	3, 'read-only queries on standby 1');
diff --git a/src/test/regress/expected/page_compression.out b/src/test/regress/expected/page_compression.out
new file mode 100644
index 0000000000..e7ab05935a
--- /dev/null
+++ b/src/test/regress/expected/page_compression.out
@@ -0,0 +1,877 @@
+--
+-- Page compression tests
+--
+--
+-- create compressed table
+--
+CREATE TABLE tbl_pc(id int, c1 text) WITH(compresstype=pglz);
+\d+ tbl_pc
+                                   Table "public.tbl_pc"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ id     | integer |           |          |         | plain    |              | 
+ c1     | text    |           |          |         | extended |              | 
+Options: compresstype=pglz
+
+DROP TABLE tbl_pc;
+CREATE TABLE tbl_pc(id int, c1 text) WITH(compresstype=pglz, compress_chunk_size=512);
+\d+ tbl_pc
+                                   Table "public.tbl_pc"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ id     | integer |           |          |         | plain    |              | 
+ c1     | text    |           |          |         | extended |              | 
+Options: compresstype=pglz, compress_chunk_size=512
+
+DROP TABLE tbl_pc;
+CREATE TABLE tbl_pc(id int, c1 text) WITH(compresstype=pglz, compress_chunk_size=1024);
+\d+ tbl_pc
+                                   Table "public.tbl_pc"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ id     | integer |           |          |         | plain    |              | 
+ c1     | text    |           |          |         | extended |              | 
+Options: compresstype=pglz, compress_chunk_size=1024
+
+DROP TABLE tbl_pc;
+CREATE TABLE tbl_pc(id int, c1 text) WITH(compresstype=pglz, compress_chunk_size=2048);
+\d+ tbl_pc
+                                   Table "public.tbl_pc"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ id     | integer |           |          |         | plain    |              | 
+ c1     | text    |           |          |         | extended |              | 
+Options: compresstype=pglz, compress_chunk_size=2048
+
+DROP TABLE tbl_pc;
+CREATE TABLE tbl_pc(id int, c1 text) WITH(compresstype=pglz, compresslevel=0, compress_chunk_size=4096, compress_prealloc_chunks=0);
+\d+ tbl_pc
+                                   Table "public.tbl_pc"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ id     | integer |           |          |         | plain    |              | 
+ c1     | text    |           |          |         | extended |              | 
+Options: compresstype=pglz, compresslevel=0, compress_chunk_size=4096, compress_prealloc_chunks=0
+
+DROP TABLE tbl_pc;
+CREATE TABLE tbl_pc(id int PRIMARY KEY, c1 text) WITH(compresstype=pglz, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
+\d+ tbl_pc
+                                   Table "public.tbl_pc"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ id     | integer |           | not null |         | plain    |              | 
+ c1     | text    |           |          |         | extended |              | 
+Indexes:
+    "tbl_pc_pkey" PRIMARY KEY, btree (id)
+Options: compresstype=pglz, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2
+
+DROP TABLE tbl_pc;
+CREATE TABLE tbl_pc(id int PRIMARY KEY, c1 text) WITH(compresstype=pglz, compresslevel=-1, compress_chunk_size=1024, compress_prealloc_chunks=15);
+\d+ tbl_pc
+                                   Table "public.tbl_pc"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ id     | integer |           | not null |         | plain    |              | 
+ c1     | text    |           |          |         | extended |              | 
+Indexes:
+    "tbl_pc_pkey" PRIMARY KEY, btree (id)
+Options: compresstype=pglz, compresslevel=-1, compress_chunk_size=1024, compress_prealloc_chunks=15
+
+DROP TABLE tbl_pc;
+CREATE TABLE tbl_pc WITH(compresstype=pglz, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=15)
+  AS SELECT id, id::text c1 FROM generate_series(1,1000)id;
+\d+ tbl_pc
+                                   Table "public.tbl_pc"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ id     | integer |           |          |         | plain    |              | 
+ c1     | text    |           |          |         | extended |              | 
+Options: compresstype=pglz, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=15
+
+DROP TABLE tbl_pc;
+CREATE TABLE tbl_pc(id int PRIMARY KEY WITH(compresstype=pglz, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2), c1 text) WITH(compresstype=pglz, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
+CREATE TABLE tbl_pc2(LIKE tbl_pc INCLUDING ALL);
+\d+ tbl_pc
+                                   Table "public.tbl_pc"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ id     | integer |           | not null |         | plain    |              | 
+ c1     | text    |           |          |         | extended |              | 
+Indexes:
+    "tbl_pc_pkey" PRIMARY KEY, btree (id) WITH (compresstype=pglz, compresslevel='1', compress_chunk_size='1024', compress_prealloc_chunks='2')
+Options: compresstype=pglz, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2
+
+DROP TABLE tbl_pc;
+DROP TABLE tbl_pc2;
+CREATE TABLE tbl_pc(id int PRIMARY KEY, c1 text) WITH(compresstype=none);
+\d+ tbl_pc
+                                   Table "public.tbl_pc"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ id     | integer |           | not null |         | plain    |              | 
+ c1     | text    |           |          |         | extended |              | 
+Indexes:
+    "tbl_pc_pkey" PRIMARY KEY, btree (id)
+Options: compresstype=none
+
+DROP TABLE tbl_pc;
+-- invalid storage parameter
+CREATE TABLE tbl_pc_error(id int, c1 text) WITH(compresstype=xyz); -- fail
+ERROR:  invalid value for enum option "compresstype": xyz
+DETAIL:  Valid values are "none", "pglz", "lz4" and "zstd".
+CREATE TABLE tbl_pc_error(id int, c1 text) WITH(compresstype=pglz, compresslevel=xyz); -- fail
+ERROR:  invalid value for integer option "compresslevel": xyz
+CREATE TABLE tbl_pc_error(id int, c1 text) WITH(compresstype=pglz, compress_chunk_size=1025); -- fail
+ERROR:  invalid compress_chunk_size 1025, must be one of 512, 1024, 2048 or 4096
+CREATE TABLE tbl_pc_error(id int, c1 text) WITH(compresstype=pglz, compress_prealloc_chunks=16); -- fail
+ERROR:  value 16 out of bounds for option "compress_prealloc_chunks"
+DETAIL:  Valid values are between "0" and "15".
+--
+-- create compressed index
+--
+SET enable_seqscan = OFF;
+CREATE TABLE tbl_pc(id int PRIMARY KEY WITH(compresstype=pglz, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2), c1 text) WITH(compresstype=pglz, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
+CREATE INDEX tbl_pc_idx1 on tbl_pc(c1) WITH(compresstype=pglz, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
+\d+ tbl_pc
+                                   Table "public.tbl_pc"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ id     | integer |           | not null |         | plain    |              | 
+ c1     | text    |           |          |         | extended |              | 
+Indexes:
+    "tbl_pc_pkey" PRIMARY KEY, btree (id) WITH (compresstype=pglz, compresslevel='1', compress_chunk_size='1024', compress_prealloc_chunks='2')
+    "tbl_pc_idx1" btree (c1) WITH (compresstype=pglz, compresslevel='1', compress_chunk_size='1024', compress_prealloc_chunks='2')
+Options: compresstype=pglz, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2
+
+INSERT INTO tbl_pc SELECT id, id::text FROM generate_series(1,1000)id;
+-- call CHECKPOINT to flush shared buffer to compressed relation file 
+CHECKPOINT;
+-- run ANALYZE REINDEX VACUUM and CLUSTER on compressed table and index
+ANALYZE tbl_pc;
+SELECT count(*) FROM tbl_pc;
+ count 
+-------
+  1000
+(1 row)
+
+SELECT * FROM tbl_pc WHERE c1='100';
+ id  | c1  
+-----+-----
+ 100 | 100
+(1 row)
+
+EXPLAIN(COSTS off) SELECT * FROM tbl_pc WHERE c1='100';
+               QUERY PLAN               
+----------------------------------------
+ Index Scan using tbl_pc_idx1 on tbl_pc
+   Index Cond: (c1 = '100'::text)
+(2 rows)
+
+REINDEX INDEX tbl_pc_idx1;
+CHECKPOINT;
+SELECT * FROM tbl_pc WHERE c1='100';
+ id  | c1  
+-----+-----
+ 100 | 100
+(1 row)
+
+REINDEX TABLE tbl_pc;
+CHECKPOINT;
+SELECT * FROM tbl_pc WHERE c1='100';
+ id  | c1  
+-----+-----
+ 100 | 100
+(1 row)
+
+VACUUM tbl_pc;
+CHECKPOINT;
+SELECT count(*) FROM tbl_pc;
+ count 
+-------
+  1000
+(1 row)
+
+SELECT * FROM tbl_pc WHERE c1='100';
+ id  | c1  
+-----+-----
+ 100 | 100
+(1 row)
+
+VACUUM FULL tbl_pc;
+CHECKPOINT;
+SELECT count(*) FROM tbl_pc;
+ count 
+-------
+  1000
+(1 row)
+
+SELECT * FROM tbl_pc WHERE c1='100';
+ id  | c1  
+-----+-----
+ 100 | 100
+(1 row)
+
+CLUSTER tbl_pc USING tbl_pc_idx1;
+CHECKPOINT;
+SELECT count(*) FROM tbl_pc;
+ count 
+-------
+  1000
+(1 row)
+
+SELECT * FROM tbl_pc WHERE c1='100';
+ id  | c1  
+-----+-----
+ 100 | 100
+(1 row)
+
+DROP INDEX tbl_pc_idx1;
+-- check usage of compressed index with data
+CREATE INDEX tbl_pc_idx1 on tbl_pc USING hash(c1) WITH(compresstype=pglz, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
+CHECKPOINT;
+SELECT * FROM tbl_pc WHERE c1='100';
+ id  | c1  
+-----+-----
+ 100 | 100
+(1 row)
+
+EXPLAIN(COSTS off) SELECT * FROM tbl_pc WHERE c1='100';
+               QUERY PLAN               
+----------------------------------------
+ Index Scan using tbl_pc_idx1 on tbl_pc
+   Index Cond: (c1 = '100'::text)
+(2 rows)
+
+DROP INDEX tbl_pc_idx1;
+CREATE INDEX tbl_pc_idx1 on tbl_pc USING gin((ARRAY[id])) WITH(compresstype=pglz, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
+CHECKPOINT;
+SELECT * FROM tbl_pc WHERE ARRAY[id] @> ARRAY[100];
+ id  | c1  
+-----+-----
+ 100 | 100
+(1 row)
+
+EXPLAIN(COSTS off) SELECT * FROM tbl_pc WHERE ARRAY[id] @> ARRAY[100];
+                      QUERY PLAN                       
+-------------------------------------------------------
+ Bitmap Heap Scan on tbl_pc
+   Recheck Cond: (ARRAY[id] @> '{100}'::integer[])
+   ->  Bitmap Index Scan on tbl_pc_idx1
+         Index Cond: (ARRAY[id] @> '{100}'::integer[])
+(4 rows)
+
+DROP INDEX tbl_pc_idx1;
+CREATE INDEX tbl_pc_idx1 on tbl_pc USING gist((point(id,id))) WITH(compresstype=pglz, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
+CHECKPOINT;
+SELECT * FROM tbl_pc ORDER BY point(id,id) <-> point(100,100) limit 1;
+ id  | c1  
+-----+-----
+ 100 | 100
+(1 row)
+
+EXPLAIN(COSTS off) SELECT * FROM tbl_pc ORDER BY point(id,id) <-> point(100,100) limit 1;
+                                            QUERY PLAN                                            
+--------------------------------------------------------------------------------------------------
+ Limit
+   ->  Index Scan using tbl_pc_idx1 on tbl_pc
+         Order By: (point((id)::double precision, (id)::double precision) <-> '(100,100)'::point)
+(3 rows)
+
+DROP INDEX tbl_pc_idx1;
+CREATE INDEX tbl_pc_idx1 on tbl_pc USING spgist(c1) WITH(compresstype=pglz, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
+CHECKPOINT;
+SELECT * FROM tbl_pc WHERE c1='100';
+ id  | c1  
+-----+-----
+ 100 | 100
+(1 row)
+
+EXPLAIN(COSTS off) SELECT * FROM tbl_pc WHERE c1='100';
+               QUERY PLAN               
+----------------------------------------
+ Index Scan using tbl_pc_idx1 on tbl_pc
+   Index Cond: (c1 = '100'::text)
+(2 rows)
+
+DROP INDEX tbl_pc_idx1;
+-- brin index does not support compression
+CREATE INDEX tbl_pc_idx1 on tbl_pc USING brin(c1) WITH(compresstype=pglz, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2); -- fail
+ERROR:  unrecognized parameter "compresstype"
+DROP TABLE tbl_pc;
+RESET enable_seqscan;
+--
+-- alter table and index
+--
+-- ALTER TABLE
+-- ALTER compresstype and compress_chunk_size currently is not supported
+CREATE TABLE tbl_pc(id int, c1 text);
+ALTER TABLE tbl_pc SET(compresstype=pglz); -- fail
+ERROR:  change compresstype parameter is not supported
+DROP TABLE tbl_pc;
+CREATE TABLE tbl_pc(id int PRIMARY KEY, c1 text) WITH(compresstype=pglz, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
+ALTER TABLE tbl_pc SET(compresstype=none); -- fail
+ERROR:  change compresstype parameter is not supported
+ALTER TABLE tbl_pc SET(compress_chunk_size=2048); -- fail
+ERROR:  change compress_chunk_size parameter is not supported
+ALTER TABLE tbl_pc SET(compress_prealloc_chunks=16);  -- fail
+ERROR:  value 16 out of bounds for option "compress_prealloc_chunks"
+DETAIL:  Valid values are between "0" and "15".
+ALTER TABLE tbl_pc SET(compresslevel=2, compress_prealloc_chunks=0); -- ok
+\d+ tbl_pc
+                                   Table "public.tbl_pc"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ id     | integer |           | not null |         | plain    |              | 
+ c1     | text    |           |          |         | extended |              | 
+Indexes:
+    "tbl_pc_pkey" PRIMARY KEY, btree (id)
+Options: compresstype=pglz, compress_chunk_size=1024, compresslevel=2, compress_prealloc_chunks=0
+
+ALTER TABLE tbl_pc RESET(compresstype); -- fail
+ERROR:  change compresstype parameter is not supported
+ALTER TABLE tbl_pc RESET(compress_chunk_size); -- fail
+ERROR:  change compress_chunk_size parameter is not supported
+ALTER TABLE tbl_pc RESET(compresslevel); -- ok
+ALTER TABLE tbl_pc RESET(compress_prealloc_chunks); -- ok
+\d+ tbl_pc
+                                   Table "public.tbl_pc"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ id     | integer |           | not null |         | plain    |              | 
+ c1     | text    |           |          |         | extended |              | 
+Indexes:
+    "tbl_pc_pkey" PRIMARY KEY, btree (id)
+Options: compresstype=pglz, compress_chunk_size=1024
+
+CHECKPOINT;
+SELECT count(*) FROM tbl_pc;
+ count 
+-------
+     0
+(1 row)
+
+-- ALTER INDEX
+-- ALTER compresstype and compress_chunk_size currently is not supported
+CREATE INDEX tbl_pc_idx1 on tbl_pc USING btree(c1);
+ALTER INDEX tbl_pc_idx1 SET(compresstype=pglz); -- fail
+ERROR:  change compresstype parameter is not supported
+DROP INDEX tbl_pc_idx1;
+CREATE INDEX tbl_pc_idx1 on tbl_pc USING btree(c1) WITH(compresstype=pglz, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
+ALTER INDEX tbl_pc_idx1 SET(compresstype=none); -- fail
+ERROR:  change compresstype parameter is not supported
+ALTER INDEX tbl_pc_idx1 SET(compress_chunk_size=2048); -- fail
+ERROR:  change compress_chunk_size parameter is not supported
+ALTER INDEX tbl_pc_idx1 SET(compress_prealloc_chunks=16);  -- fail
+ERROR:  value 16 out of bounds for option "compress_prealloc_chunks"
+DETAIL:  Valid values are between "0" and "15".
+ALTER INDEX tbl_pc_idx1 SET(compresslevel=2, compress_prealloc_chunks=0); -- ok
+\d+ tbl_pc
+                                   Table "public.tbl_pc"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ id     | integer |           | not null |         | plain    |              | 
+ c1     | text    |           |          |         | extended |              | 
+Indexes:
+    "tbl_pc_pkey" PRIMARY KEY, btree (id)
+    "tbl_pc_idx1" btree (c1) WITH (compresstype=pglz, compress_chunk_size='1024', compresslevel='2', compress_prealloc_chunks='0')
+Options: compresstype=pglz, compress_chunk_size=1024
+
+ALTER INDEX tbl_pc_idx1 RESET(compresstype); -- fail
+ERROR:  change compresstype parameter is not supported
+ALTER INDEX tbl_pc_idx1 RESET(compress_chunk_size); -- fail
+ERROR:  change compress_chunk_size parameter is not supported
+ALTER INDEX tbl_pc_idx1 RESET(compresslevel); -- ok
+ALTER INDEX tbl_pc_idx1 RESET(compress_prealloc_chunks); -- ok
+\d+ tbl_pc
+                                   Table "public.tbl_pc"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ id     | integer |           | not null |         | plain    |              | 
+ c1     | text    |           |          |         | extended |              | 
+Indexes:
+    "tbl_pc_pkey" PRIMARY KEY, btree (id)
+    "tbl_pc_idx1" btree (c1) WITH (compresstype=pglz, compress_chunk_size='1024')
+Options: compresstype=pglz, compress_chunk_size=1024
+
+CHECKPOINT;
+SELECT * FROM tbl_pc WHERE c1='100';
+ id | c1 
+----+----
+(0 rows)
+
+EXPLAIN(COSTS off) SELECT * FROM tbl_pc WHERE c1='100';
+               QUERY PLAN               
+----------------------------------------
+ Bitmap Heap Scan on tbl_pc
+   Recheck Cond: (c1 = '100'::text)
+   ->  Bitmap Index Scan on tbl_pc_idx1
+         Index Cond: (c1 = '100'::text)
+(4 rows)
+
+-- alter hash index
+CREATE INDEX tbl_pc_idx_hash on tbl_pc USING hash(c1) WITH(compresstype=pglz, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
+ALTER INDEX tbl_pc_idx_hash SET(compresstype=none); -- fail
+ERROR:  change compresstype parameter is not supported
+ALTER INDEX tbl_pc_idx_hash SET(compress_chunk_size=2048); -- fail
+ERROR:  change compress_chunk_size parameter is not supported
+ALTER INDEX tbl_pc_idx_hash SET(compress_prealloc_chunks=16);  -- fail
+ERROR:  value 16 out of bounds for option "compress_prealloc_chunks"
+DETAIL:  Valid values are between "0" and "15".
+ALTER INDEX tbl_pc_idx_hash SET(compresslevel=2, compress_prealloc_chunks=0); -- ok
+\d+ tbl_pc_idx_hash
+                Index "public.tbl_pc_idx_hash"
+ Column |  Type   | Key? | Definition | Storage | Stats target 
+--------+---------+------+------------+---------+--------------
+ c1     | integer | yes  | c1         | plain   | 
+hash, for table "public.tbl_pc"
+Options: compresstype=pglz, compress_chunk_size=1024, compresslevel=2, compress_prealloc_chunks=0
+
+ALTER INDEX tbl_pc_idx_hash RESET(compresstype); -- fail
+ERROR:  change compresstype parameter is not supported
+ALTER INDEX tbl_pc_idx_hash RESET(compress_chunk_size); -- fail
+ERROR:  change compress_chunk_size parameter is not supported
+ALTER INDEX tbl_pc_idx_hash RESET(compresslevel); -- ok
+ALTER INDEX tbl_pc_idx_hash RESET(compress_prealloc_chunks); -- ok
+\d+ tbl_pc_idx_hash
+                Index "public.tbl_pc_idx_hash"
+ Column |  Type   | Key? | Definition | Storage | Stats target 
+--------+---------+------+------------+---------+--------------
+ c1     | integer | yes  | c1         | plain   | 
+hash, for table "public.tbl_pc"
+Options: compresstype=pglz, compress_chunk_size=1024
+
+-- alter gin index
+CREATE INDEX tbl_pc_idx_gin on tbl_pc USING gin((ARRAY[id])) WITH(compresstype=pglz, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
+ALTER INDEX tbl_pc_idx_gin SET(compresstype=none); -- fail
+ERROR:  change compresstype parameter is not supported
+ALTER INDEX tbl_pc_idx_gin SET(compress_chunk_size=2048); -- fail
+ERROR:  change compress_chunk_size parameter is not supported
+ALTER INDEX tbl_pc_idx_gin SET(compress_prealloc_chunks=16);  -- fail
+ERROR:  value 16 out of bounds for option "compress_prealloc_chunks"
+DETAIL:  Valid values are between "0" and "15".
+ALTER INDEX tbl_pc_idx_gin SET(compresslevel=2, compress_prealloc_chunks=0); -- ok
+\d+ tbl_pc_idx_gin
+                 Index "public.tbl_pc_idx_gin"
+ Column |  Type   | Key? | Definition  | Storage | Stats target 
+--------+---------+------+-------------+---------+--------------
+ array  | integer | yes  | (ARRAY[id]) | plain   | 
+gin, for table "public.tbl_pc"
+Options: compresstype=pglz, compress_chunk_size=1024, compresslevel=2, compress_prealloc_chunks=0
+
+ALTER INDEX tbl_pc_idx_gin RESET(compresstype); -- fail
+ERROR:  change compresstype parameter is not supported
+ALTER INDEX tbl_pc_idx_gin RESET(compress_chunk_size); -- fail
+ERROR:  change compress_chunk_size parameter is not supported
+ALTER INDEX tbl_pc_idx_gin RESET(compresslevel); -- ok
+ALTER INDEX tbl_pc_idx_gin RESET(compress_prealloc_chunks); -- ok
+\d+ tbl_pc_idx_gin
+                 Index "public.tbl_pc_idx_gin"
+ Column |  Type   | Key? | Definition  | Storage | Stats target 
+--------+---------+------+-------------+---------+--------------
+ array  | integer | yes  | (ARRAY[id]) | plain   | 
+gin, for table "public.tbl_pc"
+Options: compresstype=pglz, compress_chunk_size=1024
+
+-- alter gist index
+CREATE INDEX tbl_pc_idx_gist on tbl_pc USING gist((point(id,id))) WITH(compresstype=pglz, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
+ALTER INDEX tbl_pc_idx_gist SET(compresstype=none); -- fail
+ERROR:  change compresstype parameter is not supported
+ALTER INDEX tbl_pc_idx_gist SET(compress_chunk_size=2048); -- fail
+ERROR:  change compress_chunk_size parameter is not supported
+ALTER INDEX tbl_pc_idx_gist SET(compress_prealloc_chunks=16);  -- fail
+ERROR:  value 16 out of bounds for option "compress_prealloc_chunks"
+DETAIL:  Valid values are between "0" and "15".
+ALTER INDEX tbl_pc_idx_gist SET(compresslevel=2, compress_prealloc_chunks=0); -- ok
+\d+ tbl_pc_idx_gist
+                                  Index "public.tbl_pc_idx_gist"
+ Column | Type | Key? |                    Definition                     | Storage | Stats target 
+--------+------+------+---------------------------------------------------+---------+--------------
+ point  | box  | yes  | point(id::double precision, id::double precision) | plain   | 
+gist, for table "public.tbl_pc"
+Options: compresstype=pglz, compress_chunk_size=1024, compresslevel=2, compress_prealloc_chunks=0
+
+ALTER INDEX tbl_pc_idx_gist RESET(compresstype); -- fail
+ERROR:  change compresstype parameter is not supported
+ALTER INDEX tbl_pc_idx_gist RESET(compress_chunk_size); -- fail
+ERROR:  change compress_chunk_size parameter is not supported
+ALTER INDEX tbl_pc_idx_gist RESET(compresslevel); -- ok
+ALTER INDEX tbl_pc_idx_gist RESET(compress_prealloc_chunks); -- ok
+\d+ tbl_pc_idx_gist
+                                  Index "public.tbl_pc_idx_gist"
+ Column | Type | Key? |                    Definition                     | Storage | Stats target 
+--------+------+------+---------------------------------------------------+---------+--------------
+ point  | box  | yes  | point(id::double precision, id::double precision) | plain   | 
+gist, for table "public.tbl_pc"
+Options: compresstype=pglz, compress_chunk_size=1024
+
+-- alter spgist index
+CREATE INDEX tbl_pc_idx_spgist on tbl_pc USING spgist(c1) WITH(compresstype=pglz, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
+ALTER INDEX tbl_pc_idx_spgist SET(compresstype=none); -- fail
+ERROR:  change compresstype parameter is not supported
+ALTER INDEX tbl_pc_idx_spgist SET(compress_chunk_size=2048); -- fail
+ERROR:  change compress_chunk_size parameter is not supported
+ALTER INDEX tbl_pc_idx_spgist SET(compress_prealloc_chunks=16);  -- fail
+ERROR:  value 16 out of bounds for option "compress_prealloc_chunks"
+DETAIL:  Valid values are between "0" and "15".
+ALTER INDEX tbl_pc_idx_spgist SET(compresslevel=2, compress_prealloc_chunks=0); -- ok
+\d+ tbl_pc_idx_spgist
+              Index "public.tbl_pc_idx_spgist"
+ Column | Type | Key? | Definition | Storage  | Stats target 
+--------+------+------+------------+----------+--------------
+ c1     | text | yes  | c1         | extended | 
+spgist, for table "public.tbl_pc"
+Options: compresstype=pglz, compress_chunk_size=1024, compresslevel=2, compress_prealloc_chunks=0
+
+ALTER INDEX tbl_pc_idx_spgist RESET(compresstype); -- fail
+ERROR:  change compresstype parameter is not supported
+ALTER INDEX tbl_pc_idx_spgist RESET(compress_chunk_size); -- fail
+ERROR:  change compress_chunk_size parameter is not supported
+ALTER INDEX tbl_pc_idx_spgist RESET(compresslevel); -- ok
+ALTER INDEX tbl_pc_idx_spgist RESET(compress_prealloc_chunks); -- ok
+\d+ tbl_pc_idx_spgist
+              Index "public.tbl_pc_idx_spgist"
+ Column | Type | Key? | Definition | Storage  | Stats target 
+--------+------+------+------------+----------+--------------
+ c1     | text | yes  | c1         | extended | 
+spgist, for table "public.tbl_pc"
+Options: compresstype=pglz, compress_chunk_size=1024
+
+-- alter brin index (do not support compression)
+CREATE INDEX tbl_pc_idx_brin on tbl_pc USING brin(c1);
+ALTER INDEX tbl_pc_idx_brin SET(compress_prealloc_chunks=3);  -- fail
+ERROR:  unrecognized parameter "compress_prealloc_chunks"
+DROP TABLE tbl_pc;
+--
+-- partitioned table and index
+--
+-- partition table does not support compression, but index of partition table and its child tables can use compression
+CREATE TABLE tbl_pc_part (id int, c1 text) PARTITION BY RANGE (id) WITH(compresstype=pglz); -- fail
+ERROR:  unrecognized parameter "compresstype"
+CREATE TABLE tbl_pc_part (id int, c1 text) PARTITION BY RANGE (id);
+CREATE TABLE tbl_pc_part_1 PARTITION OF tbl_pc_part FOR VALUES FROM (1) TO (1001);
+CREATE TABLE tbl_pc_part_2 PARTITION OF tbl_pc_part FOR VALUES FROM (1001) TO (2001) WITH(compresstype=pglz, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
+CREATE INDEX part_id_idx ON tbl_pc_part(id) WITH(compresstype=pglz, compresslevel=2, compress_chunk_size=1024, compress_prealloc_chunks=2);
+CREATE TABLE tbl_pc_part_3 PARTITION OF tbl_pc_part FOR VALUES FROM (2001) TO (3001);
+CREATE INDEX part3_id_idx1 ON tbl_pc_part_3(id) WITH(compresstype=pglz, compresslevel=2, compress_chunk_size=1024, compress_prealloc_chunks=2);
+\d+ tbl_pc_part
+                          Partitioned table "public.tbl_pc_part"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ id     | integer |           |          |         | plain    |              | 
+ c1     | text    |           |          |         | extended |              | 
+Partition key: RANGE (id)
+Indexes:
+    "part_id_idx" btree (id) WITH (compresstype=pglz, compresslevel='2', compress_chunk_size='1024', compress_prealloc_chunks='2')
+Partitions: tbl_pc_part_1 FOR VALUES FROM (1) TO (1001),
+            tbl_pc_part_2 FOR VALUES FROM (1001) TO (2001),
+            tbl_pc_part_3 FOR VALUES FROM (2001) TO (3001)
+
+\d+ part_id_idx
+            Partitioned index "public.part_id_idx"
+ Column |  Type   | Key? | Definition | Storage | Stats target 
+--------+---------+------+------------+---------+--------------
+ id     | integer | yes  | id         | plain   | 
+btree, for table "public.tbl_pc_part"
+Partitions: tbl_pc_part_1_id_idx,
+            tbl_pc_part_2_id_idx,
+            tbl_pc_part_3_id_idx
+Options: compresstype=pglz, compresslevel=2, compress_chunk_size=1024, compress_prealloc_chunks=2
+
+\d+ tbl_pc_part_1
+                               Table "public.tbl_pc_part_1"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ id     | integer |           |          |         | plain    |              | 
+ c1     | text    |           |          |         | extended |              | 
+Partition of: tbl_pc_part FOR VALUES FROM (1) TO (1001)
+Partition constraint: ((id IS NOT NULL) AND (id >= 1) AND (id < 1001))
+Indexes:
+    "tbl_pc_part_1_id_idx" btree (id) WITH (compresstype=pglz, compresslevel='2', compress_chunk_size='1024', compress_prealloc_chunks='2')
+
+\d+ tbl_pc_part_2
+                               Table "public.tbl_pc_part_2"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ id     | integer |           |          |         | plain    |              | 
+ c1     | text    |           |          |         | extended |              | 
+Partition of: tbl_pc_part FOR VALUES FROM (1001) TO (2001)
+Partition constraint: ((id IS NOT NULL) AND (id >= 1001) AND (id < 2001))
+Indexes:
+    "tbl_pc_part_2_id_idx" btree (id) WITH (compresstype=pglz, compresslevel='2', compress_chunk_size='1024', compress_prealloc_chunks='2')
+Options: compresstype=pglz, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2
+
+\d+ tbl_pc_part_3
+                               Table "public.tbl_pc_part_3"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ id     | integer |           |          |         | plain    |              | 
+ c1     | text    |           |          |         | extended |              | 
+Partition of: tbl_pc_part FOR VALUES FROM (2001) TO (3001)
+Partition constraint: ((id IS NOT NULL) AND (id >= 2001) AND (id < 3001))
+Indexes:
+    "part3_id_idx1" btree (id) WITH (compresstype=pglz, compresslevel='2', compress_chunk_size='1024', compress_prealloc_chunks='2')
+    "tbl_pc_part_3_id_idx" btree (id) WITH (compresstype=pglz, compresslevel='2', compress_chunk_size='1024', compress_prealloc_chunks='2')
+
+INSERT INTO tbl_pc_part SELECT id, id::text FROM generate_series(1,3000)id;
+CHECKPOINT;
+SELECT count(*) FROM tbl_pc_part;
+ count 
+-------
+  3000
+(1 row)
+
+SELECT * FROM tbl_pc_part WHERE id=100;
+ id  | c1  
+-----+-----
+ 100 | 100
+(1 row)
+
+SELECT * FROM tbl_pc_part WHERE id=1100;
+  id  |  c1  
+------+------
+ 1100 | 1100
+(1 row)
+
+SELECT * FROM tbl_pc_part WHERE id=2100;
+  id  |  c1  
+------+------
+ 2100 | 2100
+(1 row)
+
+ALTER TABLE tbl_pc_part SET(compresstype=pglz); -- fail
+ERROR:  unrecognized parameter "compresstype"
+ALTER TABLE tbl_pc_part_1 SET(compresstype=pglz); -- fail
+ERROR:  change compresstype parameter is not supported
+ALTER TABLE tbl_pc_part_2 SET(compresstype=none); -- fail
+ERROR:  change compresstype parameter is not supported
+ALTER TABLE tbl_pc_part_2 SET(compress_chunk_size=2048); -- fail
+ERROR:  change compress_chunk_size parameter is not supported
+ALTER TABLE tbl_pc_part_2 SET(compress_prealloc_chunks=16);  -- fail
+ERROR:  value 16 out of bounds for option "compress_prealloc_chunks"
+DETAIL:  Valid values are between "0" and "15".
+ALTER TABLE tbl_pc_part_2 SET(compresslevel=2, compress_prealloc_chunks=0); -- ok
+\d+ tbl_pc_part_2
+                               Table "public.tbl_pc_part_2"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ id     | integer |           |          |         | plain    |              | 
+ c1     | text    |           |          |         | extended |              | 
+Partition of: tbl_pc_part FOR VALUES FROM (1001) TO (2001)
+Partition constraint: ((id IS NOT NULL) AND (id >= 1001) AND (id < 2001))
+Indexes:
+    "tbl_pc_part_2_id_idx" btree (id) WITH (compresstype=pglz, compresslevel='2', compress_chunk_size='1024', compress_prealloc_chunks='2')
+Options: compresstype=pglz, compress_chunk_size=1024, compresslevel=2, compress_prealloc_chunks=0
+
+ALTER INDEX part3_id_idx1 SET(compresstype=none); -- fail
+ERROR:  change compresstype parameter is not supported
+ALTER INDEX part3_id_idx1 SET(compress_chunk_size=2048); -- fail
+ERROR:  change compress_chunk_size parameter is not supported
+ALTER INDEX part3_id_idx1 SET(compress_prealloc_chunks=16);  -- fail
+ERROR:  value 16 out of bounds for option "compress_prealloc_chunks"
+DETAIL:  Valid values are between "0" and "15".
+ALTER INDEX part3_id_idx1 SET(compresslevel=2, compress_prealloc_chunks=0); -- ok
+\d+ part3_id_idx1
+                 Index "public.part3_id_idx1"
+ Column |  Type   | Key? | Definition | Storage | Stats target 
+--------+---------+------+------------+---------+--------------
+ id     | integer | yes  | id         | plain   | 
+btree, for table "public.tbl_pc_part_3"
+Options: compresstype=pglz, compress_chunk_size=1024, compresslevel=2, compress_prealloc_chunks=0
+
+ALTER INDEX part_id_idx SET(compresstype=pglz); -- fail
+ERROR:  ALTER action SET cannot be performed on relation "part_id_idx"
+DETAIL:  This operation is not supported for partitioned indexes.
+ALTER INDEX part_id_idx SET(compress_chunk_size=2048); -- fail
+ERROR:  ALTER action SET cannot be performed on relation "part_id_idx"
+DETAIL:  This operation is not supported for partitioned indexes.
+ALTER INDEX part_id_idx SET(compress_prealloc_chunks=16);  -- fail
+ERROR:  ALTER action SET cannot be performed on relation "part_id_idx"
+DETAIL:  This operation is not supported for partitioned indexes.
+ALTER INDEX part_id_idx SET(compresslevel=2, compress_prealloc_chunks=0); -- fail
+ERROR:  ALTER action SET cannot be performed on relation "part_id_idx"
+DETAIL:  This operation is not supported for partitioned indexes.
+\d+ tbl_pc_part
+                          Partitioned table "public.tbl_pc_part"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ id     | integer |           |          |         | plain    |              | 
+ c1     | text    |           |          |         | extended |              | 
+Partition key: RANGE (id)
+Indexes:
+    "part_id_idx" btree (id) WITH (compresstype=pglz, compresslevel='2', compress_chunk_size='1024', compress_prealloc_chunks='2')
+Partitions: tbl_pc_part_1 FOR VALUES FROM (1) TO (1001),
+            tbl_pc_part_2 FOR VALUES FROM (1001) TO (2001),
+            tbl_pc_part_3 FOR VALUES FROM (2001) TO (3001)
+
+INSERT INTO tbl_pc_part SELECT id, id::text FROM generate_series(1,3000)id;
+CHECKPOINT;
+SELECT count(*) FROM tbl_pc_part;
+ count 
+-------
+  6000
+(1 row)
+
+SELECT * FROM tbl_pc_part WHERE id=100;
+ id  | c1  
+-----+-----
+ 100 | 100
+ 100 | 100
+(2 rows)
+
+SELECT * FROM tbl_pc_part WHERE id=1100;
+  id  |  c1  
+------+------
+ 1100 | 1100
+ 1100 | 1100
+(2 rows)
+
+SELECT * FROM tbl_pc_part WHERE id=2100;
+  id  |  c1  
+------+------
+ 2100 | 2100
+ 2100 | 2100
+(2 rows)
+
+DROP TABLE tbl_pc_part;
+--
+-- recycling space with vacuum (do not support)
+--
+CREATE TABLE tbl_pc(id int PRIMARY KEY, c1 text) WITH(compresstype=pglz, compress_chunk_size=2048, compress_prealloc_chunks=0);
+CHECKPOINT;
+SELECT pg_relation_size('tbl_pc') size_0 \gset
+INSERT INTO tbl_pc SELECT id, id::text FROM generate_series(1,1000)id;
+CHECKPOINT;
+SELECT pg_relation_size('tbl_pc') size_1000 \gset
+SELECT :size_1000 > :size_0; -- true
+ ?column? 
+----------
+ t
+(1 row)
+
+DELETE FROM tbl_pc WHERE id > 500;
+VACUUM tbl_pc;
+SELECT pg_relation_size('tbl_pc') size_500 \gset
+SELECT count(*) FROM tbl_pc;
+ count 
+-------
+   500
+(1 row)
+
+SELECT :size_500 = :size_1000;  -- true
+ ?column? 
+----------
+ t
+(1 row)
+
+DELETE FROM tbl_pc WHERE id < 500;
+VACUUM tbl_pc;
+SELECT pg_relation_size('tbl_pc') size_1 \gset
+SELECT count(*) FROM tbl_pc;
+ count 
+-------
+     1
+(1 row)
+
+SELECT :size_1 = :size_500;  -- true
+ ?column? 
+----------
+ t
+(1 row)
+
+DELETE FROM tbl_pc;
+VACUUM tbl_pc;
+SELECT pg_relation_size('tbl_pc') size_0_2 \gset
+SELECT :size_0_2 = :size_500;  -- true
+ ?column? 
+----------
+ t
+(1 row)
+
+VACUUM FULL tbl_pc;
+SELECT pg_relation_size('tbl_pc') size_0_3 \gset
+SELECT :size_0_3 = :size_0;  -- true
+ ?column? 
+----------
+ t
+(1 row)
+
+DROP TABLE tbl_pc;
+--
+-- truncate
+--
+CREATE TABLE tbl_pc(id int) WITH(compresstype=pglz, compress_chunk_size=1024);
+CREATE INDEX tbl_pc_idx ON tbl_pc(id) WITH(compresstype=pglz, compress_chunk_size=1024);
+SELECT pg_relation_size('tbl_pc'::regclass) init_tbl_size, pg_relation_size('tbl_pc_idx'::regclass) init_idx_size \gset
+INSERT INTO tbl_pc SELECT generate_series(1,3000);
+CHECKPOINT;
+SELECT pg_relation_size('tbl_pc'::regclass) > :init_tbl_size; -- true
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT pg_relation_size('tbl_pc_idx'::regclass) > :init_idx_size; -- true
+ ?column? 
+----------
+ t
+(1 row)
+
+TRUNCATE tbl_pc;
+SELECT pg_relation_size('tbl_pc'::regclass) = :init_tbl_size; -- true
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT pg_relation_size('tbl_pc_idx'::regclass) = :init_idx_size; -- true
+ ?column? 
+----------
+ t
+(1 row)
+
+DROP TABLE tbl_pc;
+-- combining commands(CREATE + TRUNCATE)
+CREATE TABLE tbl_pc(id int) WITH(compresstype=pglz, compress_chunk_size=1024) \;
+CREATE INDEX tbl_pc_idx ON tbl_pc(id) WITH(compresstype=pglz, compress_chunk_size=1024) \;
+INSERT INTO tbl_pc SELECT generate_series(1,3000) \;
+TRUNCATE tbl_pc;
+SELECT pg_relation_size('tbl_pc'::regclass) = :init_tbl_size; -- true
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT pg_relation_size('tbl_pc_idx'::regclass) = :init_idx_size; -- true
+ ?column? 
+----------
+ t
+(1 row)
+
+DROP TABLE tbl_pc;
+--
+-- database templete support
+--
+SELECT current_database() olddb \gset
+CREATE DATABASE pctest_dbt;
+\c pctest_dbt
+CREATE TABLE tbl_pc(id int) WITH(compresstype=pglz, compress_chunk_size=1024);
+INSERT INTO tbl_pc SELECT generate_series(1,3000);
+CREATE INDEX tbl_pc_idx ON tbl_pc(id) WITH(compresstype=pglz, compress_chunk_size=1024);
+\c :olddb
+CREATE DATABASE pctest_db1 template pctest_dbt;
+\c pctest_db1
+SELECT count(*) FROM tbl_pc;
+ count 
+-------
+  3000
+(1 row)
+
+SELECT id FROM tbl_pc WHERE id = 3000;
+  id  
+------
+ 3000
+(1 row)
+
+\c :olddb
+DROP DATABASE pctest_dbt;
+DROP DATABASE pctest_db1;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 103e11483d..82f0e71990 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -135,3 +135,6 @@ test: event_trigger oidjoins
 
 # this test also uses event triggers, so likewise run it by itself
 test: fast_default
+
+# run page_compression alone temporarily
+test: page_compression
diff --git a/src/test/regress/sql/page_compression.sql b/src/test/regress/sql/page_compression.sql
new file mode 100644
index 0000000000..67a146baf9
--- /dev/null
+++ b/src/test/regress/sql/page_compression.sql
@@ -0,0 +1,387 @@
+--
+-- Page compression tests
+--
+
+--
+-- create compressed table
+--
+CREATE TABLE tbl_pc(id int, c1 text) WITH(compresstype=pglz);
+\d+ tbl_pc
+DROP TABLE tbl_pc;
+
+CREATE TABLE tbl_pc(id int, c1 text) WITH(compresstype=pglz, compress_chunk_size=512);
+\d+ tbl_pc
+DROP TABLE tbl_pc;
+
+CREATE TABLE tbl_pc(id int, c1 text) WITH(compresstype=pglz, compress_chunk_size=1024);
+\d+ tbl_pc
+DROP TABLE tbl_pc;
+
+CREATE TABLE tbl_pc(id int, c1 text) WITH(compresstype=pglz, compress_chunk_size=2048);
+\d+ tbl_pc
+DROP TABLE tbl_pc;
+
+CREATE TABLE tbl_pc(id int, c1 text) WITH(compresstype=pglz, compresslevel=0, compress_chunk_size=4096, compress_prealloc_chunks=0);
+\d+ tbl_pc
+DROP TABLE tbl_pc;
+
+CREATE TABLE tbl_pc(id int PRIMARY KEY, c1 text) WITH(compresstype=pglz, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
+\d+ tbl_pc
+DROP TABLE tbl_pc;
+
+CREATE TABLE tbl_pc(id int PRIMARY KEY, c1 text) WITH(compresstype=pglz, compresslevel=-1, compress_chunk_size=1024, compress_prealloc_chunks=15);
+\d+ tbl_pc
+DROP TABLE tbl_pc;
+
+CREATE TABLE tbl_pc WITH(compresstype=pglz, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=15)
+  AS SELECT id, id::text c1 FROM generate_series(1,1000)id;
+\d+ tbl_pc
+DROP TABLE tbl_pc;
+
+CREATE TABLE tbl_pc(id int PRIMARY KEY WITH(compresstype=pglz, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2), c1 text) WITH(compresstype=pglz, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
+CREATE TABLE tbl_pc2(LIKE tbl_pc INCLUDING ALL);
+\d+ tbl_pc
+DROP TABLE tbl_pc;
+DROP TABLE tbl_pc2;
+
+CREATE TABLE tbl_pc(id int PRIMARY KEY, c1 text) WITH(compresstype=none);
+\d+ tbl_pc
+DROP TABLE tbl_pc;
+
+-- invalid storage parameter
+CREATE TABLE tbl_pc_error(id int, c1 text) WITH(compresstype=xyz); -- fail
+CREATE TABLE tbl_pc_error(id int, c1 text) WITH(compresstype=pglz, compresslevel=xyz); -- fail
+CREATE TABLE tbl_pc_error(id int, c1 text) WITH(compresstype=pglz, compress_chunk_size=1025); -- fail
+CREATE TABLE tbl_pc_error(id int, c1 text) WITH(compresstype=pglz, compress_prealloc_chunks=16); -- fail
+
+
+--
+-- create compressed index
+--
+SET enable_seqscan = OFF;
+
+CREATE TABLE tbl_pc(id int PRIMARY KEY WITH(compresstype=pglz, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2), c1 text) WITH(compresstype=pglz, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
+
+CREATE INDEX tbl_pc_idx1 on tbl_pc(c1) WITH(compresstype=pglz, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
+
+\d+ tbl_pc
+
+INSERT INTO tbl_pc SELECT id, id::text FROM generate_series(1,1000)id;
+
+-- call CHECKPOINT to flush shared buffer to compressed relation file 
+CHECKPOINT;
+
+-- run ANALYZE REINDEX VACUUM and CLUSTER on compressed table and index
+ANALYZE tbl_pc;
+
+SELECT count(*) FROM tbl_pc;
+SELECT * FROM tbl_pc WHERE c1='100';
+EXPLAIN(COSTS off) SELECT * FROM tbl_pc WHERE c1='100';
+
+REINDEX INDEX tbl_pc_idx1;
+CHECKPOINT;
+SELECT * FROM tbl_pc WHERE c1='100';
+
+REINDEX TABLE tbl_pc;
+CHECKPOINT;
+SELECT * FROM tbl_pc WHERE c1='100';
+
+VACUUM tbl_pc;
+CHECKPOINT;
+SELECT count(*) FROM tbl_pc;
+SELECT * FROM tbl_pc WHERE c1='100';
+
+VACUUM FULL tbl_pc;
+CHECKPOINT;
+SELECT count(*) FROM tbl_pc;
+SELECT * FROM tbl_pc WHERE c1='100';
+
+CLUSTER tbl_pc USING tbl_pc_idx1;
+CHECKPOINT;
+SELECT count(*) FROM tbl_pc;
+SELECT * FROM tbl_pc WHERE c1='100';
+
+DROP INDEX tbl_pc_idx1;
+
+-- check usage of compressed index with data
+CREATE INDEX tbl_pc_idx1 on tbl_pc USING hash(c1) WITH(compresstype=pglz, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
+CHECKPOINT;
+SELECT * FROM tbl_pc WHERE c1='100';
+EXPLAIN(COSTS off) SELECT * FROM tbl_pc WHERE c1='100';
+DROP INDEX tbl_pc_idx1;
+
+CREATE INDEX tbl_pc_idx1 on tbl_pc USING gin((ARRAY[id])) WITH(compresstype=pglz, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
+CHECKPOINT;
+SELECT * FROM tbl_pc WHERE ARRAY[id] @> ARRAY[100];
+EXPLAIN(COSTS off) SELECT * FROM tbl_pc WHERE ARRAY[id] @> ARRAY[100];
+DROP INDEX tbl_pc_idx1;
+
+CREATE INDEX tbl_pc_idx1 on tbl_pc USING gist((point(id,id))) WITH(compresstype=pglz, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
+CHECKPOINT;
+SELECT * FROM tbl_pc ORDER BY point(id,id) <-> point(100,100) limit 1;
+EXPLAIN(COSTS off) SELECT * FROM tbl_pc ORDER BY point(id,id) <-> point(100,100) limit 1;
+DROP INDEX tbl_pc_idx1;
+
+CREATE INDEX tbl_pc_idx1 on tbl_pc USING spgist(c1) WITH(compresstype=pglz, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
+CHECKPOINT;
+SELECT * FROM tbl_pc WHERE c1='100';
+EXPLAIN(COSTS off) SELECT * FROM tbl_pc WHERE c1='100';
+DROP INDEX tbl_pc_idx1;
+
+-- brin index does not support compression
+CREATE INDEX tbl_pc_idx1 on tbl_pc USING brin(c1) WITH(compresstype=pglz, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2); -- fail
+
+DROP TABLE tbl_pc;
+RESET enable_seqscan;
+
+--
+-- alter table and index
+--
+
+-- ALTER TABLE
+-- ALTER compresstype and compress_chunk_size currently is not supported
+CREATE TABLE tbl_pc(id int, c1 text);
+ALTER TABLE tbl_pc SET(compresstype=pglz); -- fail
+DROP TABLE tbl_pc;
+
+CREATE TABLE tbl_pc(id int PRIMARY KEY, c1 text) WITH(compresstype=pglz, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
+ALTER TABLE tbl_pc SET(compresstype=none); -- fail
+ALTER TABLE tbl_pc SET(compress_chunk_size=2048); -- fail
+ALTER TABLE tbl_pc SET(compress_prealloc_chunks=16);  -- fail
+ALTER TABLE tbl_pc SET(compresslevel=2, compress_prealloc_chunks=0); -- ok
+\d+ tbl_pc
+ALTER TABLE tbl_pc RESET(compresstype); -- fail
+ALTER TABLE tbl_pc RESET(compress_chunk_size); -- fail
+ALTER TABLE tbl_pc RESET(compresslevel); -- ok
+ALTER TABLE tbl_pc RESET(compress_prealloc_chunks); -- ok
+\d+ tbl_pc
+CHECKPOINT;
+SELECT count(*) FROM tbl_pc;
+
+-- ALTER INDEX
+-- ALTER compresstype and compress_chunk_size currently is not supported
+CREATE INDEX tbl_pc_idx1 on tbl_pc USING btree(c1);
+ALTER INDEX tbl_pc_idx1 SET(compresstype=pglz); -- fail
+DROP INDEX tbl_pc_idx1;
+
+CREATE INDEX tbl_pc_idx1 on tbl_pc USING btree(c1) WITH(compresstype=pglz, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
+ALTER INDEX tbl_pc_idx1 SET(compresstype=none); -- fail
+ALTER INDEX tbl_pc_idx1 SET(compress_chunk_size=2048); -- fail
+ALTER INDEX tbl_pc_idx1 SET(compress_prealloc_chunks=16);  -- fail
+ALTER INDEX tbl_pc_idx1 SET(compresslevel=2, compress_prealloc_chunks=0); -- ok
+\d+ tbl_pc
+ALTER INDEX tbl_pc_idx1 RESET(compresstype); -- fail
+ALTER INDEX tbl_pc_idx1 RESET(compress_chunk_size); -- fail
+ALTER INDEX tbl_pc_idx1 RESET(compresslevel); -- ok
+ALTER INDEX tbl_pc_idx1 RESET(compress_prealloc_chunks); -- ok
+\d+ tbl_pc
+CHECKPOINT;
+SELECT * FROM tbl_pc WHERE c1='100';
+EXPLAIN(COSTS off) SELECT * FROM tbl_pc WHERE c1='100';
+
+-- alter hash index
+CREATE INDEX tbl_pc_idx_hash on tbl_pc USING hash(c1) WITH(compresstype=pglz, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
+
+ALTER INDEX tbl_pc_idx_hash SET(compresstype=none); -- fail
+ALTER INDEX tbl_pc_idx_hash SET(compress_chunk_size=2048); -- fail
+ALTER INDEX tbl_pc_idx_hash SET(compress_prealloc_chunks=16);  -- fail
+ALTER INDEX tbl_pc_idx_hash SET(compresslevel=2, compress_prealloc_chunks=0); -- ok
+\d+ tbl_pc_idx_hash
+ALTER INDEX tbl_pc_idx_hash RESET(compresstype); -- fail
+ALTER INDEX tbl_pc_idx_hash RESET(compress_chunk_size); -- fail
+ALTER INDEX tbl_pc_idx_hash RESET(compresslevel); -- ok
+ALTER INDEX tbl_pc_idx_hash RESET(compress_prealloc_chunks); -- ok
+\d+ tbl_pc_idx_hash
+
+-- alter gin index
+CREATE INDEX tbl_pc_idx_gin on tbl_pc USING gin((ARRAY[id])) WITH(compresstype=pglz, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
+
+ALTER INDEX tbl_pc_idx_gin SET(compresstype=none); -- fail
+ALTER INDEX tbl_pc_idx_gin SET(compress_chunk_size=2048); -- fail
+ALTER INDEX tbl_pc_idx_gin SET(compress_prealloc_chunks=16);  -- fail
+ALTER INDEX tbl_pc_idx_gin SET(compresslevel=2, compress_prealloc_chunks=0); -- ok
+\d+ tbl_pc_idx_gin
+ALTER INDEX tbl_pc_idx_gin RESET(compresstype); -- fail
+ALTER INDEX tbl_pc_idx_gin RESET(compress_chunk_size); -- fail
+ALTER INDEX tbl_pc_idx_gin RESET(compresslevel); -- ok
+ALTER INDEX tbl_pc_idx_gin RESET(compress_prealloc_chunks); -- ok
+\d+ tbl_pc_idx_gin
+
+-- alter gist index
+CREATE INDEX tbl_pc_idx_gist on tbl_pc USING gist((point(id,id))) WITH(compresstype=pglz, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
+
+ALTER INDEX tbl_pc_idx_gist SET(compresstype=none); -- fail
+ALTER INDEX tbl_pc_idx_gist SET(compress_chunk_size=2048); -- fail
+ALTER INDEX tbl_pc_idx_gist SET(compress_prealloc_chunks=16);  -- fail
+ALTER INDEX tbl_pc_idx_gist SET(compresslevel=2, compress_prealloc_chunks=0); -- ok
+\d+ tbl_pc_idx_gist
+ALTER INDEX tbl_pc_idx_gist RESET(compresstype); -- fail
+ALTER INDEX tbl_pc_idx_gist RESET(compress_chunk_size); -- fail
+ALTER INDEX tbl_pc_idx_gist RESET(compresslevel); -- ok
+ALTER INDEX tbl_pc_idx_gist RESET(compress_prealloc_chunks); -- ok
+\d+ tbl_pc_idx_gist
+
+-- alter spgist index
+CREATE INDEX tbl_pc_idx_spgist on tbl_pc USING spgist(c1) WITH(compresstype=pglz, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
+
+ALTER INDEX tbl_pc_idx_spgist SET(compresstype=none); -- fail
+ALTER INDEX tbl_pc_idx_spgist SET(compress_chunk_size=2048); -- fail
+ALTER INDEX tbl_pc_idx_spgist SET(compress_prealloc_chunks=16);  -- fail
+ALTER INDEX tbl_pc_idx_spgist SET(compresslevel=2, compress_prealloc_chunks=0); -- ok
+\d+ tbl_pc_idx_spgist
+ALTER INDEX tbl_pc_idx_spgist RESET(compresstype); -- fail
+ALTER INDEX tbl_pc_idx_spgist RESET(compress_chunk_size); -- fail
+ALTER INDEX tbl_pc_idx_spgist RESET(compresslevel); -- ok
+ALTER INDEX tbl_pc_idx_spgist RESET(compress_prealloc_chunks); -- ok
+\d+ tbl_pc_idx_spgist
+
+-- alter brin index (do not support compression)
+CREATE INDEX tbl_pc_idx_brin on tbl_pc USING brin(c1);
+ALTER INDEX tbl_pc_idx_brin SET(compress_prealloc_chunks=3);  -- fail
+
+DROP TABLE tbl_pc;
+
+--
+-- partitioned table and index
+--
+
+-- partition table does not support compression, but index of partition table and its child tables can use compression
+CREATE TABLE tbl_pc_part (id int, c1 text) PARTITION BY RANGE (id) WITH(compresstype=pglz); -- fail
+
+CREATE TABLE tbl_pc_part (id int, c1 text) PARTITION BY RANGE (id);
+CREATE TABLE tbl_pc_part_1 PARTITION OF tbl_pc_part FOR VALUES FROM (1) TO (1001);
+CREATE TABLE tbl_pc_part_2 PARTITION OF tbl_pc_part FOR VALUES FROM (1001) TO (2001) WITH(compresstype=pglz, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
+
+CREATE INDEX part_id_idx ON tbl_pc_part(id) WITH(compresstype=pglz, compresslevel=2, compress_chunk_size=1024, compress_prealloc_chunks=2);
+
+CREATE TABLE tbl_pc_part_3 PARTITION OF tbl_pc_part FOR VALUES FROM (2001) TO (3001);
+
+CREATE INDEX part3_id_idx1 ON tbl_pc_part_3(id) WITH(compresstype=pglz, compresslevel=2, compress_chunk_size=1024, compress_prealloc_chunks=2);
+
+\d+ tbl_pc_part
+\d+ part_id_idx
+\d+ tbl_pc_part_1
+\d+ tbl_pc_part_2
+\d+ tbl_pc_part_3
+
+INSERT INTO tbl_pc_part SELECT id, id::text FROM generate_series(1,3000)id;
+CHECKPOINT;
+SELECT count(*) FROM tbl_pc_part;
+SELECT * FROM tbl_pc_part WHERE id=100;
+SELECT * FROM tbl_pc_part WHERE id=1100;
+SELECT * FROM tbl_pc_part WHERE id=2100;
+
+ALTER TABLE tbl_pc_part SET(compresstype=pglz); -- fail
+ALTER TABLE tbl_pc_part_1 SET(compresstype=pglz); -- fail
+
+ALTER TABLE tbl_pc_part_2 SET(compresstype=none); -- fail
+ALTER TABLE tbl_pc_part_2 SET(compress_chunk_size=2048); -- fail
+ALTER TABLE tbl_pc_part_2 SET(compress_prealloc_chunks=16);  -- fail
+ALTER TABLE tbl_pc_part_2 SET(compresslevel=2, compress_prealloc_chunks=0); -- ok
+\d+ tbl_pc_part_2
+
+ALTER INDEX part3_id_idx1 SET(compresstype=none); -- fail
+ALTER INDEX part3_id_idx1 SET(compress_chunk_size=2048); -- fail
+ALTER INDEX part3_id_idx1 SET(compress_prealloc_chunks=16);  -- fail
+ALTER INDEX part3_id_idx1 SET(compresslevel=2, compress_prealloc_chunks=0); -- ok
+\d+ part3_id_idx1
+
+ALTER INDEX part_id_idx SET(compresstype=pglz); -- fail
+ALTER INDEX part_id_idx SET(compress_chunk_size=2048); -- fail
+ALTER INDEX part_id_idx SET(compress_prealloc_chunks=16);  -- fail
+ALTER INDEX part_id_idx SET(compresslevel=2, compress_prealloc_chunks=0); -- fail
+\d+ tbl_pc_part
+
+INSERT INTO tbl_pc_part SELECT id, id::text FROM generate_series(1,3000)id;
+CHECKPOINT;
+SELECT count(*) FROM tbl_pc_part;
+SELECT * FROM tbl_pc_part WHERE id=100;
+SELECT * FROM tbl_pc_part WHERE id=1100;
+SELECT * FROM tbl_pc_part WHERE id=2100;
+
+DROP TABLE tbl_pc_part;
+
+
+--
+-- recycling space with vacuum (do not support)
+--
+CREATE TABLE tbl_pc(id int PRIMARY KEY, c1 text) WITH(compresstype=pglz, compress_chunk_size=2048, compress_prealloc_chunks=0);
+CHECKPOINT;
+SELECT pg_relation_size('tbl_pc') size_0 \gset
+
+INSERT INTO tbl_pc SELECT id, id::text FROM generate_series(1,1000)id;
+CHECKPOINT;
+SELECT pg_relation_size('tbl_pc') size_1000 \gset
+
+SELECT :size_1000 > :size_0; -- true
+
+DELETE FROM tbl_pc WHERE id > 500;
+VACUUM tbl_pc;
+SELECT pg_relation_size('tbl_pc') size_500 \gset
+SELECT count(*) FROM tbl_pc;
+SELECT :size_500 = :size_1000;  -- true
+
+DELETE FROM tbl_pc WHERE id < 500;
+VACUUM tbl_pc;
+SELECT pg_relation_size('tbl_pc') size_1 \gset
+SELECT count(*) FROM tbl_pc;
+SELECT :size_1 = :size_500;  -- true
+
+DELETE FROM tbl_pc;
+VACUUM tbl_pc;
+SELECT pg_relation_size('tbl_pc') size_0_2 \gset
+
+SELECT :size_0_2 = :size_500;  -- true
+
+VACUUM FULL tbl_pc;
+SELECT pg_relation_size('tbl_pc') size_0_3 \gset
+
+SELECT :size_0_3 = :size_0;  -- true
+
+DROP TABLE tbl_pc;
+
+--
+-- truncate
+--
+
+CREATE TABLE tbl_pc(id int) WITH(compresstype=pglz, compress_chunk_size=1024);
+CREATE INDEX tbl_pc_idx ON tbl_pc(id) WITH(compresstype=pglz, compress_chunk_size=1024);
+SELECT pg_relation_size('tbl_pc'::regclass) init_tbl_size, pg_relation_size('tbl_pc_idx'::regclass) init_idx_size \gset
+INSERT INTO tbl_pc SELECT generate_series(1,3000);
+CHECKPOINT;
+SELECT pg_relation_size('tbl_pc'::regclass) > :init_tbl_size; -- true
+SELECT pg_relation_size('tbl_pc_idx'::regclass) > :init_idx_size; -- true
+
+TRUNCATE tbl_pc;
+SELECT pg_relation_size('tbl_pc'::regclass) = :init_tbl_size; -- true
+SELECT pg_relation_size('tbl_pc_idx'::regclass) = :init_idx_size; -- true
+DROP TABLE tbl_pc;
+
+-- combining commands(CREATE + TRUNCATE)
+CREATE TABLE tbl_pc(id int) WITH(compresstype=pglz, compress_chunk_size=1024) \;
+CREATE INDEX tbl_pc_idx ON tbl_pc(id) WITH(compresstype=pglz, compress_chunk_size=1024) \;
+INSERT INTO tbl_pc SELECT generate_series(1,3000) \;
+TRUNCATE tbl_pc;
+
+SELECT pg_relation_size('tbl_pc'::regclass) = :init_tbl_size; -- true
+SELECT pg_relation_size('tbl_pc_idx'::regclass) = :init_idx_size; -- true
+DROP TABLE tbl_pc;
+
+--
+-- database templete support
+--
+SELECT current_database() olddb \gset
+CREATE DATABASE pctest_dbt;
+\c pctest_dbt
+CREATE TABLE tbl_pc(id int) WITH(compresstype=pglz, compress_chunk_size=1024);
+INSERT INTO tbl_pc SELECT generate_series(1,3000);
+CREATE INDEX tbl_pc_idx ON tbl_pc(id) WITH(compresstype=pglz, compress_chunk_size=1024);
+\c :olddb
+CREATE DATABASE pctest_db1 template pctest_dbt;
+\c pctest_db1
+SELECT count(*) FROM tbl_pc;
+SELECT id FROM tbl_pc WHERE id = 3000;
+\c :olddb
+DROP DATABASE pctest_dbt;
+DROP DATABASE pctest_db1;
+
diff --git a/src/test/zstd/.gitignore b/src/test/zstd/.gitignore
new file mode 100644
index 0000000000..5dcb3ff972
--- /dev/null
+++ b/src/test/zstd/.gitignore
@@ -0,0 +1,4 @@
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
diff --git a/src/test/zstd/Makefile b/src/test/zstd/Makefile
new file mode 100644
index 0000000000..0f6e446b9e
--- /dev/null
+++ b/src/test/zstd/Makefile
@@ -0,0 +1,14 @@
+# src/test/zstd/Makefile
+
+REGRESS = page_compression_with_zstd
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/zstd
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/zstd/README b/src/test/zstd/README
new file mode 100644
index 0000000000..b8b7c592d7
--- /dev/null
+++ b/src/test/zstd/README
@@ -0,0 +1,3 @@
+This directory contains tests for page compression with ztsd.
+
+The premise of running this test is to enable the '-with-zstd' option when running configure
diff --git a/src/test/zstd/expected/page_compression_with_zstd.out b/src/test/zstd/expected/page_compression_with_zstd.out
new file mode 100644
index 0000000000..1b7fe635d3
--- /dev/null
+++ b/src/test/zstd/expected/page_compression_with_zstd.out
@@ -0,0 +1,798 @@
+--
+-- Page compression tests
+--
+--
+-- create compressed table
+--
+CREATE TABLE tbl_pc(id int, c1 text) WITH(compresstype=zstd);
+\d+ tbl_pc
+                                   Table "public.tbl_pc"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ id     | integer |           |          |         | plain    |              | 
+ c1     | text    |           |          |         | extended |              | 
+Options: compresstype=zstd
+
+DROP TABLE tbl_pc;
+CREATE TABLE tbl_pc(id int, c1 text) WITH(compresstype=zstd, compress_chunk_size=512);
+\d+ tbl_pc
+                                   Table "public.tbl_pc"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ id     | integer |           |          |         | plain    |              | 
+ c1     | text    |           |          |         | extended |              | 
+Options: compresstype=zstd, compress_chunk_size=512
+
+DROP TABLE tbl_pc;
+CREATE TABLE tbl_pc(id int, c1 text) WITH(compresstype=zstd, compress_chunk_size=1024);
+\d+ tbl_pc
+                                   Table "public.tbl_pc"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ id     | integer |           |          |         | plain    |              | 
+ c1     | text    |           |          |         | extended |              | 
+Options: compresstype=zstd, compress_chunk_size=1024
+
+DROP TABLE tbl_pc;
+CREATE TABLE tbl_pc(id int, c1 text) WITH(compresstype=zstd, compress_chunk_size=2048);
+\d+ tbl_pc
+                                   Table "public.tbl_pc"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ id     | integer |           |          |         | plain    |              | 
+ c1     | text    |           |          |         | extended |              | 
+Options: compresstype=zstd, compress_chunk_size=2048
+
+DROP TABLE tbl_pc;
+CREATE TABLE tbl_pc(id int, c1 text) WITH(compresstype=zstd, compresslevel=0, compress_chunk_size=4096, compress_prealloc_chunks=0);
+\d+ tbl_pc
+                                   Table "public.tbl_pc"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ id     | integer |           |          |         | plain    |              | 
+ c1     | text    |           |          |         | extended |              | 
+Options: compresstype=zstd, compresslevel=0, compress_chunk_size=4096, compress_prealloc_chunks=0
+
+DROP TABLE tbl_pc;
+CREATE TABLE tbl_pc(id int PRIMARY KEY, c1 text) WITH(compresstype=zstd, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
+\d+ tbl_pc
+                                   Table "public.tbl_pc"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ id     | integer |           | not null |         | plain    |              | 
+ c1     | text    |           |          |         | extended |              | 
+Indexes:
+    "tbl_pc_pkey" PRIMARY KEY, btree (id)
+Options: compresstype=zstd, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2
+
+DROP TABLE tbl_pc;
+CREATE TABLE tbl_pc(id int PRIMARY KEY, c1 text) WITH(compresstype=zstd, compresslevel=-1, compress_chunk_size=1024, compress_prealloc_chunks=15);
+\d+ tbl_pc
+                                   Table "public.tbl_pc"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ id     | integer |           | not null |         | plain    |              | 
+ c1     | text    |           |          |         | extended |              | 
+Indexes:
+    "tbl_pc_pkey" PRIMARY KEY, btree (id)
+Options: compresstype=zstd, compresslevel=-1, compress_chunk_size=1024, compress_prealloc_chunks=15
+
+DROP TABLE tbl_pc;
+CREATE TABLE tbl_pc WITH(compresstype=zstd, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=15)
+  AS SELECT id, id::text c1 FROM generate_series(1,1000)id;
+\d+ tbl_pc
+                                   Table "public.tbl_pc"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ id     | integer |           |          |         | plain    |              | 
+ c1     | text    |           |          |         | extended |              | 
+Options: compresstype=zstd, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=15
+
+DROP TABLE tbl_pc;
+CREATE TABLE tbl_pc(id int PRIMARY KEY WITH(compresstype=zstd, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2), c1 text) WITH(compresstype=zstd, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
+CREATE TABLE tbl_pc2(LIKE tbl_pc INCLUDING ALL);
+\d+ tbl_pc
+                                   Table "public.tbl_pc"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ id     | integer |           | not null |         | plain    |              | 
+ c1     | text    |           |          |         | extended |              | 
+Indexes:
+    "tbl_pc_pkey" PRIMARY KEY, btree (id) WITH (compresstype=zstd, compresslevel='1', compress_chunk_size='1024', compress_prealloc_chunks='2')
+Options: compresstype=zstd, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2
+
+DROP TABLE tbl_pc;
+DROP TABLE tbl_pc2;
+CREATE TABLE tbl_pc(id int PRIMARY KEY, c1 text) WITH(compresstype=none);
+\d+ tbl_pc
+                                   Table "public.tbl_pc"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ id     | integer |           | not null |         | plain    |              | 
+ c1     | text    |           |          |         | extended |              | 
+Indexes:
+    "tbl_pc_pkey" PRIMARY KEY, btree (id)
+Options: compresstype=none
+
+DROP TABLE tbl_pc;
+-- invalid storage parameter
+CREATE TABLE tbl_pc_error(id int, c1 text) WITH(compresstype=xyz); -- fail
+ERROR:  invalid value for enum option "compresstype": xyz
+DETAIL:  Valid values are "none", "pglz", "lz4" and "zstd".
+CREATE TABLE tbl_pc_error(id int, c1 text) WITH(compresstype=zstd, compresslevel=xyz); -- fail
+ERROR:  invalid value for integer option "compresslevel": xyz
+CREATE TABLE tbl_pc_error(id int, c1 text) WITH(compresstype=zstd, compress_chunk_size=1025); -- fail
+ERROR:  invalid compress_chunk_size 1025, must be one of 512, 1024, 2048 or 4096
+CREATE TABLE tbl_pc_error(id int, c1 text) WITH(compresstype=zstd, compress_prealloc_chunks=16); -- fail
+ERROR:  value 16 out of bounds for option "compress_prealloc_chunks"
+DETAIL:  Valid values are between "0" and "15".
+--
+-- create compressed index
+--
+SET enable_seqscan = OFF;
+CREATE TABLE tbl_pc(id int PRIMARY KEY WITH(compresstype=zstd, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2), c1 text) WITH(compresstype=zstd, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
+CREATE INDEX tbl_pc_idx1 on tbl_pc(c1) WITH(compresstype=zstd, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
+\d+ tbl_pc
+                                   Table "public.tbl_pc"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ id     | integer |           | not null |         | plain    |              | 
+ c1     | text    |           |          |         | extended |              | 
+Indexes:
+    "tbl_pc_pkey" PRIMARY KEY, btree (id) WITH (compresstype=zstd, compresslevel='1', compress_chunk_size='1024', compress_prealloc_chunks='2')
+    "tbl_pc_idx1" btree (c1) WITH (compresstype=zstd, compresslevel='1', compress_chunk_size='1024', compress_prealloc_chunks='2')
+Options: compresstype=zstd, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2
+
+INSERT INTO tbl_pc SELECT id, id::text FROM generate_series(1,1000)id;
+-- call CHECKPOINT to flush shared buffer to compressed relation file 
+CHECKPOINT;
+-- run ANALYZE REINDEX VACUUM and CLUSTER on compressed table and index
+ANALYZE tbl_pc;
+SELECT count(*) FROM tbl_pc;
+ count 
+-------
+  1000
+(1 row)
+
+SELECT * FROM tbl_pc WHERE c1='100';
+ id  | c1  
+-----+-----
+ 100 | 100
+(1 row)
+
+EXPLAIN(COSTS off) SELECT * FROM tbl_pc WHERE c1='100';
+               QUERY PLAN               
+----------------------------------------
+ Index Scan using tbl_pc_idx1 on tbl_pc
+   Index Cond: (c1 = '100'::text)
+(2 rows)
+
+REINDEX INDEX tbl_pc_idx1;
+CHECKPOINT;
+SELECT * FROM tbl_pc WHERE c1='100';
+ id  | c1  
+-----+-----
+ 100 | 100
+(1 row)
+
+REINDEX TABLE tbl_pc;
+CHECKPOINT;
+SELECT * FROM tbl_pc WHERE c1='100';
+ id  | c1  
+-----+-----
+ 100 | 100
+(1 row)
+
+VACUUM tbl_pc;
+CHECKPOINT;
+SELECT count(*) FROM tbl_pc;
+ count 
+-------
+  1000
+(1 row)
+
+SELECT * FROM tbl_pc WHERE c1='100';
+ id  | c1  
+-----+-----
+ 100 | 100
+(1 row)
+
+VACUUM FULL tbl_pc;
+CHECKPOINT;
+SELECT count(*) FROM tbl_pc;
+ count 
+-------
+  1000
+(1 row)
+
+SELECT * FROM tbl_pc WHERE c1='100';
+ id  | c1  
+-----+-----
+ 100 | 100
+(1 row)
+
+CLUSTER tbl_pc USING tbl_pc_idx1;
+CHECKPOINT;
+SELECT count(*) FROM tbl_pc;
+ count 
+-------
+  1000
+(1 row)
+
+SELECT * FROM tbl_pc WHERE c1='100';
+ id  | c1  
+-----+-----
+ 100 | 100
+(1 row)
+
+DROP INDEX tbl_pc_idx1;
+-- check usage of compressed index with data
+CREATE INDEX tbl_pc_idx1 on tbl_pc USING hash(c1) WITH(compresstype=zstd, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
+CHECKPOINT;
+SELECT * FROM tbl_pc WHERE c1='100';
+ id  | c1  
+-----+-----
+ 100 | 100
+(1 row)
+
+EXPLAIN(COSTS off) SELECT * FROM tbl_pc WHERE c1='100';
+               QUERY PLAN               
+----------------------------------------
+ Index Scan using tbl_pc_idx1 on tbl_pc
+   Index Cond: (c1 = '100'::text)
+(2 rows)
+
+DROP INDEX tbl_pc_idx1;
+CREATE INDEX tbl_pc_idx1 on tbl_pc USING gin((ARRAY[id])) WITH(compresstype=zstd, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
+CHECKPOINT;
+SELECT * FROM tbl_pc WHERE ARRAY[id] @> ARRAY[100];
+ id  | c1  
+-----+-----
+ 100 | 100
+(1 row)
+
+EXPLAIN(COSTS off) SELECT * FROM tbl_pc WHERE ARRAY[id] @> ARRAY[100];
+                      QUERY PLAN                       
+-------------------------------------------------------
+ Bitmap Heap Scan on tbl_pc
+   Recheck Cond: (ARRAY[id] @> '{100}'::integer[])
+   ->  Bitmap Index Scan on tbl_pc_idx1
+         Index Cond: (ARRAY[id] @> '{100}'::integer[])
+(4 rows)
+
+DROP INDEX tbl_pc_idx1;
+CREATE INDEX tbl_pc_idx1 on tbl_pc USING gist((point(id,id))) WITH(compresstype=zstd, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
+CHECKPOINT;
+SELECT * FROM tbl_pc ORDER BY point(id,id) <-> point(100,100) limit 1;
+ id  | c1  
+-----+-----
+ 100 | 100
+(1 row)
+
+EXPLAIN(COSTS off) SELECT * FROM tbl_pc ORDER BY point(id,id) <-> point(100,100) limit 1;
+                                            QUERY PLAN                                            
+--------------------------------------------------------------------------------------------------
+ Limit
+   ->  Index Scan using tbl_pc_idx1 on tbl_pc
+         Order By: (point((id)::double precision, (id)::double precision) <-> '(100,100)'::point)
+(3 rows)
+
+DROP INDEX tbl_pc_idx1;
+CREATE INDEX tbl_pc_idx1 on tbl_pc USING spgist(c1) WITH(compresstype=zstd, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
+CHECKPOINT;
+SELECT * FROM tbl_pc WHERE c1='100';
+ id  | c1  
+-----+-----
+ 100 | 100
+(1 row)
+
+EXPLAIN(COSTS off) SELECT * FROM tbl_pc WHERE c1='100';
+               QUERY PLAN               
+----------------------------------------
+ Index Scan using tbl_pc_idx1 on tbl_pc
+   Index Cond: (c1 = '100'::text)
+(2 rows)
+
+DROP INDEX tbl_pc_idx1;
+-- brin index does not support compression
+CREATE INDEX tbl_pc_idx1 on tbl_pc USING brin(c1) WITH(compresstype=zstd, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2); -- fail
+ERROR:  unrecognized parameter "compresstype"
+DROP TABLE tbl_pc;
+RESET enable_seqscan;
+--
+-- alter table and index
+--
+-- ALTER TABLE
+-- ALTER compresstype and compress_chunk_size currently is not supported
+CREATE TABLE tbl_pc(id int, c1 text);
+ALTER TABLE tbl_pc SET(compresstype=zstd); -- fail
+ERROR:  change compresstype parameter is not supported
+DROP TABLE tbl_pc;
+CREATE TABLE tbl_pc(id int PRIMARY KEY, c1 text) WITH(compresstype=zstd, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
+ALTER TABLE tbl_pc SET(compresstype=none); -- fail
+ERROR:  change compresstype parameter is not supported
+ALTER TABLE tbl_pc SET(compress_chunk_size=2048); -- fail
+ERROR:  change compress_chunk_size parameter is not supported
+ALTER TABLE tbl_pc SET(compress_prealloc_chunks=16);  -- fail
+ERROR:  value 16 out of bounds for option "compress_prealloc_chunks"
+DETAIL:  Valid values are between "0" and "15".
+ALTER TABLE tbl_pc SET(compresslevel=2, compress_prealloc_chunks=0); -- ok
+\d+ tbl_pc
+                                   Table "public.tbl_pc"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ id     | integer |           | not null |         | plain    |              | 
+ c1     | text    |           |          |         | extended |              | 
+Indexes:
+    "tbl_pc_pkey" PRIMARY KEY, btree (id)
+Options: compresstype=zstd, compress_chunk_size=1024, compresslevel=2, compress_prealloc_chunks=0
+
+ALTER TABLE tbl_pc RESET(compresstype); -- fail
+ERROR:  change compresstype parameter is not supported
+ALTER TABLE tbl_pc RESET(compress_chunk_size); -- fail
+ERROR:  change compress_chunk_size parameter is not supported
+ALTER TABLE tbl_pc RESET(compresslevel); -- ok
+ALTER TABLE tbl_pc RESET(compress_prealloc_chunks); -- ok
+\d+ tbl_pc
+                                   Table "public.tbl_pc"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ id     | integer |           | not null |         | plain    |              | 
+ c1     | text    |           |          |         | extended |              | 
+Indexes:
+    "tbl_pc_pkey" PRIMARY KEY, btree (id)
+Options: compresstype=zstd, compress_chunk_size=1024
+
+CHECKPOINT;
+SELECT count(*) FROM tbl_pc;
+ count 
+-------
+     0
+(1 row)
+
+-- ALTER INDEX
+-- ALTER compresstype and compress_chunk_size currently is not supported
+CREATE INDEX tbl_pc_idx1 on tbl_pc USING btree(c1);
+ALTER INDEX tbl_pc_idx1 SET(compresstype=zstd); -- fail
+ERROR:  change compresstype parameter is not supported
+DROP INDEX tbl_pc_idx1;
+CREATE INDEX tbl_pc_idx1 on tbl_pc USING btree(c1) WITH(compresstype=zstd, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
+ALTER INDEX tbl_pc_idx1 SET(compresstype=none); -- fail
+ERROR:  change compresstype parameter is not supported
+ALTER INDEX tbl_pc_idx1 SET(compress_chunk_size=2048); -- fail
+ERROR:  change compress_chunk_size parameter is not supported
+ALTER INDEX tbl_pc_idx1 SET(compress_prealloc_chunks=16);  -- fail
+ERROR:  value 16 out of bounds for option "compress_prealloc_chunks"
+DETAIL:  Valid values are between "0" and "15".
+ALTER INDEX tbl_pc_idx1 SET(compresslevel=2, compress_prealloc_chunks=0); -- ok
+\d+ tbl_pc
+                                   Table "public.tbl_pc"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ id     | integer |           | not null |         | plain    |              | 
+ c1     | text    |           |          |         | extended |              | 
+Indexes:
+    "tbl_pc_pkey" PRIMARY KEY, btree (id)
+    "tbl_pc_idx1" btree (c1) WITH (compresstype=zstd, compress_chunk_size='1024', compresslevel='2', compress_prealloc_chunks='0')
+Options: compresstype=zstd, compress_chunk_size=1024
+
+ALTER INDEX tbl_pc_idx1 RESET(compresstype); -- fail
+ERROR:  change compresstype parameter is not supported
+ALTER INDEX tbl_pc_idx1 RESET(compress_chunk_size); -- fail
+ERROR:  change compress_chunk_size parameter is not supported
+ALTER INDEX tbl_pc_idx1 RESET(compresslevel); -- ok
+ALTER INDEX tbl_pc_idx1 RESET(compress_prealloc_chunks); -- ok
+\d+ tbl_pc
+                                   Table "public.tbl_pc"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ id     | integer |           | not null |         | plain    |              | 
+ c1     | text    |           |          |         | extended |              | 
+Indexes:
+    "tbl_pc_pkey" PRIMARY KEY, btree (id)
+    "tbl_pc_idx1" btree (c1) WITH (compresstype=zstd, compress_chunk_size='1024')
+Options: compresstype=zstd, compress_chunk_size=1024
+
+CHECKPOINT;
+SELECT * FROM tbl_pc WHERE c1='100';
+ id | c1 
+----+----
+(0 rows)
+
+EXPLAIN(COSTS off) SELECT * FROM tbl_pc WHERE c1='100';
+               QUERY PLAN               
+----------------------------------------
+ Bitmap Heap Scan on tbl_pc
+   Recheck Cond: (c1 = '100'::text)
+   ->  Bitmap Index Scan on tbl_pc_idx1
+         Index Cond: (c1 = '100'::text)
+(4 rows)
+
+-- alter hash index
+CREATE INDEX tbl_pc_idx_hash on tbl_pc USING hash(c1) WITH(compresstype=zstd, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
+ALTER INDEX tbl_pc_idx_hash SET(compresstype=none); -- fail
+ERROR:  change compresstype parameter is not supported
+ALTER INDEX tbl_pc_idx_hash SET(compress_chunk_size=2048); -- fail
+ERROR:  change compress_chunk_size parameter is not supported
+ALTER INDEX tbl_pc_idx_hash SET(compress_prealloc_chunks=16);  -- fail
+ERROR:  value 16 out of bounds for option "compress_prealloc_chunks"
+DETAIL:  Valid values are between "0" and "15".
+ALTER INDEX tbl_pc_idx_hash SET(compresslevel=2, compress_prealloc_chunks=0); -- ok
+\d+ tbl_pc_idx_hash
+                Index "public.tbl_pc_idx_hash"
+ Column |  Type   | Key? | Definition | Storage | Stats target 
+--------+---------+------+------------+---------+--------------
+ c1     | integer | yes  | c1         | plain   | 
+hash, for table "public.tbl_pc"
+Options: compresstype=zstd, compress_chunk_size=1024, compresslevel=2, compress_prealloc_chunks=0
+
+ALTER INDEX tbl_pc_idx_hash RESET(compresstype); -- fail
+ERROR:  change compresstype parameter is not supported
+ALTER INDEX tbl_pc_idx_hash RESET(compress_chunk_size); -- fail
+ERROR:  change compress_chunk_size parameter is not supported
+ALTER INDEX tbl_pc_idx_hash RESET(compresslevel); -- ok
+ALTER INDEX tbl_pc_idx_hash RESET(compress_prealloc_chunks); -- ok
+\d+ tbl_pc_idx_hash
+                Index "public.tbl_pc_idx_hash"
+ Column |  Type   | Key? | Definition | Storage | Stats target 
+--------+---------+------+------------+---------+--------------
+ c1     | integer | yes  | c1         | plain   | 
+hash, for table "public.tbl_pc"
+Options: compresstype=zstd, compress_chunk_size=1024
+
+-- alter gin index
+CREATE INDEX tbl_pc_idx_gin on tbl_pc USING gin((ARRAY[id])) WITH(compresstype=zstd, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
+ALTER INDEX tbl_pc_idx_gin SET(compresstype=none); -- fail
+ERROR:  change compresstype parameter is not supported
+ALTER INDEX tbl_pc_idx_gin SET(compress_chunk_size=2048); -- fail
+ERROR:  change compress_chunk_size parameter is not supported
+ALTER INDEX tbl_pc_idx_gin SET(compress_prealloc_chunks=16);  -- fail
+ERROR:  value 16 out of bounds for option "compress_prealloc_chunks"
+DETAIL:  Valid values are between "0" and "15".
+ALTER INDEX tbl_pc_idx_gin SET(compresslevel=2, compress_prealloc_chunks=0); -- ok
+\d+ tbl_pc_idx_gin
+                 Index "public.tbl_pc_idx_gin"
+ Column |  Type   | Key? | Definition  | Storage | Stats target 
+--------+---------+------+-------------+---------+--------------
+ array  | integer | yes  | (ARRAY[id]) | plain   | 
+gin, for table "public.tbl_pc"
+Options: compresstype=zstd, compress_chunk_size=1024, compresslevel=2, compress_prealloc_chunks=0
+
+ALTER INDEX tbl_pc_idx_gin RESET(compresstype); -- fail
+ERROR:  change compresstype parameter is not supported
+ALTER INDEX tbl_pc_idx_gin RESET(compress_chunk_size); -- fail
+ERROR:  change compress_chunk_size parameter is not supported
+ALTER INDEX tbl_pc_idx_gin RESET(compresslevel); -- ok
+ALTER INDEX tbl_pc_idx_gin RESET(compress_prealloc_chunks); -- ok
+\d+ tbl_pc_idx_gin
+                 Index "public.tbl_pc_idx_gin"
+ Column |  Type   | Key? | Definition  | Storage | Stats target 
+--------+---------+------+-------------+---------+--------------
+ array  | integer | yes  | (ARRAY[id]) | plain   | 
+gin, for table "public.tbl_pc"
+Options: compresstype=zstd, compress_chunk_size=1024
+
+-- alter gist index
+CREATE INDEX tbl_pc_idx_gist on tbl_pc USING gist((point(id,id))) WITH(compresstype=zstd, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
+ALTER INDEX tbl_pc_idx_gist SET(compresstype=none); -- fail
+ERROR:  change compresstype parameter is not supported
+ALTER INDEX tbl_pc_idx_gist SET(compress_chunk_size=2048); -- fail
+ERROR:  change compress_chunk_size parameter is not supported
+ALTER INDEX tbl_pc_idx_gist SET(compress_prealloc_chunks=16);  -- fail
+ERROR:  value 16 out of bounds for option "compress_prealloc_chunks"
+DETAIL:  Valid values are between "0" and "15".
+ALTER INDEX tbl_pc_idx_gist SET(compresslevel=2, compress_prealloc_chunks=0); -- ok
+\d+ tbl_pc_idx_gist
+                                  Index "public.tbl_pc_idx_gist"
+ Column | Type | Key? |                    Definition                     | Storage | Stats target 
+--------+------+------+---------------------------------------------------+---------+--------------
+ point  | box  | yes  | point(id::double precision, id::double precision) | plain   | 
+gist, for table "public.tbl_pc"
+Options: compresstype=zstd, compress_chunk_size=1024, compresslevel=2, compress_prealloc_chunks=0
+
+ALTER INDEX tbl_pc_idx_gist RESET(compresstype); -- fail
+ERROR:  change compresstype parameter is not supported
+ALTER INDEX tbl_pc_idx_gist RESET(compress_chunk_size); -- fail
+ERROR:  change compress_chunk_size parameter is not supported
+ALTER INDEX tbl_pc_idx_gist RESET(compresslevel); -- ok
+ALTER INDEX tbl_pc_idx_gist RESET(compress_prealloc_chunks); -- ok
+\d+ tbl_pc_idx_gist
+                                  Index "public.tbl_pc_idx_gist"
+ Column | Type | Key? |                    Definition                     | Storage | Stats target 
+--------+------+------+---------------------------------------------------+---------+--------------
+ point  | box  | yes  | point(id::double precision, id::double precision) | plain   | 
+gist, for table "public.tbl_pc"
+Options: compresstype=zstd, compress_chunk_size=1024
+
+-- alter spgist index
+CREATE INDEX tbl_pc_idx_spgist on tbl_pc USING spgist(c1) WITH(compresstype=zstd, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
+ALTER INDEX tbl_pc_idx_spgist SET(compresstype=none); -- fail
+ERROR:  change compresstype parameter is not supported
+ALTER INDEX tbl_pc_idx_spgist SET(compress_chunk_size=2048); -- fail
+ERROR:  change compress_chunk_size parameter is not supported
+ALTER INDEX tbl_pc_idx_spgist SET(compress_prealloc_chunks=16);  -- fail
+ERROR:  value 16 out of bounds for option "compress_prealloc_chunks"
+DETAIL:  Valid values are between "0" and "15".
+ALTER INDEX tbl_pc_idx_spgist SET(compresslevel=2, compress_prealloc_chunks=0); -- ok
+\d+ tbl_pc_idx_spgist
+              Index "public.tbl_pc_idx_spgist"
+ Column | Type | Key? | Definition | Storage  | Stats target 
+--------+------+------+------------+----------+--------------
+ c1     | text | yes  | c1         | extended | 
+spgist, for table "public.tbl_pc"
+Options: compresstype=zstd, compress_chunk_size=1024, compresslevel=2, compress_prealloc_chunks=0
+
+ALTER INDEX tbl_pc_idx_spgist RESET(compresstype); -- fail
+ERROR:  change compresstype parameter is not supported
+ALTER INDEX tbl_pc_idx_spgist RESET(compress_chunk_size); -- fail
+ERROR:  change compress_chunk_size parameter is not supported
+ALTER INDEX tbl_pc_idx_spgist RESET(compresslevel); -- ok
+ALTER INDEX tbl_pc_idx_spgist RESET(compress_prealloc_chunks); -- ok
+\d+ tbl_pc_idx_spgist
+              Index "public.tbl_pc_idx_spgist"
+ Column | Type | Key? | Definition | Storage  | Stats target 
+--------+------+------+------------+----------+--------------
+ c1     | text | yes  | c1         | extended | 
+spgist, for table "public.tbl_pc"
+Options: compresstype=zstd, compress_chunk_size=1024
+
+-- alter brin index (do not support compression)
+CREATE INDEX tbl_pc_idx_brin on tbl_pc USING brin(c1);
+ALTER INDEX tbl_pc_idx_brin SET(compress_prealloc_chunks=3);  -- fail
+ERROR:  unrecognized parameter "compress_prealloc_chunks"
+DROP TABLE tbl_pc;
+--
+-- partitioned table and index
+--
+-- partition table does not support compression, but index of partition table and its child tables can use compression
+CREATE TABLE tbl_pc_part (id int, c1 text) PARTITION BY RANGE (id) WITH(compresstype=zstd); -- fail
+ERROR:  unrecognized parameter "compresstype"
+CREATE TABLE tbl_pc_part (id int, c1 text) PARTITION BY RANGE (id);
+CREATE TABLE tbl_pc_part_1 PARTITION OF tbl_pc_part FOR VALUES FROM (1) TO (1001);
+CREATE TABLE tbl_pc_part_2 PARTITION OF tbl_pc_part FOR VALUES FROM (1001) TO (2001) WITH(compresstype=zstd, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
+CREATE INDEX part_id_idx ON tbl_pc_part(id) WITH(compresstype=zstd, compresslevel=2, compress_chunk_size=1024, compress_prealloc_chunks=2);
+CREATE TABLE tbl_pc_part_3 PARTITION OF tbl_pc_part FOR VALUES FROM (2001) TO (3001);
+CREATE INDEX part3_id_idx1 ON tbl_pc_part_3(id) WITH(compresstype=zstd, compresslevel=2, compress_chunk_size=1024, compress_prealloc_chunks=2);
+\d+ tbl_pc_part
+                          Partitioned table "public.tbl_pc_part"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ id     | integer |           |          |         | plain    |              | 
+ c1     | text    |           |          |         | extended |              | 
+Partition key: RANGE (id)
+Indexes:
+    "part_id_idx" btree (id) WITH (compresstype=zstd, compresslevel='2', compress_chunk_size='1024', compress_prealloc_chunks='2')
+Partitions: tbl_pc_part_1 FOR VALUES FROM (1) TO (1001),
+            tbl_pc_part_2 FOR VALUES FROM (1001) TO (2001),
+            tbl_pc_part_3 FOR VALUES FROM (2001) TO (3001)
+
+\d+ part_id_idx
+            Partitioned index "public.part_id_idx"
+ Column |  Type   | Key? | Definition | Storage | Stats target 
+--------+---------+------+------------+---------+--------------
+ id     | integer | yes  | id         | plain   | 
+btree, for table "public.tbl_pc_part"
+Partitions: tbl_pc_part_1_id_idx,
+            tbl_pc_part_2_id_idx,
+            tbl_pc_part_3_id_idx
+Options: compresstype=zstd, compresslevel=2, compress_chunk_size=1024, compress_prealloc_chunks=2
+
+\d+ tbl_pc_part_1
+                               Table "public.tbl_pc_part_1"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ id     | integer |           |          |         | plain    |              | 
+ c1     | text    |           |          |         | extended |              | 
+Partition of: tbl_pc_part FOR VALUES FROM (1) TO (1001)
+Partition constraint: ((id IS NOT NULL) AND (id >= 1) AND (id < 1001))
+Indexes:
+    "tbl_pc_part_1_id_idx" btree (id) WITH (compresstype=zstd, compresslevel='2', compress_chunk_size='1024', compress_prealloc_chunks='2')
+
+\d+ tbl_pc_part_2
+                               Table "public.tbl_pc_part_2"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ id     | integer |           |          |         | plain    |              | 
+ c1     | text    |           |          |         | extended |              | 
+Partition of: tbl_pc_part FOR VALUES FROM (1001) TO (2001)
+Partition constraint: ((id IS NOT NULL) AND (id >= 1001) AND (id < 2001))
+Indexes:
+    "tbl_pc_part_2_id_idx" btree (id) WITH (compresstype=zstd, compresslevel='2', compress_chunk_size='1024', compress_prealloc_chunks='2')
+Options: compresstype=zstd, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2
+
+\d+ tbl_pc_part_3
+                               Table "public.tbl_pc_part_3"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ id     | integer |           |          |         | plain    |              | 
+ c1     | text    |           |          |         | extended |              | 
+Partition of: tbl_pc_part FOR VALUES FROM (2001) TO (3001)
+Partition constraint: ((id IS NOT NULL) AND (id >= 2001) AND (id < 3001))
+Indexes:
+    "part3_id_idx1" btree (id) WITH (compresstype=zstd, compresslevel='2', compress_chunk_size='1024', compress_prealloc_chunks='2')
+    "tbl_pc_part_3_id_idx" btree (id) WITH (compresstype=zstd, compresslevel='2', compress_chunk_size='1024', compress_prealloc_chunks='2')
+
+INSERT INTO tbl_pc_part SELECT id, id::text FROM generate_series(1,3000)id;
+CHECKPOINT;
+SELECT count(*) FROM tbl_pc_part;
+ count 
+-------
+  3000
+(1 row)
+
+SELECT * FROM tbl_pc_part WHERE id=100;
+ id  | c1  
+-----+-----
+ 100 | 100
+(1 row)
+
+SELECT * FROM tbl_pc_part WHERE id=1100;
+  id  |  c1  
+------+------
+ 1100 | 1100
+(1 row)
+
+SELECT * FROM tbl_pc_part WHERE id=2100;
+  id  |  c1  
+------+------
+ 2100 | 2100
+(1 row)
+
+ALTER TABLE tbl_pc_part SET(compresstype=zstd); -- fail
+ERROR:  unrecognized parameter "compresstype"
+ALTER TABLE tbl_pc_part_1 SET(compresstype=zstd); -- fail
+ERROR:  change compresstype parameter is not supported
+ALTER TABLE tbl_pc_part_2 SET(compresstype=none); -- fail
+ERROR:  change compresstype parameter is not supported
+ALTER TABLE tbl_pc_part_2 SET(compress_chunk_size=2048); -- fail
+ERROR:  change compress_chunk_size parameter is not supported
+ALTER TABLE tbl_pc_part_2 SET(compress_prealloc_chunks=16);  -- fail
+ERROR:  value 16 out of bounds for option "compress_prealloc_chunks"
+DETAIL:  Valid values are between "0" and "15".
+ALTER TABLE tbl_pc_part_2 SET(compresslevel=2, compress_prealloc_chunks=0); -- ok
+\d+ tbl_pc_part_2
+                               Table "public.tbl_pc_part_2"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ id     | integer |           |          |         | plain    |              | 
+ c1     | text    |           |          |         | extended |              | 
+Partition of: tbl_pc_part FOR VALUES FROM (1001) TO (2001)
+Partition constraint: ((id IS NOT NULL) AND (id >= 1001) AND (id < 2001))
+Indexes:
+    "tbl_pc_part_2_id_idx" btree (id) WITH (compresstype=zstd, compresslevel='2', compress_chunk_size='1024', compress_prealloc_chunks='2')
+Options: compresstype=zstd, compress_chunk_size=1024, compresslevel=2, compress_prealloc_chunks=0
+
+ALTER INDEX part3_id_idx1 SET(compresstype=none); -- fail
+ERROR:  change compresstype parameter is not supported
+ALTER INDEX part3_id_idx1 SET(compress_chunk_size=2048); -- fail
+ERROR:  change compress_chunk_size parameter is not supported
+ALTER INDEX part3_id_idx1 SET(compress_prealloc_chunks=16);  -- fail
+ERROR:  value 16 out of bounds for option "compress_prealloc_chunks"
+DETAIL:  Valid values are between "0" and "15".
+ALTER INDEX part3_id_idx1 SET(compresslevel=2, compress_prealloc_chunks=0); -- ok
+\d+ part3_id_idx1
+                 Index "public.part3_id_idx1"
+ Column |  Type   | Key? | Definition | Storage | Stats target 
+--------+---------+------+------------+---------+--------------
+ id     | integer | yes  | id         | plain   | 
+btree, for table "public.tbl_pc_part_3"
+Options: compresstype=zstd, compress_chunk_size=1024, compresslevel=2, compress_prealloc_chunks=0
+
+ALTER INDEX part_id_idx SET(compresstype=zstd); -- fail
+ERROR:  ALTER action SET cannot be performed on relation "part_id_idx"
+DETAIL:  This operation is not supported for partitioned indexes.
+ALTER INDEX part_id_idx SET(compress_chunk_size=2048); -- fail
+ERROR:  ALTER action SET cannot be performed on relation "part_id_idx"
+DETAIL:  This operation is not supported for partitioned indexes.
+ALTER INDEX part_id_idx SET(compress_prealloc_chunks=16);  -- fail
+ERROR:  ALTER action SET cannot be performed on relation "part_id_idx"
+DETAIL:  This operation is not supported for partitioned indexes.
+ALTER INDEX part_id_idx SET(compresslevel=2, compress_prealloc_chunks=0); -- fail
+ERROR:  ALTER action SET cannot be performed on relation "part_id_idx"
+DETAIL:  This operation is not supported for partitioned indexes.
+\d+ tbl_pc_part
+                          Partitioned table "public.tbl_pc_part"
+ Column |  Type   | Collation | Nullable | Default | Storage  | Stats target | Description 
+--------+---------+-----------+----------+---------+----------+--------------+-------------
+ id     | integer |           |          |         | plain    |              | 
+ c1     | text    |           |          |         | extended |              | 
+Partition key: RANGE (id)
+Indexes:
+    "part_id_idx" btree (id) WITH (compresstype=zstd, compresslevel='2', compress_chunk_size='1024', compress_prealloc_chunks='2')
+Partitions: tbl_pc_part_1 FOR VALUES FROM (1) TO (1001),
+            tbl_pc_part_2 FOR VALUES FROM (1001) TO (2001),
+            tbl_pc_part_3 FOR VALUES FROM (2001) TO (3001)
+
+INSERT INTO tbl_pc_part SELECT id, id::text FROM generate_series(1,3000)id;
+CHECKPOINT;
+SELECT count(*) FROM tbl_pc_part;
+ count 
+-------
+  6000
+(1 row)
+
+SELECT * FROM tbl_pc_part WHERE id=100;
+ id  | c1  
+-----+-----
+ 100 | 100
+ 100 | 100
+(2 rows)
+
+SELECT * FROM tbl_pc_part WHERE id=1100;
+  id  |  c1  
+------+------
+ 1100 | 1100
+ 1100 | 1100
+(2 rows)
+
+SELECT * FROM tbl_pc_part WHERE id=2100;
+  id  |  c1  
+------+------
+ 2100 | 2100
+ 2100 | 2100
+(2 rows)
+
+DROP TABLE tbl_pc_part;
+--
+-- recycling space with vacuum (do not support)
+--
+CREATE TABLE tbl_pc(id int PRIMARY KEY, c1 text) WITH(compresstype=zstd, compress_chunk_size=2048, compress_prealloc_chunks=0);
+CHECKPOINT;
+SELECT pg_relation_size('tbl_pc') size_0 \gset
+INSERT INTO tbl_pc SELECT id, id::text FROM generate_series(1,1000)id;
+CHECKPOINT;
+SELECT pg_relation_size('tbl_pc') size_1000 \gset
+SELECT :size_1000 > :size_0; -- true
+ ?column? 
+----------
+ t
+(1 row)
+
+DELETE FROM tbl_pc WHERE id > 500;
+VACUUM tbl_pc;
+SELECT pg_relation_size('tbl_pc') size_500 \gset
+SELECT count(*) FROM tbl_pc;
+ count 
+-------
+   500
+(1 row)
+
+SELECT :size_500 = :size_1000;  -- true
+ ?column? 
+----------
+ t
+(1 row)
+
+DELETE FROM tbl_pc WHERE id < 500;
+VACUUM tbl_pc;
+SELECT pg_relation_size('tbl_pc') size_1 \gset
+SELECT count(*) FROM tbl_pc;
+ count 
+-------
+     1
+(1 row)
+
+SELECT :size_1 = :size_500;  -- true
+ ?column? 
+----------
+ t
+(1 row)
+
+DELETE FROM tbl_pc;
+VACUUM tbl_pc;
+SELECT pg_relation_size('tbl_pc') size_0_2 \gset
+SELECT :size_0_2 = :size_500;  -- true
+ ?column? 
+----------
+ t
+(1 row)
+
+VACUUM FULL tbl_pc;
+SELECT pg_relation_size('tbl_pc') size_0_3 \gset
+SELECT :size_0_3 = :size_0;  -- true
+ ?column? 
+----------
+ t
+(1 row)
+
+DROP TABLE tbl_pc;
diff --git a/src/test/zstd/sql/page_compression_with_zstd.sql b/src/test/zstd/sql/page_compression_with_zstd.sql
new file mode 100644
index 0000000000..c68fbb0ef5
--- /dev/null
+++ b/src/test/zstd/sql/page_compression_with_zstd.sql
@@ -0,0 +1,341 @@
+--
+-- Page compression tests
+--
+
+--
+-- create compressed table
+--
+CREATE TABLE tbl_pc(id int, c1 text) WITH(compresstype=zstd);
+\d+ tbl_pc
+DROP TABLE tbl_pc;
+
+CREATE TABLE tbl_pc(id int, c1 text) WITH(compresstype=zstd, compress_chunk_size=512);
+\d+ tbl_pc
+DROP TABLE tbl_pc;
+
+CREATE TABLE tbl_pc(id int, c1 text) WITH(compresstype=zstd, compress_chunk_size=1024);
+\d+ tbl_pc
+DROP TABLE tbl_pc;
+
+CREATE TABLE tbl_pc(id int, c1 text) WITH(compresstype=zstd, compress_chunk_size=2048);
+\d+ tbl_pc
+DROP TABLE tbl_pc;
+
+CREATE TABLE tbl_pc(id int, c1 text) WITH(compresstype=zstd, compresslevel=0, compress_chunk_size=4096, compress_prealloc_chunks=0);
+\d+ tbl_pc
+DROP TABLE tbl_pc;
+
+CREATE TABLE tbl_pc(id int PRIMARY KEY, c1 text) WITH(compresstype=zstd, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
+\d+ tbl_pc
+DROP TABLE tbl_pc;
+
+CREATE TABLE tbl_pc(id int PRIMARY KEY, c1 text) WITH(compresstype=zstd, compresslevel=-1, compress_chunk_size=1024, compress_prealloc_chunks=15);
+\d+ tbl_pc
+DROP TABLE tbl_pc;
+
+CREATE TABLE tbl_pc WITH(compresstype=zstd, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=15)
+  AS SELECT id, id::text c1 FROM generate_series(1,1000)id;
+\d+ tbl_pc
+DROP TABLE tbl_pc;
+
+CREATE TABLE tbl_pc(id int PRIMARY KEY WITH(compresstype=zstd, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2), c1 text) WITH(compresstype=zstd, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
+CREATE TABLE tbl_pc2(LIKE tbl_pc INCLUDING ALL);
+\d+ tbl_pc
+DROP TABLE tbl_pc;
+DROP TABLE tbl_pc2;
+
+CREATE TABLE tbl_pc(id int PRIMARY KEY, c1 text) WITH(compresstype=none);
+\d+ tbl_pc
+DROP TABLE tbl_pc;
+
+-- invalid storage parameter
+CREATE TABLE tbl_pc_error(id int, c1 text) WITH(compresstype=xyz); -- fail
+CREATE TABLE tbl_pc_error(id int, c1 text) WITH(compresstype=zstd, compresslevel=xyz); -- fail
+CREATE TABLE tbl_pc_error(id int, c1 text) WITH(compresstype=zstd, compress_chunk_size=1025); -- fail
+CREATE TABLE tbl_pc_error(id int, c1 text) WITH(compresstype=zstd, compress_prealloc_chunks=16); -- fail
+
+
+--
+-- create compressed index
+--
+SET enable_seqscan = OFF;
+
+CREATE TABLE tbl_pc(id int PRIMARY KEY WITH(compresstype=zstd, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2), c1 text) WITH(compresstype=zstd, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
+
+CREATE INDEX tbl_pc_idx1 on tbl_pc(c1) WITH(compresstype=zstd, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
+
+\d+ tbl_pc
+
+INSERT INTO tbl_pc SELECT id, id::text FROM generate_series(1,1000)id;
+
+-- call CHECKPOINT to flush shared buffer to compressed relation file 
+CHECKPOINT;
+
+-- run ANALYZE REINDEX VACUUM and CLUSTER on compressed table and index
+ANALYZE tbl_pc;
+
+SELECT count(*) FROM tbl_pc;
+SELECT * FROM tbl_pc WHERE c1='100';
+EXPLAIN(COSTS off) SELECT * FROM tbl_pc WHERE c1='100';
+
+REINDEX INDEX tbl_pc_idx1;
+CHECKPOINT;
+SELECT * FROM tbl_pc WHERE c1='100';
+
+REINDEX TABLE tbl_pc;
+CHECKPOINT;
+SELECT * FROM tbl_pc WHERE c1='100';
+
+VACUUM tbl_pc;
+CHECKPOINT;
+SELECT count(*) FROM tbl_pc;
+SELECT * FROM tbl_pc WHERE c1='100';
+
+VACUUM FULL tbl_pc;
+CHECKPOINT;
+SELECT count(*) FROM tbl_pc;
+SELECT * FROM tbl_pc WHERE c1='100';
+
+CLUSTER tbl_pc USING tbl_pc_idx1;
+CHECKPOINT;
+SELECT count(*) FROM tbl_pc;
+SELECT * FROM tbl_pc WHERE c1='100';
+
+DROP INDEX tbl_pc_idx1;
+
+-- check usage of compressed index with data
+CREATE INDEX tbl_pc_idx1 on tbl_pc USING hash(c1) WITH(compresstype=zstd, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
+CHECKPOINT;
+SELECT * FROM tbl_pc WHERE c1='100';
+EXPLAIN(COSTS off) SELECT * FROM tbl_pc WHERE c1='100';
+DROP INDEX tbl_pc_idx1;
+
+CREATE INDEX tbl_pc_idx1 on tbl_pc USING gin((ARRAY[id])) WITH(compresstype=zstd, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
+CHECKPOINT;
+SELECT * FROM tbl_pc WHERE ARRAY[id] @> ARRAY[100];
+EXPLAIN(COSTS off) SELECT * FROM tbl_pc WHERE ARRAY[id] @> ARRAY[100];
+DROP INDEX tbl_pc_idx1;
+
+CREATE INDEX tbl_pc_idx1 on tbl_pc USING gist((point(id,id))) WITH(compresstype=zstd, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
+CHECKPOINT;
+SELECT * FROM tbl_pc ORDER BY point(id,id) <-> point(100,100) limit 1;
+EXPLAIN(COSTS off) SELECT * FROM tbl_pc ORDER BY point(id,id) <-> point(100,100) limit 1;
+DROP INDEX tbl_pc_idx1;
+
+CREATE INDEX tbl_pc_idx1 on tbl_pc USING spgist(c1) WITH(compresstype=zstd, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
+CHECKPOINT;
+SELECT * FROM tbl_pc WHERE c1='100';
+EXPLAIN(COSTS off) SELECT * FROM tbl_pc WHERE c1='100';
+DROP INDEX tbl_pc_idx1;
+
+-- brin index does not support compression
+CREATE INDEX tbl_pc_idx1 on tbl_pc USING brin(c1) WITH(compresstype=zstd, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2); -- fail
+
+DROP TABLE tbl_pc;
+RESET enable_seqscan;
+
+--
+-- alter table and index
+--
+
+-- ALTER TABLE
+-- ALTER compresstype and compress_chunk_size currently is not supported
+CREATE TABLE tbl_pc(id int, c1 text);
+ALTER TABLE tbl_pc SET(compresstype=zstd); -- fail
+DROP TABLE tbl_pc;
+
+CREATE TABLE tbl_pc(id int PRIMARY KEY, c1 text) WITH(compresstype=zstd, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
+ALTER TABLE tbl_pc SET(compresstype=none); -- fail
+ALTER TABLE tbl_pc SET(compress_chunk_size=2048); -- fail
+ALTER TABLE tbl_pc SET(compress_prealloc_chunks=16);  -- fail
+ALTER TABLE tbl_pc SET(compresslevel=2, compress_prealloc_chunks=0); -- ok
+\d+ tbl_pc
+ALTER TABLE tbl_pc RESET(compresstype); -- fail
+ALTER TABLE tbl_pc RESET(compress_chunk_size); -- fail
+ALTER TABLE tbl_pc RESET(compresslevel); -- ok
+ALTER TABLE tbl_pc RESET(compress_prealloc_chunks); -- ok
+\d+ tbl_pc
+CHECKPOINT;
+SELECT count(*) FROM tbl_pc;
+
+-- ALTER INDEX
+-- ALTER compresstype and compress_chunk_size currently is not supported
+CREATE INDEX tbl_pc_idx1 on tbl_pc USING btree(c1);
+ALTER INDEX tbl_pc_idx1 SET(compresstype=zstd); -- fail
+DROP INDEX tbl_pc_idx1;
+
+CREATE INDEX tbl_pc_idx1 on tbl_pc USING btree(c1) WITH(compresstype=zstd, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
+ALTER INDEX tbl_pc_idx1 SET(compresstype=none); -- fail
+ALTER INDEX tbl_pc_idx1 SET(compress_chunk_size=2048); -- fail
+ALTER INDEX tbl_pc_idx1 SET(compress_prealloc_chunks=16);  -- fail
+ALTER INDEX tbl_pc_idx1 SET(compresslevel=2, compress_prealloc_chunks=0); -- ok
+\d+ tbl_pc
+ALTER INDEX tbl_pc_idx1 RESET(compresstype); -- fail
+ALTER INDEX tbl_pc_idx1 RESET(compress_chunk_size); -- fail
+ALTER INDEX tbl_pc_idx1 RESET(compresslevel); -- ok
+ALTER INDEX tbl_pc_idx1 RESET(compress_prealloc_chunks); -- ok
+\d+ tbl_pc
+CHECKPOINT;
+SELECT * FROM tbl_pc WHERE c1='100';
+EXPLAIN(COSTS off) SELECT * FROM tbl_pc WHERE c1='100';
+
+-- alter hash index
+CREATE INDEX tbl_pc_idx_hash on tbl_pc USING hash(c1) WITH(compresstype=zstd, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
+
+ALTER INDEX tbl_pc_idx_hash SET(compresstype=none); -- fail
+ALTER INDEX tbl_pc_idx_hash SET(compress_chunk_size=2048); -- fail
+ALTER INDEX tbl_pc_idx_hash SET(compress_prealloc_chunks=16);  -- fail
+ALTER INDEX tbl_pc_idx_hash SET(compresslevel=2, compress_prealloc_chunks=0); -- ok
+\d+ tbl_pc_idx_hash
+ALTER INDEX tbl_pc_idx_hash RESET(compresstype); -- fail
+ALTER INDEX tbl_pc_idx_hash RESET(compress_chunk_size); -- fail
+ALTER INDEX tbl_pc_idx_hash RESET(compresslevel); -- ok
+ALTER INDEX tbl_pc_idx_hash RESET(compress_prealloc_chunks); -- ok
+\d+ tbl_pc_idx_hash
+
+-- alter gin index
+CREATE INDEX tbl_pc_idx_gin on tbl_pc USING gin((ARRAY[id])) WITH(compresstype=zstd, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
+
+ALTER INDEX tbl_pc_idx_gin SET(compresstype=none); -- fail
+ALTER INDEX tbl_pc_idx_gin SET(compress_chunk_size=2048); -- fail
+ALTER INDEX tbl_pc_idx_gin SET(compress_prealloc_chunks=16);  -- fail
+ALTER INDEX tbl_pc_idx_gin SET(compresslevel=2, compress_prealloc_chunks=0); -- ok
+\d+ tbl_pc_idx_gin
+ALTER INDEX tbl_pc_idx_gin RESET(compresstype); -- fail
+ALTER INDEX tbl_pc_idx_gin RESET(compress_chunk_size); -- fail
+ALTER INDEX tbl_pc_idx_gin RESET(compresslevel); -- ok
+ALTER INDEX tbl_pc_idx_gin RESET(compress_prealloc_chunks); -- ok
+\d+ tbl_pc_idx_gin
+
+-- alter gist index
+CREATE INDEX tbl_pc_idx_gist on tbl_pc USING gist((point(id,id))) WITH(compresstype=zstd, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
+
+ALTER INDEX tbl_pc_idx_gist SET(compresstype=none); -- fail
+ALTER INDEX tbl_pc_idx_gist SET(compress_chunk_size=2048); -- fail
+ALTER INDEX tbl_pc_idx_gist SET(compress_prealloc_chunks=16);  -- fail
+ALTER INDEX tbl_pc_idx_gist SET(compresslevel=2, compress_prealloc_chunks=0); -- ok
+\d+ tbl_pc_idx_gist
+ALTER INDEX tbl_pc_idx_gist RESET(compresstype); -- fail
+ALTER INDEX tbl_pc_idx_gist RESET(compress_chunk_size); -- fail
+ALTER INDEX tbl_pc_idx_gist RESET(compresslevel); -- ok
+ALTER INDEX tbl_pc_idx_gist RESET(compress_prealloc_chunks); -- ok
+\d+ tbl_pc_idx_gist
+
+-- alter spgist index
+CREATE INDEX tbl_pc_idx_spgist on tbl_pc USING spgist(c1) WITH(compresstype=zstd, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
+
+ALTER INDEX tbl_pc_idx_spgist SET(compresstype=none); -- fail
+ALTER INDEX tbl_pc_idx_spgist SET(compress_chunk_size=2048); -- fail
+ALTER INDEX tbl_pc_idx_spgist SET(compress_prealloc_chunks=16);  -- fail
+ALTER INDEX tbl_pc_idx_spgist SET(compresslevel=2, compress_prealloc_chunks=0); -- ok
+\d+ tbl_pc_idx_spgist
+ALTER INDEX tbl_pc_idx_spgist RESET(compresstype); -- fail
+ALTER INDEX tbl_pc_idx_spgist RESET(compress_chunk_size); -- fail
+ALTER INDEX tbl_pc_idx_spgist RESET(compresslevel); -- ok
+ALTER INDEX tbl_pc_idx_spgist RESET(compress_prealloc_chunks); -- ok
+\d+ tbl_pc_idx_spgist
+
+-- alter brin index (do not support compression)
+CREATE INDEX tbl_pc_idx_brin on tbl_pc USING brin(c1);
+ALTER INDEX tbl_pc_idx_brin SET(compress_prealloc_chunks=3);  -- fail
+
+DROP TABLE tbl_pc;
+
+--
+-- partitioned table and index
+--
+
+-- partition table does not support compression, but index of partition table and its child tables can use compression
+CREATE TABLE tbl_pc_part (id int, c1 text) PARTITION BY RANGE (id) WITH(compresstype=zstd); -- fail
+
+CREATE TABLE tbl_pc_part (id int, c1 text) PARTITION BY RANGE (id);
+CREATE TABLE tbl_pc_part_1 PARTITION OF tbl_pc_part FOR VALUES FROM (1) TO (1001);
+CREATE TABLE tbl_pc_part_2 PARTITION OF tbl_pc_part FOR VALUES FROM (1001) TO (2001) WITH(compresstype=zstd, compresslevel=1, compress_chunk_size=1024, compress_prealloc_chunks=2);
+
+CREATE INDEX part_id_idx ON tbl_pc_part(id) WITH(compresstype=zstd, compresslevel=2, compress_chunk_size=1024, compress_prealloc_chunks=2);
+
+CREATE TABLE tbl_pc_part_3 PARTITION OF tbl_pc_part FOR VALUES FROM (2001) TO (3001);
+
+CREATE INDEX part3_id_idx1 ON tbl_pc_part_3(id) WITH(compresstype=zstd, compresslevel=2, compress_chunk_size=1024, compress_prealloc_chunks=2);
+
+\d+ tbl_pc_part
+\d+ part_id_idx
+\d+ tbl_pc_part_1
+\d+ tbl_pc_part_2
+\d+ tbl_pc_part_3
+
+INSERT INTO tbl_pc_part SELECT id, id::text FROM generate_series(1,3000)id;
+CHECKPOINT;
+SELECT count(*) FROM tbl_pc_part;
+SELECT * FROM tbl_pc_part WHERE id=100;
+SELECT * FROM tbl_pc_part WHERE id=1100;
+SELECT * FROM tbl_pc_part WHERE id=2100;
+
+ALTER TABLE tbl_pc_part SET(compresstype=zstd); -- fail
+ALTER TABLE tbl_pc_part_1 SET(compresstype=zstd); -- fail
+
+ALTER TABLE tbl_pc_part_2 SET(compresstype=none); -- fail
+ALTER TABLE tbl_pc_part_2 SET(compress_chunk_size=2048); -- fail
+ALTER TABLE tbl_pc_part_2 SET(compress_prealloc_chunks=16);  -- fail
+ALTER TABLE tbl_pc_part_2 SET(compresslevel=2, compress_prealloc_chunks=0); -- ok
+\d+ tbl_pc_part_2
+
+ALTER INDEX part3_id_idx1 SET(compresstype=none); -- fail
+ALTER INDEX part3_id_idx1 SET(compress_chunk_size=2048); -- fail
+ALTER INDEX part3_id_idx1 SET(compress_prealloc_chunks=16);  -- fail
+ALTER INDEX part3_id_idx1 SET(compresslevel=2, compress_prealloc_chunks=0); -- ok
+\d+ part3_id_idx1
+
+ALTER INDEX part_id_idx SET(compresstype=zstd); -- fail
+ALTER INDEX part_id_idx SET(compress_chunk_size=2048); -- fail
+ALTER INDEX part_id_idx SET(compress_prealloc_chunks=16);  -- fail
+ALTER INDEX part_id_idx SET(compresslevel=2, compress_prealloc_chunks=0); -- fail
+\d+ tbl_pc_part
+
+INSERT INTO tbl_pc_part SELECT id, id::text FROM generate_series(1,3000)id;
+CHECKPOINT;
+SELECT count(*) FROM tbl_pc_part;
+SELECT * FROM tbl_pc_part WHERE id=100;
+SELECT * FROM tbl_pc_part WHERE id=1100;
+SELECT * FROM tbl_pc_part WHERE id=2100;
+
+DROP TABLE tbl_pc_part;
+
+
+--
+-- recycling space with vacuum (do not support)
+--
+CREATE TABLE tbl_pc(id int PRIMARY KEY, c1 text) WITH(compresstype=zstd, compress_chunk_size=2048, compress_prealloc_chunks=0);
+CHECKPOINT;
+SELECT pg_relation_size('tbl_pc') size_0 \gset
+
+INSERT INTO tbl_pc SELECT id, id::text FROM generate_series(1,1000)id;
+CHECKPOINT;
+SELECT pg_relation_size('tbl_pc') size_1000 \gset
+
+SELECT :size_1000 > :size_0; -- true
+
+DELETE FROM tbl_pc WHERE id > 500;
+VACUUM tbl_pc;
+SELECT pg_relation_size('tbl_pc') size_500 \gset
+SELECT count(*) FROM tbl_pc;
+SELECT :size_500 = :size_1000;  -- true
+
+DELETE FROM tbl_pc WHERE id < 500;
+VACUUM tbl_pc;
+SELECT pg_relation_size('tbl_pc') size_1 \gset
+SELECT count(*) FROM tbl_pc;
+SELECT :size_1 = :size_500;  -- true
+
+DELETE FROM tbl_pc;
+VACUUM tbl_pc;
+SELECT pg_relation_size('tbl_pc') size_0_2 \gset
+
+SELECT :size_0_2 = :size_500;  -- true
+
+VACUUM FULL tbl_pc;
+SELECT pg_relation_size('tbl_pc') size_0_3 \gset
+
+SELECT :size_0_3 = :size_0;  -- true
+
+DROP TABLE tbl_pc;
