
The code was enforcing AccessExclusiveLock for all custom relation options, which is incorrect as the APIs allow a custom lock level to be set. While on it, fix a couple of inconsistencies in the tests and the README of dummy_index_am. Oversights in commit 773df88. Discussion: https://postgr.es/m/20190925234152.GA2115@paquier.xyz
1725 lines
45 KiB
C
1725 lines
45 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* reloptions.c
|
|
* Core support for relation options (pg_class.reloptions)
|
|
*
|
|
* Portions Copyright (c) 1996-2019, 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.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
|
|
},
|
|
{
|
|
{
|
|
"vacuum_index_cleanup",
|
|
"Enables index vacuuming and index cleanup",
|
|
RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
|
|
ShareUpdateExclusiveLock
|
|
},
|
|
true
|
|
},
|
|
{
|
|
{
|
|
"vacuum_truncate",
|
|
"Enables vacuum to truncate empty pages at the end of this table",
|
|
RELOPT_KIND_HEAP | RELOPT_KIND_TOAST,
|
|
ShareUpdateExclusiveLock
|
|
},
|
|
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_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
|
|
},
|
|
{
|
|
{
|
|
"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_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",
|
|
"Number of tuple inserts prior to index cleanup as a fraction of reltuples.",
|
|
RELOPT_KIND_BTREE,
|
|
ShareUpdateExclusiveLock
|
|
},
|
|
-1, 0.0, 1e10
|
|
},
|
|
/* list terminator */
|
|
{{NULL}}
|
|
};
|
|
|
|
/* values from GistOptBufferingMode */
|
|
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 */
|
|
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[] =
|
|
{
|
|
{
|
|
{
|
|
"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);
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
|
|
oldcxt = MemoryContextSwitchTo(TopMemoryContext);
|
|
|
|
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;
|
|
|
|
MemoryContextSwitchTo(oldcxt);
|
|
|
|
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;
|
|
|
|
newoption = (relopt_bool *) allocate_reloption(kinds, RELOPT_TYPE_BOOL,
|
|
name, desc, lockmode);
|
|
newoption->default_val = default_val;
|
|
|
|
add_reloption((relopt_gen *) 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;
|
|
|
|
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;
|
|
|
|
add_reloption((relopt_gen *) 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;
|
|
|
|
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;
|
|
|
|
add_reloption((relopt_gen *) 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;
|
|
|
|
newoption = (relopt_enum *) allocate_reloption(kinds, RELOPT_TYPE_ENUM,
|
|
name, desc, lockmode);
|
|
newoption->members = members;
|
|
newoption->default_val = default_val;
|
|
newoption->detailmsg = detailmsg;
|
|
|
|
add_reloption((relopt_gen *) 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;
|
|
|
|
/* 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;
|
|
if (default_val)
|
|
{
|
|
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;
|
|
}
|
|
|
|
add_reloption((relopt_gen *) newoption);
|
|
}
|
|
|
|
/*
|
|
* 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(array, TEXTOID, -1, false, 'i',
|
|
&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(array, TEXTOID, -1, false, 'i',
|
|
&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(pstrdup(p));
|
|
}
|
|
result = lappend(result, makeDefElem(pstrdup(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:
|
|
case RELKIND_PARTITIONED_TABLE:
|
|
options = heap_reloptions(classForm->relkind, 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;
|
|
}
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
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)))
|
|
{
|
|
ArrayType *array = DatumGetArrayTypeP(options);
|
|
Datum *optiondatums;
|
|
int noptions;
|
|
|
|
deconstruct_array(array, TEXTOID, -1, false, 'i',
|
|
&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);
|
|
}
|
|
|
|
*numrelopts = numoptions;
|
|
return reloptions;
|
|
}
|
|
|
|
/*
|
|
* 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).
|
|
*/
|
|
void *
|
|
allocateReloptStruct(Size base, relopt_value *options, int numoptions)
|
|
{
|
|
Size size = base;
|
|
int i;
|
|
|
|
for (i = 0; i < numoptions; i++)
|
|
if (options[i].gen->type == RELOPT_TYPE_STRING)
|
|
size += GET_STRING_RELOPTION_LEN(options[i]) + 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.
|
|
*/
|
|
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 (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)
|
|
{
|
|
relopt_value *options;
|
|
StdRdOptions *rdopts;
|
|
int numoptions;
|
|
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_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_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_cleanup_index_scale_factor", RELOPT_TYPE_REAL,
|
|
offsetof(StdRdOptions, vacuum_cleanup_index_scale_factor)},
|
|
{"vacuum_index_cleanup", RELOPT_TYPE_BOOL,
|
|
offsetof(StdRdOptions, vacuum_index_cleanup)},
|
|
{"vacuum_truncate", RELOPT_TYPE_BOOL,
|
|
offsetof(StdRdOptions, vacuum_truncate)}
|
|
};
|
|
|
|
options = parseRelOptions(reloptions, validate, kind, &numoptions);
|
|
|
|
/* if none set, we're done */
|
|
if (numoptions == 0)
|
|
return NULL;
|
|
|
|
rdopts = allocateReloptStruct(sizeof(StdRdOptions), options, numoptions);
|
|
|
|
fillRelOptions((void *) rdopts, sizeof(StdRdOptions), options, numoptions,
|
|
validate, tab, lengthof(tab));
|
|
|
|
pfree(options);
|
|
|
|
return (bytea *) rdopts;
|
|
}
|
|
|
|
/*
|
|
* Option parser for views
|
|
*/
|
|
bytea *
|
|
view_reloptions(Datum reloptions, bool validate)
|
|
{
|
|
relopt_value *options;
|
|
ViewOptions *vopts;
|
|
int numoptions;
|
|
static const relopt_parse_elt tab[] = {
|
|
{"security_barrier", RELOPT_TYPE_BOOL,
|
|
offsetof(ViewOptions, security_barrier)},
|
|
{"check_option", RELOPT_TYPE_ENUM,
|
|
offsetof(ViewOptions, check_option)}
|
|
};
|
|
|
|
options = parseRelOptions(reloptions, validate, RELOPT_KIND_VIEW, &numoptions);
|
|
|
|
/* if none set, we're done */
|
|
if (numoptions == 0)
|
|
return NULL;
|
|
|
|
vopts = allocateReloptStruct(sizeof(ViewOptions), options, numoptions);
|
|
|
|
fillRelOptions((void *) vopts, sizeof(ViewOptions), options, numoptions,
|
|
validate, tab, lengthof(tab));
|
|
|
|
pfree(options);
|
|
|
|
return (bytea *) vopts;
|
|
}
|
|
|
|
/*
|
|
* 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);
|
|
case RELKIND_PARTITIONED_TABLE:
|
|
return default_reloptions(reloptions, validate,
|
|
RELOPT_KIND_PARTITIONED);
|
|
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)
|
|
{
|
|
relopt_value *options;
|
|
AttributeOpts *aopts;
|
|
int numoptions;
|
|
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)}
|
|
};
|
|
|
|
options = parseRelOptions(reloptions, validate, RELOPT_KIND_ATTRIBUTE,
|
|
&numoptions);
|
|
|
|
/* if none set, we're done */
|
|
if (numoptions == 0)
|
|
return NULL;
|
|
|
|
aopts = allocateReloptStruct(sizeof(AttributeOpts), options, numoptions);
|
|
|
|
fillRelOptions((void *) aopts, sizeof(AttributeOpts), options, numoptions,
|
|
validate, tab, lengthof(tab));
|
|
|
|
pfree(options);
|
|
|
|
return (bytea *) aopts;
|
|
}
|
|
|
|
/*
|
|
* Option parser for tablespace reloptions
|
|
*/
|
|
bytea *
|
|
tablespace_reloptions(Datum reloptions, bool validate)
|
|
{
|
|
relopt_value *options;
|
|
TableSpaceOpts *tsopts;
|
|
int numoptions;
|
|
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)}
|
|
};
|
|
|
|
options = parseRelOptions(reloptions, validate, RELOPT_KIND_TABLESPACE,
|
|
&numoptions);
|
|
|
|
/* if none set, we're done */
|
|
if (numoptions == 0)
|
|
return NULL;
|
|
|
|
tsopts = allocateReloptStruct(sizeof(TableSpaceOpts), options, numoptions);
|
|
|
|
fillRelOptions((void *) tsopts, sizeof(TableSpaceOpts), options, numoptions,
|
|
validate, tab, lengthof(tab));
|
|
|
|
pfree(options);
|
|
|
|
return (bytea *) tsopts;
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
}
|