On Mon, May 3, 2021 at 6:27 PM Dilip Kumar <dilipbal...@gmail.com> wrote:
>
> We have already pushed the configurable lz4 toast compression code[1].
> In the custom compression thread, we were already having the patch to
> support the compression method options[2].  But the design for the
> base patches was heavily modified before commit but I never rebased
> this patch based on the new design.  Now, I have rebased this patch so
> that we don't lose track and we can work on this for v15.  This is
> still a WIP patch.
>
> For v15 I will work on improving the code and I will also work on
> analyzing the usage of compression method options (compression
> speed/ratio).
>
> [1] 
> https://www.postgresql.org/message-id/E1lNKw9-0008DT-1L%40gemulon.postgresql.org
> [2] 
> https://www.postgresql.org/message-id/CAFiTN-s7fno8pGwfK7jwSf7uNaVhPZ38C3LAcF%2B%3DWHu7jNvy7g%40mail.gmail.com
>

I have fixed some comments offlist reported by Justin.  Apart from
that, I have also improved documentation and test case.  Stil it has a
lot of cleanup to be done but I am not planning to do that
immediately.


-- 
Regards,
Dilip Kumar
EnterpriseDB: http://www.enterprisedb.com
From e82986dcc9c65efd255afd0a4bcf34e101db4d7a Mon Sep 17 00:00:00 2001
From: Dilip Kumar <dilipkumar@localhost.localdomain>
Date: Mon, 3 May 2021 17:25:57 +0530
Subject: [PATCH v2] Toast compression method options

---
 doc/src/sgml/ref/alter_table.sgml             |  10 +-
 doc/src/sgml/ref/create_table.sgml            |   5 +-
 src/backend/access/brin/brin_tuple.c          |   7 +-
 src/backend/access/common/indextuple.c        |   6 +-
 src/backend/access/common/reloptions.c        |  64 ++++++++
 src/backend/access/common/toast_compression.c | 170 +++++++++++++++++++-
 src/backend/access/common/toast_internals.c   |   6 +-
 src/backend/access/table/toast_helper.c       |   7 +-
 src/backend/bootstrap/bootparse.y             |   1 +
 src/backend/catalog/heap.c                    |  15 +-
 src/backend/catalog/index.c                   |  43 ++++-
 src/backend/catalog/toasting.c                |   1 +
 src/backend/commands/cluster.c                |   1 +
 src/backend/commands/foreigncmds.c            |  44 -----
 src/backend/commands/tablecmds.c              | 223 +++++++++++++++++++++-----
 src/backend/nodes/copyfuncs.c                 |  16 +-
 src/backend/nodes/equalfuncs.c                |  14 +-
 src/backend/nodes/outfuncs.c                  |  14 +-
 src/backend/parser/gram.y                     |  32 ++--
 src/backend/parser/parse_utilcmd.c            |   3 +-
 src/include/access/toast_compression.h        |   8 +-
 src/include/access/toast_helper.h             |   2 +
 src/include/access/toast_internals.h          |   2 +-
 src/include/catalog/heap.h                    |   2 +
 src/include/catalog/pg_attribute.h            |   3 +
 src/include/commands/defrem.h                 |   7 +-
 src/include/nodes/nodes.h                     |   1 +
 src/include/nodes/parsenodes.h                |  15 +-
 src/test/regress/expected/compression.out     |  33 ++++
 src/test/regress/expected/compression_1.out   |  36 +++++
 src/test/regress/expected/misc_sanity.out     |   3 +-
 src/test/regress/sql/compression.sql          |  15 ++
 32 files changed, 675 insertions(+), 134 deletions(-)

diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 6166b26..b97b08c 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -54,7 +54,7 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
     ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> SET ( <replaceable class="parameter">attribute_option</replaceable> = <replaceable class="parameter">value</replaceable> [, ... ] )
     ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> RESET ( <replaceable class="parameter">attribute_option</replaceable> [, ... ] )
     ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> SET STORAGE { PLAIN | EXTERNAL | EXTENDED | MAIN }
-    ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> SET COMPRESSION <replaceable class="parameter">compression_method</replaceable>
+    ALTER [ COLUMN ] <replaceable class="parameter">column_name</replaceable> SET COMPRESSION <replaceable class="parameter">compression_method</replaceable> [ WITH (<replaceable class="parameter">compression_options</replaceable>) ]
     ADD <replaceable class="parameter">table_constraint</replaceable> [ NOT VALID ]
     ADD <replaceable class="parameter">table_constraint_using_index</replaceable>
     ALTER CONSTRAINT <replaceable class="parameter">constraint_name</replaceable> [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
@@ -104,7 +104,7 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
   GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY [ ( <replaceable>sequence_options</replaceable> ) ] |
   UNIQUE <replaceable class="parameter">index_parameters</replaceable> |
   PRIMARY KEY <replaceable class="parameter">index_parameters</replaceable> |
-  COMPRESSION <replaceable class="parameter">compression_method</replaceable> |
+  COMPRESSION <replaceable class="parameter">compression_method</replaceable> [ WITH (<replaceable class="parameter">compression_options</replaceable>) ] |
   REFERENCES <replaceable class="parameter">reftable</replaceable> [ ( <replaceable class="parameter">refcolumn</replaceable> ) ] [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ]
     [ ON DELETE <replaceable class="parameter">referential_action</replaceable> ] [ ON UPDATE <replaceable class="parameter">referential_action</replaceable> ] }
 [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
@@ -387,14 +387,16 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
 
    <varlistentry>
     <term>
-     <literal>SET COMPRESSION <replaceable class="parameter">compression_method</replaceable></literal>
+     <literal>SET COMPRESSION <replaceable class="parameter">compression_method</replaceable> [ WITH (<replaceable class="parameter">compression_options</replaceable>) ]</literal>
     </term>
     <listitem>
      <para>
       This sets the compression method for a column.  The supported compression
       methods are <literal>pglz</literal> and <literal>lz4</literal>.
       <literal>lz4</literal> is available only if <literal>--with-lz4</literal>
-      was used when building <productname>PostgreSQL</productname>.
+      was used when building <productname>PostgreSQL</productname>.  The
+      compression options can be specified using <literal>WITH</literal>
+      parameter.
      </para>
     </listitem>
    </varlistentry>
diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml
index a8c5e40..0fc078c 100644
--- a/doc/src/sgml/ref/create_table.sgml
+++ b/doc/src/sgml/ref/create_table.sgml
@@ -289,7 +289,7 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
    </varlistentry>
 
    <varlistentry>
-    <term><literal>COMPRESSION <replaceable class="parameter">compression_method</replaceable></literal></term>
+    <term><literal>COMPRESSION <replaceable class="parameter">compression_method</replaceable> [ WITH (<replaceable class="parameter">compression_options</replaceable>) ]</literal></term>
     <listitem>
      <para>
       The <literal>COMPRESSION</literal> clause sets the compression method
@@ -303,7 +303,8 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
       <literal>lz4</literal>.  <literal>lz4</literal> is available only if
       <literal>--with-lz4</literal> was used when building
       <productname>PostgreSQL</productname>. The default
-      is <literal>pglz</literal>.
+      is <literal>pglz</literal>.  The compression options can be specified
+      using <literal>WITH</literal> parameter.
      </para>
     </listitem>
    </varlistentry>
diff --git a/src/backend/access/brin/brin_tuple.c b/src/backend/access/brin/brin_tuple.c
index 8c94e4a..b6ced88 100644
--- a/src/backend/access/brin/brin_tuple.c
+++ b/src/backend/access/brin/brin_tuple.c
@@ -38,6 +38,7 @@
 #include "access/toast_internals.h"
 #include "access/tupdesc.h"
 #include "access/tupmacs.h"
+#include "commands/defrem.h"
 #include "utils/datum.h"
 #include "utils/memutils.h"
 
@@ -223,6 +224,7 @@ brin_form_tuple(BrinDesc *brdesc, BlockNumber blkno, BrinMemTuple *tuple,
 			{
 				Datum	cvalue;
 				char	compression;
+				List   *acoption = NULL;
 				Form_pg_attribute att = TupleDescAttr(brdesc->bd_tupdesc,
 													  keyno);
 
@@ -234,11 +236,14 @@ brin_form_tuple(BrinDesc *brdesc, BlockNumber blkno, BrinMemTuple *tuple,
 				 */
 				if (att->atttypid == atttype->type_id &&
 					CompressionMethodIsValid(att->attcompression))
+				{
 					compression = att->attcompression;
+					acoption = GetAttributeCompressionOptions(att);
+				}
 				else
 					compression = GetDefaultToastCompression();
 
-				cvalue = toast_compress_datum(value, compression);
+				cvalue = toast_compress_datum(value, compression, acoption);
 
 				if (DatumGetPointer(cvalue) != NULL)
 				{
diff --git a/src/backend/access/common/indextuple.c b/src/backend/access/common/indextuple.c
index a4cb891..85e63b9 100644
--- a/src/backend/access/common/indextuple.c
+++ b/src/backend/access/common/indextuple.c
@@ -21,6 +21,7 @@
 #include "access/htup_details.h"
 #include "access/itup.h"
 #include "access/toast_internals.h"
+#include "commands/defrem.h"
 
 /*
  * This enables de-toasting of index entries.  Needed until VACUUM is
@@ -104,6 +105,7 @@ index_form_tuple(TupleDesc tupleDescriptor,
 			 att->attstorage == TYPSTORAGE_MAIN))
 		{
 			Datum	cvalue;
+			List   *acoption = NULL;
 			char	compression = att->attcompression;
 
 			/*
@@ -114,8 +116,10 @@ index_form_tuple(TupleDesc tupleDescriptor,
 			 */
 			if (!CompressionMethodIsValid(compression))
 				compression = GetDefaultToastCompression();
+			else
+				acoption = GetAttributeCompressionOptions(att);
 
-			cvalue = toast_compress_datum(untoasted_values[i], compression);
+			cvalue = toast_compress_datum(untoasted_values[i], compression, acoption);
 
 			if (DatumGetPointer(cvalue) != NULL)
 			{
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 5554275..c155f49 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -2111,3 +2111,67 @@ AlterTableGetRelOptionsLockLevel(List *defList)
 
 	return lockmode;
 }
+
+/*
+ * Convert a DefElem list to the text array format that is used in
+ * pg_foreign_data_wrapper, pg_foreign_server, pg_user_mapping, and
+ * pg_foreign_table.
+ *
+ * Returns the array in the form of a Datum, or PointerGetDatum(NULL)
+ * if the list is empty.
+ *
+ * Note: The array is usually stored to database without further
+ * processing, hence any validation should be done before this
+ * conversion.
+ */
+Datum
+optionListToArray(List *options)
+{
+	ArrayBuildState *astate = NULL;
+	ListCell   *cell;
+
+	foreach(cell, options)
+	{
+		DefElem    *def = lfirst(cell);
+		const char *value;
+		Size		len;
+		text	   *t;
+
+		value = defGetString(def);
+		len = VARHDRSZ + strlen(def->defname) + 1 + strlen(value);
+		t = palloc(len + 1);
+		SET_VARSIZE(t, len);
+		sprintf(VARDATA(t), "%s=%s", def->defname, value);
+
+		astate = accumArrayResult(astate, PointerGetDatum(t),
+								  false, TEXTOID,
+								  CurrentMemoryContext);
+	}
+
+	if (astate)
+		return makeArrayResult(astate, CurrentMemoryContext);
+
+	return PointerGetDatum(NULL);
+}
+
+/*
+ * Return human readable list of reloptions
+ */
+char *
+formatRelOptions(List *options)
+{
+	StringInfoData buf;
+	ListCell   *cell;
+
+	initStringInfo(&buf);
+
+	foreach(cell, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(cell);
+
+		appendStringInfo(&buf, "%s%s=%s", buf.len > 0 ? ", " : "",
+						 def->defname, defGetString(def));
+	}
+
+	return buf.data;
+}
diff --git a/src/backend/access/common/toast_compression.c b/src/backend/access/common/toast_compression.c
index 52dedac..ad2fea6 100644
--- a/src/backend/access/common/toast_compression.c
+++ b/src/backend/access/common/toast_compression.c
@@ -19,6 +19,7 @@
 
 #include "access/detoast.h"
 #include "access/toast_compression.h"
+#include "commands/defrem.h"
 #include "common/pg_lzcompress.h"
 #include "fmgr.h"
 #include "utils/builtins.h"
@@ -33,26 +34,108 @@ int	   default_toast_compression = TOAST_PGLZ_COMPRESSION;
 			 errdetail("This functionality requires the server to be built with lz4 support."), \
 			 errhint("You need to rebuild PostgreSQL using --with-lz4.")))
 
+#define PGLZ_OPTIONS_COUNT 6
+
+static char *PGLZ_options[PGLZ_OPTIONS_COUNT] = {
+	"min_input_size",
+	"max_input_size",
+	"min_comp_rate",
+	"first_success_by",
+	"match_size_good",
+	"match_size_drop"
+};
+
+/*
+ * Convert value from reloptions to int32, and report if it is not correct.
+ * Also checks parameter names
+ */
+static int32
+parse_option(char *name, char *value)
+{
+	int			i;
+
+	for (i = 0; i < PGLZ_OPTIONS_COUNT; i++)
+	{
+		if (strcmp(PGLZ_options[i], name) == 0)
+			return pg_atoi(value, 4, 0);
+	}
+
+	ereport(ERROR,
+			(errcode(ERRCODE_UNDEFINED_PARAMETER),
+			 errmsg("unknown compression option for pglz: \"%s\"", name)));
+}
+
+/*
+ * Check PGLZ options if specified
+ */
+void
+pglz_check_options(List *options)
+{
+	ListCell   *lc;
+
+	foreach(lc, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(lc);
+
+		parse_option(def->defname, defGetString(def));
+	}
+}
+
+/*
+ * Configure PGLZ_Strategy struct for compression function
+ */
+static inline PGLZ_Strategy *
+pglz_init_options(List *options)
+{
+	ListCell   *lc;
+	PGLZ_Strategy *strategy = palloc(sizeof(PGLZ_Strategy));
+
+	/* initialize with default strategy values */
+	memcpy(strategy, PGLZ_strategy_default, sizeof(PGLZ_Strategy));
+	foreach(lc, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(lc);
+		int32		val = parse_option(def->defname, defGetString(def));
+
+		/* fill the strategy */
+		if (strcmp(def->defname, "min_input_size") == 0)
+			strategy->min_input_size = val;
+		else if (strcmp(def->defname, "max_input_size") == 0)
+			strategy->max_input_size = val;
+		else if (strcmp(def->defname, "min_comp_rate") == 0)
+			strategy->min_comp_rate = val;
+		else if (strcmp(def->defname, "first_success_by") == 0)
+			strategy->first_success_by = val;
+		else if (strcmp(def->defname, "match_size_good") == 0)
+			strategy->match_size_good = val;
+		else if (strcmp(def->defname, "match_size_drop") == 0)
+			strategy->match_size_drop = val;
+	}
+	return strategy;
+}
+
 /*
  * Compress a varlena using PGLZ.
  *
  * Returns the compressed varlena, or NULL if compression fails.
  */
 struct varlena *
-pglz_compress_datum(const struct varlena *value)
+pglz_compress_datum(const struct varlena *value, List *options)
 {
 	int32		valsize,
 				len;
 	struct varlena *tmp = NULL;
+	PGLZ_Strategy  *strategy;
 
 	valsize = VARSIZE_ANY_EXHDR(DatumGetPointer(value));
+	strategy = pglz_init_options(options);
 
 	/*
 	 * No point in wasting a palloc cycle if value size is outside the allowed
 	 * range for compression.
 	 */
-	if (valsize < PGLZ_strategy_default->min_input_size ||
-		valsize > PGLZ_strategy_default->max_input_size)
+	if (valsize < strategy->min_input_size ||
+		valsize > strategy->max_input_size)
 		return NULL;
 
 	/*
@@ -65,7 +148,10 @@ pglz_compress_datum(const struct varlena *value)
 	len = pglz_compress(VARDATA_ANY(value),
 						valsize,
 						(char *) tmp + VARHDRSZ_COMPRESSED,
-						NULL);
+						strategy);
+	if (options != NULL)
+		pfree(strategy);
+
 	if (len < 0)
 	{
 		pfree(tmp);
@@ -133,12 +219,77 @@ pglz_decompress_datum_slice(const struct varlena *value,
 }
 
 /*
+ * Check options if specified. All validation is located here so
+ * we don't need to do it again in cminitstate function.
+ */
+void
+lz4_check_options(List *options)
+{
+#ifndef USE_LZ4
+	NO_LZ4_SUPPORT();
+	return NULL;				/* keep compiler quiet */
+#else
+	ListCell	*lc;
+
+	foreach(lc, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(lc);
+
+		if (strcmp(def->defname, "acceleration") == 0)
+		{
+			int32 acceleration =
+				pg_atoi(defGetString(def), sizeof(acceleration), 0);
+
+			if (acceleration < INT32_MIN || acceleration > INT32_MAX)
+				ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("unexpected value for lz4 compression acceleration: \"%s\"",
+								defGetString(def)),
+					 errhint("expected value between INT32_MIN and INT32_MAX")
+					));
+		}
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_PARAMETER),
+					 errmsg("unknown compression option for lz4: \"%s\"", def->defname)));
+	}
+#endif
+}
+
+static int32
+lz4_init_options(List *options)
+{
+#ifndef USE_LZ4
+	NO_LZ4_SUPPORT();
+	return NULL;				/* keep compiler quiet */
+#else
+	int32	acceleration;
+
+	acceleration = 1;
+	if (list_length(options) > 0)
+	{
+		ListCell	*lc;
+
+		foreach(lc, options)
+		{
+			DefElem    *def = (DefElem *) lfirst(lc);
+
+			if (strcmp(def->defname, "acceleration") == 0)
+				acceleration = pg_atoi(defGetString(def), sizeof(int32), 0);
+		}
+	}
+
+	return acceleration;
+#endif
+}
+
+/*
  * Compress a varlena using LZ4.
  *
  * Returns the compressed varlena, or NULL if compression fails.
  */
 struct varlena *
-lz4_compress_datum(const struct varlena *value)
+lz4_compress_datum(const struct varlena *value, List *options)
 {
 #ifndef USE_LZ4
 	NO_LZ4_SUPPORT();
@@ -147,8 +298,11 @@ lz4_compress_datum(const struct varlena *value)
 	int32		valsize;
 	int32		len;
 	int32		max_size;
+	int32		acceleration;
 	struct varlena *tmp = NULL;
 
+	acceleration = lz4_init_options(options);
+
 	valsize = VARSIZE_ANY_EXHDR(value);
 
 	/*
@@ -158,9 +312,9 @@ lz4_compress_datum(const struct varlena *value)
 	max_size = LZ4_compressBound(valsize);
 	tmp = (struct varlena *) palloc(max_size + VARHDRSZ_COMPRESSED);
 
-	len = LZ4_compress_default(VARDATA_ANY(value),
-							   (char *) tmp + VARHDRSZ_COMPRESSED,
-							   valsize, max_size);
+	len = LZ4_compress_fast(VARDATA_ANY(value),
+							(char *) tmp + VARHDRSZ_COMPRESSED,
+							valsize, max_size, acceleration);							   
 	if (len <= 0)
 		elog(ERROR, "lz4 compression failed");
 
diff --git a/src/backend/access/common/toast_internals.c b/src/backend/access/common/toast_internals.c
index 730cd04..40902b3 100644
--- a/src/backend/access/common/toast_internals.c
+++ b/src/backend/access/common/toast_internals.c
@@ -44,7 +44,7 @@ static bool toastid_valueid_exists(Oid toastrelid, Oid valueid);
  * ----------
  */
 Datum
-toast_compress_datum(Datum value, char cmethod)
+toast_compress_datum(Datum value, char cmethod, List *cmoptions)
 {
 	struct varlena *tmp = NULL;
 	int32		valsize;
@@ -63,11 +63,11 @@ toast_compress_datum(Datum value, char cmethod)
 	switch (cmethod)
 	{
 		case TOAST_PGLZ_COMPRESSION:
-			tmp = pglz_compress_datum((const struct varlena *) value);
+			tmp = pglz_compress_datum((const struct varlena *) value, cmoptions);
 			cmid = TOAST_PGLZ_COMPRESSION_ID;
 			break;
 		case TOAST_LZ4_COMPRESSION:
-			tmp = lz4_compress_datum((const struct varlena *) value);
+			tmp = lz4_compress_datum((const struct varlena *) value, cmoptions);
 			cmid = TOAST_LZ4_COMPRESSION_ID;
 			break;
 		default:
diff --git a/src/backend/access/table/toast_helper.c b/src/backend/access/table/toast_helper.c
index 53f78f9..302b2e5 100644
--- a/src/backend/access/table/toast_helper.c
+++ b/src/backend/access/table/toast_helper.c
@@ -15,10 +15,13 @@
 #include "postgres.h"
 
 #include "access/detoast.h"
+#include "access/reloptions.h"
 #include "access/table.h"
 #include "access/toast_helper.h"
 #include "access/toast_internals.h"
 #include "catalog/pg_type_d.h"
+#include "commands/defrem.h"
+#include "utils/syscache.h"
 
 
 /*
@@ -55,6 +58,7 @@ toast_tuple_init(ToastTupleContext *ttc)
 		ttc->ttc_attr[i].tai_colflags = 0;
 		ttc->ttc_attr[i].tai_oldexternal = NULL;
 		ttc->ttc_attr[i].tai_compression = att->attcompression;
+		ttc->ttc_attr[i].tai_cmoptions = GetAttributeCompressionOptions(att);
 
 		if (ttc->ttc_oldvalues != NULL)
 		{
@@ -230,7 +234,8 @@ toast_tuple_try_compression(ToastTupleContext *ttc, int attribute)
 	Datum		new_value;
 	ToastAttrInfo *attr = &ttc->ttc_attr[attribute];
 
-	new_value = toast_compress_datum(*value, attr->tai_compression);
+	new_value = toast_compress_datum(*value, attr->tai_compression,
+									 attr->tai_cmoptions);
 
 	if (DatumGetPointer(new_value) != NULL)
 	{
diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 5fcd004..a43e0e1 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -239,6 +239,7 @@ Boot_CreateStmt:
 													  true,
 													  false,
 													  InvalidOid,
+													  NULL,
 													  NULL);
 						elog(DEBUG4, "relation created with OID %u", id);
 					}
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 42ff175..f241b82 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -741,6 +741,7 @@ InsertPgAttributeTuples(Relation pg_attribute_rel,
 						TupleDesc tupdesc,
 						Oid new_rel_oid,
 						Datum *attoptions,
+						Datum *attcmoptions,
 						CatalogIndexState indstate)
 {
 	TupleTableSlot **slot;
@@ -796,6 +797,11 @@ InsertPgAttributeTuples(Relation pg_attribute_rel,
 		else
 			slot[slotCount]->tts_isnull[Anum_pg_attribute_attoptions - 1] = true;
 
+		if (attcmoptions && attcmoptions[natts] != (Datum) 0)
+			slot[slotCount]->tts_values[Anum_pg_attribute_attcmoptions - 1] = attcmoptions[natts];
+		else
+			slot[slotCount]->tts_isnull[Anum_pg_attribute_attcmoptions - 1] = true;
+
 		/* start out with empty permissions and empty options */
 		slot[slotCount]->tts_isnull[Anum_pg_attribute_attacl - 1] = true;
 		slot[slotCount]->tts_isnull[Anum_pg_attribute_attfdwoptions - 1] = true;
@@ -843,6 +849,7 @@ InsertPgAttributeTuples(Relation pg_attribute_rel,
 static void
 AddNewAttributeTuples(Oid new_rel_oid,
 					  TupleDesc tupdesc,
+					  Datum *acoption,
 					  char relkind)
 {
 	Relation	rel;
@@ -861,7 +868,8 @@ AddNewAttributeTuples(Oid new_rel_oid,
 	/* set stats detail level to a sane default */
 	for (int i = 0; i < natts; i++)
 		tupdesc->attrs[i].attstattarget = -1;
-	InsertPgAttributeTuples(rel, tupdesc, new_rel_oid, NULL, indstate);
+	InsertPgAttributeTuples(rel, tupdesc, new_rel_oid,
+							NULL, acoption, indstate);
 
 	/* add dependencies on their datatypes and collations */
 	for (int i = 0; i < natts; i++)
@@ -893,7 +901,7 @@ AddNewAttributeTuples(Oid new_rel_oid,
 
 		td = CreateTupleDesc(lengthof(SysAtt), (FormData_pg_attribute **) &SysAtt);
 
-		InsertPgAttributeTuples(rel, td, new_rel_oid, NULL, indstate);
+		InsertPgAttributeTuples(rel, td, new_rel_oid, NULL, NULL, indstate);
 		FreeTupleDesc(td);
 	}
 
@@ -1161,6 +1169,7 @@ heap_create_with_catalog(const char *relname,
 						 bool allow_system_table_mods,
 						 bool is_internal,
 						 Oid relrewrite,
+						 Datum *acoptions,
 						 ObjectAddress *typaddress)
 {
 	Relation	pg_class_desc;
@@ -1419,7 +1428,7 @@ heap_create_with_catalog(const char *relname,
 	/*
 	 * now add tuples to pg_attribute for the attributes in our new relation.
 	 */
-	AddNewAttributeTuples(relid, new_rel_desc->rd_att, relkind);
+	AddNewAttributeTuples(relid, new_rel_desc->rd_att, acoptions, relkind);
 
 	/*
 	 * Make a dependency link to force the relation to be deleted if its
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index a628b32..42fe3bc 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -108,10 +108,12 @@ static TupleDesc ConstructTupleDescriptor(Relation heapRelation,
 										  List *indexColNames,
 										  Oid accessMethodObjectId,
 										  Oid *collationObjectId,
-										  Oid *classObjectId);
+										  Oid *classObjectId,
+										  Datum *acoptions);
 static void InitializeAttributeOids(Relation indexRelation,
 									int numatts, Oid indexoid);
-static void AppendAttributeTuples(Relation indexRelation, Datum *attopts);
+static void AppendAttributeTuples(Relation indexRelation, Datum *attopts,
+								  Datum *attcmopts);
 static void UpdateIndexRelation(Oid indexoid, Oid heapoid,
 								Oid parentIndexId,
 								IndexInfo *indexInfo,
@@ -273,7 +275,8 @@ ConstructTupleDescriptor(Relation heapRelation,
 						 List *indexColNames,
 						 Oid accessMethodObjectId,
 						 Oid *collationObjectId,
-						 Oid *classObjectId)
+						 Oid *classObjectId,
+						 Datum *acoptions)
 {
 	int			numatts = indexInfo->ii_NumIndexAttrs;
 	int			numkeyatts = indexInfo->ii_NumIndexKeyAttrs;
@@ -334,6 +337,9 @@ ConstructTupleDescriptor(Relation heapRelation,
 		{
 			/* Simple index column */
 			const FormData_pg_attribute *from;
+			HeapTuple	attr_tuple;
+			Datum		attcmoptions;
+			bool		isNull;
 
 			Assert(atnum > 0);	/* should've been caught above */
 
@@ -350,6 +356,23 @@ ConstructTupleDescriptor(Relation heapRelation,
 			to->attstorage = from->attstorage;
 			to->attalign = from->attalign;
 			to->attcompression = from->attcompression;
+
+			attr_tuple = SearchSysCache2(ATTNUM,
+										 ObjectIdGetDatum(from->attrelid),
+										 Int16GetDatum(from->attnum));
+			if (!HeapTupleIsValid(attr_tuple))
+				elog(ERROR, "cache lookup failed for attribute %d of relation %u",
+					 from->attnum, from->attrelid);
+
+			attcmoptions = SysCacheGetAttr(ATTNUM, attr_tuple,
+										   Anum_pg_attribute_attcmoptions,
+										   &isNull);
+			if (isNull)
+				acoptions[i] = PointerGetDatum(NULL);
+			else
+				acoptions[i] =
+					PointerGetDatum(DatumGetArrayTypePCopy(attcmoptions));
+			ReleaseSysCache(attr_tuple);
 		}
 		else
 		{
@@ -499,7 +522,7 @@ InitializeAttributeOids(Relation indexRelation,
  * ----------------------------------------------------------------
  */
 static void
-AppendAttributeTuples(Relation indexRelation, Datum *attopts)
+AppendAttributeTuples(Relation indexRelation, Datum *attopts, Datum *attcmopts)
 {
 	Relation	pg_attribute;
 	CatalogIndexState indstate;
@@ -517,7 +540,8 @@ AppendAttributeTuples(Relation indexRelation, Datum *attopts)
 	 */
 	indexTupDesc = RelationGetDescr(indexRelation);
 
-	InsertPgAttributeTuples(pg_attribute, indexTupDesc, InvalidOid, attopts, indstate);
+	InsertPgAttributeTuples(pg_attribute, indexTupDesc, InvalidOid,
+							attopts, attcmopts, indstate);
 
 	CatalogCloseIndexes(indstate);
 
@@ -730,6 +754,7 @@ index_create(Relation heapRelation,
 	bool		concurrent = (flags & INDEX_CREATE_CONCURRENT) != 0;
 	bool		partitioned = (flags & INDEX_CREATE_PARTITIONED) != 0;
 	char		relkind;
+	Datum	   *acoptions;
 	TransactionId relfrozenxid;
 	MultiXactId relminmxid;
 
@@ -885,6 +910,8 @@ index_create(Relation heapRelation,
 						indexRelationName, RelationGetRelationName(heapRelation))));
 	}
 
+	acoptions = (Datum *) palloc0(sizeof(Datum) * indexInfo->ii_NumIndexAttrs);
+
 	/*
 	 * construct tuple descriptor for index tuples
 	 */
@@ -893,7 +920,8 @@ index_create(Relation heapRelation,
 											indexColNames,
 											accessMethodObjectId,
 											collationObjectId,
-											classObjectId);
+											classObjectId,
+											acoptions);
 
 	/*
 	 * Allocate an OID for the index, unless we were told what to use.
@@ -984,7 +1012,8 @@ index_create(Relation heapRelation,
 	/*
 	 * append ATTRIBUTE tuples for the index
 	 */
-	AppendAttributeTuples(indexRelation, indexInfo->ii_OpclassOptions);
+	AppendAttributeTuples(indexRelation, indexInfo->ii_OpclassOptions,
+						  acoptions);
 
 	/* ----------------
 	 *	  update pg_index
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 933a073..9030140 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -261,6 +261,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
 										   true,
 										   true,
 										   InvalidOid,
+										   NULL,
 										   NULL);
 	Assert(toast_relid != InvalidOid);
 
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 6487a9e..b5f183e 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -699,6 +699,7 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, char relpersistence,
 										  true,
 										  true,
 										  OIDOldHeap,
+										  NULL,
 										  NULL);
 	Assert(OIDNewHeap != InvalidOid);
 
diff --git a/src/backend/commands/foreigncmds.c b/src/backend/commands/foreigncmds.c
index eb7103f..ae9ae2c 100644
--- a/src/backend/commands/foreigncmds.c
+++ b/src/backend/commands/foreigncmds.c
@@ -49,50 +49,6 @@ typedef struct
 /* Internal functions */
 static void import_error_callback(void *arg);
 
-
-/*
- * Convert a DefElem list to the text array format that is used in
- * pg_foreign_data_wrapper, pg_foreign_server, pg_user_mapping, and
- * pg_foreign_table.
- *
- * Returns the array in the form of a Datum, or PointerGetDatum(NULL)
- * if the list is empty.
- *
- * Note: The array is usually stored to database without further
- * processing, hence any validation should be done before this
- * conversion.
- */
-static Datum
-optionListToArray(List *options)
-{
-	ArrayBuildState *astate = NULL;
-	ListCell   *cell;
-
-	foreach(cell, options)
-	{
-		DefElem    *def = lfirst(cell);
-		const char *value;
-		Size		len;
-		text	   *t;
-
-		value = defGetString(def);
-		len = VARHDRSZ + strlen(def->defname) + 1 + strlen(value);
-		t = palloc(len + 1);
-		SET_VARSIZE(t, len);
-		sprintf(VARDATA(t), "%s=%s", def->defname, value);
-
-		astate = accumArrayResult(astate, PointerGetDatum(t),
-								  false, TEXTOID,
-								  CurrentMemoryContext);
-	}
-
-	if (astate)
-		return makeArrayResult(astate, CurrentMemoryContext);
-
-	return PointerGetDatum(NULL);
-}
-
-
 /*
  * Transform a list of DefElem into text array format.  This is substantially
  * the same thing as optionListToArray(), except we recognize SET/ADD/DROP
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index d9ba87a..e524542 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -561,7 +561,8 @@ static void ATExecGenericOptions(Relation rel, List *options);
 static void ATExecSetRowSecurity(Relation rel, bool rls);
 static void ATExecForceNoForceRowSecurity(Relation rel, bool force_rls);
 static ObjectAddress ATExecSetCompression(AlteredTableInfo *tab, Relation rel,
-					 const char *column, Node *newValue, LOCKMODE lockmode);
+					 					  const char *column, ColumnCompression *compression,
+										  LOCKMODE lockmode);
 
 static void index_copy_data(Relation rel, RelFileNode newrnode);
 static const char *storage_name(char c);
@@ -600,7 +601,9 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
 static void ATExecAlterCollationRefreshVersion(Relation rel, List *coll);
-static char GetAttributeCompression(Form_pg_attribute att, char *compression);
+static char GetAttributeCompression(Form_pg_attribute att,
+									ColumnCompression *compression,
+									Datum *acoptions);
 
 
 /* ----------------------------------------------------------------
@@ -646,6 +649,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	Datum	   *acoptions;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -850,6 +854,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	cookedDefaults = NIL;
 	attnum = 0;
 
+	acoptions = (Datum *) palloc0(sizeof(Datum) * descriptor->natts);
+
 	foreach(listptr, stmt->tableElts)
 	{
 		ColumnDef  *colDef = lfirst(listptr);
@@ -904,7 +910,8 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 			relkind == RELKIND_PARTITIONED_TABLE ||
 			relkind == RELKIND_MATVIEW)
 			attr->attcompression =
-				GetAttributeCompression(attr, colDef->compression);
+				GetAttributeCompression(attr, colDef->compression,
+										&acoptions[attnum - 1]);
 		else
 			attr->attcompression = InvalidCompressionMethod;
 	}
@@ -958,8 +965,11 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 										  allowSystemTableMods,
 										  false,
 										  InvalidOid,
+										  acoptions,
 										  typaddress);
 
+	pfree(acoptions);
+
 	/*
 	 * We must bump the command counter to make the newly-created relation
 	 * tuple visible for opening.
@@ -2591,17 +2601,14 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 				/* Copy/check compression parameter */
 				if (CompressionMethodIsValid(attribute->attcompression))
 				{
-					const char *compression =
-						GetCompressionMethodName(attribute->attcompression);
+					ColumnCompression *compression = MakeColumnCompression(attribute);
 
 					if (def->compression == NULL)
-						def->compression = pstrdup(compression);
-					else if (strcmp(def->compression, compression) != 0)
-						ereport(ERROR,
-								(errcode(ERRCODE_DATATYPE_MISMATCH),
-								 errmsg("column \"%s\" has a compression method conflict",
-										attributeName),
-								 errdetail("%s versus %s", def->compression, compression)));
+						def->compression = compression;
+					else
+						CheckCompressionMismatch(def->compression,
+												 compression,
+												 attributeName);
 				}
 
 				def->inhcount++;
@@ -2639,8 +2646,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 				def->constraints = NIL;
 				def->location = -1;
 				if (CompressionMethodIsValid(attribute->attcompression))
-					def->compression = pstrdup(GetCompressionMethodName(
-													attribute->attcompression));
+					def->compression = MakeColumnCompression(attribute);
 				else
 					def->compression = NULL;
 				inhSchema = lappend(inhSchema, def);
@@ -2894,15 +2900,10 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 				/* Copy compression parameter */
 				if (def->compression == NULL)
 					def->compression = newdef->compression;
-				else if (newdef->compression != NULL)
-				{
-					if (strcmp(def->compression, newdef->compression) != 0)
-						ereport(ERROR,
-								(errcode(ERRCODE_DATATYPE_MISMATCH),
-								 errmsg("column \"%s\" has a compression method conflict",
-										attributeName),
-								 errdetail("%s versus %s", def->compression, newdef->compression)));
-				}
+				else if (newdef->compression)
+					CheckCompressionMismatch(def->compression,
+											 newdef->compression,
+											 attributeName);
 
 				/* Mark the column as locally defined */
 				def->is_local = true;
@@ -4894,7 +4895,8 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab,
 			address = ATExecSetStorage(rel, cmd->name, cmd->def, lockmode);
 			break;
 		case AT_SetCompression:
-			address = ATExecSetCompression(tab, rel, cmd->name, cmd->def,
+			address = ATExecSetCompression(tab, rel, cmd->name,
+										   (ColumnCompression *) cmd->def,
 										   lockmode);
 			break;
 		case AT_DropColumn:		/* DROP COLUMN */
@@ -6467,6 +6469,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	AclResult	aclresult;
 	ObjectAddress address;
 	TupleDesc	tupdesc;
+	Datum		acoptions = PointerGetDatum(NULL);
 	FormData_pg_attribute *aattr[] = {&attribute};
 
 	/* At top level, permission check was done in ATPrepCmd, else do it */
@@ -6629,7 +6632,8 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	if (rel->rd_rel->relkind == RELKIND_RELATION ||
 		rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		attribute.attcompression = GetAttributeCompression(&attribute,
-														   colDef->compression);
+														   colDef->compression,
+														   &acoptions);
 	else
 		attribute.attcompression = InvalidCompressionMethod;
 
@@ -6639,7 +6643,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 
 	tupdesc = CreateTupleDesc(lengthof(aattr), (FormData_pg_attribute **) &aattr);
 
-	InsertPgAttributeTuples(attrdesc, tupdesc, myrelid, NULL, NULL);
+	InsertPgAttributeTuples(attrdesc, tupdesc, myrelid, NULL, &acoptions, NULL);
 
 	table_close(attrdesc, RowExclusiveLock);
 
@@ -8015,12 +8019,19 @@ ATExecSetOptions(Relation rel, const char *colName, Node *options,
 static void
 SetIndexStorageProperties(Relation rel, Relation attrelation,
 						  AttrNumber attnum, char newcompression,
-						  char newstorage, LOCKMODE lockmode)
+						  char newstorage, Datum acoptions,
+						  LOCKMODE lockmode)
 {
 	HeapTuple	tuple;
 	ListCell   *lc;
 	Form_pg_attribute attrtuple;
 
+	/*
+	 * Compression option can only be valid if we are updating the compression
+	 * method.
+	 */
+	Assert(DatumGetPointer(acoptions) == NULL || OidIsValid(newcompression));
+
 	foreach(lc, RelationGetIndexList(rel))
 	{
 		Oid			indexoid = lfirst_oid(lc);
@@ -8056,7 +8067,29 @@ SetIndexStorageProperties(Relation rel, Relation attrelation,
 			if (newstorage != '\0')
 				attrtuple->attstorage = newstorage;
 
-			CatalogTupleUpdate(attrelation, &tuple->t_self, tuple);
+			if (DatumGetPointer(acoptions) != NULL)
+			{
+				Datum	values[Natts_pg_attribute];
+				bool	nulls[Natts_pg_attribute];
+				bool	replace[Natts_pg_attribute];
+				HeapTuple	newtuple;
+
+				/* Initialize buffers for new tuple values */
+				memset(values, 0, sizeof(values));
+				memset(nulls, false, sizeof(nulls));
+				memset(replace, false, sizeof(replace));
+
+				values[Anum_pg_attribute_attcmoptions - 1] = acoptions;
+				replace[Anum_pg_attribute_attcmoptions - 1] = true;
+
+				newtuple = heap_modify_tuple(tuple, RelationGetDescr(attrelation),
+											 values, nulls, replace);
+				CatalogTupleUpdate(attrelation, &newtuple->t_self, newtuple);
+
+				heap_freetuple(newtuple);
+			}
+			else
+				CatalogTupleUpdate(attrelation, &tuple->t_self, tuple);
 
 			InvokeObjectPostAlterHook(RelationRelationId,
 									  RelationGetRelid(rel),
@@ -8149,7 +8182,8 @@ ATExecSetStorage(Relation rel, const char *colName, Node *newValue, LOCKMODE loc
 	 */
 	SetIndexStorageProperties(rel, attrelation, attnum,
 							  InvalidCompressionMethod,
-							  newstorage, lockmode);
+							  newstorage, PointerGetDatum(NULL),
+							  lockmode);
 
 	table_close(attrelation, RowExclusiveLock);
 
@@ -15443,21 +15477,22 @@ static ObjectAddress
 ATExecSetCompression(AlteredTableInfo *tab,
 					 Relation rel,
 					 const char *column,
-					 Node *newValue,
+					 ColumnCompression *compression,
 					 LOCKMODE lockmode)
 {
 	Relation	attrel;
 	HeapTuple	tuple;
 	Form_pg_attribute atttableform;
 	AttrNumber	attnum;
-	char	   *compression;
 	char		typstorage;
 	char		cmethod;
+	HeapTuple	newtuple;
+	Datum		values[Natts_pg_attribute];
+	bool		nulls[Natts_pg_attribute];
+	bool		replace[Natts_pg_attribute];	
+	Datum		acoptions;
 	ObjectAddress address;
 
-	Assert(IsA(newValue, String));
-	compression = strVal(newValue);
-
 	attrel = table_open(AttributeRelationId, RowExclusiveLock);
 
 	/* copy the cache entry so we can scribble on it below */
@@ -15485,12 +15520,28 @@ ATExecSetCompression(AlteredTableInfo *tab,
 					errmsg("column data type %s does not support compression",
 						format_type_be(atttableform->atttypid))));
 
+	/* Initialize buffers for new tuple values */
+	memset(values, 0, sizeof(values));
+	memset(nulls, false, sizeof(nulls));
+	memset(replace, false, sizeof(replace));
+
 	/* get the attribute compression method. */
-	cmethod = GetAttributeCompression(atttableform, compression);
+	cmethod = GetAttributeCompression(atttableform, compression, &acoptions);
 
 	/* update pg_attribute entry */
 	atttableform->attcompression = cmethod;
-	CatalogTupleUpdate(attrel, &tuple->t_self, tuple);
+
+	/* update an existing entry */
+	if (acoptions)
+	{
+		values[Anum_pg_attribute_attcmoptions - 1] = acoptions;
+		replace[Anum_pg_attribute_attcmoptions - 1] = true;
+		newtuple = heap_modify_tuple(tuple, RelationGetDescr(attrel),
+									 values, nulls, replace);
+		CatalogTupleUpdate(attrel, &newtuple->t_self, newtuple);
+	}
+	else
+		CatalogTupleUpdate(attrel, &tuple->t_self, tuple);
 
 	InvokeObjectPostAlterHook(RelationRelationId,
 							  RelationGetRelid(rel),
@@ -15500,7 +15551,8 @@ ATExecSetCompression(AlteredTableInfo *tab,
 	 * Apply the change to indexes as well (only for simple index columns,
 	 * matching behavior of index.c ConstructTupleDescriptor()).
 	 */
-	SetIndexStorageProperties(rel, attrel, attnum, cmethod, '\0', lockmode);
+	SetIndexStorageProperties(rel, attrel, attnum, cmethod,
+							  '\0', acoptions, lockmode);
 
 	heap_freetuple(tuple);
 
@@ -18461,10 +18513,42 @@ ATExecAlterCollationRefreshVersion(Relation rel, List *coll)
 }
 
 /*
+ * Fetch atttribute's compression options
+ */
+List *
+GetAttributeCompressionOptions(Form_pg_attribute att)
+{
+	HeapTuple	attr_tuple;
+	Datum		attcmoptions;
+	List	   *acoptions;
+	bool		isNull;
+
+	attr_tuple = SearchSysCache2(ATTNUM,
+								 ObjectIdGetDatum(att->attrelid),
+								 Int16GetDatum(att->attnum));
+	if (!HeapTupleIsValid(attr_tuple))
+		elog(ERROR, "cache lookup failed for attribute %d of relation %u",
+			 att->attnum, att->attrelid);
+
+	attcmoptions = SysCacheGetAttr(ATTNUM, attr_tuple,
+								   Anum_pg_attribute_attcmoptions,
+								   &isNull);
+	if (isNull)
+		acoptions = NIL;
+	else
+		acoptions = untransformRelOptions(attcmoptions);
+
+	ReleaseSysCache(attr_tuple);
+
+	return acoptions;
+}
+
+/*
  * resolve column compression specification to compression method.
  */
 static char
-GetAttributeCompression(Form_pg_attribute att, char *compression)
+GetAttributeCompression(Form_pg_attribute att, ColumnCompression *compression,
+						Datum *acoptions)
 {
 	char		typstorage = get_typstorage(att->atttypid);
 	char		cmethod;
@@ -18488,11 +18572,70 @@ GetAttributeCompression(Form_pg_attribute att, char *compression)
 	if (compression == NULL)
 		return GetDefaultToastCompression();
 
-	cmethod = CompressionNameToMethod(compression);
+	cmethod = CompressionNameToMethod(compression->cmname);
 	if (!CompressionMethodIsValid(cmethod))
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("invalid compression method \"%s\"", compression)));
+				 errmsg("invalid compression method \"%s\"", compression->cmname)));
+
+	/* if compression options are given then check them */
+	if (compression->options)
+	{
+		switch (cmethod)
+		{
+			case TOAST_PGLZ_COMPRESSION:
+				pglz_check_options(compression->options);
+				break;
+			case TOAST_LZ4_COMPRESSION:
+				lz4_check_options(compression->options);
+				break;
+			default:
+				elog(ERROR, "unknown compression method: %c", cmethod);
+		}
+
+		*acoptions = optionListToArray(compression->options);
+	}
+	else
+		*acoptions = PointerGetDatum(NULL);
 
 	return cmethod;
 }
+
+ColumnCompression *
+MakeColumnCompression(Form_pg_attribute att)
+{
+	ColumnCompression *node;
+
+	if (!OidIsValid(att->attcompression))
+		return NULL;
+
+	node = makeNode(ColumnCompression);
+	node->cmname = pstrdup(GetCompressionMethodName(att->attcompression));
+	node->options = GetAttributeCompressionOptions(att);
+
+	return node;
+}
+
+/*
+ * Compare compression options for two columns.
+ */
+void
+CheckCompressionMismatch(ColumnCompression *c1, ColumnCompression *c2,
+						 const char *attributeName)
+{
+	if (strcmp(c1->cmname, c2->cmname) != 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("column \"%s\" has a compression method conflict",
+						attributeName),
+				 errdetail("%s versus %s", c1->cmname, c2->cmname)));
+
+	if (!equal(c1->options, c2->options))
+		ereport(ERROR,
+				(errcode(ERRCODE_DATATYPE_MISMATCH),
+				 errmsg("column \"%s\" has a compression options conflict",
+						attributeName),
+				 errdetail("(%s) versus (%s)",
+						   formatRelOptions(c1->options),
+						   formatRelOptions(c2->options))));
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 632cc31..5518dd8 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3032,7 +3032,7 @@ _copyColumnDef(const ColumnDef *from)
 
 	COPY_STRING_FIELD(colname);
 	COPY_NODE_FIELD(typeName);
-	COPY_STRING_FIELD(compression);
+	COPY_NODE_FIELD(compression);
 	COPY_SCALAR_FIELD(inhcount);
 	COPY_SCALAR_FIELD(is_local);
 	COPY_SCALAR_FIELD(is_not_null);
@@ -3052,6 +3052,17 @@ _copyColumnDef(const ColumnDef *from)
 	return newnode;
 }
 
+static ColumnCompression *
+_copyColumnCompression(const ColumnCompression *from)
+{
+	ColumnCompression *newnode = makeNode(ColumnCompression);
+
+	COPY_STRING_FIELD(cmname);
+	COPY_NODE_FIELD(options);
+
+	return newnode;
+}
+
 static Constraint *
 _copyConstraint(const Constraint *from)
 {
@@ -5768,6 +5779,9 @@ copyObjectImpl(const void *from)
 		case T_ColumnDef:
 			retval = _copyColumnDef(from);
 			break;
+		case T_ColumnCompression:
+			retval = _copyColumnCompression(from);
+			break;
 		case T_Constraint:
 			retval = _copyConstraint(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index a410a29..defb6a2 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2628,7 +2628,7 @@ _equalColumnDef(const ColumnDef *a, const ColumnDef *b)
 {
 	COMPARE_STRING_FIELD(colname);
 	COMPARE_NODE_FIELD(typeName);
-	COMPARE_STRING_FIELD(compression);
+	COMPARE_NODE_FIELD(compression);
 	COMPARE_SCALAR_FIELD(inhcount);
 	COMPARE_SCALAR_FIELD(is_local);
 	COMPARE_SCALAR_FIELD(is_not_null);
@@ -2649,6 +2649,15 @@ _equalColumnDef(const ColumnDef *a, const ColumnDef *b)
 }
 
 static bool
+_equalColumnCompression(const ColumnCompression *a, const ColumnCompression *b)
+{
+	COMPARE_STRING_FIELD(cmname);
+	COMPARE_NODE_FIELD(options);
+
+	return true;
+}
+
+static bool
 _equalConstraint(const Constraint *a, const Constraint *b)
 {
 	COMPARE_SCALAR_FIELD(contype);
@@ -3761,6 +3770,9 @@ equal(const void *a, const void *b)
 		case T_ColumnDef:
 			retval = _equalColumnDef(a, b);
 			break;
+		case T_ColumnCompression:
+			retval = _equalColumnCompression(a, b);
+			break;
 		case T_Constraint:
 			retval = _equalConstraint(a, b);
 			break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index c723f6d..23b1c2d 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2934,7 +2934,7 @@ _outColumnDef(StringInfo str, const ColumnDef *node)
 
 	WRITE_STRING_FIELD(colname);
 	WRITE_NODE_FIELD(typeName);
-	WRITE_STRING_FIELD(compression);
+	WRITE_NODE_FIELD(compression);
 	WRITE_INT_FIELD(inhcount);
 	WRITE_BOOL_FIELD(is_local);
 	WRITE_BOOL_FIELD(is_not_null);
@@ -2953,6 +2953,15 @@ _outColumnDef(StringInfo str, const ColumnDef *node)
 }
 
 static void
+_outColumnCompression(StringInfo str, const ColumnCompression *node)
+{
+	WRITE_NODE_TYPE("COLUMNCOMPRESSION");
+
+	WRITE_STRING_FIELD(cmname);
+	WRITE_NODE_FIELD(options);
+}
+
+static void
 _outTypeName(StringInfo str, const TypeName *node)
 {
 	WRITE_NODE_TYPE("TYPENAME");
@@ -4356,6 +4365,9 @@ outNode(StringInfo str, const void *obj)
 			case T_ColumnDef:
 				_outColumnDef(str, obj);
 				break;
+			case T_ColumnCompression:
+				_outColumnCompression(str, obj);
+				break;
 			case T_TypeName:
 				_outTypeName(str, obj);
 				break;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index b4ab401..9190473 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -425,6 +425,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 				relation_expr_list dostmt_opt_list
 				transform_element_list transform_type_list
 				TriggerTransitions TriggerReferencing
+				optCompressionParameters
 				vacuum_relation_list opt_vacuum_relation_list
 				drop_option_list
 
@@ -609,7 +610,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <list>		hash_partbound
 %type <defelt>		hash_partbound_elem
 
-%type <str>	optColumnCompression
+%type <node>	optColumnCompression
+%type <str>		compressionClause
 
 /*
  * Non-keyword token types.  These are hard-wired into the "flex" lexer.
@@ -2351,7 +2353,7 @@ alter_table_cmd:
 					AlterTableCmd *n = makeNode(AlterTableCmd);
 					n->subtype = AT_SetCompression;
 					n->name = $3;
-					n->def = (Node *) makeString($5);
+					n->def = $5;
 					$$ = (Node *)n;
 				}
 			/* ALTER TABLE <name> DROP [COLUMN] IF EXISTS <colname> [RESTRICT|CASCADE] */
@@ -3474,7 +3476,7 @@ columnDef:	ColId Typename optColumnCompression create_generic_options ColQualLis
 					ColumnDef *n = makeNode(ColumnDef);
 					n->colname = $1;
 					n->typeName = $2;
-					n->compression = $3;
+					n->compression = (ColumnCompression *) $3;
 					n->inhcount = 0;
 					n->is_local = true;
 					n->is_not_null = false;
@@ -3529,13 +3531,25 @@ columnOptions:	ColId ColQualList
 				}
 		;
 
+compressionClause:
+			COMPRESSION name { $$ = pstrdup($2); }
+		;
+
+optCompressionParameters:
+			WITH '(' generic_option_list ')' { $$ = $3; }
+			| /*EMPTY*/	{ $$ = NULL; }
+		;
+
 optColumnCompression:
-					COMPRESSION name
-					{
-						$$ = $2;
-					}
-					| /*EMPTY*/	{ $$ = NULL; }
-				;
+			compressionClause optCompressionParameters
+				{
+					ColumnCompression *n = makeNode(ColumnCompression);
+					n->cmname = $1;
+					n->options = (List *) $2;
+					$$ = (Node *) n;
+				}
+			| /*EMPTY*/	{ $$ = NULL; }
+		;
 
 ColQualList:
 			ColQualList ColConstraint				{ $$ = lappend($1, $2); }
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 9dd3037..8b9fb12 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -1096,8 +1096,7 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
 		/* Likewise, copy compression if requested */
 		if ((table_like_clause->options & CREATE_TABLE_LIKE_COMPRESSION) != 0
 			&& CompressionMethodIsValid(attribute->attcompression))
-			def->compression =
-				pstrdup(GetCompressionMethodName(attribute->attcompression));
+			def->compression = MakeColumnCompression(attribute);
 		else
 			def->compression = NULL;
 
diff --git a/src/include/access/toast_compression.h b/src/include/access/toast_compression.h
index 46c2544..2663793 100644
--- a/src/include/access/toast_compression.h
+++ b/src/include/access/toast_compression.h
@@ -13,6 +13,8 @@
 #ifndef TOAST_COMPRESSION_H
 #define TOAST_COMPRESSION_H
 
+#include "nodes/pg_list.h"
+
 /*
  * GUC support.
  *
@@ -66,13 +68,15 @@ GetDefaultToastCompression(void)
 }
 
 /* pglz compression/decompression routines */
-extern struct varlena *pglz_compress_datum(const struct varlena *value);
+extern void pglz_check_options(List *options);
+extern struct varlena *pglz_compress_datum(const struct varlena *value, List *options);
 extern struct varlena *pglz_decompress_datum(const struct varlena *value);
 extern struct varlena *pglz_decompress_datum_slice(const struct varlena *value,
 												   int32 slicelength);
 
 /* lz4 compression/decompression routines */
-extern struct varlena *lz4_compress_datum(const struct varlena *value);
+extern void lz4_check_options(List *options);
+extern struct varlena *lz4_compress_datum(const struct varlena *value, List *options);
 extern struct varlena *lz4_decompress_datum(const struct varlena *value);
 extern struct varlena *lz4_decompress_datum_slice(const struct varlena *value,
 												  int32 slicelength);
diff --git a/src/include/access/toast_helper.h b/src/include/access/toast_helper.h
index 05104ce..312e54b 100644
--- a/src/include/access/toast_helper.h
+++ b/src/include/access/toast_helper.h
@@ -15,6 +15,7 @@
 #define TOAST_HELPER_H
 
 #include "utils/rel.h"
+#include "nodes/pg_list.h"
 
 /*
  * Information about one column of a tuple being toasted.
@@ -33,6 +34,7 @@ typedef struct
 	int32		tai_size;
 	uint8		tai_colflags;
 	char		tai_compression;
+	List	   *tai_cmoptions;
 } ToastAttrInfo;
 
 /*
diff --git a/src/include/access/toast_internals.h b/src/include/access/toast_internals.h
index 1c28b07..301e892 100644
--- a/src/include/access/toast_internals.h
+++ b/src/include/access/toast_internals.h
@@ -45,7 +45,7 @@ typedef struct toast_compress_header
 			(len) | ((uint32) (cm_method) << VARLENA_EXTSIZE_BITS); \
 	} while (0)
 
-extern Datum toast_compress_datum(Datum value, char cmethod);
+extern Datum toast_compress_datum(Datum value, char cmethod, List *cmoptions);
 extern Oid	toast_get_valid_index(Oid toastoid, LOCKMODE lock);
 
 extern void toast_delete_datum(Relation rel, Datum value, bool is_speculative);
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 6ce480b..5ecb6f3 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -81,6 +81,7 @@ extern Oid	heap_create_with_catalog(const char *relname,
 									 bool allow_system_table_mods,
 									 bool is_internal,
 									 Oid relrewrite,
+									 Datum *acoptions,
 									 ObjectAddress *typaddress);
 
 extern void heap_drop_with_catalog(Oid relid);
@@ -97,6 +98,7 @@ extern void InsertPgAttributeTuples(Relation pg_attribute_rel,
 									TupleDesc tupdesc,
 									Oid new_rel_oid,
 									Datum *attoptions,
+									Datum *attcmoptions,
 									CatalogIndexState indstate);
 
 extern void InsertPgClassTuple(Relation pg_class_desc,
diff --git a/src/include/catalog/pg_attribute.h b/src/include/catalog/pg_attribute.h
index 560f8f0..5a6cff7 100644
--- a/src/include/catalog/pg_attribute.h
+++ b/src/include/catalog/pg_attribute.h
@@ -178,6 +178,9 @@ CATALOG(pg_attribute,1249,AttributeRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(75,
 	/* Column-level FDW options */
 	text		attfdwoptions[1] BKI_DEFAULT(_null_);
 
+	/* current compression options */
+	text		attcmoptions[1] BKI_DEFAULT(_null_);
+
 	/*
 	 * Missing value for added columns. This is a one element array which lets
 	 * us store a value of the attribute type here.
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 6bce4d7..3dde048 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -134,7 +134,12 @@ extern Datum transformGenericOptions(Oid catalogId,
 									 Datum oldOptions,
 									 List *options,
 									 Oid fdwvalidator);
-
+extern Datum optionListToArray(List *options);
+extern char *formatRelOptions(List *options);
+extern ColumnCompression *MakeColumnCompression(Form_pg_attribute att);
+extern List *GetAttributeCompressionOptions(Form_pg_attribute att);
+extern void CheckCompressionMismatch(ColumnCompression *c1, ColumnCompression *c2,
+									 const char *attributeName);
 /* commands/amcmds.c */
 extern ObjectAddress CreateAccessMethod(CreateAmStmt *stmt);
 extern Oid	get_index_am_oid(const char *amname, bool missing_ok);
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index d9e417b..f0398a5 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -458,6 +458,7 @@ typedef enum NodeTag
 	T_RangeTableFuncCol,
 	T_TypeName,
 	T_ColumnDef,
+	T_ColumnCompression,
 	T_IndexElem,
 	T_StatsElem,
 	T_Constraint,
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 615dfa2..3e9d395 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -635,6 +635,19 @@ typedef struct RangeTableSample
 } RangeTableSample;
 
 /*
+ * ColumnCompression - compression parameters for some attribute
+ *
+ * This represents compression information defined using clause:
+ * .. COMPRESSION <compression method> <compression method options>
+ */
+typedef struct ColumnCompression
+{
+	NodeTag		type;
+	char	   *cmname;
+	List	   *options;
+} ColumnCompression;
+
+/*
  * ColumnDef - column definition (used in various creates)
  *
  * If the column has a default value, we may have the value expression
@@ -657,7 +670,7 @@ typedef struct ColumnDef
 	NodeTag		type;
 	char	   *colname;		/* name of column */
 	TypeName   *typeName;		/* type of column */
-	char	   *compression;	/* compression method for column */
+	ColumnCompression *compression;	/* column compression */
 	int			inhcount;		/* number of times column is inherited */
 	bool		is_local;		/* column has local (non-inherited) def'n */
 	bool		is_not_null;	/* NOT NULL constraint specified? */
diff --git a/src/test/regress/expected/compression.out b/src/test/regress/expected/compression.out
index 61e97cb..d3b1ec5 100644
--- a/src/test/regress/expected/compression.out
+++ b/src/test/regress/expected/compression.out
@@ -319,6 +319,39 @@ CREATE TABLE cmdata2 (f1 TEXT COMPRESSION pglz, f2 TEXT COMPRESSION lz4);
 CREATE UNIQUE INDEX idx1 ON cmdata2 ((f1 || f2));
 INSERT INTO cmdata2 VALUES((SELECT array_agg(md5(g::TEXT))::TEXT FROM
 generate_series(1, 50) g), VERSION());
+-- compression options
+CREATE TABLE cmdata3(f1 TEXT COMPRESSION pglz WITH (min_input_size '100'));
+CREATE INDEX idx2 ON cmdata3(f1);
+INSERT INTO cmdata3 VALUES(repeat('1234567890',1000));
+SELECT attcmoptions FROM pg_attribute WHERE attrelid = 'cmdata3'::regclass AND attname='f1';
+     attcmoptions     
+----------------------
+ {min_input_size=100}
+(1 row)
+
+SELECT attcmoptions FROM pg_attribute WHERE attrelid = 'idx2'::regclass AND attname='f1';
+     attcmoptions     
+----------------------
+ {min_input_size=100}
+(1 row)
+
+ALTER TABLE cmdata3 ALTER COLUMN f1 SET COMPRESSION lz4 WITH (acceleration '50');
+INSERT INTO cmdata3 VALUES(repeat('1234567890',1004));
+SELECT attcmoptions FROM pg_attribute WHERE attrelid = 'cmdata3'::regclass AND attname='f1';
+   attcmoptions    
+-------------------
+ {acceleration=50}
+(1 row)
+
+SELECT attcmoptions FROM pg_attribute WHERE attrelid = 'idx2'::regclass AND attname='f1';
+   attcmoptions    
+-------------------
+ {acceleration=50}
+(1 row)
+
+--error
+ALTER TABLE cmdata3 ALTER COLUMN f1 SET COMPRESSION pglz WITH (invalid_option '50');
+ERROR:  unknown compression option for pglz: "invalid_option"
 -- check data is ok
 SELECT length(f1) FROM cmdata;
  length 
diff --git a/src/test/regress/expected/compression_1.out b/src/test/regress/expected/compression_1.out
index d03d625..49e9d3f 100644
--- a/src/test/regress/expected/compression_1.out
+++ b/src/test/regress/expected/compression_1.out
@@ -322,6 +322,42 @@ generate_series(1, 50) g), VERSION());
 ERROR:  relation "cmdata2" does not exist
 LINE 1: INSERT INTO cmdata2 VALUES((SELECT array_agg(md5(g::TEXT))::...
                     ^
+-- compression options
+CREATE TABLE cmdata3(f1 TEXT COMPRESSION pglz WITH (min_input_size '100'));
+CREATE INDEX idx2 ON cmdata3(f1);
+INSERT INTO cmdata3 VALUES(repeat('1234567890',1000));
+SELECT attcmoptions FROM pg_attribute WHERE attrelid = 'cmdata3'::regclass AND attname='f1';
+     attcmoptions     
+----------------------
+ {min_input_size=100}
+(1 row)
+
+SELECT attcmoptions FROM pg_attribute WHERE attrelid = 'idx2'::regclass AND attname='f1';
+     attcmoptions     
+----------------------
+ {min_input_size=100}
+(1 row)
+
+ALTER TABLE cmdata3 ALTER COLUMN f1 SET COMPRESSION lz4 WITH (acceleration '50');
+ERROR:  unsupported LZ4 compression method
+DETAIL:  This functionality requires the server to be built with lz4 support.
+HINT:  You need to rebuild PostgreSQL using --with-lz4.
+INSERT INTO cmdata3 VALUES(repeat('1234567890',1004));
+SELECT attcmoptions FROM pg_attribute WHERE attrelid = 'cmdata3'::regclass AND attname='f1';
+     attcmoptions     
+----------------------
+ {min_input_size=100}
+(1 row)
+
+SELECT attcmoptions FROM pg_attribute WHERE attrelid = 'idx2'::regclass AND attname='f1';
+     attcmoptions     
+----------------------
+ {min_input_size=100}
+(1 row)
+
+--error
+ALTER TABLE cmdata3 ALTER COLUMN f1 SET COMPRESSION pglz WITH (invalid_option '50');
+ERROR:  unknown compression option for pglz: "invalid_option"
 -- check data is ok
 SELECT length(f1) FROM cmdata;
  length 
diff --git a/src/test/regress/expected/misc_sanity.out b/src/test/regress/expected/misc_sanity.out
index 9ebe28a..4e3b0c1 100644
--- a/src/test/regress/expected/misc_sanity.out
+++ b/src/test/regress/expected/misc_sanity.out
@@ -99,6 +99,7 @@ ORDER BY 1, 2;
          relname         |    attname    |   atttypid   
 -------------------------+---------------+--------------
  pg_attribute            | attacl        | aclitem[]
+ pg_attribute            | attcmoptions  | text[]
  pg_attribute            | attfdwoptions | text[]
  pg_attribute            | attmissingval | anyarray
  pg_attribute            | attoptions    | text[]
@@ -109,7 +110,7 @@ ORDER BY 1, 2;
  pg_index                | indpred       | pg_node_tree
  pg_largeobject          | data          | bytea
  pg_largeobject_metadata | lomacl        | aclitem[]
-(11 rows)
+(12 rows)
 
 -- system catalogs without primary keys
 --
diff --git a/src/test/regress/sql/compression.sql b/src/test/regress/sql/compression.sql
index 76d1776..935494c 100644
--- a/src/test/regress/sql/compression.sql
+++ b/src/test/regress/sql/compression.sql
@@ -137,6 +137,21 @@ CREATE UNIQUE INDEX idx1 ON cmdata2 ((f1 || f2));
 INSERT INTO cmdata2 VALUES((SELECT array_agg(md5(g::TEXT))::TEXT FROM
 generate_series(1, 50) g), VERSION());
 
+-- compression options
+CREATE TABLE cmdata3(f1 TEXT COMPRESSION pglz WITH (min_input_size '100'));
+CREATE INDEX idx2 ON cmdata3(f1);
+INSERT INTO cmdata3 VALUES(repeat('1234567890',1000));
+SELECT attcmoptions FROM pg_attribute WHERE attrelid = 'cmdata3'::regclass AND attname='f1';
+SELECT attcmoptions FROM pg_attribute WHERE attrelid = 'idx2'::regclass AND attname='f1';
+
+ALTER TABLE cmdata3 ALTER COLUMN f1 SET COMPRESSION lz4 WITH (acceleration '50');
+INSERT INTO cmdata3 VALUES(repeat('1234567890',1004));
+SELECT attcmoptions FROM pg_attribute WHERE attrelid = 'cmdata3'::regclass AND attname='f1';
+SELECT attcmoptions FROM pg_attribute WHERE attrelid = 'idx2'::regclass AND attname='f1';
+
+--error
+ALTER TABLE cmdata3 ALTER COLUMN f1 SET COMPRESSION pglz WITH (invalid_option '50');
+
 -- check data is ok
 SELECT length(f1) FROM cmdata;
 SELECT length(f1) FROM cmdata1;
-- 
1.8.3.1

Reply via email to