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