summaryrefslogtreecommitdiffstats
path: root/src/backend/access/common/reloptions.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/access/common/reloptions.c')
-rw-r--r--src/backend/access/common/reloptions.c2139
1 files changed, 2139 insertions, 0 deletions
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
new file mode 100644
index 0000000..469de9b
--- /dev/null
+++ b/src/backend/access/common/reloptions.c
@@ -0,0 +1,2139 @@
+/*-------------------------------------------------------------------------
+ *
+ * reloptions.c
+ * Core support for relation options (pg_class.reloptions)
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/access/common/reloptions.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <float.h>
+
+#include "access/gist_private.h"
+#include "access/hash.h"
+#include "access/heaptoast.h"
+#include "access/htup_details.h"
+#include "access/nbtree.h"
+#include "access/reloptions.h"
+#include "access/spgist_private.h"
+#include "catalog/pg_type.h"
+#include "commands/defrem.h"
+#include "commands/tablespace.h"
+#include "commands/view.h"
+#include "nodes/makefuncs.h"
+#include "postmaster/postmaster.h"
+#include "utils/array.h"
+#include "utils/attoptcache.h"
+#include "utils/builtins.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+
+/*
+ * Contents of pg_class.reloptions
+ *
+ * To add an option:
+ *
+ * (i) decide on a type (integer, real, bool, string), name, default 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)
+ * (iv) add it to the appropriate handling routine (perhaps
+ * default_reloptions)
+ * (v) make sure the lock level is set correctly for that operation
+ * (vi) don't forget to document the option
+ *
+ * The default choice for any new option should be AccessExclusiveLock.
+ * In some cases the lock level can be reduced from there, but the lock
+ * level chosen should always conflict with itself to ensure that multiple
+ * changes aren't lost when we attempt concurrent changes.
+ * The choice of lock level depends completely upon how that parameter
+ * is used within the server, not upon how and when you'd like to change it.
+ * Safety first. Existing choices are documented here, and elsewhere in
+ * backend code where the parameters are used.
+ *
+ * In general, anything that affects the results obtained from a SELECT must be
+ * protected by AccessExclusiveLock.
+ *
+ * Autovacuum related parameters can be set at ShareUpdateExclusiveLock
+ * since they are only used by the AV procs and don't change anything
+ * currently executing.
+ *
+ * Fillfactor can be set because it applies only to subsequent changes made to
+ * data blocks, as documented in hio.c
+ *
+ * n_distinct options can be set at ShareUpdateExclusiveLock because they
+ * are only used during ANALYZE, which uses a ShareUpdateExclusiveLock,
+ * so the ANALYZE will not be affected by in-flight changes. Changing those
+ * values has no effect until the next ANALYZE, so no need for stronger lock.
+ *
+ * Planner-related parameters can be set with ShareUpdateExclusiveLock because
+ * they only affect planning and not the correctness of the execution. Plans
+ * cannot be changed in mid-flight, so changes here could not easily result in
+ * new improved plans in any case. So we allow existing queries to continue
+ * and existing plans to survive, a small price to pay for allowing better
+ * plans to be introduced concurrently without interfering with users.
+ *
+ * Setting parallel_workers is safe, since it acts the same as
+ * max_parallel_workers_per_gather which is a USERSET parameter that doesn't
+ * affect existing plans or queries.
+ *
+ * vacuum_truncate can be set at ShareUpdateExclusiveLock because it
+ * is only used during VACUUM, which uses a ShareUpdateExclusiveLock,
+ * so the VACUUM will not be affected by in-flight changes. Changing its
+ * value has no effect until the next VACUUM, so no need for stronger lock.
+ */
+
+static relopt_bool boolRelOpts[] =
+{
+ {
+ {
+ "autosummarize",
+ "Enables automatic summarization on this BRIN index",
+ RELOPT_KIND_BRIN,
+ AccessExclusiveLock
+ },
+ false
+ },
+ {
+ {
+ "autovacuum_enabled",
+ "Enables autovacuum in this relation",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ true
+ },
+ {
+ {
+ "user_catalog_table",
+ "Declare a table as an additional catalog table, e.g. for the purpose of logical replication",
+ RELOPT_KIND_HEAP,
+ AccessExclusiveLock
+ },
+ false
+ },
+ {
+ {
+ "fastupdate",
+ "Enables \"fast update\" feature for this GIN index",
+ RELOPT_KIND_GIN,
+ AccessExclusiveLock
+ },
+ true
+ },
+ {
+ {
+ "security_barrier",
+ "View acts as a row security barrier",
+ RELOPT_KIND_VIEW,
+ AccessExclusiveLock
+ },
+ false
+ },
+ {
+ {
+ "security_invoker",
+ "Privileges on underlying relations are checked as the invoking user, not the view owner",
+ RELOPT_KIND_VIEW,
+ AccessExclusiveLock
+ },
+ false
+ },
+ {
+ {
+ "vacuum_truncate",
+ "Enables vacuum to truncate empty pages at the end of this table",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ true
+ },
+ {
+ {
+ "deduplicate_items",
+ "Enables \"deduplicate items\" feature for this btree index",
+ RELOPT_KIND_BTREE,
+ ShareUpdateExclusiveLock /* since it applies only to later
+ * inserts */
+ },
+ true
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
+static relopt_int intRelOpts[] =
+{
+ {
+ {
+ "fillfactor",
+ "Packs table pages only to this percentage",
+ RELOPT_KIND_HEAP,
+ ShareUpdateExclusiveLock /* since it applies only to later
+ * inserts */
+ },
+ HEAP_DEFAULT_FILLFACTOR, HEAP_MIN_FILLFACTOR, 100
+ },
+ {
+ {
+ "fillfactor",
+ "Packs btree index pages only to this percentage",
+ RELOPT_KIND_BTREE,
+ ShareUpdateExclusiveLock /* since it applies only to later
+ * inserts */
+ },
+ BTREE_DEFAULT_FILLFACTOR, BTREE_MIN_FILLFACTOR, 100
+ },
+ {
+ {
+ "fillfactor",
+ "Packs hash index pages only to this percentage",
+ RELOPT_KIND_HASH,
+ ShareUpdateExclusiveLock /* since it applies only to later
+ * inserts */
+ },
+ HASH_DEFAULT_FILLFACTOR, HASH_MIN_FILLFACTOR, 100
+ },
+ {
+ {
+ "fillfactor",
+ "Packs gist index pages only to this percentage",
+ RELOPT_KIND_GIST,
+ ShareUpdateExclusiveLock /* since it applies only to later
+ * inserts */
+ },
+ GIST_DEFAULT_FILLFACTOR, GIST_MIN_FILLFACTOR, 100
+ },
+ {
+ {
+ "fillfactor",
+ "Packs spgist index pages only to this percentage",
+ RELOPT_KIND_SPGIST,
+ ShareUpdateExclusiveLock /* since it applies only to later
+ * inserts */
+ },
+ SPGIST_DEFAULT_FILLFACTOR, SPGIST_MIN_FILLFACTOR, 100
+ },
+ {
+ {
+ "autovacuum_vacuum_threshold",
+ "Minimum number of tuple updates or deletes prior to vacuum",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ -1, 0, INT_MAX
+ },
+ {
+ {
+ "autovacuum_vacuum_insert_threshold",
+ "Minimum number of tuple inserts prior to vacuum, or -1 to disable insert vacuums",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ -2, -1, INT_MAX
+ },
+ {
+ {
+ "autovacuum_analyze_threshold",
+ "Minimum number of tuple inserts, updates or deletes prior to analyze",
+ RELOPT_KIND_HEAP,
+ ShareUpdateExclusiveLock
+ },
+ -1, 0, INT_MAX
+ },
+ {
+ {
+ "autovacuum_vacuum_cost_limit",
+ "Vacuum cost amount available before napping, for autovacuum",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ -1, 1, 10000
+ },
+ {
+ {
+ "autovacuum_freeze_min_age",
+ "Minimum age at which VACUUM should freeze a table row, for autovacuum",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ -1, 0, 1000000000
+ },
+ {
+ {
+ "autovacuum_multixact_freeze_min_age",
+ "Minimum multixact age at which VACUUM should freeze a row multixact's, for autovacuum",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ -1, 0, 1000000000
+ },
+ {
+ {
+ "autovacuum_freeze_max_age",
+ "Age at which to autovacuum a table to prevent transaction ID wraparound",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ -1, 100000, 2000000000
+ },
+ {
+ {
+ "autovacuum_multixact_freeze_max_age",
+ "Multixact age at which to autovacuum a table to prevent multixact wraparound",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ -1, 10000, 2000000000
+ },
+ {
+ {
+ "autovacuum_freeze_table_age",
+ "Age at which VACUUM should perform a full table sweep to freeze row versions",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ }, -1, 0, 2000000000
+ },
+ {
+ {
+ "autovacuum_multixact_freeze_table_age",
+ "Age of multixact at which VACUUM should perform a full table sweep to freeze row versions",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ }, -1, 0, 2000000000
+ },
+ {
+ {
+ "log_autovacuum_min_duration",
+ "Sets the minimum execution time above which autovacuum actions will be logged",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ -1, -1, INT_MAX
+ },
+ {
+ {
+ "toast_tuple_target",
+ "Sets the target tuple length at which external columns will be toasted",
+ RELOPT_KIND_HEAP,
+ ShareUpdateExclusiveLock
+ },
+ TOAST_TUPLE_TARGET, 128, TOAST_TUPLE_TARGET_MAIN
+ },
+ {
+ {
+ "pages_per_range",
+ "Number of pages that each page range covers in a BRIN index",
+ RELOPT_KIND_BRIN,
+ AccessExclusiveLock
+ }, 128, 1, 131072
+ },
+ {
+ {
+ "gin_pending_list_limit",
+ "Maximum size of the pending list for this GIN index, in kilobytes.",
+ RELOPT_KIND_GIN,
+ AccessExclusiveLock
+ },
+ -1, 64, MAX_KILOBYTES
+ },
+ {
+ {
+ "effective_io_concurrency",
+ "Number of simultaneous requests that can be handled efficiently by the disk subsystem.",
+ RELOPT_KIND_TABLESPACE,
+ ShareUpdateExclusiveLock
+ },
+#ifdef USE_PREFETCH
+ -1, 0, MAX_IO_CONCURRENCY
+#else
+ 0, 0, 0
+#endif
+ },
+ {
+ {
+ "maintenance_io_concurrency",
+ "Number of simultaneous requests that can be handled efficiently by the disk subsystem for maintenance work.",
+ RELOPT_KIND_TABLESPACE,
+ ShareUpdateExclusiveLock
+ },
+#ifdef USE_PREFETCH
+ -1, 0, MAX_IO_CONCURRENCY
+#else
+ 0, 0, 0
+#endif
+ },
+ {
+ {
+ "parallel_workers",
+ "Number of parallel processes that can be used per executor node for this relation.",
+ RELOPT_KIND_HEAP,
+ ShareUpdateExclusiveLock
+ },
+ -1, 0, 1024
+ },
+
+ /* list terminator */
+ {{NULL}}
+};
+
+static relopt_real realRelOpts[] =
+{
+ {
+ {
+ "autovacuum_vacuum_cost_delay",
+ "Vacuum cost delay in milliseconds, for autovacuum",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ -1, 0.0, 100.0
+ },
+ {
+ {
+ "autovacuum_vacuum_scale_factor",
+ "Number of tuple updates or deletes prior to vacuum as a fraction of reltuples",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ -1, 0.0, 100.0
+ },
+ {
+ {
+ "autovacuum_vacuum_insert_scale_factor",
+ "Number of tuple inserts prior to vacuum as a fraction of reltuples",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ -1, 0.0, 100.0
+ },
+ {
+ {
+ "autovacuum_analyze_scale_factor",
+ "Number of tuple inserts, updates or deletes prior to analyze as a fraction of reltuples",
+ RELOPT_KIND_HEAP,
+ ShareUpdateExclusiveLock
+ },
+ -1, 0.0, 100.0
+ },
+ {
+ {
+ "seq_page_cost",
+ "Sets the planner's estimate of the cost of a sequentially fetched disk page.",
+ RELOPT_KIND_TABLESPACE,
+ ShareUpdateExclusiveLock
+ },
+ -1, 0.0, DBL_MAX
+ },
+ {
+ {
+ "random_page_cost",
+ "Sets the planner's estimate of the cost of a nonsequentially fetched disk page.",
+ RELOPT_KIND_TABLESPACE,
+ ShareUpdateExclusiveLock
+ },
+ -1, 0.0, DBL_MAX
+ },
+ {
+ {
+ "n_distinct",
+ "Sets the planner's estimate of the number of distinct values appearing in a column (excluding child relations).",
+ RELOPT_KIND_ATTRIBUTE,
+ ShareUpdateExclusiveLock
+ },
+ 0, -1.0, DBL_MAX
+ },
+ {
+ {
+ "n_distinct_inherited",
+ "Sets the planner's estimate of the number of distinct values appearing in a column (including child relations).",
+ RELOPT_KIND_ATTRIBUTE,
+ ShareUpdateExclusiveLock
+ },
+ 0, -1.0, DBL_MAX
+ },
+ {
+ {
+ "vacuum_cleanup_index_scale_factor",
+ "Deprecated B-Tree parameter.",
+ RELOPT_KIND_BTREE,
+ ShareUpdateExclusiveLock
+ },
+ -1, 0.0, 1e10
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
+/* values from StdRdOptIndexCleanup */
+static relopt_enum_elt_def StdRdOptIndexCleanupValues[] =
+{
+ {"auto", STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO},
+ {"on", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
+ {"off", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
+ {"true", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
+ {"false", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
+ {"yes", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
+ {"no", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
+ {"1", STDRD_OPTION_VACUUM_INDEX_CLEANUP_ON},
+ {"0", STDRD_OPTION_VACUUM_INDEX_CLEANUP_OFF},
+ {(const char *) NULL} /* list terminator */
+};
+
+/* values from GistOptBufferingMode */
+static relopt_enum_elt_def gistBufferingOptValues[] =
+{
+ {"auto", GIST_OPTION_BUFFERING_AUTO},
+ {"on", GIST_OPTION_BUFFERING_ON},
+ {"off", GIST_OPTION_BUFFERING_OFF},
+ {(const char *) NULL} /* list terminator */
+};
+
+/* values from ViewOptCheckOption */
+static relopt_enum_elt_def viewCheckOptValues[] =
+{
+ /* no value for NOT_SET */
+ {"local", VIEW_OPTION_CHECK_OPTION_LOCAL},
+ {"cascaded", VIEW_OPTION_CHECK_OPTION_CASCADED},
+ {(const char *) NULL} /* list terminator */
+};
+
+static relopt_enum enumRelOpts[] =
+{
+ {
+ {
+ "vacuum_index_cleanup",
+ "Controls index vacuuming and index cleanup",
+ RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
+ ShareUpdateExclusiveLock
+ },
+ StdRdOptIndexCleanupValues,
+ STDRD_OPTION_VACUUM_INDEX_CLEANUP_AUTO,
+ gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
+ },
+ {
+ {
+ "buffering",
+ "Enables buffering build for this GiST index",
+ RELOPT_KIND_GIST,
+ AccessExclusiveLock
+ },
+ gistBufferingOptValues,
+ GIST_OPTION_BUFFERING_AUTO,
+ gettext_noop("Valid values are \"on\", \"off\", and \"auto\".")
+ },
+ {
+ {
+ "check_option",
+ "View has WITH CHECK OPTION defined (local or cascaded).",
+ RELOPT_KIND_VIEW,
+ AccessExclusiveLock
+ },
+ viewCheckOptValues,
+ VIEW_OPTION_CHECK_OPTION_NOT_SET,
+ gettext_noop("Valid values are \"local\" and \"cascaded\".")
+ },
+ /* list terminator */
+ {{NULL}}
+};
+
+static relopt_string stringRelOpts[] =
+{
+ /* list terminator */
+ {{NULL}}
+};
+
+static relopt_gen **relOpts = NULL;
+static bits32 last_assigned_kind = RELOPT_KIND_LAST_DEFAULT;
+
+static int num_custom_options = 0;
+static relopt_gen **custom_options = NULL;
+static bool need_initialization = true;
+
+static void initialize_reloptions(void);
+static void parse_one_reloption(relopt_value *option, char *text_str,
+ int text_len, bool validate);
+
+/*
+ * Get the length of a string reloption (either default or the user-defined
+ * value). This is used for allocation purposes when building a set of
+ * relation options.
+ */
+#define GET_STRING_RELOPTION_LEN(option) \
+ ((option).isset ? strlen((option).values.string_val) : \
+ ((relopt_string *) (option).gen)->default_len)
+
+/*
+ * initialize_reloptions
+ * initialization routine, must be called before parsing
+ *
+ * Initialize the relOpts array and fill each variable's type and name length.
+ */
+static void
+initialize_reloptions(void)
+{
+ int i;
+ int j;
+
+ j = 0;
+ for (i = 0; boolRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(boolRelOpts[i].gen.lockmode,
+ boolRelOpts[i].gen.lockmode));
+ j++;
+ }
+ for (i = 0; intRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode,
+ intRelOpts[i].gen.lockmode));
+ j++;
+ }
+ for (i = 0; realRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(realRelOpts[i].gen.lockmode,
+ realRelOpts[i].gen.lockmode));
+ j++;
+ }
+ for (i = 0; enumRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(enumRelOpts[i].gen.lockmode,
+ enumRelOpts[i].gen.lockmode));
+ j++;
+ }
+ for (i = 0; stringRelOpts[i].gen.name; i++)
+ {
+ Assert(DoLockModesConflict(stringRelOpts[i].gen.lockmode,
+ stringRelOpts[i].gen.lockmode));
+ j++;
+ }
+ j += num_custom_options;
+
+ if (relOpts)
+ pfree(relOpts);
+ relOpts = MemoryContextAlloc(TopMemoryContext,
+ (j + 1) * sizeof(relopt_gen *));
+
+ j = 0;
+ for (i = 0; boolRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &boolRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_BOOL;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
+ for (i = 0; intRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &intRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_INT;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
+ for (i = 0; realRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &realRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_REAL;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
+ for (i = 0; enumRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &enumRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_ENUM;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
+ for (i = 0; stringRelOpts[i].gen.name; i++)
+ {
+ relOpts[j] = &stringRelOpts[i].gen;
+ relOpts[j]->type = RELOPT_TYPE_STRING;
+ relOpts[j]->namelen = strlen(relOpts[j]->name);
+ j++;
+ }
+
+ for (i = 0; i < num_custom_options; i++)
+ {
+ relOpts[j] = custom_options[i];
+ j++;
+ }
+
+ /* add a list terminator */
+ relOpts[j] = NULL;
+
+ /* flag the work is complete */
+ need_initialization = false;
+}
+
+/*
+ * add_reloption_kind
+ * Create a new relopt_kind value, to be used in custom reloptions by
+ * user-defined AMs.
+ */
+relopt_kind
+add_reloption_kind(void)
+{
+ /* don't hand out the last bit so that the enum's behavior is portable */
+ if (last_assigned_kind >= RELOPT_KIND_MAX)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("user-defined relation parameter types limit exceeded")));
+ last_assigned_kind <<= 1;
+ return (relopt_kind) last_assigned_kind;
+}
+
+/*
+ * add_reloption
+ * Add an already-created custom reloption to the list, and recompute the
+ * main parser table.
+ */
+static void
+add_reloption(relopt_gen *newoption)
+{
+ static int max_custom_options = 0;
+
+ if (num_custom_options >= max_custom_options)
+ {
+ MemoryContext oldcxt;
+
+ oldcxt = MemoryContextSwitchTo(TopMemoryContext);
+
+ if (max_custom_options == 0)
+ {
+ max_custom_options = 8;
+ custom_options = palloc(max_custom_options * sizeof(relopt_gen *));
+ }
+ else
+ {
+ max_custom_options *= 2;
+ custom_options = repalloc(custom_options,
+ max_custom_options * sizeof(relopt_gen *));
+ }
+ MemoryContextSwitchTo(oldcxt);
+ }
+ custom_options[num_custom_options++] = newoption;
+
+ need_initialization = true;
+}
+
+/*
+ * init_local_reloptions
+ * Initialize local reloptions that will parsed into bytea structure of
+ * 'relopt_struct_size'.
+ */
+void
+init_local_reloptions(local_relopts *relopts, Size relopt_struct_size)
+{
+ relopts->options = NIL;
+ relopts->validators = NIL;
+ relopts->relopt_struct_size = relopt_struct_size;
+}
+
+/*
+ * register_reloptions_validator
+ * Register custom validation callback that will be called at the end of
+ * build_local_reloptions().
+ */
+void
+register_reloptions_validator(local_relopts *relopts, relopts_validator validator)
+{
+ relopts->validators = lappend(relopts->validators, validator);
+}
+
+/*
+ * add_local_reloption
+ * Add an already-created custom reloption to the local list.
+ */
+static void
+add_local_reloption(local_relopts *relopts, relopt_gen *newoption, int offset)
+{
+ local_relopt *opt = palloc(sizeof(*opt));
+
+ Assert(offset < relopts->relopt_struct_size);
+
+ opt->option = newoption;
+ opt->offset = offset;
+
+ relopts->options = lappend(relopts->options, opt);
+}
+
+/*
+ * allocate_reloption
+ * Allocate a new reloption and initialize the type-agnostic fields
+ * (for types other than string)
+ */
+static relopt_gen *
+allocate_reloption(bits32 kinds, int type, const char *name, const char *desc,
+ LOCKMODE lockmode)
+{
+ MemoryContext oldcxt;
+ size_t size;
+ relopt_gen *newoption;
+
+ if (kinds != RELOPT_KIND_LOCAL)
+ oldcxt = MemoryContextSwitchTo(TopMemoryContext);
+ else
+ oldcxt = NULL;
+
+ switch (type)
+ {
+ case RELOPT_TYPE_BOOL:
+ size = sizeof(relopt_bool);
+ break;
+ case RELOPT_TYPE_INT:
+ size = sizeof(relopt_int);
+ break;
+ case RELOPT_TYPE_REAL:
+ size = sizeof(relopt_real);
+ break;
+ case RELOPT_TYPE_ENUM:
+ size = sizeof(relopt_enum);
+ break;
+ case RELOPT_TYPE_STRING:
+ size = sizeof(relopt_string);
+ break;
+ default:
+ elog(ERROR, "unsupported reloption type %d", type);
+ return NULL; /* keep compiler quiet */
+ }
+
+ newoption = palloc(size);
+
+ newoption->name = pstrdup(name);
+ if (desc)
+ newoption->desc = pstrdup(desc);
+ else
+ newoption->desc = NULL;
+ newoption->kinds = kinds;
+ newoption->namelen = strlen(name);
+ newoption->type = type;
+ newoption->lockmode = lockmode;
+
+ if (oldcxt != NULL)
+ MemoryContextSwitchTo(oldcxt);
+
+ return newoption;
+}
+
+/*
+ * init_bool_reloption
+ * Allocate and initialize a new boolean reloption
+ */
+static relopt_bool *
+init_bool_reloption(bits32 kinds, const char *name, const char *desc,
+ bool default_val, LOCKMODE lockmode)
+{
+ relopt_bool *newoption;
+
+ newoption = (relopt_bool *) allocate_reloption(kinds, RELOPT_TYPE_BOOL,
+ name, desc, lockmode);
+ newoption->default_val = default_val;
+
+ return newoption;
+}
+
+/*
+ * add_bool_reloption
+ * Add a new boolean reloption
+ */
+void
+add_bool_reloption(bits32 kinds, const char *name, const char *desc,
+ bool default_val, LOCKMODE lockmode)
+{
+ relopt_bool *newoption = init_bool_reloption(kinds, name, desc,
+ default_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_bool_reloption
+ * Add a new boolean local reloption
+ *
+ * 'offset' is offset of bool-typed field.
+ */
+void
+add_local_bool_reloption(local_relopts *relopts, const char *name,
+ const char *desc, bool default_val, int offset)
+{
+ relopt_bool *newoption = init_bool_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
+
+
+/*
+ * init_real_reloption
+ * Allocate and initialize a new integer reloption
+ */
+static relopt_int *
+init_int_reloption(bits32 kinds, const char *name, const char *desc,
+ int default_val, int min_val, int max_val,
+ LOCKMODE lockmode)
+{
+ relopt_int *newoption;
+
+ newoption = (relopt_int *) allocate_reloption(kinds, RELOPT_TYPE_INT,
+ name, desc, lockmode);
+ newoption->default_val = default_val;
+ newoption->min = min_val;
+ newoption->max = max_val;
+
+ return newoption;
+}
+
+/*
+ * add_int_reloption
+ * Add a new integer reloption
+ */
+void
+add_int_reloption(bits32 kinds, const char *name, const char *desc, int default_val,
+ int min_val, int max_val, LOCKMODE lockmode)
+{
+ relopt_int *newoption = init_int_reloption(kinds, name, desc,
+ default_val, min_val,
+ max_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_int_reloption
+ * Add a new local integer reloption
+ *
+ * 'offset' is offset of int-typed field.
+ */
+void
+add_local_int_reloption(local_relopts *relopts, const char *name,
+ const char *desc, int default_val, int min_val,
+ int max_val, int offset)
+{
+ relopt_int *newoption = init_int_reloption(RELOPT_KIND_LOCAL,
+ name, desc, default_val,
+ min_val, max_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
+
+/*
+ * init_real_reloption
+ * Allocate and initialize a new real reloption
+ */
+static relopt_real *
+init_real_reloption(bits32 kinds, const char *name, const char *desc,
+ double default_val, double min_val, double max_val,
+ LOCKMODE lockmode)
+{
+ relopt_real *newoption;
+
+ newoption = (relopt_real *) allocate_reloption(kinds, RELOPT_TYPE_REAL,
+ name, desc, lockmode);
+ newoption->default_val = default_val;
+ newoption->min = min_val;
+ newoption->max = max_val;
+
+ return newoption;
+}
+
+/*
+ * add_real_reloption
+ * Add a new float reloption
+ */
+void
+add_real_reloption(bits32 kinds, const char *name, const char *desc,
+ double default_val, double min_val, double max_val,
+ LOCKMODE lockmode)
+{
+ relopt_real *newoption = init_real_reloption(kinds, name, desc,
+ default_val, min_val,
+ max_val, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_real_reloption
+ * Add a new local float reloption
+ *
+ * 'offset' is offset of double-typed field.
+ */
+void
+add_local_real_reloption(local_relopts *relopts, const char *name,
+ const char *desc, double default_val,
+ double min_val, double max_val, int offset)
+{
+ relopt_real *newoption = init_real_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val, min_val,
+ max_val, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
+
+/*
+ * init_enum_reloption
+ * Allocate and initialize a new enum reloption
+ */
+static relopt_enum *
+init_enum_reloption(bits32 kinds, const char *name, const char *desc,
+ relopt_enum_elt_def *members, int default_val,
+ const char *detailmsg, LOCKMODE lockmode)
+{
+ relopt_enum *newoption;
+
+ newoption = (relopt_enum *) allocate_reloption(kinds, RELOPT_TYPE_ENUM,
+ name, desc, lockmode);
+ newoption->members = members;
+ newoption->default_val = default_val;
+ newoption->detailmsg = detailmsg;
+
+ return newoption;
+}
+
+
+/*
+ * add_enum_reloption
+ * Add a new enum reloption
+ *
+ * The members array must have a terminating NULL entry.
+ *
+ * The detailmsg is shown when unsupported values are passed, and has this
+ * form: "Valid values are \"foo\", \"bar\", and \"bar\"."
+ *
+ * The members array and detailmsg are not copied -- caller must ensure that
+ * they are valid throughout the life of the process.
+ */
+void
+add_enum_reloption(bits32 kinds, const char *name, const char *desc,
+ relopt_enum_elt_def *members, int default_val,
+ const char *detailmsg, LOCKMODE lockmode)
+{
+ relopt_enum *newoption = init_enum_reloption(kinds, name, desc,
+ members, default_val,
+ detailmsg, lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_enum_reloption
+ * Add a new local enum reloption
+ *
+ * 'offset' is offset of int-typed field.
+ */
+void
+add_local_enum_reloption(local_relopts *relopts, const char *name,
+ const char *desc, relopt_enum_elt_def *members,
+ int default_val, const char *detailmsg, int offset)
+{
+ relopt_enum *newoption = init_enum_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ members, default_val,
+ detailmsg, 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
+
+/*
+ * init_string_reloption
+ * Allocate and initialize a new string reloption
+ */
+static relopt_string *
+init_string_reloption(bits32 kinds, const char *name, const char *desc,
+ const char *default_val,
+ validate_string_relopt validator,
+ fill_string_relopt filler,
+ LOCKMODE lockmode)
+{
+ relopt_string *newoption;
+
+ /* make sure the validator/default combination is sane */
+ if (validator)
+ (validator) (default_val);
+
+ newoption = (relopt_string *) allocate_reloption(kinds, RELOPT_TYPE_STRING,
+ name, desc, lockmode);
+ newoption->validate_cb = validator;
+ newoption->fill_cb = filler;
+ if (default_val)
+ {
+ if (kinds == RELOPT_KIND_LOCAL)
+ newoption->default_val = strdup(default_val);
+ else
+ newoption->default_val = MemoryContextStrdup(TopMemoryContext, default_val);
+ newoption->default_len = strlen(default_val);
+ newoption->default_isnull = false;
+ }
+ else
+ {
+ newoption->default_val = "";
+ newoption->default_len = 0;
+ newoption->default_isnull = true;
+ }
+
+ return newoption;
+}
+
+/*
+ * add_string_reloption
+ * Add a new string reloption
+ *
+ * "validator" is an optional function pointer that can be used to test the
+ * validity of the values. It must elog(ERROR) when the argument string is
+ * not acceptable for the variable. Note that the default value must pass
+ * the validation.
+ */
+void
+add_string_reloption(bits32 kinds, const char *name, const char *desc,
+ const char *default_val, validate_string_relopt validator,
+ LOCKMODE lockmode)
+{
+ relopt_string *newoption = init_string_reloption(kinds, name, desc,
+ default_val,
+ validator, NULL,
+ lockmode);
+
+ add_reloption((relopt_gen *) newoption);
+}
+
+/*
+ * add_local_string_reloption
+ * Add a new local string reloption
+ *
+ * 'offset' is offset of int-typed field that will store offset of string value
+ * in the resulting bytea structure.
+ */
+void
+add_local_string_reloption(local_relopts *relopts, const char *name,
+ const char *desc, const char *default_val,
+ validate_string_relopt validator,
+ fill_string_relopt filler, int offset)
+{
+ relopt_string *newoption = init_string_reloption(RELOPT_KIND_LOCAL,
+ name, desc,
+ default_val,
+ validator, filler,
+ 0);
+
+ add_local_reloption(relopts, (relopt_gen *) newoption, offset);
+}
+
+/*
+ * Transform a relation options list (list of DefElem) into the text array
+ * format that is kept in pg_class.reloptions, including only those options
+ * that are in the passed namespace. The output values do not include the
+ * namespace.
+ *
+ * This is used for three cases: CREATE TABLE/INDEX, ALTER TABLE SET, and
+ * ALTER TABLE RESET. In the ALTER cases, oldOptions is the existing
+ * reloptions value (possibly NULL), and we replace or remove entries
+ * as needed.
+ *
+ * If acceptOidsOff is true, then we allow oids = false, but throw error when
+ * on. This is solely needed for backwards compatibility.
+ *
+ * Note that this is not responsible for determining whether the options
+ * are valid, but it does check that namespaces for all the options given are
+ * listed in validnsps. The NULL namespace is always valid and need not be
+ * explicitly listed. Passing a NULL pointer means that only the NULL
+ * namespace is valid.
+ *
+ * Both oldOptions and the result are text arrays (or NULL for "default"),
+ * but we declare them as Datums to avoid including array.h in reloptions.h.
+ */
+Datum
+transformRelOptions(Datum oldOptions, List *defList, const char *namspace,
+ char *validnsps[], bool acceptOidsOff, bool isReset)
+{
+ Datum result;
+ ArrayBuildState *astate;
+ ListCell *cell;
+
+ /* no change if empty list */
+ if (defList == NIL)
+ return oldOptions;
+
+ /* We build new array using accumArrayResult */
+ astate = NULL;
+
+ /* Copy any oldOptions that aren't to be replaced */
+ if (PointerIsValid(DatumGetPointer(oldOptions)))
+ {
+ ArrayType *array = DatumGetArrayTypeP(oldOptions);
+ Datum *oldoptions;
+ int noldoptions;
+ int i;
+
+ deconstruct_array_builtin(array, TEXTOID, &oldoptions, NULL, &noldoptions);
+
+ for (i = 0; i < noldoptions; i++)
+ {
+ char *text_str = VARDATA(oldoptions[i]);
+ int text_len = VARSIZE(oldoptions[i]) - VARHDRSZ;
+
+ /* Search for a match in defList */
+ foreach(cell, defList)
+ {
+ DefElem *def = (DefElem *) lfirst(cell);
+ int kw_len;
+
+ /* ignore if not in the same namespace */
+ if (namspace == NULL)
+ {
+ if (def->defnamespace != NULL)
+ continue;
+ }
+ else if (def->defnamespace == NULL)
+ continue;
+ else if (strcmp(def->defnamespace, namspace) != 0)
+ continue;
+
+ kw_len = strlen(def->defname);
+ if (text_len > kw_len && text_str[kw_len] == '=' &&
+ strncmp(text_str, def->defname, kw_len) == 0)
+ break;
+ }
+ if (!cell)
+ {
+ /* No match, so keep old option */
+ astate = accumArrayResult(astate, oldoptions[i],
+ false, TEXTOID,
+ CurrentMemoryContext);
+ }
+ }
+ }
+
+ /*
+ * If CREATE/SET, add new options to array; if RESET, just check that the
+ * user didn't say RESET (option=val). (Must do this because the grammar
+ * doesn't enforce it.)
+ */
+ foreach(cell, defList)
+ {
+ DefElem *def = (DefElem *) lfirst(cell);
+
+ if (isReset)
+ {
+ if (def->arg != NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("RESET must not include values for parameters")));
+ }
+ else
+ {
+ text *t;
+ const char *value;
+ Size len;
+
+ /*
+ * Error out if the namespace is not valid. A NULL namespace is
+ * always valid.
+ */
+ if (def->defnamespace != NULL)
+ {
+ bool valid = false;
+ int i;
+
+ if (validnsps)
+ {
+ for (i = 0; validnsps[i]; i++)
+ {
+ if (strcmp(def->defnamespace, validnsps[i]) == 0)
+ {
+ valid = true;
+ break;
+ }
+ }
+ }
+
+ if (!valid)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("unrecognized parameter namespace \"%s\"",
+ def->defnamespace)));
+ }
+
+ /* ignore if not in the same namespace */
+ if (namspace == NULL)
+ {
+ if (def->defnamespace != NULL)
+ continue;
+ }
+ else if (def->defnamespace == NULL)
+ continue;
+ else if (strcmp(def->defnamespace, namspace) != 0)
+ continue;
+
+ /*
+ * Flatten the DefElem into a text string like "name=arg". If we
+ * have just "name", assume "name=true" is meant. Note: the
+ * namespace is not output.
+ */
+ if (def->arg != NULL)
+ value = defGetString(def);
+ else
+ value = "true";
+
+ /*
+ * This is not a great place for this test, but there's no other
+ * convenient place to filter the option out. As WITH (oids =
+ * false) will be removed someday, this seems like an acceptable
+ * amount of ugly.
+ */
+ if (acceptOidsOff && def->defnamespace == NULL &&
+ strcmp(def->defname, "oids") == 0)
+ {
+ if (defGetBoolean(def))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("tables declared WITH OIDS are not supported")));
+ /* skip over option, reloptions machinery doesn't know it */
+ continue;
+ }
+
+ len = VARHDRSZ + strlen(def->defname) + 1 + strlen(value);
+ /* +1 leaves room for sprintf's trailing null */
+ t = (text *) 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)
+ result = makeArrayResult(astate, CurrentMemoryContext);
+ else
+ result = (Datum) 0;
+
+ return result;
+}
+
+
+/*
+ * Convert the text-array format of reloptions into a List of DefElem.
+ * This is the inverse of transformRelOptions().
+ */
+List *
+untransformRelOptions(Datum options)
+{
+ List *result = NIL;
+ ArrayType *array;
+ Datum *optiondatums;
+ int noptions;
+ int i;
+
+ /* Nothing to do if no options */
+ if (!PointerIsValid(DatumGetPointer(options)))
+ return result;
+
+ array = DatumGetArrayTypeP(options);
+
+ deconstruct_array_builtin(array, TEXTOID, &optiondatums, NULL, &noptions);
+
+ for (i = 0; i < noptions; i++)
+ {
+ char *s;
+ char *p;
+ Node *val = NULL;
+
+ s = TextDatumGetCString(optiondatums[i]);
+ p = strchr(s, '=');
+ if (p)
+ {
+ *p++ = '\0';
+ val = (Node *) makeString(p);
+ }
+ result = lappend(result, makeDefElem(s, val, -1));
+ }
+
+ return result;
+}
+
+/*
+ * Extract and parse reloptions from a pg_class tuple.
+ *
+ * This is a low-level routine, expected to be used by relcache code and
+ * callers that do not have a table's relcache entry (e.g. autovacuum). For
+ * other uses, consider grabbing the rd_options pointer from the relcache entry
+ * instead.
+ *
+ * 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.
+ */
+bytea *
+extractRelOptions(HeapTuple tuple, TupleDesc tupdesc,
+ amoptions_function amoptions)
+{
+ bytea *options;
+ bool isnull;
+ Datum datum;
+ Form_pg_class classForm;
+
+ datum = fastgetattr(tuple,
+ Anum_pg_class_reloptions,
+ tupdesc,
+ &isnull);
+ if (isnull)
+ return NULL;
+
+ classForm = (Form_pg_class) GETSTRUCT(tuple);
+
+ /* Parse into appropriate format; don't error out here */
+ switch (classForm->relkind)
+ {
+ case RELKIND_RELATION:
+ case RELKIND_TOASTVALUE:
+ case RELKIND_MATVIEW:
+ options = heap_reloptions(classForm->relkind, datum, false);
+ break;
+ case RELKIND_PARTITIONED_TABLE:
+ options = partitioned_table_reloptions(datum, false);
+ break;
+ case RELKIND_VIEW:
+ options = view_reloptions(datum, false);
+ break;
+ case RELKIND_INDEX:
+ case RELKIND_PARTITIONED_INDEX:
+ options = index_reloptions(amoptions, datum, false);
+ break;
+ case RELKIND_FOREIGN_TABLE:
+ options = NULL;
+ break;
+ default:
+ Assert(false); /* can't get here */
+ options = NULL; /* keep compiler quiet */
+ break;
+ }
+
+ return options;
+}
+
+static void
+parseRelOptionsInternal(Datum options, bool validate,
+ relopt_value *reloptions, int numoptions)
+{
+ ArrayType *array = DatumGetArrayTypeP(options);
+ Datum *optiondatums;
+ int noptions;
+ int i;
+
+ deconstruct_array_builtin(array, TEXTOID, &optiondatums, NULL, &noptions);
+
+ for (i = 0; i < noptions; i++)
+ {
+ char *text_str = VARDATA(optiondatums[i]);
+ int text_len = VARSIZE(optiondatums[i]) - VARHDRSZ;
+ int j;
+
+ /* Search for a match in reloptions */
+ for (j = 0; j < numoptions; j++)
+ {
+ int kw_len = reloptions[j].gen->namelen;
+
+ if (text_len > kw_len && text_str[kw_len] == '=' &&
+ strncmp(text_str, reloptions[j].gen->name, kw_len) == 0)
+ {
+ parse_one_reloption(&reloptions[j], text_str, text_len,
+ validate);
+ break;
+ }
+ }
+
+ if (j >= numoptions && validate)
+ {
+ char *s;
+ char *p;
+
+ s = TextDatumGetCString(optiondatums[i]);
+ p = strchr(s, '=');
+ if (p)
+ *p = '\0';
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("unrecognized parameter \"%s\"", s)));
+ }
+ }
+
+ /* It's worth avoiding memory leaks in this function */
+ pfree(optiondatums);
+
+ if (((void *) array) != DatumGetPointer(options))
+ pfree(array);
+}
+
+/*
+ * Interpret reloptions that are given in text-array format.
+ *
+ * options is a reloption text array as constructed by transformRelOptions.
+ * kind specifies the family of options to be processed.
+ *
+ * The return value is a relopt_value * array on which the options actually
+ * set in the options array are marked with isset=true. The length of this
+ * array is returned in *numrelopts. Options not set are also present in the
+ * array; this is so that the caller can easily locate the default values.
+ *
+ * If there are no options of the given kind, numrelopts is set to 0 and NULL
+ * is returned (unless options are illegally supplied despite none being
+ * defined, in which case an error occurs).
+ *
+ * Note: values of type int, bool and real are allocated as part of the
+ * returned array. Values of type string are allocated separately and must
+ * be freed by the caller.
+ */
+static relopt_value *
+parseRelOptions(Datum options, bool validate, relopt_kind kind,
+ int *numrelopts)
+{
+ relopt_value *reloptions = NULL;
+ int numoptions = 0;
+ int i;
+ int j;
+
+ if (need_initialization)
+ initialize_reloptions();
+
+ /* Build a list of expected options, based on kind */
+
+ for (i = 0; relOpts[i]; i++)
+ if (relOpts[i]->kinds & kind)
+ numoptions++;
+
+ if (numoptions > 0)
+ {
+ reloptions = palloc(numoptions * sizeof(relopt_value));
+
+ for (i = 0, j = 0; relOpts[i]; i++)
+ {
+ if (relOpts[i]->kinds & kind)
+ {
+ reloptions[j].gen = relOpts[i];
+ reloptions[j].isset = false;
+ j++;
+ }
+ }
+ }
+
+ /* Done if no options */
+ if (PointerIsValid(DatumGetPointer(options)))
+ parseRelOptionsInternal(options, validate, reloptions, numoptions);
+
+ *numrelopts = numoptions;
+ return reloptions;
+}
+
+/* Parse local unregistered options. */
+static relopt_value *
+parseLocalRelOptions(local_relopts *relopts, Datum options, bool validate)
+{
+ int nopts = list_length(relopts->options);
+ relopt_value *values = palloc(sizeof(*values) * nopts);
+ ListCell *lc;
+ int i = 0;
+
+ foreach(lc, relopts->options)
+ {
+ local_relopt *opt = lfirst(lc);
+
+ values[i].gen = opt->option;
+ values[i].isset = false;
+
+ i++;
+ }
+
+ if (options != (Datum) 0)
+ parseRelOptionsInternal(options, validate, values, nopts);
+
+ return values;
+}
+
+/*
+ * Subroutine for parseRelOptions, to parse and validate a single option's
+ * value
+ */
+static void
+parse_one_reloption(relopt_value *option, char *text_str, int text_len,
+ bool validate)
+{
+ char *value;
+ int value_len;
+ bool parsed;
+ bool nofree = false;
+
+ if (option->isset && validate)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("parameter \"%s\" specified more than once",
+ option->gen->name)));
+
+ value_len = text_len - option->gen->namelen - 1;
+ value = (char *) palloc(value_len + 1);
+ memcpy(value, text_str + option->gen->namelen + 1, value_len);
+ value[value_len] = '\0';
+
+ switch (option->gen->type)
+ {
+ case RELOPT_TYPE_BOOL:
+ {
+ parsed = parse_bool(value, &option->values.bool_val);
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for boolean option \"%s\": %s",
+ option->gen->name, value)));
+ }
+ break;
+ case RELOPT_TYPE_INT:
+ {
+ relopt_int *optint = (relopt_int *) option->gen;
+
+ parsed = parse_int(value, &option->values.int_val, 0, NULL);
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for integer option \"%s\": %s",
+ option->gen->name, value)));
+ if (validate && (option->values.int_val < optint->min ||
+ option->values.int_val > optint->max))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("value %s out of bounds for option \"%s\"",
+ value, option->gen->name),
+ errdetail("Valid values are between \"%d\" and \"%d\".",
+ optint->min, optint->max)));
+ }
+ break;
+ case RELOPT_TYPE_REAL:
+ {
+ relopt_real *optreal = (relopt_real *) option->gen;
+
+ parsed = parse_real(value, &option->values.real_val, 0, NULL);
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for floating point option \"%s\": %s",
+ option->gen->name, value)));
+ if (validate && (option->values.real_val < optreal->min ||
+ option->values.real_val > optreal->max))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("value %s out of bounds for option \"%s\"",
+ value, option->gen->name),
+ errdetail("Valid values are between \"%f\" and \"%f\".",
+ optreal->min, optreal->max)));
+ }
+ break;
+ case RELOPT_TYPE_ENUM:
+ {
+ relopt_enum *optenum = (relopt_enum *) option->gen;
+ relopt_enum_elt_def *elt;
+
+ parsed = false;
+ for (elt = optenum->members; elt->string_val; elt++)
+ {
+ if (pg_strcasecmp(value, elt->string_val) == 0)
+ {
+ option->values.enum_val = elt->symbol_val;
+ parsed = true;
+ break;
+ }
+ }
+ if (validate && !parsed)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid value for enum option \"%s\": %s",
+ option->gen->name, value),
+ optenum->detailmsg ?
+ errdetail_internal("%s", _(optenum->detailmsg)) : 0));
+
+ /*
+ * If value is not among the allowed string values, but we are
+ * not asked to validate, just use the default numeric value.
+ */
+ if (!parsed)
+ option->values.enum_val = optenum->default_val;
+ }
+ break;
+ case RELOPT_TYPE_STRING:
+ {
+ relopt_string *optstring = (relopt_string *) option->gen;
+
+ option->values.string_val = value;
+ nofree = true;
+ if (validate && optstring->validate_cb)
+ (optstring->validate_cb) (value);
+ parsed = true;
+ }
+ break;
+ default:
+ elog(ERROR, "unsupported reloption type %d", option->gen->type);
+ parsed = true; /* quiet compiler */
+ break;
+ }
+
+ if (parsed)
+ option->isset = true;
+ if (!nofree)
+ pfree(value);
+}
+
+/*
+ * 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
+ * equivalent).
+ */
+static void *
+allocateReloptStruct(Size base, relopt_value *options, int numoptions)
+{
+ Size size = base;
+ int i;
+
+ for (i = 0; i < numoptions; i++)
+ {
+ relopt_value *optval = &options[i];
+
+ if (optval->gen->type == RELOPT_TYPE_STRING)
+ {
+ relopt_string *optstr = (relopt_string *) optval->gen;
+
+ if (optstr->fill_cb)
+ {
+ const char *val = optval->isset ? optval->values.string_val :
+ optstr->default_isnull ? NULL : optstr->default_val;
+
+ size += optstr->fill_cb(val, NULL);
+ }
+ else
+ size += GET_STRING_RELOPTION_LEN(*optval) + 1;
+ }
+ }
+
+ return palloc0(size);
+}
+
+/*
+ * Given the result of parseRelOptions and a parsing table, fill in the
+ * struct (previously allocated with allocateReloptStruct) with the parsed
+ * values.
+ *
+ * rdopts is the pointer to the allocated struct to be filled.
+ * basesize is the sizeof(struct) that was passed to allocateReloptStruct.
+ * options, of length numoptions, is parseRelOptions' output.
+ * elems, of length numelems, is the table describing the allowed options.
+ * When validate is true, it is expected that all options appear in elems.
+ */
+static void
+fillRelOptions(void *rdopts, Size basesize,
+ relopt_value *options, int numoptions,
+ bool validate,
+ const relopt_parse_elt *elems, int numelems)
+{
+ int i;
+ int offset = basesize;
+
+ for (i = 0; i < numoptions; i++)
+ {
+ int j;
+ bool found = false;
+
+ for (j = 0; j < numelems; j++)
+ {
+ if (strcmp(options[i].gen->name, elems[j].optname) == 0)
+ {
+ relopt_string *optstring;
+ char *itempos = ((char *) rdopts) + elems[j].offset;
+ char *string_val;
+
+ switch (options[i].gen->type)
+ {
+ case RELOPT_TYPE_BOOL:
+ *(bool *) itempos = options[i].isset ?
+ options[i].values.bool_val :
+ ((relopt_bool *) options[i].gen)->default_val;
+ break;
+ case RELOPT_TYPE_INT:
+ *(int *) itempos = options[i].isset ?
+ options[i].values.int_val :
+ ((relopt_int *) options[i].gen)->default_val;
+ break;
+ case RELOPT_TYPE_REAL:
+ *(double *) itempos = options[i].isset ?
+ options[i].values.real_val :
+ ((relopt_real *) options[i].gen)->default_val;
+ break;
+ case RELOPT_TYPE_ENUM:
+ *(int *) itempos = options[i].isset ?
+ options[i].values.enum_val :
+ ((relopt_enum *) options[i].gen)->default_val;
+ break;
+ case RELOPT_TYPE_STRING:
+ optstring = (relopt_string *) options[i].gen;
+ if (options[i].isset)
+ string_val = options[i].values.string_val;
+ else if (!optstring->default_isnull)
+ string_val = optstring->default_val;
+ else
+ string_val = NULL;
+
+ if (optstring->fill_cb)
+ {
+ Size size =
+ optstring->fill_cb(string_val,
+ (char *) rdopts + offset);
+
+ if (size)
+ {
+ *(int *) itempos = offset;
+ offset += size;
+ }
+ else
+ *(int *) itempos = 0;
+ }
+ else if (string_val == NULL)
+ *(int *) itempos = 0;
+ else
+ {
+ strcpy((char *) rdopts + offset, string_val);
+ *(int *) itempos = offset;
+ offset += strlen(string_val) + 1;
+ }
+ break;
+ default:
+ elog(ERROR, "unsupported reloption type %d",
+ options[i].gen->type);
+ break;
+ }
+ found = true;
+ break;
+ }
+ }
+ if (validate && !found)
+ elog(ERROR, "reloption \"%s\" not found in parse table",
+ options[i].gen->name);
+ }
+ SET_VARSIZE(rdopts, offset);
+}
+
+
+/*
+ * Option parser for anything that uses StdRdOptions.
+ */
+bytea *
+default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
+{
+ static const relopt_parse_elt tab[] = {
+ {"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)},
+ {"autovacuum_enabled", RELOPT_TYPE_BOOL,
+ offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)},
+ {"autovacuum_vacuum_threshold", RELOPT_TYPE_INT,
+ offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_threshold)},
+ {"autovacuum_vacuum_insert_threshold", RELOPT_TYPE_INT,
+ offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_ins_threshold)},
+ {"autovacuum_analyze_threshold", RELOPT_TYPE_INT,
+ offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, analyze_threshold)},
+ {"autovacuum_vacuum_cost_limit", RELOPT_TYPE_INT,
+ offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_cost_limit)},
+ {"autovacuum_freeze_min_age", RELOPT_TYPE_INT,
+ offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, freeze_min_age)},
+ {"autovacuum_freeze_max_age", RELOPT_TYPE_INT,
+ offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, freeze_max_age)},
+ {"autovacuum_freeze_table_age", RELOPT_TYPE_INT,
+ offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, freeze_table_age)},
+ {"autovacuum_multixact_freeze_min_age", RELOPT_TYPE_INT,
+ offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, multixact_freeze_min_age)},
+ {"autovacuum_multixact_freeze_max_age", RELOPT_TYPE_INT,
+ offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, multixact_freeze_max_age)},
+ {"autovacuum_multixact_freeze_table_age", RELOPT_TYPE_INT,
+ offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, multixact_freeze_table_age)},
+ {"log_autovacuum_min_duration", RELOPT_TYPE_INT,
+ offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, log_min_duration)},
+ {"toast_tuple_target", RELOPT_TYPE_INT,
+ offsetof(StdRdOptions, toast_tuple_target)},
+ {"autovacuum_vacuum_cost_delay", RELOPT_TYPE_REAL,
+ offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_cost_delay)},
+ {"autovacuum_vacuum_scale_factor", RELOPT_TYPE_REAL,
+ offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_scale_factor)},
+ {"autovacuum_vacuum_insert_scale_factor", RELOPT_TYPE_REAL,
+ offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, vacuum_ins_scale_factor)},
+ {"autovacuum_analyze_scale_factor", RELOPT_TYPE_REAL,
+ offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, analyze_scale_factor)},
+ {"user_catalog_table", RELOPT_TYPE_BOOL,
+ offsetof(StdRdOptions, user_catalog_table)},
+ {"parallel_workers", RELOPT_TYPE_INT,
+ offsetof(StdRdOptions, parallel_workers)},
+ {"vacuum_index_cleanup", RELOPT_TYPE_ENUM,
+ offsetof(StdRdOptions, vacuum_index_cleanup)},
+ {"vacuum_truncate", RELOPT_TYPE_BOOL,
+ offsetof(StdRdOptions, vacuum_truncate)}
+ };
+
+ return (bytea *) build_reloptions(reloptions, validate, kind,
+ sizeof(StdRdOptions),
+ tab, lengthof(tab));
+}
+
+/*
+ * build_reloptions
+ *
+ * Parses "reloptions" provided by the caller, returning them in a
+ * structure containing the parsed options. The parsing is done with
+ * the help of a parsing table describing the allowed options, defined
+ * by "relopt_elems" of length "num_relopt_elems".
+ *
+ * "validate" must be true if reloptions value is freshly built by
+ * transformRelOptions(), as opposed to being read from the catalog, in which
+ * case the values contained in it must already be valid.
+ *
+ * NULL is returned if the passed-in options did not match any of the options
+ * in the parsing table, unless validate is true in which case an error would
+ * be reported.
+ */
+void *
+build_reloptions(Datum reloptions, bool validate,
+ relopt_kind kind,
+ Size relopt_struct_size,
+ const relopt_parse_elt *relopt_elems,
+ int num_relopt_elems)
+{
+ int numoptions;
+ relopt_value *options;
+ void *rdopts;
+
+ /* parse options specific to given relation option kind */
+ options = parseRelOptions(reloptions, validate, kind, &numoptions);
+ Assert(numoptions <= num_relopt_elems);
+
+ /* if none set, we're done */
+ if (numoptions == 0)
+ {
+ Assert(options == NULL);
+ return NULL;
+ }
+
+ /* allocate and fill the structure */
+ rdopts = allocateReloptStruct(relopt_struct_size, options, numoptions);
+ fillRelOptions(rdopts, relopt_struct_size, options, numoptions,
+ validate, relopt_elems, num_relopt_elems);
+
+ pfree(options);
+
+ return rdopts;
+}
+
+/*
+ * Parse local options, allocate a bytea struct that's of the specified
+ * 'base_size' plus any extra space that's needed for string variables,
+ * fill its option's fields located at the given offsets and return it.
+ */
+void *
+build_local_reloptions(local_relopts *relopts, Datum options, bool validate)
+{
+ int noptions = list_length(relopts->options);
+ relopt_parse_elt *elems = palloc(sizeof(*elems) * noptions);
+ relopt_value *vals;
+ void *opts;
+ int i = 0;
+ ListCell *lc;
+
+ foreach(lc, relopts->options)
+ {
+ local_relopt *opt = lfirst(lc);
+
+ elems[i].optname = opt->option->name;
+ elems[i].opttype = opt->option->type;
+ elems[i].offset = opt->offset;
+
+ i++;
+ }
+
+ vals = parseLocalRelOptions(relopts, options, validate);
+ opts = allocateReloptStruct(relopts->relopt_struct_size, vals, noptions);
+ fillRelOptions(opts, relopts->relopt_struct_size, vals, noptions, validate,
+ elems, noptions);
+
+ if (validate)
+ foreach(lc, relopts->validators)
+ ((relopts_validator) lfirst(lc)) (opts, vals, noptions);
+
+ if (elems)
+ pfree(elems);
+
+ return opts;
+}
+
+/*
+ * Option parser for partitioned tables
+ */
+bytea *
+partitioned_table_reloptions(Datum reloptions, bool validate)
+{
+ if (validate && reloptions)
+ ereport(ERROR,
+ errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot specify storage parameters for a partitioned table"),
+ errhint("Specify storage parameters for its leaf partitions instead."));
+ return NULL;
+}
+
+/*
+ * Option parser for views
+ */
+bytea *
+view_reloptions(Datum reloptions, bool validate)
+{
+ static const relopt_parse_elt tab[] = {
+ {"security_barrier", RELOPT_TYPE_BOOL,
+ offsetof(ViewOptions, security_barrier)},
+ {"security_invoker", RELOPT_TYPE_BOOL,
+ offsetof(ViewOptions, security_invoker)},
+ {"check_option", RELOPT_TYPE_ENUM,
+ offsetof(ViewOptions, check_option)}
+ };
+
+ return (bytea *) build_reloptions(reloptions, validate,
+ RELOPT_KIND_VIEW,
+ sizeof(ViewOptions),
+ tab, lengthof(tab));
+}
+
+/*
+ * Parse options for heaps, views and toast tables.
+ */
+bytea *
+heap_reloptions(char relkind, Datum reloptions, bool validate)
+{
+ StdRdOptions *rdopts;
+
+ switch (relkind)
+ {
+ case RELKIND_TOASTVALUE:
+ rdopts = (StdRdOptions *)
+ 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;
+ }
+ return (bytea *) rdopts;
+ case RELKIND_RELATION:
+ case RELKIND_MATVIEW:
+ return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP);
+ default:
+ /* other relkinds are not supported */
+ return NULL;
+ }
+}
+
+
+/*
+ * Parse options for indexes.
+ *
+ * amoptions index AM's option parser function
+ * reloptions options as text[] datum
+ * validate error flag
+ */
+bytea *
+index_reloptions(amoptions_function amoptions, Datum reloptions, bool validate)
+{
+ Assert(amoptions != NULL);
+
+ /* Assume function is strict */
+ if (!PointerIsValid(DatumGetPointer(reloptions)))
+ return NULL;
+
+ return amoptions(reloptions, validate);
+}
+
+/*
+ * Option parser for attribute reloptions
+ */
+bytea *
+attribute_reloptions(Datum reloptions, bool validate)
+{
+ static const relopt_parse_elt tab[] = {
+ {"n_distinct", RELOPT_TYPE_REAL, offsetof(AttributeOpts, n_distinct)},
+ {"n_distinct_inherited", RELOPT_TYPE_REAL, offsetof(AttributeOpts, n_distinct_inherited)}
+ };
+
+ return (bytea *) build_reloptions(reloptions, validate,
+ RELOPT_KIND_ATTRIBUTE,
+ sizeof(AttributeOpts),
+ tab, lengthof(tab));
+}
+
+/*
+ * Option parser for tablespace reloptions
+ */
+bytea *
+tablespace_reloptions(Datum reloptions, bool validate)
+{
+ static const relopt_parse_elt tab[] = {
+ {"random_page_cost", RELOPT_TYPE_REAL, offsetof(TableSpaceOpts, random_page_cost)},
+ {"seq_page_cost", RELOPT_TYPE_REAL, offsetof(TableSpaceOpts, seq_page_cost)},
+ {"effective_io_concurrency", RELOPT_TYPE_INT, offsetof(TableSpaceOpts, effective_io_concurrency)},
+ {"maintenance_io_concurrency", RELOPT_TYPE_INT, offsetof(TableSpaceOpts, maintenance_io_concurrency)}
+ };
+
+ return (bytea *) build_reloptions(reloptions, validate,
+ RELOPT_KIND_TABLESPACE,
+ sizeof(TableSpaceOpts),
+ tab, lengthof(tab));
+}
+
+/*
+ * Determine the required LOCKMODE from an option list.
+ *
+ * Called from AlterTableGetLockLevel(), see that function
+ * for a longer explanation of how this works.
+ */
+LOCKMODE
+AlterTableGetRelOptionsLockLevel(List *defList)
+{
+ LOCKMODE lockmode = NoLock;
+ ListCell *cell;
+
+ if (defList == NIL)
+ return AccessExclusiveLock;
+
+ if (need_initialization)
+ initialize_reloptions();
+
+ foreach(cell, defList)
+ {
+ DefElem *def = (DefElem *) lfirst(cell);
+ int i;
+
+ for (i = 0; relOpts[i]; i++)
+ {
+ if (strncmp(relOpts[i]->name,
+ def->defname,
+ relOpts[i]->namelen + 1) == 0)
+ {
+ if (lockmode < relOpts[i]->lockmode)
+ lockmode = relOpts[i]->lockmode;
+ }
+ }
+ }
+
+ return lockmode;
+}