From 94c17da56fde56e232e70284e72814cf07c48f09 Mon Sep 17 00:00:00 2001
From: Ildus Kurbangaliev <i.kurbangaliev@gmail.com>
Date: Mon, 18 Jun 2018 15:57:13 +0300
Subject: [PATCH 6/8] Add psql, pg_dump and pg_upgrade support

---
 src/bin/pg_dump/pg_backup.h          |   5 +
 src/bin/pg_dump/pg_backup_archiver.c |   1 +
 src/bin/pg_dump/pg_dump.c            | 339 ++++++++++++++++++++++++++-
 src/bin/pg_dump/pg_dump.h            |  28 ++-
 src/bin/pg_dump/pg_dump_sort.c       |  10 +-
 src/bin/pg_dump/pg_dumpall.c         |   5 +
 src/bin/pg_dump/pg_restore.c         |   3 +
 src/bin/pg_dump/t/002_pg_dump.pl     | 144 +++++++++++-
 src/bin/psql/describe.c              |  47 +++-
 src/bin/psql/tab-complete.c          |   5 +-
 10 files changed, 569 insertions(+), 18 deletions(-)

diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index ceedd481fb..b33fcbc168 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -78,6 +78,7 @@ typedef struct _restoreOptions
 	int			no_publications;	/* Skip publication entries */
 	int			no_security_labels; /* Skip security label entries */
 	int			no_subscriptions;	/* Skip subscription entries */
+	int			no_compression_methods; /* Skip compression methods */
 	int			strict_names;
 
 	const char *filename;
@@ -122,6 +123,7 @@ typedef struct _restoreOptions
 	bool	   *idWanted;		/* array showing which dump IDs to emit */
 	int			enable_row_security;
 	int			sequence_data;	/* dump sequence data even in schema-only mode */
+	int			compression_data;	/* dump attribute compression table */
 	int			binary_upgrade;
 } RestoreOptions;
 
@@ -151,6 +153,7 @@ typedef struct _dumpOptions
 	int			no_security_labels;
 	int			no_publications;
 	int			no_subscriptions;
+	int			no_compression_methods;
 	int			no_synchronized_snapshots;
 	int			no_unlogged_table_data;
 	int			serializable_deferrable;
@@ -172,6 +175,8 @@ typedef struct _dumpOptions
 	char	   *outputSuperuser;
 
 	int			sequence_data;	/* dump sequence data even in schema-only mode */
+	int			compression_data;	/* dump compression options even in
+									 * schema-only mode */
 } DumpOptions;
 
 /*
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index 83c976eaf7..319d78b548 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -180,6 +180,7 @@ dumpOptionsFromRestoreOptions(RestoreOptions *ropt)
 	dopt->include_everything = ropt->include_everything;
 	dopt->enable_row_security = ropt->enable_row_security;
 	dopt->sequence_data = ropt->sequence_data;
+	dopt->compression_data = ropt->compression_data;
 
 	return dopt;
 }
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index ea2f022eee..1ba564c781 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -40,11 +40,13 @@
 #include "getopt_long.h"
 
 #include "access/attnum.h"
+#include "access/cmapi.h"
 #include "access/sysattr.h"
 #include "access/transam.h"
 #include "catalog/pg_aggregate_d.h"
 #include "catalog/pg_am_d.h"
 #include "catalog/pg_attribute_d.h"
+#include "catalog/pg_attr_compression_d.h"
 #include "catalog/pg_cast_d.h"
 #include "catalog/pg_class_d.h"
 #include "catalog/pg_default_acl_d.h"
@@ -377,6 +379,7 @@ main(int argc, char **argv)
 		{"no-synchronized-snapshots", no_argument, &dopt.no_synchronized_snapshots, 1},
 		{"no-unlogged-table-data", no_argument, &dopt.no_unlogged_table_data, 1},
 		{"no-subscriptions", no_argument, &dopt.no_subscriptions, 1},
+		{"no-compression-methods", no_argument, &dopt.no_compression_methods, 1},
 		{"no-sync", no_argument, NULL, 7},
 
 		{NULL, 0, NULL, 0}
@@ -597,6 +600,9 @@ main(int argc, char **argv)
 	if (dopt.binary_upgrade)
 		dopt.sequence_data = 1;
 
+	if (dopt.binary_upgrade)
+		dopt.compression_data = 1;
+
 	if (dopt.dataOnly && dopt.schemaOnly)
 	{
 		write_msg(NULL, "options -s/--schema-only and -a/--data-only cannot be used together\n");
@@ -807,6 +813,9 @@ main(int argc, char **argv)
 	if (dopt.schemaOnly && dopt.sequence_data)
 		getTableData(&dopt, tblinfo, numTables, dopt.oids, RELKIND_SEQUENCE);
 
+	if (dopt.schemaOnly && dopt.compression_data)
+		getAttributeCompressionData(fout);
+
 	/*
 	 * In binary-upgrade mode, we do not have to worry about the actual blob
 	 * data or the associated metadata that resides in the pg_largeobject and
@@ -4212,6 +4221,233 @@ dumpSubscription(Archive *fout, SubscriptionInfo *subinfo)
 	free(qsubname);
 }
 
+/*
+ * getAttributeCompressionData
+ *	  get information from pg_attr_compression
+ *	  this information should be migrated at binary upgrade
+ */
+void
+getAttributeCompressionData(Archive *fout)
+{
+	PQExpBuffer query;
+	PGresult   *res;
+	AttributeCompressionInfo *cminfo;
+	int			i_tableoid;
+	int			i_oid;
+	int			i_relid;
+	int			i_attnum;
+	int			i_name;
+	int			i_options;
+	int			i_iscurrent;
+	int			i_attname;
+	int			i_parsedoptions;
+	int			i_preserve;
+	int			i_cnt;
+	int			i,
+				j,
+				k,
+				attcnt,
+				ntups;
+
+	if (fout->remoteVersion < 110000)
+		return;
+
+	res = ExecuteSqlQuery(fout,
+						  "SELECT tableoid, acrelid::pg_catalog.regclass as acrelid, acattnum, COUNT(*) AS cnt "
+						  "FROM pg_catalog.pg_attr_compression "
+						  "WHERE acrelid > 0 AND acattnum > 0 "
+						  "GROUP BY tableoid, acrelid, acattnum;",
+						  PGRES_TUPLES_OK);
+
+	attcnt = PQntuples(res);
+	i_tableoid = PQfnumber(res, "tableoid");
+	i_relid = PQfnumber(res, "acrelid");
+	i_attnum = PQfnumber(res, "acattnum");
+	i_cnt = PQfnumber(res, "cnt");
+
+	cminfo = pg_malloc(attcnt * sizeof(AttributeCompressionInfo));
+	for (i = 0; i < attcnt; i++)
+	{
+		char	   *acrelid = pg_strdup(PQgetvalue(res, i, i_relid));
+		uint16		attnum = atoi(PQgetvalue(res, i, i_attnum));
+		int			cnt = atoi(PQgetvalue(res, i, i_cnt));
+
+		cminfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_tableoid));
+		cminfo[i].dobj.dump = DUMP_COMPONENT_DEFINITION;
+		cminfo[i].dobj.objType = DO_ATTRIBUTE_COMPRESSION;
+		AssignDumpId(&cminfo[i].dobj);
+
+		cminfo[i].acrelid = acrelid;
+		cminfo[i].acattnum = attnum;
+		cminfo[i].preserve = NULL;
+		cminfo[i].attname = NULL;
+		cminfo[i].nitems = cnt;
+		cminfo[i].items = pg_malloc0(sizeof(AttributeCompressionItem) * cnt);
+		cminfo[i].dobj.name = psprintf("%s.%d", acrelid, attnum);
+	}
+	PQclear(res);
+
+	query = createPQExpBuffer();
+	resetPQExpBuffer(query);
+	appendPQExpBuffer(query,
+					  "SELECT c.acoid, c.acname, c.acattnum,"
+					  " c.acrelid::pg_catalog.regclass as acrelid, c.acoptions,"
+					  " a.attcompression = c.acoid as iscurrent, a.attname,"
+					  " pg_catalog.pg_column_compression(a.attrelid, a.attname) as preserve,"
+					  " pg_catalog.array_to_string(ARRAY("
+					  " SELECT pg_catalog.quote_ident(option_name) || "
+					  " ' ' || pg_catalog.quote_literal(option_value) "
+					  " FROM pg_catalog.pg_options_to_table(c.acoptions) "
+					  " ORDER BY option_name"
+					  " ), E',\n    ') AS parsedoptions "
+					  "FROM pg_catalog.pg_attr_compression c "
+					  "JOIN pg_attribute a ON (c.acrelid = a.attrelid "
+					  "                        AND c.acattnum = a.attnum) "
+					  "WHERE acrelid > 0 AND attnum > 0 "
+					  "ORDER BY iscurrent");
+
+	res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+	ntups = PQntuples(res);
+
+	i_oid = PQfnumber(res, "acoid");
+	i_name = PQfnumber(res, "acname");
+	i_relid = PQfnumber(res, "acrelid");
+	i_attnum = PQfnumber(res, "acattnum");
+	i_options = PQfnumber(res, "acoptions");
+	i_iscurrent = PQfnumber(res, "iscurrent");
+	i_attname = PQfnumber(res, "attname");
+	i_parsedoptions = PQfnumber(res, "parsedoptions");
+	i_preserve = PQfnumber(res, "preserve");
+
+	for (i = 0; i < ntups; i++)
+	{
+		char	   *acrelid = pg_strdup(PQgetvalue(res, i, i_relid));
+		uint16		attnum = atoi(PQgetvalue(res, i, i_attnum));
+
+		for (j = 0; j < attcnt; j++)
+		{
+			if (strcmp(cminfo[j].acrelid, acrelid) == 0 &&
+				cminfo[j].acattnum == attnum)
+			{
+				/* in the end it will be current */
+				cminfo[j].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+
+				if (cminfo[j].preserve == NULL)
+					cminfo[j].preserve = pg_strdup(PQgetvalue(res, i, i_preserve));
+				if (cminfo[j].attname == NULL)
+					cminfo[j].attname = pg_strdup(PQgetvalue(res, i, i_attname));
+
+				for (k = 0; k < cminfo[j].nitems; k++)
+				{
+					AttributeCompressionItem *item = &cminfo[j].items[k];
+
+					if (item->acname == NULL)
+					{
+						item->acoid = atooid(PQgetvalue(res, i, i_oid));
+						item->acname = pg_strdup(PQgetvalue(res, i, i_name));
+						item->acoptions = pg_strdup(PQgetvalue(res, i, i_options));
+						item->iscurrent = PQgetvalue(res, i, i_iscurrent)[0] == 't';
+						item->parsedoptions = pg_strdup(PQgetvalue(res, i, i_parsedoptions));
+
+						/* to next tuple */
+						break;
+					}
+				}
+
+				/* to next tuple */
+				break;
+			}
+		}
+	}
+
+	PQclear(res);
+	destroyPQExpBuffer(query);
+}
+
+/*
+ * dumpAttributeCompression
+ *	  dump the given compression options
+ */
+static void
+dumpAttributeCompression(Archive *fout, AttributeCompressionInfo *cminfo)
+{
+	PQExpBuffer query;
+	AttributeCompressionItem *item;
+	int			i;
+
+	if (!(cminfo->dobj.dump & DUMP_COMPONENT_DEFINITION))
+		return;
+
+	query = createPQExpBuffer();
+	resetPQExpBuffer(query);
+
+	for (i = 0; i < cminfo->nitems; i++)
+	{
+		item = &cminfo->items[i];
+		appendPQExpBuffer(query, "INSERT INTO pg_catalog.pg_attr_compression\n\t"
+						  "VALUES (%d, '%s', '%s'::pg_catalog.regclass, %d, ",
+						  item->acoid,
+						  item->acname,
+						  cminfo->acrelid,
+						  cminfo->acattnum);
+		if (strlen(item->acoptions) > 0)
+			appendPQExpBuffer(query, "'%s');\n", item->acoptions);
+		else
+			appendPQExpBuffer(query, "NULL);\n");
+
+		/*
+		 * Restore dependency with access method. pglz is pinned by the
+		 * system, no need to create dependency.
+		 */
+		if (strcmp(item->acname, "pglz") != 0)
+			appendPQExpBuffer(query,
+							  "WITH t AS (SELECT oid FROM pg_am WHERE amname = '%s')\n\t"
+							  "INSERT INTO pg_catalog.pg_depend "
+							  "SELECT %d, %d, 0, %d, t.oid, 0, 'n' FROM t;\n",
+							  item->acname,
+							  AttrCompressionRelationId,
+							  item->acoid,
+							  AccessMethodRelationId);
+
+		if (item->iscurrent)
+		{
+			appendPQExpBuffer(query, "ALTER TABLE %s ALTER COLUMN %s\n\tSET COMPRESSION %s",
+							  cminfo->acrelid,
+							  cminfo->attname,
+							  item->acname);
+			if (strlen(item->parsedoptions) > 0)
+				appendPQExpBuffer(query, " WITH (%s)", item->parsedoptions);
+			appendPQExpBuffer(query, " PRESERVE (%s);\n", cminfo->preserve);
+		}
+		else if (!IsBuiltinCompression(item->acoid))
+		{
+			/* restore dependency */
+			appendPQExpBuffer(query, "INSERT INTO pg_catalog.pg_depend VALUES "
+							  "(%d, %d, 0, %d, '%s'::pg_catalog.regclass, %d, 'i');\n",
+							  AttrCompressionRelationId,
+							  item->acoid,
+							  RelationRelationId,
+							  cminfo->acrelid,
+							  cminfo->acattnum);
+		}
+	}
+
+	ArchiveEntry(fout,
+				 cminfo->dobj.catId,
+				 cminfo->dobj.dumpId,
+				 cminfo->dobj.name,
+				 NULL,
+				 NULL,
+				 "", false,
+				 "ATTRIBUTE COMPRESSION", SECTION_PRE_DATA,
+				 query->data, "", NULL,
+				 NULL, 0,
+				 NULL, NULL);
+
+	destroyPQExpBuffer(query);
+}
+
 static void
 binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 										 PQExpBuffer upgrade_buffer,
@@ -8103,6 +8339,8 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
 	int			i_attoptions;
 	int			i_attcollation;
 	int			i_attfdwoptions;
+	int			i_attcmoptions;
+	int			i_attcmname;
 	PGresult   *res;
 	int			ntups;
 	bool		hasdefaults;
@@ -8132,7 +8370,46 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
 
 		resetPQExpBuffer(q);
 
-		if (fout->remoteVersion >= 100000)
+		if (fout->remoteVersion >= 110000)
+		{
+			/*
+			 * attcompression is new in version 11
+			 */
+			appendPQExpBuffer(q, "SELECT a.attnum, a.attname, a.atttypmod, "
+							  "a.attstattarget, a.attstorage, t.typstorage, "
+							  "a.attnotnull, a.atthasdef, a.attisdropped, "
+							  "a.attlen, a.attalign, a.attislocal, "
+							  "pg_catalog.format_type(t.oid,a.atttypmod) AS atttypname, "
+							  "array_to_string(a.attoptions, ', ') AS attoptions, "
+							  "CASE WHEN a.attcollation <> t.typcollation "
+							  "THEN a.attcollation ELSE 0 END AS attcollation, "
+							  "a.attidentity, "
+			/* fdw options */
+							  "pg_catalog.array_to_string(ARRAY("
+							  "SELECT pg_catalog.quote_ident(option_name) || "
+							  "' ' || pg_catalog.quote_literal(option_value) "
+							  "FROM pg_catalog.pg_options_to_table(attfdwoptions) "
+							  "ORDER BY option_name"
+							  "), E',\n    ') AS attfdwoptions, "
+			/* compression */
+							  "pg_catalog.array_to_string(ARRAY("
+							  "SELECT pg_catalog.quote_ident(option_name) || "
+							  "' ' || pg_catalog.quote_literal(option_value) "
+							  "FROM pg_catalog.pg_options_to_table(c.acoptions) "
+							  "ORDER BY option_name"
+							  "), E',\n    ') AS attcmoptions, "
+							  "c.acname AS attcmname "
+			/* FROM */
+							  "FROM pg_catalog.pg_attribute a LEFT JOIN pg_catalog.pg_type t "
+							  "ON a.atttypid = t.oid "
+							  "LEFT JOIN pg_catalog.pg_attr_compression c "
+							  "ON a.attcompression = c.acoid "
+							  "WHERE a.attrelid = '%u'::pg_catalog.oid "
+							  "AND a.attnum > 0::pg_catalog.int2 "
+							  "ORDER BY a.attnum",
+							  tbinfo->dobj.catId.oid);
+		}
+		else if (fout->remoteVersion >= 100000)
 		{
 			/*
 			 * attidentity is new in version 10.
@@ -8151,7 +8428,9 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
 							  "' ' || pg_catalog.quote_literal(option_value) "
 							  "FROM pg_catalog.pg_options_to_table(attfdwoptions) "
 							  "ORDER BY option_name"
-							  "), E',\n    ') AS attfdwoptions "
+							  "), E',\n    ') AS attfdwoptions, "
+							  "NULL AS attcmoptions, "
+							  "NULL AS attcmname "
 							  "FROM pg_catalog.pg_attribute a LEFT JOIN pg_catalog.pg_type t "
 							  "ON a.atttypid = t.oid "
 							  "WHERE a.attrelid = '%u'::pg_catalog.oid "
@@ -8177,7 +8456,9 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
 							  "' ' || pg_catalog.quote_literal(option_value) "
 							  "FROM pg_catalog.pg_options_to_table(attfdwoptions) "
 							  "ORDER BY option_name"
-							  "), E',\n    ') AS attfdwoptions "
+							  "), E',\n    ') AS attfdwoptions, "
+							  "NULL AS attcmoptions, "
+							  "NULL AS attcmname "
 							  "FROM pg_catalog.pg_attribute a LEFT JOIN pg_catalog.pg_type t "
 							  "ON a.atttypid = t.oid "
 							  "WHERE a.attrelid = '%u'::pg_catalog.oid "
@@ -8201,7 +8482,9 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
 							  "array_to_string(a.attoptions, ', ') AS attoptions, "
 							  "CASE WHEN a.attcollation <> t.typcollation "
 							  "THEN a.attcollation ELSE 0 END AS attcollation, "
-							  "NULL AS attfdwoptions "
+							  "NULL AS attfdwoptions, "
+							  "NULL AS attcmoptions, "
+							  "NULL AS attcmname "
 							  "FROM pg_catalog.pg_attribute a LEFT JOIN pg_catalog.pg_type t "
 							  "ON a.atttypid = t.oid "
 							  "WHERE a.attrelid = '%u'::pg_catalog.oid "
@@ -8219,7 +8502,9 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
 							  "pg_catalog.format_type(t.oid,a.atttypmod) AS atttypname, "
 							  "array_to_string(a.attoptions, ', ') AS attoptions, "
 							  "0 AS attcollation, "
-							  "NULL AS attfdwoptions "
+							  "NULL AS attfdwoptions, "
+							  "NULL AS attcmoptions, "
+							  "NULL AS attcmname "
 							  "FROM pg_catalog.pg_attribute a LEFT JOIN pg_catalog.pg_type t "
 							  "ON a.atttypid = t.oid "
 							  "WHERE a.attrelid = '%u'::pg_catalog.oid "
@@ -8236,7 +8521,9 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
 							  "a.attlen, a.attalign, a.attislocal, "
 							  "pg_catalog.format_type(t.oid,a.atttypmod) AS atttypname, "
 							  "'' AS attoptions, 0 AS attcollation, "
-							  "NULL AS attfdwoptions "
+							  "NULL AS attfdwoptions, "
+							  "NULL AS attcmoptions, "
+							  "NULL AS attcmname "
 							  "FROM pg_catalog.pg_attribute a LEFT JOIN pg_catalog.pg_type t "
 							  "ON a.atttypid = t.oid "
 							  "WHERE a.attrelid = '%u'::pg_catalog.oid "
@@ -8266,6 +8553,8 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
 		i_attoptions = PQfnumber(res, "attoptions");
 		i_attcollation = PQfnumber(res, "attcollation");
 		i_attfdwoptions = PQfnumber(res, "attfdwoptions");
+		i_attcmname = PQfnumber(res, "attcmname");
+		i_attcmoptions = PQfnumber(res, "attcmoptions");
 
 		tbinfo->numatts = ntups;
 		tbinfo->attnames = (char **) pg_malloc(ntups * sizeof(char *));
@@ -8282,6 +8571,8 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
 		tbinfo->attoptions = (char **) pg_malloc(ntups * sizeof(char *));
 		tbinfo->attcollation = (Oid *) pg_malloc(ntups * sizeof(Oid));
 		tbinfo->attfdwoptions = (char **) pg_malloc(ntups * sizeof(char *));
+		tbinfo->attcmoptions = (char **) pg_malloc(ntups * sizeof(char *));
+		tbinfo->attcmnames = (char **) pg_malloc(ntups * sizeof(char *));
 		tbinfo->notnull = (bool *) pg_malloc(ntups * sizeof(bool));
 		tbinfo->inhNotNull = (bool *) pg_malloc(ntups * sizeof(bool));
 		tbinfo->attrdefs = (AttrDefInfo **) pg_malloc(ntups * sizeof(AttrDefInfo *));
@@ -8309,6 +8600,8 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
 			tbinfo->attoptions[j] = pg_strdup(PQgetvalue(res, j, i_attoptions));
 			tbinfo->attcollation[j] = atooid(PQgetvalue(res, j, i_attcollation));
 			tbinfo->attfdwoptions[j] = pg_strdup(PQgetvalue(res, j, i_attfdwoptions));
+			tbinfo->attcmoptions[j] = pg_strdup(PQgetvalue(res, j, i_attcmoptions));
+			tbinfo->attcmnames[j] = pg_strdup(PQgetvalue(res, j, i_attcmname));
 			tbinfo->attrdefs[j] = NULL; /* fix below */
 			if (PQgetvalue(res, j, i_atthasdef)[0] == 't')
 				hasdefaults = true;
@@ -9799,6 +10092,9 @@ dumpDumpableObject(Archive *fout, DumpableObject *dobj)
 		case DO_SUBSCRIPTION:
 			dumpSubscription(fout, (SubscriptionInfo *) dobj);
 			break;
+		case DO_ATTRIBUTE_COMPRESSION:
+			dumpAttributeCompression(fout, (AttributeCompressionInfo *) dobj);
+			break;
 		case DO_PRE_DATA_BOUNDARY:
 		case DO_POST_DATA_BOUNDARY:
 			/* never dumped, nothing to do */
@@ -12561,6 +12857,9 @@ dumpAccessMethod(Archive *fout, AccessMethodInfo *aminfo)
 		case AMTYPE_INDEX:
 			appendPQExpBuffer(q, "TYPE INDEX ");
 			break;
+		case AMTYPE_COMPRESSION:
+			appendPQExpBuffer(q, "TYPE COMPRESSION ");
+			break;
 		default:
 			write_msg(NULL, "WARNING: invalid type \"%c\" of access method \"%s\"\n",
 					  aminfo->amtype, qamname);
@@ -15486,6 +15785,14 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 											   (!tbinfo->inhNotNull[j] ||
 												dopt->binary_upgrade));
 
+					/*
+					 * Compression will require a record in
+					 * pg_attr_compression
+					 */
+					bool		has_custom_compression = (tbinfo->attcmnames[j] &&
+														  ((strcmp(tbinfo->attcmnames[j], "pglz") != 0) ||
+														   nonemptyReloptions(tbinfo->attcmoptions[j])));
+
 					/*
 					 * Skip column if fully defined by reloftype or the
 					 * partition parent.
@@ -15544,6 +15851,25 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 											  fmtQualifiedDumpable(coll));
 					}
 
+					/*
+					 * Compression
+					 *
+					 * In binary-upgrade mode, compression is assigned by
+					 * ALTER. Even we're skipping compression the attribute
+					 * will get builtin compression. It's the task for ALTER
+					 * command to change to right one and remove dependency to
+					 * builtin compression.
+					 */
+					if (tbinfo->attcmnames[j] && strlen(tbinfo->attcmnames[j])
+						&& !(dopt->binary_upgrade && has_custom_compression))
+					{
+						appendPQExpBuffer(q, " COMPRESSION %s",
+										  tbinfo->attcmnames[j]);
+						if (nonemptyReloptions(tbinfo->attcmoptions[j]))
+							appendPQExpBuffer(q, " WITH (%s)",
+											  tbinfo->attcmoptions[j]);
+					}
+
 					if (has_default)
 						appendPQExpBuffer(q, " DEFAULT %s",
 										  tbinfo->attrdefs[j]->adef_expr);
@@ -17827,6 +18153,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
 			case DO_FOREIGN_SERVER:
 			case DO_TRANSFORM:
 			case DO_BLOB:
+			case DO_ATTRIBUTE_COMPRESSION:
 				/* Pre-data objects: must come before the pre-data boundary */
 				addObjectDependency(preDataBound, dobj->dumpId);
 				break;
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index e96c662b1e..86f0d47ce3 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -84,7 +84,8 @@ typedef enum
 	DO_POLICY,
 	DO_PUBLICATION,
 	DO_PUBLICATION_REL,
-	DO_SUBSCRIPTION
+	DO_SUBSCRIPTION,
+	DO_ATTRIBUTE_COMPRESSION
 } DumpableObjectType;
 
 /* component types of an object which can be selected for dumping */
@@ -316,6 +317,8 @@ typedef struct _tableInfo
 	char	  **attoptions;		/* per-attribute options */
 	Oid		   *attcollation;	/* per-attribute collation selection */
 	char	  **attfdwoptions;	/* per-attribute fdw options */
+	char	  **attcmoptions;	/* per-attribute compression options */
+	char	  **attcmnames;		/* per-attribute compression method names */
 	bool	   *notnull;		/* NOT NULL constraints on attributes */
 	bool	   *inhNotNull;		/* true if NOT NULL is inherited */
 	struct _attrDefInfo **attrdefs; /* DEFAULT expressions */
@@ -624,6 +627,28 @@ typedef struct _SubscriptionInfo
 	char	   *subpublications;
 } SubscriptionInfo;
 
+typedef struct _AttributeCompressionItem
+{
+	Oid			acoid;
+	char	   *acname;
+	char	   *acoptions;
+	char	   *parsedoptions;
+	bool		iscurrent;
+} AttributeCompressionItem;
+
+/* The AttributeCompressionInfo struct is used to represent attribute compression */
+typedef struct _AttributeCompressionInfo
+{
+	DumpableObject dobj;
+	char	   *acrelid;
+	uint16		acattnum;
+	char	   *attname;
+	char	   *preserve;
+
+	int			nitems;
+	AttributeCompressionItem *items;
+} AttributeCompressionInfo;
+
 /*
  * We build an array of these with an entry for each object that is an
  * extension member according to pg_depend.
@@ -724,5 +749,6 @@ extern void getPublications(Archive *fout);
 extern void getPublicationTables(Archive *fout, TableInfo tblinfo[],
 					 int numTables);
 extern void getSubscriptions(Archive *fout);
+extern void getAttributeCompressionData(Archive *fout);
 
 #endif							/* PG_DUMP_H */
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index d2b0949d6b..6ef978a50c 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -49,7 +49,7 @@ static const int dbObjectTypePriority[] =
 	6,							/* DO_FUNC */
 	7,							/* DO_AGG */
 	8,							/* DO_OPERATOR */
-	8,							/* DO_ACCESS_METHOD */
+	7,							/* DO_ACCESS_METHOD */
 	9,							/* DO_OPCLASS */
 	9,							/* DO_OPFAMILY */
 	3,							/* DO_COLLATION */
@@ -85,7 +85,8 @@ static const int dbObjectTypePriority[] =
 	35,							/* DO_POLICY */
 	36,							/* DO_PUBLICATION */
 	37,							/* DO_PUBLICATION_REL */
-	38							/* DO_SUBSCRIPTION */
+	38,							/* DO_SUBSCRIPTION */
+	21							/* DO_ATTRIBUTE_COMPRESSION */
 };
 
 static DumpId preDataBoundId;
@@ -1470,6 +1471,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
 					 "POST-DATA BOUNDARY  (ID %d)",
 					 obj->dumpId);
 			return;
+		case DO_ATTRIBUTE_COMPRESSION:
+			snprintf(buf, bufsize,
+					 "ATTRIBUTE COMPRESSION %s  (ID %d OID %u)",
+					 obj->name, obj->dumpId, obj->catId.oid);
+			return;
 	}
 	/* shouldn't get here */
 	snprintf(buf, bufsize,
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index 54db0cd174..41a05bb383 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -74,6 +74,7 @@ static int	no_comments = 0;
 static int	no_publications = 0;
 static int	no_security_labels = 0;
 static int	no_subscriptions = 0;
+static int	no_compression_methods = 0;
 static int	no_unlogged_table_data = 0;
 static int	no_role_passwords = 0;
 static int	server_version;
@@ -135,6 +136,7 @@ main(int argc, char *argv[])
 		{"no-role-passwords", no_argument, &no_role_passwords, 1},
 		{"no-security-labels", no_argument, &no_security_labels, 1},
 		{"no-subscriptions", no_argument, &no_subscriptions, 1},
+		{"no-compression-methods", no_argument, &no_compression_methods, 1},
 		{"no-sync", no_argument, NULL, 4},
 		{"no-unlogged-table-data", no_argument, &no_unlogged_table_data, 1},
 
@@ -404,6 +406,8 @@ main(int argc, char *argv[])
 		appendPQExpBufferStr(pgdumpopts, " --no-security-labels");
 	if (no_subscriptions)
 		appendPQExpBufferStr(pgdumpopts, " --no-subscriptions");
+	if (no_compression_methods)
+		appendPQExpBufferStr(pgdumpopts, " --no-compression-methods");
 	if (no_unlogged_table_data)
 		appendPQExpBufferStr(pgdumpopts, " --no-unlogged-table-data");
 
@@ -618,6 +622,7 @@ help(void)
 	printf(_("  --no-role-passwords          do not dump passwords for roles\n"));
 	printf(_("  --no-security-labels         do not dump security label assignments\n"));
 	printf(_("  --no-subscriptions           do not dump subscriptions\n"));
+	printf(_("  --no-compression-methods     do not dump compression methods\n"));
 	printf(_("  --no-sync                    do not wait for changes to be written safely to disk\n"));
 	printf(_("  --no-tablespaces             do not dump tablespace assignments\n"));
 	printf(_("  --no-unlogged-table-data     do not dump unlogged table data\n"));
diff --git a/src/bin/pg_dump/pg_restore.c b/src/bin/pg_dump/pg_restore.c
index 2342a63571..b78c6e8ed0 100644
--- a/src/bin/pg_dump/pg_restore.c
+++ b/src/bin/pg_dump/pg_restore.c
@@ -75,6 +75,7 @@ main(int argc, char **argv)
 	static int	no_publications = 0;
 	static int	no_security_labels = 0;
 	static int	no_subscriptions = 0;
+	static int	no_compression_methods = 0;
 	static int	strict_names = 0;
 
 	struct option cmdopts[] = {
@@ -124,6 +125,7 @@ main(int argc, char **argv)
 		{"no-publications", no_argument, &no_publications, 1},
 		{"no-security-labels", no_argument, &no_security_labels, 1},
 		{"no-subscriptions", no_argument, &no_subscriptions, 1},
+		{"no-compression-methods", no_argument, &no_compression_methods, 1},
 
 		{NULL, 0, NULL, 0}
 	};
@@ -364,6 +366,7 @@ main(int argc, char **argv)
 	opts->no_publications = no_publications;
 	opts->no_security_labels = no_security_labels;
 	opts->no_subscriptions = no_subscriptions;
+	opts->no_compression_methods = no_compression_methods;
 
 	if (if_exists && !opts->dropSchema)
 	{
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 7eee870259..ed41cc9b20 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -631,6 +631,62 @@ my %tests = (
 		},
 	},
 
+	# compression data in binary upgrade mode
+	'ALTER TABLE test_table_compression ALTER COLUMN ... SET COMPRESSION' => {
+		all_runs  => 1,
+		catch_all => 'ALTER TABLE ... commands',
+		regexp    => qr/^
+			\QCREATE TABLE dump_test.test_table_compression (\E\n
+			\s+\Qcol1 text COMPRESSION pglz,\E\n
+			\s+\Qcol2 text,\E\n
+			\s+\Qcol3 text,\E\n
+			\s+\Qcol4 text\E\n
+			\);
+			.*
+			\QINSERT INTO pg_catalog.pg_attr_compression\E\n
+			\s+\QVALUES (\E\d+\Q, 'pglz2', 'dump_test.test_table_compression'::pg_catalog.regclass, 2, NULL);\E\n
+			\QWITH t AS (SELECT oid FROM pg_am WHERE amname = 'pglz2')\E\n
+			\s+\QINSERT INTO pg_catalog.pg_depend SELECT 4001, \E\d+\Q, 0, 2601, t.oid, 0, 'n' FROM t;\E\n
+			\QALTER TABLE dump_test.test_table_compression ALTER COLUMN col2\E\n
+			\s+\QSET COMPRESSION pglz2 PRESERVE (pglz2);\E\n
+			.*
+			\QINSERT INTO pg_catalog.pg_attr_compression\E\n
+			\s+\QVALUES (\E\d+\Q, 'pglz', 'dump_test.test_table_compression'::pg_catalog.regclass, 3, '{min_input_size=1000}');\E\n
+			\QALTER TABLE dump_test.test_table_compression ALTER COLUMN col3\E\n
+			\s+\QSET COMPRESSION pglz WITH (min_input_size '1000') PRESERVE (pglz);\E\n
+			.*
+			\QINSERT INTO pg_catalog.pg_attr_compression\E\n
+			\s+\QVALUES (\E\d+\Q, 'pglz2', 'dump_test.test_table_compression'::pg_catalog.regclass, 4, '{min_input_size=1000}');\E\n
+			\QWITH t AS (SELECT oid FROM pg_am WHERE amname = 'pglz2')\E\n
+			\s+\QINSERT INTO pg_catalog.pg_depend SELECT 4001, \E\d+\Q, 0, 2601, t.oid, 0, 'n' FROM t;\E\n
+			\QALTER TABLE dump_test.test_table_compression ALTER COLUMN col4\E\n
+			\s+\QSET COMPRESSION pglz2 WITH (min_input_size '1000') PRESERVE (pglz2);\E\n
+			/xms,
+		like => {
+			binary_upgrade          => 1, },
+		unlike => {
+			clean                   => 1,
+			clean_if_exists         => 1,
+			createdb                => 1,
+			defaults                => 1,
+			exclude_test_table_data => 1,
+			no_blobs                => 1,
+			no_privs                => 1,
+			no_owner                => 1,
+			only_dump_test_schema   => 1,
+			only_dump_test_table    => 1,
+			pg_dumpall_dbprivs      => 1,
+			schema_only             => 1,
+			section_post_data       => 1,
+			test_schema_plus_blobs  => 1,
+			with_oids               => 1,
+			data_only                => 1,
+			exclude_dump_test_schema => 1,
+			exclude_test_table       => 1,
+			role                     => 1,
+			section_pre_data         => 1,
+			section_data             => 1, }, },
+
 	'ALTER TABLE ONLY test_table ALTER COLUMN col1 SET STATISTICS 90' => {
 		create_order => 93,
 		create_sql =>
@@ -1381,6 +1437,39 @@ my %tests = (
 		like => { %full_runs, section_pre_data => 1, },
 	},
 
+	'CREATE ACCESS METHOD pglz2' => {
+		all_runs     => 1,
+		catch_all    => 'CREATE ... commands',
+		create_order => 52,
+		create_sql =>
+		  'CREATE ACCESS METHOD pglz2 TYPE COMPRESSION HANDLER pglzhandler;',
+		regexp =>
+		  qr/CREATE ACCESS METHOD pglz2 TYPE COMPRESSION HANDLER pglzhandler;/m,
+		like => {
+			binary_upgrade           => 1,
+			clean                    => 1,
+			clean_if_exists          => 1,
+			createdb                 => 1,
+			defaults                 => 1,
+			exclude_dump_test_schema => 1,
+			exclude_test_table       => 1,
+			exclude_test_table_data  => 1,
+			no_blobs                 => 1,
+			no_privs                 => 1,
+			no_owner                 => 1,
+			pg_dumpall_dbprivs       => 1,
+			schema_only              => 1,
+			section_pre_data         => 1,
+			with_oids                => 1, },
+		unlike => {
+			only_dump_test_schema    => 1,
+			only_dump_test_table     => 1,
+			pg_dumpall_globals       => 1,
+			pg_dumpall_globals_clean => 1,
+			role                     => 1,
+			section_post_data        => 1,
+			test_schema_plus_blobs   => 1, }, },
+
 	'CREATE COLLATION test0 FROM "C"' => {
 		create_order => 76,
 		create_sql   => 'CREATE COLLATION test0 FROM "C";',
@@ -2214,9 +2303,9 @@ my %tests = (
 		regexp => qr/^
 			\QCREATE TABLE dump_test.test_table (\E\n
 			\s+\Qcol1 integer NOT NULL,\E\n
-			\s+\Qcol2 text,\E\n
-			\s+\Qcol3 text,\E\n
-			\s+\Qcol4 text,\E\n
+			\s+\Qcol2 text COMPRESSION pglz,\E\n
+			\s+\Qcol3 text COMPRESSION pglz,\E\n
+			\s+\Qcol4 text COMPRESSION pglz,\E\n
 			\s+\QCONSTRAINT test_table_col1_check CHECK ((col1 <= 1000))\E\n
 			\Q)\E\n
 			\QWITH (autovacuum_enabled='false', fillfactor='80');\E\n/xm,
@@ -2256,7 +2345,7 @@ my %tests = (
 		regexp => qr/^
 			\QCREATE TABLE dump_test.test_second_table (\E
 			\n\s+\Qcol1 integer,\E
-			\n\s+\Qcol2 text\E
+			\n\s+\Qcol2 text COMPRESSION pglz\E
 			\n\);
 			/xm,
 		like =>
@@ -2366,7 +2455,7 @@ my %tests = (
 			\n\s+\Qcol1 integer,\E
 			\n\s+\Qcol2 boolean,\E
 			\n\s+\Qcol3 boolean,\E
-			\n\s+\Qcol4 bit(5),\E
+			\n\s+\Qcol4 bit(5) COMPRESSION pglz,\E
 			\n\s+\Qcol5 double precision\E
 			\n\);
 			/xm,
@@ -2384,7 +2473,7 @@ my %tests = (
 		regexp => qr/^
 			\QCREATE TABLE dump_test.test_table_identity (\E\n
 			\s+\Qcol1 integer NOT NULL,\E\n
-			\s+\Qcol2 text\E\n
+			\s+\Qcol2 text COMPRESSION pglz\E\n
 			\);
 			.*
 			\QALTER TABLE dump_test.test_table_identity ALTER COLUMN col1 ADD GENERATED ALWAYS AS IDENTITY (\E\n
@@ -2401,6 +2490,49 @@ my %tests = (
 		unlike => { exclude_dump_test_schema => 1, },
 	},
 
+	'CREATE TABLE test_table_compression' => {
+		all_runs     => 1,
+		catch_all    => 'CREATE ... commands',
+		create_order => 53,
+		create_sql   => 'CREATE TABLE dump_test.test_table_compression (
+						   col1 text,
+						   col2 text COMPRESSION pglz2,
+						   col3 text COMPRESSION pglz WITH (min_input_size \'1000\'),
+						   col4 text COMPRESSION pglz2 WITH (min_input_size \'1000\')
+					   );',
+		regexp => qr/^
+			\QCREATE TABLE dump_test.test_table_compression (\E\n
+			\s+\Qcol1 text COMPRESSION pglz,\E\n
+			\s+\Qcol2 text COMPRESSION pglz2,\E\n
+			\s+\Qcol3 text COMPRESSION pglz WITH (min_input_size '1000'),\E\n
+			\s+\Qcol4 text COMPRESSION pglz2 WITH (min_input_size '1000')\E\n
+			\);
+			/xm,
+		like => {
+			clean                   => 1,
+			clean_if_exists         => 1,
+			createdb                => 1,
+			defaults                => 1,
+			exclude_test_table      => 1,
+			exclude_test_table_data => 1,
+			no_blobs                => 1,
+			no_privs                => 1,
+			no_owner                => 1,
+			only_dump_test_schema   => 1,
+			pg_dumpall_dbprivs      => 1,
+			schema_only             => 1,
+			section_pre_data        => 1,
+			test_schema_plus_blobs  => 1,
+			with_oids               => 1, },
+		unlike => {
+			binary_upgrade			 => 1,
+			exclude_dump_test_schema => 1,
+			only_dump_test_table     => 1,
+			pg_dumpall_globals       => 1,
+			pg_dumpall_globals_clean => 1,
+			role                     => 1,
+			section_post_data        => 1, }, },
+
 	'CREATE STATISTICS extended_stats_no_options' => {
 		create_order => 97,
 		create_sql   => 'CREATE STATISTICS dump_test.test_ext_stats_no_options
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index e5b3c1ebf9..d0892e1e23 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1755,6 +1755,22 @@ describeOneTableDetails(const char *schemaname,
 	if (verbose)
 	{
 		appendPQExpBufferStr(&buf, ",\n  a.attstorage");
+
+		/* compresssion info */
+		if (pset.sversion >= 110000)
+			appendPQExpBufferStr(&buf, ",\n  CASE WHEN attcompression = 0 THEN NULL ELSE "
+								 " (SELECT c.acname || "
+								 "		(CASE WHEN acoptions IS NULL "
+								 "		 THEN '' "
+								 "		 ELSE '(' || array_to_string(ARRAY(SELECT quote_ident(option_name) || ' ' || quote_literal(option_value)"
+								 "											  FROM pg_options_to_table(acoptions)), ', ') || ')'"
+								 " 		 END) "
+								 "  FROM pg_catalog.pg_attr_compression c "
+								 "  WHERE c.acoid = a.attcompression) "
+								 " END AS attcmname");
+		else
+			appendPQExpBufferStr(&buf, "\n  NULL AS attcmname");
+
 		appendPQExpBufferStr(&buf, ",\n  CASE WHEN a.attstattarget=-1 THEN NULL ELSE a.attstattarget END AS attstattarget");
 
 		/*
@@ -1871,6 +1887,11 @@ describeOneTableDetails(const char *schemaname,
 	if (verbose)
 	{
 		headers[cols++] = gettext_noop("Storage");
+
+		if (tableinfo.relkind == RELKIND_RELATION ||
+			tableinfo.relkind == RELKIND_PARTITIONED_TABLE)
+			headers[cols++] = gettext_noop("Compression");
+
 		if (tableinfo.relkind == RELKIND_RELATION ||
 			tableinfo.relkind == RELKIND_INDEX ||
 			tableinfo.relkind == RELKIND_PARTITIONED_INDEX ||
@@ -1968,6 +1989,28 @@ describeOneTableDetails(const char *schemaname,
 										 "???")))),
 							  false, false);
 
+			/* Column compression. */
+			if (tableinfo.relkind == RELKIND_RELATION ||
+				tableinfo.relkind == RELKIND_PARTITIONED_TABLE)
+			{
+				bool		mustfree = false;
+				const int	trunclen = 100;
+				char *val = PQgetvalue(res, i, firstvcol + 1);
+
+				/* truncate the options if they're too long */
+				if (strlen(val) > trunclen + 3)
+				{
+					char *trunc = pg_malloc0(trunclen + 4);
+					strncpy(trunc, val, trunclen);
+					strncpy(trunc + trunclen, "...", 4);
+
+					val = trunc;
+					mustfree = true;
+				}
+
+				printTableAddCell(&cont, val, false, mustfree);
+			}
+
 			/* Statistics target, if the relkind supports this feature */
 			if (tableinfo.relkind == RELKIND_RELATION ||
 				tableinfo.relkind == RELKIND_INDEX ||
@@ -1976,7 +2019,7 @@ describeOneTableDetails(const char *schemaname,
 				tableinfo.relkind == RELKIND_FOREIGN_TABLE ||
 				tableinfo.relkind == RELKIND_PARTITIONED_TABLE)
 			{
-				printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 1),
+				printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 2),
 								  false, false);
 			}
 
@@ -1987,7 +2030,7 @@ describeOneTableDetails(const char *schemaname,
 				tableinfo.relkind == RELKIND_COMPOSITE_TYPE ||
 				tableinfo.relkind == RELKIND_FOREIGN_TABLE ||
 				tableinfo.relkind == RELKIND_PARTITIONED_TABLE)
-				printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 2),
+				printTableAddCell(&cont, PQgetvalue(res, i, firstvcol + 3),
 								  false, false);
 		}
 	}
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 7bb47eadc6..86b6240ebc 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2161,11 +2161,14 @@ psql_completion(const char *text, int start, int end)
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET */
 	else if (Matches7("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET") ||
 			 Matches6("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET"))
-		COMPLETE_WITH_LIST5("(", "DEFAULT", "NOT NULL", "STATISTICS", "STORAGE");
+		COMPLETE_WITH_LIST6("(", "COMPRESSION", "DEFAULT", "NOT NULL", "STATISTICS", "STORAGE");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET ( */
 	else if (Matches8("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "(") ||
 			 Matches7("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "("))
 		COMPLETE_WITH_LIST2("n_distinct", "n_distinct_inherited");
+	else if (Matches9("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "COMPRESSION", MatchAny) ||
+			 Matches8("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "COMPRESSION", MatchAny))
+		COMPLETE_WITH_CONST("WITH (");
 	/* ALTER TABLE ALTER [COLUMN] <foo> SET STORAGE */
 	else if (Matches8("ALTER", "TABLE", MatchAny, "ALTER", "COLUMN", MatchAny, "SET", "STORAGE") ||
 			 Matches7("ALTER", "TABLE", MatchAny, "ALTER", MatchAny, "SET", "STORAGE"))
-- 
2.17.1

