Custom reloptions for table AM
authorAlexander Korotkov <akorotkov@postgresql.org>
Mon, 8 Apr 2024 08:23:28 +0000 (11:23 +0300)
committerAlexander Korotkov <akorotkov@postgresql.org>
Mon, 8 Apr 2024 08:23:28 +0000 (11:23 +0300)
Let table AM define custom reloptions for its tables. This allows specifying
AM-specific parameters by the WITH clause when creating a table.

The reloptions, which could be used outside of table AM, are now extracted
into the CommonRdOptions data structure.  These options could be by decision
of table AM directly specified by a user or calculated in some way.

The new test module test_tam_options evaluates the ability to set up custom
reloptions and calculate fields of CommonRdOptions on their base.

The code may use some parts from prior work by Hao Wu.

Discussion: https://postgr.es/m/CAPpHfdurb9ycV8udYqM%3Do0sPS66PJ4RCBM1g-bBpvzUfogY0EA%40mail.gmail.com
Discussion: https://postgr.es/m/AMUA1wBBBxfc3tKRLLdU64rb.1.1683276279979.Hmail.wuhao%40hashdata.cn
Reviewed-by: Reviewed-by: Pavel Borisov, Matthias van de Meent, Jess Davis
29 files changed:
src/backend/access/common/reloptions.c
src/backend/access/heap/heapam.c
src/backend/access/heap/heapam_handler.c
src/backend/access/heap/heaptoast.c
src/backend/access/heap/hio.c
src/backend/access/heap/pruneheap.c
src/backend/access/heap/rewriteheap.c
src/backend/access/table/tableam.c
src/backend/access/table/tableamapi.c
src/backend/commands/createas.c
src/backend/commands/tablecmds.c
src/backend/commands/vacuum.c
src/backend/postmaster/autovacuum.c
src/backend/tcop/utility.c
src/backend/utils/cache/relcache.c
src/include/access/reloptions.h
src/include/access/tableam.h
src/include/utils/rel.h
src/test/modules/Makefile
src/test/modules/meson.build
src/test/modules/test_tam_options/.gitignore [new file with mode: 0644]
src/test/modules/test_tam_options/Makefile [new file with mode: 0644]
src/test/modules/test_tam_options/expected/test_tam_options.out [new file with mode: 0644]
src/test/modules/test_tam_options/meson.build [new file with mode: 0644]
src/test/modules/test_tam_options/sql/test_tam_options.sql [new file with mode: 0644]
src/test/modules/test_tam_options/test_tam_options--1.0.sql [new file with mode: 0644]
src/test/modules/test_tam_options/test_tam_options.c [new file with mode: 0644]
src/test/modules/test_tam_options/test_tam_options.control [new file with mode: 0644]
src/tools/pgindent/typedefs.list

index d6eb5d8559939fef263e9cab96c9a98d13cbc45f..c1de092a42dcdfeffa5ffda154cfc54cd094d626 100644 (file)
@@ -24,6 +24,7 @@
 #include "access/nbtree.h"
 #include "access/reloptions.h"
 #include "access/spgist_private.h"
+#include "access/tableam.h"
 #include "catalog/pg_type.h"
 #include "commands/defrem.h"
 #include "commands/tablespace.h"
@@ -44,7 +45,7 @@
  * value, upper and lower bounds (if applicable); for strings, consider a
  * validation routine.
  * (ii) add a record below (or use add_<type>_reloption).
- * (iii) add it to the appropriate options struct (perhaps StdRdOptions)
+ * (iii) add it to the appropriate options struct (perhaps HeapRdOptions)
  * (iv) add it to the appropriate handling routine (perhaps
  * default_reloptions)
  * (v) make sure the lock level is set correctly for that operation
@@ -1374,10 +1375,16 @@ untransformRelOptions(Datum options)
  * tupdesc is pg_class' tuple descriptor.  amoptions is a pointer to the index
  * AM's options parser function in the case of a tuple corresponding to an
  * index, or NULL otherwise.
+ *
+ * If common pointer is provided, then the corresponding struct will be
+ * filled with options that table AM exposes for external usage.  That must
+ * be filled with defaults before passing here.
  */
+
 bytea *
 extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
-                                 amoptions_function amoptions)
+                                 const TableAmRoutine *tableam, amoptions_function amoptions,
+                                 CommonRdOptions *common)
 {
        bytea      *options;
        bool            isnull;
@@ -1399,7 +1406,8 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
                case RELKIND_RELATION:
                case RELKIND_TOASTVALUE:
                case RELKIND_MATVIEW:
-                       options = heap_reloptions(classForm->relkind, datum, false);
+                       options = tableam_reloptions(tableam, classForm->relkind,
+                                                                                datum, common, false);
                        break;
                case RELKIND_PARTITIONED_TABLE:
                        options = partitioned_table_reloptions(datum, false);
@@ -1695,7 +1703,7 @@ parse_one_reloption(relopt_value *option, char *text_str, int text_len,
  * Given the result from parseRelOptions, allocate a struct that's of the
  * specified base size plus any extra space that's needed for string variables.
  *
- * "base" should be sizeof(struct) of the reloptions struct (StdRdOptions or
+ * "base" should be sizeof(struct) of the reloptions struct (HeapRdOptions or
  * equivalent).
  */
 static void *
@@ -1832,59 +1840,95 @@ fillRelOptions(void *rdopts, Size basesize,
 
 
 /*
- * Option parser for anything that uses StdRdOptions.
+ * Option parser for anything that uses HeapRdOptions.
  */
-bytea *
+static bytea *
 default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 {
        static const relopt_parse_elt tab[] = {
-               {"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)},
+               {"fillfactor", RELOPT_TYPE_INT, offsetof(HeapRdOptions, fillfactor)},
                {"autovacuum_enabled", RELOPT_TYPE_BOOL,
-               offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)},
+                       offsetof(HeapRdOptions, common) +
+                       offsetof(CommonRdOptions, autovacuum) +
+               offsetof(AutoVacOpts, enabled)},
                {"autovacuum_vacuum_threshold", RELOPT_TYPE_INT,
-               offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_threshold)},
+                       offsetof(HeapRdOptions, common) +
+                       offsetof(CommonRdOptions, autovacuum) +
+               offsetof(AutoVacOpts, vacuum_threshold)},
                {"autovacuum_vacuum_insert_threshold", RELOPT_TYPE_INT,
-               offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_ins_threshold)},
+                       offsetof(HeapRdOptions, common) +
+                       offsetof(CommonRdOptions, autovacuum) +
+               offsetof(AutoVacOpts, vacuum_ins_threshold)},
                {"autovacuum_analyze_threshold", RELOPT_TYPE_INT,
-               offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, analyze_threshold)},
+                       offsetof(HeapRdOptions, common) +
+                       offsetof(CommonRdOptions, autovacuum) +
+               offsetof(AutoVacOpts, analyze_threshold)},
                {"autovacuum_vacuum_cost_limit", RELOPT_TYPE_INT,
-               offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_cost_limit)},
+                       offsetof(HeapRdOptions, common) +
+                       offsetof(CommonRdOptions, autovacuum) +
+               offsetof(AutoVacOpts, vacuum_cost_limit)},
                {"autovacuum_freeze_min_age", RELOPT_TYPE_INT,
-               offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, freeze_min_age)},
+                       offsetof(HeapRdOptions, common) +
+                       offsetof(CommonRdOptions, autovacuum) +
+               offsetof(AutoVacOpts, freeze_min_age)},
                {"autovacuum_freeze_max_age", RELOPT_TYPE_INT,
-               offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, freeze_max_age)},
+                       offsetof(HeapRdOptions, common) +
+                       offsetof(CommonRdOptions, autovacuum) +
+               offsetof(AutoVacOpts, freeze_max_age)},
                {"autovacuum_freeze_table_age", RELOPT_TYPE_INT,
-               offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, freeze_table_age)},
+                       offsetof(HeapRdOptions, common) +
+                       offsetof(CommonRdOptions, autovacuum) +
+               offsetof(AutoVacOpts, freeze_table_age)},
                {"autovacuum_multixact_freeze_min_age", RELOPT_TYPE_INT,
-               offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, multixact_freeze_min_age)},
+                       offsetof(HeapRdOptions, common) +
+                       offsetof(CommonRdOptions, autovacuum) +
+               offsetof(AutoVacOpts, multixact_freeze_min_age)},
                {"autovacuum_multixact_freeze_max_age", RELOPT_TYPE_INT,
-               offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, multixact_freeze_max_age)},
+                       offsetof(HeapRdOptions, common) +
+                       offsetof(CommonRdOptions, autovacuum) +
+               offsetof(AutoVacOpts, multixact_freeze_max_age)},
                {"autovacuum_multixact_freeze_table_age", RELOPT_TYPE_INT,
-               offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, multixact_freeze_table_age)},
+                       offsetof(HeapRdOptions, common) +
+                       offsetof(CommonRdOptions, autovacuum) +
+               offsetof(AutoVacOpts, multixact_freeze_table_age)},
                {"log_autovacuum_min_duration", RELOPT_TYPE_INT,
-               offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, log_min_duration)},
+                       offsetof(HeapRdOptions, common) +
+                       offsetof(CommonRdOptions, autovacuum) +
+               offsetof(AutoVacOpts, log_min_duration)},
                {"toast_tuple_target", RELOPT_TYPE_INT,
-               offsetof(StdRdOptions, toast_tuple_target)},
+               offsetof(HeapRdOptions, toast_tuple_target)},
                {"autovacuum_vacuum_cost_delay", RELOPT_TYPE_REAL,
-               offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_cost_delay)},
+                       offsetof(HeapRdOptions, common) +
+                       offsetof(CommonRdOptions, autovacuum) +
+               offsetof(AutoVacOpts, vacuum_cost_delay)},
                {"autovacuum_vacuum_scale_factor", RELOPT_TYPE_REAL,
-               offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_scale_factor)},
+                       offsetof(HeapRdOptions, common) +
+                       offsetof(CommonRdOptions, autovacuum) +
+               offsetof(AutoVacOpts, vacuum_scale_factor)},
                {"autovacuum_vacuum_insert_scale_factor", RELOPT_TYPE_REAL,
-               offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_ins_scale_factor)},
+                       offsetof(HeapRdOptions, common) +
+                       offsetof(CommonRdOptions, autovacuum) +
+               offsetof(AutoVacOpts, vacuum_ins_scale_factor)},
                {"autovacuum_analyze_scale_factor", RELOPT_TYPE_REAL,
-               offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, analyze_scale_factor)},
+                       offsetof(HeapRdOptions, common) +
+                       offsetof(CommonRdOptions, autovacuum) +
+               offsetof(AutoVacOpts, analyze_scale_factor)},
                {"user_catalog_table", RELOPT_TYPE_BOOL,
-               offsetof(StdRdOptions, user_catalog_table)},
+                       offsetof(HeapRdOptions, common) +
+               offsetof(CommonRdOptions, user_catalog_table)},
                {"parallel_workers", RELOPT_TYPE_INT,
-               offsetof(StdRdOptions, parallel_workers)},
+                       offsetof(HeapRdOptions, common) +
+               offsetof(CommonRdOptions, parallel_workers)},
                {"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
-               offsetof(StdRdOptions, vacuum_index_cleanup)},
+                       offsetof(HeapRdOptions, common) +
+               offsetof(CommonRdOptions, vacuum_index_cleanup)},
                {"vacuum_truncate", RELOPT_TYPE_BOOL,
-               offsetof(StdRdOptions, vacuum_truncate)}
+                       offsetof(HeapRdOptions, common) +
+               offsetof(CommonRdOptions, vacuum_truncate)}
        };
 
        return (bytea *) build_reloptions(reloptions, validate, kind,
-                                                                         sizeof(StdRdOptions),
+                                                                         sizeof(HeapRdOptions),
                                                                          tab, lengthof(tab));
 }
 
@@ -2016,26 +2060,33 @@ view_reloptions(Datum reloptions, bool validate)
  * Parse options for heaps, views and toast tables.
  */
 bytea *
-heap_reloptions(char relkind, Datum reloptions, bool validate)
+heap_reloptions(char relkind, Datum reloptions,
+                               CommonRdOptions *common, bool validate)
 {
-       StdRdOptions *rdopts;
+       HeapRdOptions *rdopts;
 
        switch (relkind)
        {
                case RELKIND_TOASTVALUE:
-                       rdopts = (StdRdOptions *)
+                       rdopts = (HeapRdOptions *)
                                default_reloptions(reloptions, validate, RELOPT_KIND_TOAST);
                        if (rdopts != NULL)
                        {
                                /* adjust default-only parameters for TOAST relations */
                                rdopts->fillfactor = 100;
-                               rdopts->autovacuum.analyze_threshold = -1;
-                               rdopts->autovacuum.analyze_scale_factor = -1;
+                               rdopts->common.autovacuum.analyze_threshold = -1;
+                               rdopts->common.autovacuum.analyze_scale_factor = -1;
                        }
+                       if (rdopts != NULL && common != NULL)
+                               *common = rdopts->common;
                        return (bytea *) rdopts;
                case RELKIND_RELATION:
                case RELKIND_MATVIEW:
-                       return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP);
+                       rdopts = (HeapRdOptions *)
+                               default_reloptions(reloptions, validate, RELOPT_KIND_HEAP);
+                       if (rdopts != NULL && common != NULL)
+                               *common = rdopts->common;
+                       return (bytea *) rdopts;
                default:
                        /* other relkinds are not supported */
                        return NULL;
index 08f6a10ac3da70cdcd725a0edc45b7680909c70b..4607505106d2414cd140802a38f635bf8811edc6 100644 (file)
@@ -2279,8 +2279,8 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples,
        Assert(!(options & HEAP_INSERT_NO_LOGICAL));
 
        needwal = RelationNeedsWAL(relation);
-       saveFreeSpace = RelationGetTargetPageFreeSpace(relation,
-                                                                                                  HEAP_DEFAULT_FILLFACTOR);
+       saveFreeSpace = HeapGetTargetPageFreeSpace(relation,
+                                                                                          HEAP_DEFAULT_FILLFACTOR);
 
        /* Toast and set header data in all the slots */
        heaptuples = palloc(ntuples * sizeof(HeapTuple));
index cc4d51d5514646f0e2eee9177e27d4fe46f7af98..6b1d2dd7a48a3c88d3741ab7bfea378436dc8094 100644 (file)
@@ -23,6 +23,7 @@
 #include "access/heapam.h"
 #include "access/heaptoast.h"
 #include "access/multixact.h"
+#include "access/reloptions.h"
 #include "access/rewriteheap.h"
 #include "access/syncscan.h"
 #include "access/tableam.h"
@@ -2161,6 +2162,17 @@ heapam_relation_toast_am(Relation rel)
        return rel->rd_rel->relam;
 }
 
+static bytea *
+heapam_reloptions(char relkind, Datum reloptions,
+                                 CommonRdOptions *common, bool validate)
+{
+       Assert(relkind == RELKIND_RELATION ||
+                  relkind == RELKIND_TOASTVALUE ||
+                  relkind == RELKIND_MATVIEW);
+
+       return heap_reloptions(relkind, reloptions, common, validate);
+}
+
 
 /* ------------------------------------------------------------------------
  * Planner related callbacks for the heap AM
@@ -2710,6 +2722,7 @@ static const TableAmRoutine heapam_methods = {
        .relation_needs_toast_table = heapam_relation_needs_toast_table,
        .relation_toast_am = heapam_relation_toast_am,
        .relation_fetch_toast_slice = heap_fetch_toast_slice,
+       .reloptions = heapam_reloptions,
 
        .relation_estimate_size = heapam_estimate_rel_size,
 
index a420e1653046c2385ec6d8e679f5f15b7cb44d41..cffee7e1b7a9a4bbc04f9eb2729cd4f76afaca12 100644 (file)
 #include "access/toast_internals.h"
 #include "utils/fmgroids.h"
 
+/*
+ * HeapGetToastTupleTarget
+ *      Returns the heap relation's toast_tuple_target.  Note multiple eval of argument!
+ */
+#define HeapGetToastTupleTarget(relation, defaulttarg) \
+               ((HeapRdOptions *) (relation)->rd_options ? \
+               ((HeapRdOptions *) (relation)->rd_options)->toast_tuple_target : (defaulttarg))
 
 /* ----------
  * heap_toast_delete -
@@ -174,7 +181,7 @@ heap_toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
                hoff += BITMAPLEN(numAttrs);
        hoff = MAXALIGN(hoff);
        /* now convert to a limit on the tuple data size */
-       maxDataLen = RelationGetToastTupleTarget(rel, TOAST_TUPLE_TARGET) - hoff;
+       maxDataLen = HeapGetToastTupleTarget(rel, TOAST_TUPLE_TARGET) - hoff;
 
        /*
         * Look for attributes with attstorage EXTENDED to compress.  Also find
index 7c662cdf46ecfba9c0a24e755422823485353983..ed731179b453bf56e39328c6d765b69ac9f9da88 100644 (file)
@@ -536,8 +536,8 @@ RelationGetBufferForTuple(Relation relation, Size len,
                                                len, MaxHeapTupleSize)));
 
        /* Compute desired extra freespace due to fillfactor option */
-       saveFreeSpace = RelationGetTargetPageFreeSpace(relation,
-                                                                                                  HEAP_DEFAULT_FILLFACTOR);
+       saveFreeSpace = HeapGetTargetPageFreeSpace(relation,
+                                                                                          HEAP_DEFAULT_FILLFACTOR);
 
        /*
         * Since pages without tuples can still have line pointers, we consider
index d2eecaf7ebcea3c3b566ffc08dfcc1dd764c6662..20142dd21445f45b942d6937ed35674812e4a7a3 100644 (file)
@@ -235,8 +235,8 @@ heap_page_prune_opt(Relation relation, Buffer buffer)
         * important than sometimes getting a wrong answer in what is after all
         * just a heuristic estimate.
         */
-       minfree = RelationGetTargetPageFreeSpace(relation,
-                                                                                        HEAP_DEFAULT_FILLFACTOR);
+       minfree = HeapGetTargetPageFreeSpace(relation,
+                                                                                HEAP_DEFAULT_FILLFACTOR);
        minfree = Max(minfree, BLCKSZ / 10);
 
        if (PageIsFull(page) || PageGetHeapFreeSpace(page) < minfree)
index 473f3aa9bef9e0c5abbca64efbce84de33cf08e2..2bbf121146e636a34053604f94e91bfd2fb95b2c 100644 (file)
@@ -641,8 +641,8 @@ raw_heap_insert(RewriteState state, HeapTuple tup)
                                                len, MaxHeapTupleSize)));
 
        /* Compute desired extra freespace due to fillfactor option */
-       saveFreeSpace = RelationGetTargetPageFreeSpace(state->rs_new_rel,
-                                                                                                  HEAP_DEFAULT_FILLFACTOR);
+       saveFreeSpace = HeapGetTargetPageFreeSpace(state->rs_new_rel,
+                                                                                          HEAP_DEFAULT_FILLFACTOR);
 
        /* Now we can check to see if there's enough free space already. */
        page = (Page) state->rs_buffer;
index 805d222cebc466cc2d3da2d965656b61de9f51e4..5d5f0e68fd7a851e86c3a48f9afb9cb974935283 100644 (file)
@@ -750,7 +750,7 @@ table_block_relation_estimate_size(Relation rel, int32 *attr_widths,
                 * The other branch considers it implicitly by calculating density
                 * from actual relpages/reltuples statistics.
                 */
-               fillfactor = RelationGetFillFactor(rel, HEAP_DEFAULT_FILLFACTOR);
+               fillfactor = HeapGetFillFactor(rel, HEAP_DEFAULT_FILLFACTOR);
 
                tuple_width = get_rel_data_width(rel, attr_widths);
                tuple_width += overhead_bytes_per_tuple;
index 55b8caeadf2c8b2d3b350d9661954e1f31852299..d9e23ef31754750e0c548d91e48f0cb0a1546a5b 100644 (file)
 
 #include "access/tableam.h"
 #include "access/xact.h"
+#include "catalog/pg_am.h"
 #include "commands/defrem.h"
 #include "miscadmin.h"
 #include "utils/guc_hooks.h"
+#include "utils/syscache.h"
 
 
 /*
@@ -98,6 +100,29 @@ GetTableAmRoutine(Oid amhandler)
        return routine;
 }
 
+/*
+ * GetTableAmRoutineByAmOid
+ *             Given the table access method oid get its TableAmRoutine struct, which
+ *             will be palloc'd in the caller's memory context.
+ */
+const TableAmRoutine *
+GetTableAmRoutineByAmOid(Oid amoid)
+{
+       HeapTuple       ht_am;
+       Form_pg_am      amrec;
+       const TableAmRoutine *tableam = NULL;
+
+       ht_am = SearchSysCache1(AMOID, ObjectIdGetDatum(amoid));
+       if (!HeapTupleIsValid(ht_am))
+               elog(ERROR, "cache lookup failed for access method %u",
+                        amoid);
+       amrec = (Form_pg_am) GETSTRUCT(ht_am);
+
+       tableam = GetTableAmRoutine(amrec->amhandler);
+       ReleaseSysCache(ht_am);
+       return tableam;
+}
+
 /* check_hook: validate new default_table_access_method */
 bool
 check_default_table_access_method(char **newval, void **extra, GucSource source)
index afd3dace079e1bc1a7c6f2185a3304327cc798c1..c5df96e374a01f5e7864b511232a26d548048829 100644 (file)
@@ -85,6 +85,9 @@ create_ctas_internal(List *attrList, IntoClause *into)
        Datum           toast_options;
        static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
        ObjectAddress intoRelationAddr;
+       const TableAmRoutine *tableam = NULL;
+       Oid                     accessMethodId = InvalidOid;
+       Relation        rel;
 
        /* This code supports both CREATE TABLE AS and CREATE MATERIALIZED VIEW */
        is_matview = (into->viewQuery != NULL);
@@ -125,7 +128,15 @@ create_ctas_internal(List *attrList, IntoClause *into)
                                                                                validnsps,
                                                                                true, false);
 
-       (void) heap_reloptions(RELKIND_TOASTVALUE, toast_options, true);
+       rel = relation_open(intoRelationAddr.objectId, AccessShareLock);
+       accessMethodId = table_relation_toast_am(rel);
+       relation_close(rel, AccessShareLock);
+
+       if (OidIsValid(accessMethodId))
+       {
+               tableam = GetTableAmRoutineByAmOid(accessMethodId);
+               (void) tableam_reloptions(tableam, RELKIND_TOASTVALUE, toast_options, NULL, true);
+       }
 
        NewRelationCreateToastTable(intoRelationAddr.objectId, toast_options);
 
index 582890a30256122592e92d5593140ad56547dfc9..3c74eef3e675e358d64f5f71aa193b111120c274 100644 (file)
@@ -720,6 +720,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
        ObjectAddress address;
        LOCKMODE        parentLockmode;
        Oid                     accessMethodId = InvalidOid;
+       const TableAmRoutine *tableam = NULL;
 
        /*
         * Truncate relname to appropriate length (probably a waste of time, as
@@ -855,6 +856,28 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
        if (!OidIsValid(ownerId))
                ownerId = GetUserId();
 
+       /*
+        * For relations with table AM and partitioned tables, select access
+        * method to use: an explicitly indicated one, or (in the case of a
+        * partitioned table) the parent's, if it has one.
+        */
+       if (stmt->accessMethod != NULL)
+       {
+               Assert(RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_PARTITIONED_TABLE);
+               accessMethodId = get_table_am_oid(stmt->accessMethod, false);
+       }
+       else if (RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_PARTITIONED_TABLE)
+       {
+               if (stmt->partbound)
+               {
+                       Assert(list_length(inheritOids) == 1);
+                       accessMethodId = get_rel_relam(linitial_oid(inheritOids));
+               }
+
+               if (RELKIND_HAS_TABLE_AM(relkind) && !OidIsValid(accessMethodId))
+                       accessMethodId = get_table_am_oid(default_table_access_method, false);
+       }
+
        /*
         * Parse and validate reloptions, if any.
         */
@@ -863,6 +886,12 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 
        switch (relkind)
        {
+               case RELKIND_RELATION:
+               case RELKIND_TOASTVALUE:
+               case RELKIND_MATVIEW:
+                       tableam = GetTableAmRoutineByAmOid(accessMethodId);
+                       (void) tableam_reloptions(tableam, relkind, reloptions, NULL, true);
+                       break;
                case RELKIND_VIEW:
                        (void) view_reloptions(reloptions, true);
                        break;
@@ -870,7 +899,12 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
                        (void) partitioned_table_reloptions(reloptions, true);
                        break;
                default:
-                       (void) heap_reloptions(relkind, reloptions, true);
+                       if (OidIsValid(accessMethodId))
+                       {
+                               tableam = GetTableAmRoutineByAmOid(accessMethodId);
+                               (void) tableam_reloptions(tableam, relkind, reloptions, NULL, true);
+                       }
+                       break;
        }
 
        if (stmt->ofTypename)
@@ -962,28 +996,6 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
                }
        }
 
-       /*
-        * For relations with table AM and partitioned tables, select access
-        * method to use: an explicitly indicated one, or (in the case of a
-        * partitioned table) the parent's, if it has one.
-        */
-       if (stmt->accessMethod != NULL)
-       {
-               Assert(RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_PARTITIONED_TABLE);
-               accessMethodId = get_table_am_oid(stmt->accessMethod, false);
-       }
-       else if (RELKIND_HAS_TABLE_AM(relkind) || relkind == RELKIND_PARTITIONED_TABLE)
-       {
-               if (stmt->partbound)
-               {
-                       Assert(list_length(inheritOids) == 1);
-                       accessMethodId = get_rel_relam(linitial_oid(inheritOids));
-               }
-
-               if (RELKIND_HAS_TABLE_AM(relkind) && !OidIsValid(accessMethodId))
-                       accessMethodId = get_table_am_oid(default_table_access_method, false);
-       }
-
        /*
         * Create the relation.  Inherited defaults and constraints are passed in
         * for immediate handling --- since they don't need parsing, they can be
@@ -15571,7 +15583,8 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
                case RELKIND_RELATION:
                case RELKIND_TOASTVALUE:
                case RELKIND_MATVIEW:
-                       (void) heap_reloptions(rel->rd_rel->relkind, newOptions, true);
+                       (void) table_reloptions(rel, rel->rd_rel->relkind,
+                                                                       newOptions, NULL, true);
                        break;
                case RELKIND_PARTITIONED_TABLE:
                        (void) partitioned_table_reloptions(newOptions, true);
@@ -15684,7 +15697,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
                                                                                 defList, "toast", validnsps, false,
                                                                                 operation == AT_ResetRelOptions);
 
-               (void) heap_reloptions(RELKIND_TOASTVALUE, newOptions, true);
+               (void) table_reloptions(rel, RELKIND_TOASTVALUE, newOptions, NULL, true);
 
                memset(repl_val, 0, sizeof(repl_val));
                memset(repl_null, false, sizeof(repl_null));
index b589279d49f9ec97a8c5cf8759df923015d96772..10509c6165df51a70205284c76c9205af99e99a9 100644 (file)
@@ -2121,11 +2121,11 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params,
        {
                StdRdOptIndexCleanup vacuum_index_cleanup;
 
-               if (rel->rd_options == NULL)
+               if (rel->rd_common_options == NULL)
                        vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
                else
                        vacuum_index_cleanup =
-                               ((StdRdOptions *) rel->rd_options)->vacuum_index_cleanup;
+                               rel->rd_common_options->vacuum_index_cleanup;
 
                if (vacuum_index_cleanup == STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO)
                        params->index_cleanup = VACOPTVALUE_AUTO;
@@ -2145,8 +2145,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params,
         */
        if (params->truncate == VACOPTVALUE_UNSPECIFIED)
        {
-               if (rel->rd_options == NULL ||
-                       ((StdRdOptions *) rel->rd_options)->vacuum_truncate)
+               if (rel->rd_common_options == NULL ||
+                       rel->rd_common_options->vacuum_truncate)
                        params->truncate = VACOPTVALUE_ENABLED;
                else
                        params->truncate = VACOPTVALUE_DISABLED;
index c367ede6f88530eb725fcb18b42f566b87443afa..7cb79ebcedd21d8441aadc1c71d6e1452d90f2bd 100644 (file)
@@ -2674,19 +2674,21 @@ static AutoVacOpts *
 extract_autovac_opts(HeapTuple tup, TupleDesc pg_class_desc)
 {
        bytea      *relopts;
+       CommonRdOptions common;
        AutoVacOpts *av;
 
        Assert(((Form_pg_class) GETSTRUCT(tup))->relkind == RELKIND_RELATION ||
                   ((Form_pg_class) GETSTRUCT(tup))->relkind == RELKIND_MATVIEW ||
                   ((Form_pg_class) GETSTRUCT(tup))->relkind == RELKIND_TOASTVALUE);
 
-       relopts = extractRelOptions(tup, pg_class_desc, NULL);
-       if (relopts == NULL)
-               return NULL;
+       relopts = extractRelOptions(tup, pg_class_desc,
+                                                               GetTableAmRoutineByAmOid(((Form_pg_class) GETSTRUCT(tup))->relam),
+                                                               NULL, &common);
+       if (relopts)
+               pfree(relopts);
 
        av = palloc(sizeof(AutoVacOpts));
-       memcpy(av, &(((StdRdOptions *) relopts)->autovacuum), sizeof(AutoVacOpts));
-       pfree(relopts);
+       memcpy(av, &(common.autovacuum), sizeof(AutoVacOpts));
 
        return av;
 }
index fa66b8017edea238d327011013594a6bded91b3a..c6bb3e45da4bff35e8945b0450301e8a5c1a8a29 100644 (file)
@@ -65,6 +65,7 @@
 #include "utils/acl.h"
 #include "utils/guc.h"
 #include "utils/lsyscache.h"
+#include "access/relation.h"
 
 /* Hook for plugins to get control in ProcessUtility() */
 ProcessUtility_hook_type ProcessUtility_hook = NULL;
@@ -1156,6 +1157,9 @@ ProcessUtilitySlow(ParseState *pstate,
                                                        CreateStmt *cstmt = (CreateStmt *) stmt;
                                                        Datum           toast_options;
                                                        static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
+                                                       const TableAmRoutine *tableam = NULL;
+                                                       Oid                     accessMethodId;
+                                                       Relation        rel;
 
                                                        /* Remember transformed RangeVar for LIKE */
                                                        table_rv = cstmt->relation;
@@ -1185,9 +1189,27 @@ ProcessUtilitySlow(ParseState *pstate,
                                                                                                                                validnsps,
                                                                                                                                true,
                                                                                                                                false);
-                                                       (void) heap_reloptions(RELKIND_TOASTVALUE,
-                                                                                                  toast_options,
-                                                                                                  true);
+
+                                                       /*
+                                                        * Get toast table AM to validate its options.
+                                                        * Only relevant if table itself has a table AM.
+                                                        * We don't need to place the lock given that
+                                                        * DefineRelation() already placed the
+                                                        * AccessExclusiveLock.
+                                                        */
+                                                       rel = relation_open(address.objectId, NoLock);
+                                                       accessMethodId = rel->rd_tableam ?
+                                                               table_relation_toast_am(rel) : InvalidOid;
+                                                       relation_close(rel, NoLock);
+
+                                                       if (OidIsValid(accessMethodId))
+                                                       {
+                                                               tableam = GetTableAmRoutineByAmOid(accessMethodId);
+                                                               (void) tableam_reloptions(tableam, RELKIND_TOASTVALUE,
+                                                                                                                 toast_options,
+                                                                                                                 NULL,
+                                                                                                                 true);
+                                                       }
 
                                                        NewRelationCreateToastTable(address.objectId,
                                                                                                                toast_options);
index 3fe74dabd00dbc34f4e2b39721486e5f25432de7..f6f60c21fab5fddf42f89f33caaed8cd41975497 100644 (file)
@@ -33,6 +33,7 @@
 #include "access/htup_details.h"
 #include "access/multixact.h"
 #include "access/parallel.h"
+#include "access/relation.h"
 #include "access/reloptions.h"
 #include "access/sysattr.h"
 #include "access/table.h"
@@ -464,9 +465,48 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple)
 {
        bytea      *options;
        amoptions_function amoptsfn;
+       CommonRdOptions *common = NULL;
+       const TableAmRoutine *tableam = NULL;
 
        relation->rd_options = NULL;
 
+       /*
+        * Fill the rd_common_options with default values for appropriate
+        * relkinds. The values might be later changed by extractRelOptions().
+        */
+       if (relation->rd_rel->relkind == RELKIND_RELATION ||
+               relation->rd_rel->relkind == RELKIND_TOASTVALUE ||
+               relation->rd_rel->relkind == RELKIND_MATVIEW)
+       {
+               common = MemoryContextAlloc(CacheMemoryContext,
+                                                                       sizeof(CommonRdOptions));
+               common->autovacuum.enabled = true;
+               common->autovacuum.vacuum_threshold = -1;
+               common->autovacuum.vacuum_ins_threshold = -2;
+               common->autovacuum.analyze_threshold = -1;
+               common->autovacuum.vacuum_cost_limit = -1;
+               common->autovacuum.freeze_min_age = -1;
+               common->autovacuum.freeze_max_age = -1;
+               common->autovacuum.freeze_table_age = -1;
+               common->autovacuum.multixact_freeze_min_age = -1;
+               common->autovacuum.multixact_freeze_max_age = -1;
+               common->autovacuum.multixact_freeze_table_age = -1;
+               common->autovacuum.log_min_duration = -1;
+               common->autovacuum.vacuum_cost_delay = -1;
+               common->autovacuum.vacuum_scale_factor = -1;
+               common->autovacuum.vacuum_ins_scale_factor = -1;
+               common->autovacuum.analyze_scale_factor = -1;
+               common->parallel_workers = -1;
+               common->user_catalog_table = false;
+               common->vacuum_index_cleanup = STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO;
+               common->vacuum_truncate = true;
+               relation->rd_common_options = common;
+       }
+       else
+       {
+               relation->rd_common_options = NULL;
+       }
+
        /*
         * Look up any AM-specific parse function; fall out if relkind should not
         * have options.
@@ -478,6 +518,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple)
                case RELKIND_VIEW:
                case RELKIND_MATVIEW:
                case RELKIND_PARTITIONED_TABLE:
+                       tableam = relation->rd_tableam;
                        amoptsfn = NULL;
                        break;
                case RELKIND_INDEX:
@@ -493,7 +534,8 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple)
         * we might not have any other for pg_class yet (consider executing this
         * code for pg_class itself)
         */
-       options = extractRelOptions(tuple, GetPgClassDescriptor(), amoptsfn);
+       options = extractRelOptions(tuple, GetPgClassDescriptor(),
+                                                               tableam, amoptsfn, common);
 
        /*
         * Copy parsed data into CacheMemoryContext.  To guard against the
@@ -2300,6 +2342,8 @@ RelationReloadIndexInfo(Relation relation)
        /* Reload reloptions in case they changed */
        if (relation->rd_options)
                pfree(relation->rd_options);
+       if (relation->rd_common_options)
+               pfree(relation->rd_common_options);
        RelationParseRelOptions(relation, pg_class_tuple);
        /* done with pg_class tuple */
        heap_freetuple(pg_class_tuple);
@@ -2487,6 +2531,8 @@ RelationDestroyRelation(Relation relation, bool remember_tupdesc)
                pfree(relation->rd_pubdesc);
        if (relation->rd_options)
                pfree(relation->rd_options);
+       if (relation->rd_common_options)
+               pfree(relation->rd_common_options);
        if (relation->rd_indextuple)
                pfree(relation->rd_indextuple);
        table_free_rd_amcache(relation);
@@ -4226,6 +4272,8 @@ RelationCacheInitializePhase3(void)
                        /* Update rd_options while we have the tuple */
                        if (relation->rd_options)
                                pfree(relation->rd_options);
+                       if (relation->rd_common_options)
+                               pfree(relation->rd_common_options);
                        RelationParseRelOptions(relation, htup);
 
                        /*
@@ -6162,7 +6210,7 @@ load_relcache_init_file(bool shared)
                        has_not_null |= attr->attnotnull;
                }
 
-               /* next read the access method specific field */
+               /* next read the access method specific fields */
                if (fread(&len, 1, sizeof(len), fp) != sizeof(len))
                        goto read_failed;
                if (len > 0)
@@ -6178,6 +6226,21 @@ load_relcache_init_file(bool shared)
                        rel->rd_options = NULL;
                }
 
+               if (fread(&len, 1, sizeof(len), fp) != sizeof(len))
+                       goto read_failed;
+               if (len > 0)
+               {
+                       if (len != sizeof(CommonRdOptions))
+                               goto read_failed;
+                       rel->rd_common_options = palloc(len);
+                       if (fread(rel->rd_common_options, 1, len, fp) != len)
+                               goto read_failed;
+               }
+               else
+               {
+                       rel->rd_common_options = NULL;
+               }
+
                /* mark not-null status */
                if (has_not_null)
                {
@@ -6579,11 +6642,15 @@ write_relcache_init_file(bool shared)
                                           ATTRIBUTE_FIXED_PART_SIZE, fp);
                }
 
-               /* next, do the access method specific field */
+               /* next, do the access method specific fields */
                write_item(rel->rd_options,
                                   (rel->rd_options ? VARSIZE(rel->rd_options) : 0),
                                   fp);
 
+               write_item(rel->rd_common_options,
+                                  (rel->rd_common_options ? sizeof(CommonRdOptions) : 0),
+                                  fp);
+
                /*
                 * If it's an index, there's more to do. Note we explicitly ignore
                 * partitioned indexes here.
index 81829b8270a79b9788a855d9242b45054559c601..342b9cdd6ed4128072c7412c556b17b9c88aa554 100644 (file)
@@ -21,6 +21,7 @@
 
 #include "access/amapi.h"
 #include "access/htup.h"
+#include "access/tableam.h"
 #include "access/tupdesc.h"
 #include "nodes/pg_list.h"
 #include "storage/lock.h"
@@ -224,7 +225,9 @@ extern Datum transformRelOptions(Datum oldOptions, List *defList,
                                                                 bool acceptOidsOff, bool isReset);
 extern List *untransformRelOptions(Datum options);
 extern bytea *extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
-                                                               amoptions_function amoptions);
+                                                               const TableAmRoutine *tableam,
+                                                               amoptions_function amoptions,
+                                                               CommonRdOptions *common);
 extern void *build_reloptions(Datum reloptions, bool validate,
                                                          relopt_kind kind,
                                                          Size relopt_struct_size,
@@ -233,9 +236,8 @@ extern void *build_reloptions(Datum reloptions, bool validate,
 extern void *build_local_reloptions(local_relopts *relopts, Datum options,
                                                                        bool validate);
 
-extern bytea *default_reloptions(Datum reloptions, bool validate,
-                                                                relopt_kind kind);
-extern bytea *heap_reloptions(char relkind, Datum reloptions, bool validate);
+extern bytea *heap_reloptions(char relkind, Datum reloptions,
+                                                         CommonRdOptions *common, bool validate);
 extern bytea *view_reloptions(Datum reloptions, bool validate);
 extern bytea *partitioned_table_reloptions(Datum reloptions, bool validate);
 extern bytea *index_reloptions(amoptions_function amoptions, Datum reloptions,
index be198fa3158b2569e51f022815b99ca9196fd6aa..ec827ac12bf175719ce9f417a26100561bbf6393 100644 (file)
@@ -746,6 +746,34 @@ typedef struct TableAmRoutine
                                                                                           int32 slicelength,
                                                                                           struct varlena *result);
 
+       /*
+        * This callback parses and validates the reloptions array for a table.
+        *
+        * This is called only when a non-null reloptions array exists for the
+        * table.  'reloptions' is a text array containing entries of the form
+        * "name=value".  The function should construct a bytea value, which will
+        * be copied into the rd_options field of the table's relcache entry. The
+        * data contents of the bytea value are open for the access method to
+        * define.
+        *
+        * The '*common' represents the common values, which the table access
+        * method exposes for autovacuum, query planner, and others.  The caller
+        * should fill them with default values.  The table access method may
+        * modify them on the base of options specified by a user.
+        *
+        * When 'validate' is true, the function should report a suitable error
+        * message if any of the options are unrecognized or have invalid values;
+        * when 'validate' is false, invalid entries should be silently ignored.
+        * ('validate' is false when loading options already stored in pg_catalog;
+        * an invalid entry could only be found if the access method has changed
+        * its rules for options, and in that case ignoring obsolete entries is
+        * appropriate.)
+        *
+        * It is OK to return NULL if default behavior is wanted.
+        */
+       bytea      *(*reloptions) (char relkind, Datum reloptions,
+                                                          CommonRdOptions *common, bool validate);
+
 
        /* ------------------------------------------------------------------------
         * Planner related functions.
@@ -1945,6 +1973,27 @@ table_relation_fetch_toast_slice(Relation toastrel, Oid valueid,
                                                                                                         result);
 }
 
+/*
+ * Parse table options without knowledge of particular table.
+ */
+static inline bytea *
+tableam_reloptions(const TableAmRoutine *tableam, char relkind,
+                                  Datum reloptions, CommonRdOptions *common, bool validate)
+{
+       return tableam->reloptions(relkind, reloptions, common, validate);
+}
+
+/*
+ * Parse options for given table.
+ */
+static inline bytea *
+table_reloptions(Relation rel, char relkind,
+                                Datum reloptions, CommonRdOptions *common, bool validate)
+{
+       return tableam_reloptions(rel->rd_tableam, relkind, reloptions,
+                                                         common, validate);
+}
+
 
 /* ----------------------------------------------------------------------------
  * Planner related functionality
@@ -2123,6 +2172,7 @@ extern void table_block_relation_estimate_size(Relation rel,
  */
 
 extern const TableAmRoutine *GetTableAmRoutine(Oid amhandler);
+extern const TableAmRoutine *GetTableAmRoutineByAmOid(Oid amoid);
 
 /* ----------------------------------------------------------------------------
  * Functions in heapam_handler.c
index f25f769af2b2a0a7352dc957692d4a07874d59ba..56e3e782d27e06fc7c923fa467ae0905e1590ca9 100644 (file)
@@ -48,6 +48,52 @@ typedef struct LockInfoData
 
 typedef LockInfoData *LockInfo;
 
+ /* autovacuum-related reloptions. */
+typedef struct AutoVacOpts
+{
+       bool            enabled;
+       int                     vacuum_threshold;
+       int                     vacuum_ins_threshold;
+       int                     analyze_threshold;
+       int                     vacuum_cost_limit;
+       int                     freeze_min_age;
+       int                     freeze_max_age;
+       int                     freeze_table_age;
+       int                     multixact_freeze_min_age;
+       int                     multixact_freeze_max_age;
+       int                     multixact_freeze_table_age;
+       int                     log_min_duration;
+       float8          vacuum_cost_delay;
+       float8          vacuum_scale_factor;
+       float8          vacuum_ins_scale_factor;
+       float8          analyze_scale_factor;
+} AutoVacOpts;
+
+/* StdRdOptions->vacuum_index_cleanup values */
+typedef enum StdRdOptIndexCleanup
+{
+       STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
+       STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
+       STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
+} StdRdOptIndexCleanup;
+
+/*
+ * CommonRdOptions
+ *             Contents of rd_common_options for tables.  It contains the options,
+ *             which the table access method exposes for autovacuum, query planner,
+ *             and others.  These options could be by decision of table AM directly
+ *             specified by a user or calculated in some way.
+ */
+typedef struct CommonRdOptions
+{
+       AutoVacOpts autovacuum;         /* autovacuum-related options */
+       bool            user_catalog_table; /* use as an additional catalog relation */
+       int                     parallel_workers;       /* max number of parallel workers */
+       StdRdOptIndexCleanup vacuum_index_cleanup;      /* controls index vacuuming */
+       bool            vacuum_truncate;        /* enables vacuum to truncate a relation */
+} CommonRdOptions;
+
+
 /*
  * Here are the contents of a relation cache entry.
  */
@@ -168,11 +214,19 @@ typedef struct RelationData
        PublicationDesc *rd_pubdesc;    /* publication descriptor, or NULL */
 
        /*
-        * rd_options is set whenever rd_rel is loaded into the relcache entry.
-        * Note that you can NOT look into rd_rel for this data.  NULL means "use
-        * defaults".
+        * rd_options and rd_common_options are set whenever rd_rel is loaded into
+        * the relcache entry. Note that you can NOT look into rd_rel for this
+        * data. NULLs means "use defaults".
+        */
+       CommonRdOptions *rd_common_options; /* the options, which table AM exposes
+                                                                                * for external usage */
+
+       /*
+        * am-specific part of pg_class.reloptions parsed by table am specific
+        * structure (e.g. struct HeapRdOptions) Contents are not to be accessed
+        * outside of table am
         */
-       bytea      *rd_options;         /* parsed pg_class.reloptions */
+       bytea      *rd_options;
 
        /*
         * Oid of the handler for this relation. For an index this is a function
@@ -297,88 +351,42 @@ typedef struct ForeignKeyCacheInfo
        Oid                     conpfeqop[INDEX_MAX_KEYS] pg_node_attr(array_size(nkeys));
 } ForeignKeyCacheInfo;
 
-
 /*
- * StdRdOptions
- *             Standard contents of rd_options for heaps.
- *
- * RelationGetFillFactor() and RelationGetTargetPageFreeSpace() can only
- * be applied to relations that use this format or a superset for
- * private options data.
+ * HeapRdOptions
+ *             Contents of rd_options specific for heap tables.
  */
- /* autovacuum-related reloptions. */
-typedef struct AutoVacOpts
-{
-       bool            enabled;
-       int                     vacuum_threshold;
-       int                     vacuum_ins_threshold;
-       int                     analyze_threshold;
-       int                     vacuum_cost_limit;
-       int                     freeze_min_age;
-       int                     freeze_max_age;
-       int                     freeze_table_age;
-       int                     multixact_freeze_min_age;
-       int                     multixact_freeze_max_age;
-       int                     multixact_freeze_table_age;
-       int                     log_min_duration;
-       float8          vacuum_cost_delay;
-       float8          vacuum_scale_factor;
-       float8          vacuum_ins_scale_factor;
-       float8          analyze_scale_factor;
-} AutoVacOpts;
-
-/* StdRdOptions->vacuum_index_cleanup values */
-typedef enum StdRdOptIndexCleanup
-{
-       STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO = 0,
-       STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF,
-       STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON,
-} StdRdOptIndexCleanup;
-
-typedef struct StdRdOptions
+typedef struct HeapRdOptions
 {
        int32           vl_len_;                /* varlena header (do not touch directly!) */
+       CommonRdOptions common;
        int                     fillfactor;             /* page fill factor in percent (0..100) */
        int                     toast_tuple_target; /* target for tuple toasting */
-       AutoVacOpts autovacuum;         /* autovacuum-related options */
-       bool            user_catalog_table; /* use as an additional catalog relation */
-       int                     parallel_workers;       /* max number of parallel workers */
-       StdRdOptIndexCleanup vacuum_index_cleanup;      /* controls index vacuuming */
-       bool            vacuum_truncate;        /* enables vacuum to truncate a relation */
-} StdRdOptions;
+} HeapRdOptions;
 
 #define HEAP_MIN_FILLFACTOR                    10
 #define HEAP_DEFAULT_FILLFACTOR                100
 
 /*
- * RelationGetToastTupleTarget
- *             Returns the relation's toast_tuple_target.  Note multiple eval of argument!
+ * HeapGetFillFactor
+ *             Returns the heap relation's fillfactor.  Note multiple eval of argument!
  */
-#define RelationGetToastTupleTarget(relation, defaulttarg) \
+#define HeapGetFillFactor(relation, defaultff) \
        ((relation)->rd_options ? \
-        ((StdRdOptions *) (relation)->rd_options)->toast_tuple_target : (defaulttarg))
+        ((HeapRdOptions *) (relation)->rd_options)->fillfactor : (defaultff))
 
 /*
- * RelationGetFillFactor
- *             Returns the relation's fillfactor.  Note multiple eval of argument!
- */
-#define RelationGetFillFactor(relation, defaultff) \
-       ((relation)->rd_options ? \
-        ((StdRdOptions *) (relation)->rd_options)->fillfactor : (defaultff))
-
-/*
- * RelationGetTargetPageUsage
+ * HeapGetTargetPageUsage
  *             Returns the relation's desired space usage per page in bytes.
  */
-#define RelationGetTargetPageUsage(relation, defaultff) \
-       (BLCKSZ * RelationGetFillFactor(relation, defaultff) / 100)
+#define HeapGetTargetPageUsage(relation, defaultff) \
+       (BLCKSZ * HeapGetFillFactor(relation, defaultff) / 100)
 
 /*
- * RelationGetTargetPageFreeSpace
+ * HeapGetTargetPageFreeSpace
  *             Returns the relation's desired freespace per page in bytes.
  */
-#define RelationGetTargetPageFreeSpace(relation, defaultff) \
-       (BLCKSZ * (100 - RelationGetFillFactor(relation, defaultff)) / 100)
+#define HeapGetTargetPageFreeSpace(relation, defaultff) \
+       (BLCKSZ * (100 - HeapGetFillFactor(relation, defaultff)) / 100)
 
 /*
  * RelationIsUsedAsCatalogTable
@@ -386,10 +394,10 @@ typedef struct StdRdOptions
  *             from the pov of logical decoding.  Note multiple eval of argument!
  */
 #define RelationIsUsedAsCatalogTable(relation) \
      ((relation)->rd_options && \
-        ((relation)->rd_rel->relkind == RELKIND_RELATION || \
((relation)->rd_common_options && \
+  ((relation)->rd_rel->relkind == RELKIND_RELATION || \
          (relation)->rd_rel->relkind == RELKIND_MATVIEW) ? \
-        ((StdRdOptions *) (relation)->rd_options)->user_catalog_table : false)
+        (relation)->rd_common_options->user_catalog_table : false)
 
 /*
  * RelationGetParallelWorkers
@@ -397,8 +405,8 @@ typedef struct StdRdOptions
  *             Note multiple eval of argument!
  */
 #define RelationGetParallelWorkers(relation, defaultpw) \
      ((relation)->rd_options ? \
-        ((StdRdOptions *) (relation)->rd_options)->parallel_workers : (defaultpw))
((relation)->rd_common_options ? \
+  (relation)->rd_common_options->parallel_workers : (defaultpw))
 
 /* ViewOptions->check_option values */
 typedef enum ViewOptCheckOption
index 256799f520a1cdf60e4019077f9ff1198451c19a..8f66152539981248a4410986cac76ab750c9d208 100644 (file)
@@ -36,6 +36,7 @@ SUBDIRS = \
                  test_rls_hooks \
                  test_shm_mq \
                  test_slru \
+                 test_tam_options \
                  test_tidstore \
                  unsafe_tests \
                  worker_spi \
index d8fe059d23610d678f17580ee29d0da0a458fc8a..235e342dfaab66439f90c42789bee519d1f98f43 100644 (file)
@@ -35,6 +35,7 @@ subdir('test_resowner')
 subdir('test_rls_hooks')
 subdir('test_shm_mq')
 subdir('test_slru')
+subdir('test_tam_options')
 subdir('test_tidstore')
 subdir('unsafe_tests')
 subdir('worker_spi')
diff --git a/src/test/modules/test_tam_options/.gitignore b/src/test/modules/test_tam_options/.gitignore
new file mode 100644 (file)
index 0000000..5dcb3ff
--- /dev/null
@@ -0,0 +1,4 @@
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
diff --git a/src/test/modules/test_tam_options/Makefile b/src/test/modules/test_tam_options/Makefile
new file mode 100644 (file)
index 0000000..bd6d459
--- /dev/null
@@ -0,0 +1,23 @@
+# src/test/modules/test_tam_options/Makefile
+
+MODULE_big = test_tam_options
+OBJS = \
+       $(WIN32RES) \
+       test_tam_options.o
+PGFILEDESC = "test_tam_options - test code for table access method reloptions"
+
+EXTENSION = test_tam_options
+DATA = test_tam_options--1.0.sql
+
+REGRESS = test_tam_options
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_tam_options
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/test_tam_options/expected/test_tam_options.out b/src/test/modules/test_tam_options/expected/test_tam_options.out
new file mode 100644 (file)
index 0000000..c921afc
--- /dev/null
@@ -0,0 +1,36 @@
+CREATE EXTENSION test_tam_options;
+-- encourage use of parallel plans
+SET parallel_setup_cost = 0;
+SET parallel_tuple_cost = 0;
+SET min_parallel_table_scan_size = 0;
+SET max_parallel_workers_per_gather = 4;
+CREATE TABLE test (i int) USING heap_alter_options;
+INSERT INTO test SELECT i FROM generate_series(1, 10000) i;
+VACUUM ANALYZE test;
+EXPLAIN (costs off)
+SELECT * FROM test;
+           QUERY PLAN            
+---------------------------------
+ Gather
+   Workers Planned: 4
+   ->  Parallel Seq Scan on test
+(3 rows)
+
+ALTER TABLE test SET (enable_parallel = OFF);
+EXPLAIN (costs off)
+SELECT * FROM test;
+    QUERY PLAN    
+------------------
+ Seq Scan on test
+(1 row)
+
+ALTER TABLE test SET (enable_parallel = ON);
+EXPLAIN (costs off)
+SELECT * FROM test;
+           QUERY PLAN            
+---------------------------------
+ Gather
+   Workers Planned: 4
+   ->  Parallel Seq Scan on test
+(3 rows)
+
diff --git a/src/test/modules/test_tam_options/meson.build b/src/test/modules/test_tam_options/meson.build
new file mode 100644 (file)
index 0000000..d41a32a
--- /dev/null
@@ -0,0 +1,33 @@
+# Copyright (c) 2024, PostgreSQL Global Development Group
+
+test_tam_options_sources = files(
+  'test_tam_options.c',
+)
+
+if host_system == 'windows'
+  test_tam_options_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_tam_options',
+    '--FILEDESC', 'test_tam_options -  test code for table access method reloptions',])
+endif
+
+test_tam_options = shared_module('test_tam_options',
+  test_tam_options_sources,
+  kwargs: pg_test_mod_args,
+)
+test_install_libs += test_tam_options
+
+test_install_data += files(
+  'test_tam_options.control',
+  'test_tam_options--1.0.sql',
+)
+
+tests += {
+  'name': 'test_tam_options',
+  'sd': meson.current_source_dir(),
+  'bd': meson.current_build_dir(),
+  'regress': {
+    'sql': [
+      'test_tam_options',
+    ],
+  },
+}
diff --git a/src/test/modules/test_tam_options/sql/test_tam_options.sql b/src/test/modules/test_tam_options/sql/test_tam_options.sql
new file mode 100644 (file)
index 0000000..4f97504
--- /dev/null
@@ -0,0 +1,25 @@
+CREATE EXTENSION test_tam_options;
+
+-- encourage use of parallel plans
+SET parallel_setup_cost = 0;
+SET parallel_tuple_cost = 0;
+SET min_parallel_table_scan_size = 0;
+SET max_parallel_workers_per_gather = 4;
+
+CREATE TABLE test (i int) USING heap_alter_options;
+
+INSERT INTO test SELECT i FROM generate_series(1, 10000) i;
+VACUUM ANALYZE test;
+
+EXPLAIN (costs off)
+SELECT * FROM test;
+
+ALTER TABLE test SET (enable_parallel = OFF);
+
+EXPLAIN (costs off)
+SELECT * FROM test;
+
+ALTER TABLE test SET (enable_parallel = ON);
+
+EXPLAIN (costs off)
+SELECT * FROM test;
diff --git a/src/test/modules/test_tam_options/test_tam_options--1.0.sql b/src/test/modules/test_tam_options/test_tam_options--1.0.sql
new file mode 100644 (file)
index 0000000..07569f7
--- /dev/null
@@ -0,0 +1,12 @@
+/* src/test/modules/test_tam_options/test_tam_options--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_tam_options" to load this file. \quit
+
+CREATE FUNCTION heap_alter_options_tam_handler(internal)
+RETURNS table_am_handler
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT;
+
+CREATE ACCESS METHOD heap_alter_options TYPE TABLE
+HANDLER heap_alter_options_tam_handler;
diff --git a/src/test/modules/test_tam_options/test_tam_options.c b/src/test/modules/test_tam_options/test_tam_options.c
new file mode 100644 (file)
index 0000000..13e227e
--- /dev/null
@@ -0,0 +1,66 @@
+/*--------------------------------------------------------------------------
+ *
+ * test_tam_options.c
+ *             Test code for table access method reloptions.
+ *
+ * Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *             src/test/modules/test_tam_options/test_tam_options.c
+ *
+ * -------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/reloptions.h"
+#include "access/tableam.h"
+
+PG_MODULE_MAGIC;
+
+PG_FUNCTION_INFO_V1(heap_alter_options_tam_handler);
+
+/* An alternative relation options for heap */
+typedef struct
+{
+       int32           vl_len_;                /* varlena header (do not touch directly!) */
+       bool            enable_parallel;        /* enable parallel scans? */
+} HeapAlterRdOptions;
+
+static bytea *
+heap_alter_reloptions(char relkind, Datum reloptions,
+                                         CommonRdOptions *common, bool validate)
+{
+       local_relopts relopts;
+       HeapAlterRdOptions *result;
+
+       Assert(relkind == RELKIND_RELATION ||
+                  relkind == RELKIND_TOASTVALUE ||
+                  relkind == RELKIND_MATVIEW);
+
+       init_local_reloptions(&relopts, sizeof(HeapAlterRdOptions));
+       add_local_bool_reloption(&relopts, "enable_parallel",
+                                                        "enable parallel scan", true,
+                                                        offsetof(HeapAlterRdOptions, enable_parallel));
+
+       result = (HeapAlterRdOptions *) build_local_reloptions(&relopts,
+                                                                                                                  reloptions,
+                                                                                                                  validate);
+
+       if (result != NULL && common != NULL)
+       {
+               common->parallel_workers = result->enable_parallel ? -1 : 0;
+       }
+
+       return (bytea *) result;
+}
+
+Datum
+heap_alter_options_tam_handler(PG_FUNCTION_ARGS)
+{
+       static TableAmRoutine tam_routine;
+
+       tam_routine = *GetHeapamTableAmRoutine();
+       tam_routine.reloptions = heap_alter_reloptions;
+
+       PG_RETURN_POINTER(&tam_routine);
+}
diff --git a/src/test/modules/test_tam_options/test_tam_options.control b/src/test/modules/test_tam_options/test_tam_options.control
new file mode 100644 (file)
index 0000000..dd6682e
--- /dev/null
@@ -0,0 +1,4 @@
+comment = 'Test code for table access method reloptions'
+default_version = '1.0'
+module_pathname = '$libdir/test_tam_options'
+relocatable = true
index 5a71eca96e0e0d180610f1d38a3dcf309edbc240..cb78f11119c86b10106b0203862feb32b9dccd48 100644 (file)
@@ -442,6 +442,7 @@ CommentStmt
 CommitTimestampEntry
 CommitTimestampShared
 CommonEntry
+CommonRdOptions
 CommonTableExpr
 CompareScalarsContext
 CompiledExprState
@@ -1129,8 +1130,10 @@ HbaLine
 HeadlineJsonState
 HeadlineParsedText
 HeadlineWordEntry
+HeapAlterRdOptions
 HeapCheckContext
 HeapPageFreeze
+HeapRdOptions
 HeapScanDesc
 HeapTuple
 HeapTupleData
@@ -2723,7 +2726,6 @@ StatsElem
 StatsExtInfo
 StdAnalyzeData
 StdRdOptIndexCleanup
-StdRdOptions
 Step
 StopList
 StrategyNumber