diff --git a/contrib/bloom/bloom.h b/contrib/bloom/bloom.h index a75a235..e9ccb56 100644 --- a/contrib/bloom/bloom.h +++ b/contrib/bloom/bloom.h @@ -203,7 +203,7 @@ extern IndexBulkDeleteResult *blbulkdelete(IndexVacuumInfo *info, void *callback_state); extern IndexBulkDeleteResult *blvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats); -extern bytea *bloptions(Datum reloptions, bool validate); +extern bytea *bloptions(Datum reloptions, bool validate, bool for_alter); extern void blcostestimate(PlannerInfo *root, IndexPath *path, double loop_count, Cost *indexStartupCost, Cost *indexTotalCost, Selectivity *indexSelectivity, diff --git a/contrib/bloom/blutils.c b/contrib/bloom/blutils.c index 858798d..9bd322c 100644 --- a/contrib/bloom/blutils.c +++ b/contrib/bloom/blutils.c @@ -35,49 +35,13 @@ PG_FUNCTION_INFO_V1(blhandler); -/* Kind of relation options for bloom index */ -static relopt_kind bl_relopt_kind; - -/* parse table for fillRelOptions */ -static relopt_parse_elt bl_relopt_tab[INDEX_MAX_KEYS + 1]; +/* Catalog of relation options for bloom index */ +static options_catalog * bl_relopt_catalog; static int32 myRand(void); static void mySrand(uint32 seed); - -/* - * Module initialize function: initialize info about Bloom relation options. - * - * Note: keep this in sync with makeDefaultBloomOptions(). - */ -void -_PG_init(void) -{ - int i; - char buf[16]; - - bl_relopt_kind = add_reloption_kind(); - - /* Option for length of signature */ - add_int_reloption(bl_relopt_kind, "length", - "Length of signature in bits", - DEFAULT_BLOOM_LENGTH, 1, MAX_BLOOM_LENGTH); - bl_relopt_tab[0].optname = "length"; - bl_relopt_tab[0].opttype = RELOPT_TYPE_INT; - bl_relopt_tab[0].offset = offsetof(BloomOptions, bloomLength); - - /* Number of bits for each possible index column: col1, col2, ... */ - for (i = 0; i < INDEX_MAX_KEYS; i++) - { - snprintf(buf, sizeof(buf), "col%d", i + 1); - add_int_reloption(bl_relopt_kind, buf, - "Number of bits generated for each index column", - DEFAULT_BLOOM_BITS, 1, MAX_BLOOM_BITS); - bl_relopt_tab[i + 1].optname = MemoryContextStrdup(TopMemoryContext, - buf); - bl_relopt_tab[i + 1].opttype = RELOPT_TYPE_INT; - bl_relopt_tab[i + 1].offset = offsetof(BloomOptions, bitSize[0]) + sizeof(int) * i; - } -} +static void *blrelopt_catalog(void); +static void blReloptionPostprocess(void *, bool validate); /* * Construct a default set of Bloom options. @@ -128,7 +92,7 @@ blhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = blvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = blcostestimate; - amroutine->amoptions = bloptions; + amroutine->amrelopt_catalog = blrelopt_catalog; amroutine->amproperty = NULL; amroutine->amvalidate = blvalidate; amroutine->ambeginscan = blbeginscan; @@ -145,6 +109,27 @@ blhandler(PG_FUNCTION_ARGS) PG_RETURN_POINTER(amroutine); } +void blReloptionPostprocess(void *data, bool validate) +{ + BloomOptions *opts = (BloomOptions*) data; + int i; + + if (validate) + for (i = 0; i < INDEX_MAX_KEYS; i++) + { + if (opts->bitSize[i] >= opts->bloomLength) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("col%i should not be grater than length", i))); + } + } + + /* Convert signature length from # of bits to # to words, rounding up */ + opts->bloomLength = (opts->bloomLength + SIGNWORDBITS - 1) / SIGNWORDBITS; +} + + /* * Fill BloomState structure for particular index. */ @@ -465,24 +450,38 @@ BloomInitMetapage(Relation index) UnlockReleaseBuffer(metaBuffer); } -/* - * Parse reloptions for bloom index, producing a BloomOptions struct. - */ -bytea * -bloptions(Datum reloptions, bool validate) +void * +blrelopt_catalog(void) { - relopt_value *options; - int numoptions; - BloomOptions *rdopts; + if (! bl_relopt_catalog) + { - /* Parse the user-given reloptions */ - options = parseRelOptions(reloptions, validate, bl_relopt_kind, &numoptions); - rdopts = allocateReloptStruct(sizeof(BloomOptions), options, numoptions); - fillRelOptions((void *) rdopts, sizeof(BloomOptions), options, numoptions, - validate, bl_relopt_tab, lengthof(bl_relopt_tab)); + int i; + char buf[16]; - /* Convert signature length from # of bits to # to words, rounding up */ - rdopts->bloomLength = (rdopts->bloomLength + SIGNWORDBITS - 1) / SIGNWORDBITS; + bl_relopt_catalog = allocateOptionsCatalog(NULL); + bl_relopt_catalog->struct_size = sizeof(BloomOptions); + bl_relopt_catalog->postprocess_fun = blReloptionPostprocess; + + optionsCatalogAddItemInt(bl_relopt_catalog, "length", + "Length of signature in bits", + NoLock, /* No lock as far as ALTER is forbidden */ + OPTION_DEFINITION_FLAG_FORBID_ALTER, + offsetof(BloomOptions, bloomLength), + DEFAULT_BLOOM_LENGTH, 1, MAX_BLOOM_LENGTH); - return (bytea *) rdopts; + /* Number of bits for each possible index column: col1, col2, ... */ + for (i = 0; i < INDEX_MAX_KEYS; i++) + { + snprintf(buf, 16, "col%d", i + 1); + optionsCatalogAddItemInt(bl_relopt_catalog, buf, + "Number of bits for corresponding column", + NoLock, /* No lock as far as ALTER is forbidden */ + OPTION_DEFINITION_FLAG_FORBID_ALTER, + offsetof(BloomOptions, bitSize[i]), + DEFAULT_BLOOM_BITS, 1, MAX_BLOOM_BITS); + } + + } + return (void *) bl_relopt_catalog; } diff --git a/contrib/bloom/expected/bloom.out b/contrib/bloom/expected/bloom.out index cbc50f7..7e3bbc0 100644 --- a/contrib/bloom/expected/bloom.out +++ b/contrib/bloom/expected/bloom.out @@ -210,3 +210,37 @@ ORDER BY 1; text_ops | t (2 rows) +-- reloptions test +DROP INDEX bloomidx; +CREATE INDEX bloomidx ON tst USING bloom (i, t) WITH (length=7, col1 = 4); +SELECT reloptions FROM pg_class WHERE oid = 'bloomidx'::regclass; + reloptions +------------------- + {length=7,col1=4} +(1 row) + +-- check for min and max values +CREATE INDEX bloomidx2 ON tst USING bloom (i, t) WITH (length=0); +ERROR: value 0 out of bounds for option "length" +DETAIL: Valid values are between "1" and "4096". +CREATE INDEX bloomidx2 ON tst USING bloom (i, t) WITH (length=4097); +ERROR: value 4097 out of bounds for option "length" +DETAIL: Valid values are between "1" and "4096". +CREATE INDEX bloomidx2 ON tst USING bloom (i, t) WITH (col1=0); +ERROR: value 0 out of bounds for option "col1" +DETAIL: Valid values are between "1" and "4095". +CREATE INDEX bloomidx2 ON tst USING bloom (i, t) WITH (col1=4096); +ERROR: value 4096 out of bounds for option "col1" +DETAIL: Valid values are between "1" and "4095". +-- check post_validate for colNamvacuumcleanup = brinvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = brincostestimate; - amroutine->amoptions = brinoptions; amroutine->amproperty = NULL; amroutine->amvalidate = brinvalidate; amroutine->ambeginscan = brinbeginscan; @@ -112,6 +112,7 @@ brinhandler(PG_FUNCTION_ARGS) amroutine->amendscan = brinendscan; amroutine->ammarkpos = NULL; amroutine->amrestrpos = NULL; + amroutine->amrelopt_catalog = bringetreloptcatalog; amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; @@ -750,36 +751,6 @@ brinvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats) } /* - * reloptions processor for BRIN indexes - */ -bytea * -brinoptions(Datum reloptions, bool validate) -{ - relopt_value *options; - BrinOptions *rdopts; - int numoptions; - static const relopt_parse_elt tab[] = { - {"pages_per_range", RELOPT_TYPE_INT, offsetof(BrinOptions, pagesPerRange)} - }; - - options = parseRelOptions(reloptions, validate, RELOPT_KIND_BRIN, - &numoptions); - - /* if none set, we're done */ - if (numoptions == 0) - return NULL; - - rdopts = allocateReloptStruct(sizeof(BrinOptions), options, numoptions); - - fillRelOptions((void *) rdopts, sizeof(BrinOptions), options, numoptions, - validate, tab, lengthof(tab)); - - pfree(options); - - return (bytea *) rdopts; -} - -/* * SQL-callable function to scan through an index and summarize all ranges * that are not currently summarized. */ @@ -1229,3 +1200,25 @@ brin_vacuum_scan(Relation idxrel, BufferAccessStrategy strategy) if (vacuum_fsm) FreeSpaceMapVacuum(idxrel); } + +static options_catalog * brin_relopt_catalog = NULL; + +void * +bringetreloptcatalog (void) +{ + if (! brin_relopt_catalog) + { + brin_relopt_catalog = allocateOptionsCatalog(NULL); + brin_relopt_catalog->struct_size = sizeof(BrinOptions); + + optionsCatalogAddItemInt(brin_relopt_catalog, "pages_per_range", + "Number of pages that each page range covers in a BRIN index", + NoLock, /* since ALTER is not allowed no lock needed */ + OPTION_DEFINITION_FLAG_FORBID_ALTER, + offsetof(BrinOptions, pagesPerRange), + BRIN_DEFAULT_PAGES_PER_RANGE, + BRIN_MIN_PAGES_PER_RANGE, + BRIN_MAX_PAGES_PER_RANGE); + } + return brin_relopt_catalog; +} diff --git a/src/backend/access/common/Makefile b/src/backend/access/common/Makefile index d4b8132..1ad3514 100644 --- a/src/backend/access/common/Makefile +++ b/src/backend/access/common/Makefile @@ -12,7 +12,7 @@ subdir = src/backend/access/common top_builddir = ../../../.. include $(top_builddir)/src/Makefile.global -OBJS = heaptuple.o indextuple.o printsimple.o printtup.o reloptions.o \ +OBJS = heaptuple.o indextuple.o options.o printsimple.o printtup.o reloptions.o \ scankey.o tupconvert.o tupdesc.o include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/access/common/options.c b/src/backend/access/common/options.c new file mode 100644 index 0000000..3b9a267 --- /dev/null +++ b/src/backend/access/common/options.c @@ -0,0 +1,1353 @@ +/*------------------------------------------------------------------------- + * + * options.c + * Core support for options used for relotions (pg_class.reloptions), + * attoptions (pg_attribute.attoptions), and can be used for other + * kinds of options. + * + * Here you can find functions that allow to define available options + * for certain object (certain retaion type, attribute type etc), and + * functions that allows to convert options from one represenation to + * another and validate them. + * + * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/access/common/options.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/options.h" +#include "catalog/pg_type.h" +#include "commands/defrem.h" +#include "nodes/makefuncs.h" +#include "utils/builtins.h" +#include "utils/guc.h" +#include "utils/memutils.h" +#include "mb/pg_wchar.h" + +/* + * Any object (i.e. relation type) that want to use options, should create an + * options catalog using allocateOptionsCatalog function, fill it with + * optionsCatalogAddItemXxxx functions with definition of available options. + * Then instance of this object can take option values from syntax analyzer + * when instance in created using SQL command or from attribute of + * pg_catalog.yyyy table, when object that already exists is loaded form + * pg_catalog. + * + * Now options are mainly used for all kind or relations (pg_class.reloption) + * and also for attributes (pg_attribute.attoptions) and tablespaces + * (pg_tablespace.spcoptions) + * + * Options catalog for index relation options should be available from + * amrelopt_catalog access method function. For non-index relations and for + * other objects catalogs are available via functions from this file. E.g. + * get_heap_relopt_catalog for heap relation options catalog. + * + * This file has two sections: + * + * 1. Functions for managing option catalogs + * + * 2. Functions for manipulating and transforming options + * + * More explanations are available at the beginning of each section + */ + + +static option_definition_basic * allocateOptionDefinition(int type, char *name, + char *desc, LOCKMODE lockmode, + option_definition_flags flags, int struct_offset); + +static void parse_one_option(option_value *option, char *text_str, + int text_len, bool validate); +void *optionsAllocateBytea(options_catalog *catalog, List *options); + + +/* + * Options catalog functions + */ + +/* + * Options catalog describes options available for certain object. Catalog has + * all necessary information for parsing transforming and validating options + * for an object. All parsing/validation/transformation functions should not + * know any details of option implementation for certain object, all this + * information should be stored in catalog instead and interpreted by + * pars/valid/transf functions blindly. + * + * The heart of the option catalog is an array of option definitions. Options + * definition specifies name of option, type, range of acceptable values, and + * default value. + * + * Options values can be one of the following types: bool, int, real, enum, + * string. For more info see "option_type" and "optionsCatalogAddItemYyyy" + * functions. + * + * Option definition flags allows to define parser behavior for special (or not + * so special) cases. See option_definition_flags for more info. + */ + + +/* + * allocateOptionsCatalog + * Allocate memory for a new catalog and initializes structure members. + * + * namespace - name of a namespace in which all items of the catalog + * exists (E.g. namespace.option=value). For now postgres uses only toast. + * namespace for tables. + */ + +options_catalog * +allocateOptionsCatalog(char *namespace) +{ + MemoryContext oldcxt; + options_catalog *catalog; + + oldcxt = MemoryContextSwitchTo(TopMemoryContext); + catalog = palloc(sizeof(options_catalog)); + if (namespace) + { + catalog->namespace=palloc(strlen(namespace) + 1); + strcpy(catalog->namespace, namespace); + } else + catalog->namespace = NULL; + + catalog->definitions = NULL; + catalog->num = 0; + catalog->num_allocated = 0; + catalog->struct_size = -1; /* Will Assert if not properly changed */ + catalog->postprocess_fun = NULL; + MemoryContextSwitchTo(oldcxt); + return catalog; +} + +/* + * optionCatalogAddItem + * Add an already-created option definition to the catalog + */ +static void +optionCatalogAddItem(option_definition_basic *newoption, + options_catalog * catalog) +{ + if (catalog->num + 1 >= catalog->num_allocated) + { + MemoryContext oldcxt; + + oldcxt = MemoryContextSwitchTo(TopMemoryContext); + + if (catalog->num_allocated == 0) + { + catalog->num_allocated = 8; + catalog->definitions = palloc( + catalog->num_allocated * sizeof(option_definition_basic *)); + } + else + { + catalog->num_allocated *= 2; + catalog->definitions = repalloc(catalog->definitions, + catalog->num_allocated * sizeof(option_definition_basic *)); + } + MemoryContextSwitchTo(oldcxt); + } + catalog->definitions[catalog->num] = newoption; + catalog->definitions[catalog->num + 1] = NULL; + catalog->num++; +} + +/* + * allocateOptionDefinition + * Allocate a new option definition and initialize the type-agnostic + * fields + */ +static option_definition_basic * +allocateOptionDefinition(int type, char *name, char *desc, LOCKMODE lockmode, + option_definition_flags flags, int struct_offset) +{ + MemoryContext oldcxt; + size_t size; + option_definition_basic *newoption; + + oldcxt = MemoryContextSwitchTo(TopMemoryContext); + + switch (type) + { + case OPTION_TYPE_BOOL: + size = sizeof(option_definition_bool); + break; + case OPTION_TYPE_INT: + size = sizeof(option_definition_int); + break; + case OPTION_TYPE_REAL: + size = sizeof(option_definition_real); + break; + case OPTION_TYPE_ENUM: + size = sizeof(option_definition_enum); + break; + case OPTION_TYPE_STRING: + size = sizeof(option_definition_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->type = type; + newoption->lockmode = lockmode; + newoption->flags = flags; + newoption->struct_offset = struct_offset; + + MemoryContextSwitchTo(oldcxt); + + return newoption; +} + +/* + * optionsCatalogAddItemBool + * Add a new boolean option definition to the catalog + */ +void +optionsCatalogAddItemBool(options_catalog * catalog, char *name, char *desc, + LOCKMODE lockmode, option_definition_flags flags, + int struct_offset, bool default_val) +{ + option_definition_bool *catalog_item; + + catalog_item = (option_definition_bool *) + allocateOptionDefinition(OPTION_TYPE_BOOL, name, desc, lockmode, + flags, struct_offset); + + catalog_item->default_val = default_val; + + optionCatalogAddItem((option_definition_basic *) catalog_item, catalog); +} + +/* + * optionsCatalogAddItemInt + * Add a new integer option definition to the catalog + */ +void +optionsCatalogAddItemInt(options_catalog * catalog, char *name, + char *desc, LOCKMODE lockmode, option_definition_flags flags, + int struct_offset, int default_val, int min_val, int max_val) +{ + option_definition_int *catalog_item; + + catalog_item = (option_definition_int *) + allocateOptionDefinition(OPTION_TYPE_INT, name, desc, lockmode, + flags, struct_offset); + + catalog_item->default_val = default_val; + catalog_item->min = min_val; + catalog_item->max = max_val; + + optionCatalogAddItem((option_definition_basic *) catalog_item, catalog); +} + +/* + * optionsCatalogAddItemReal + * Add a new float option to the catalog + */ +void +optionsCatalogAddItemReal(options_catalog * catalog, char *name, char *desc, + LOCKMODE lockmode, option_definition_flags flags, int struct_offset, + double default_val, double min_val, double max_val) +{ + option_definition_real *catalog_item; + + catalog_item = (option_definition_real *) + allocateOptionDefinition(OPTION_TYPE_REAL, name, desc, lockmode, + flags, struct_offset); + + catalog_item->default_val = default_val; + catalog_item->min = min_val; + catalog_item->max = max_val; + + optionCatalogAddItem((option_definition_basic *) catalog_item, catalog); +} + +/* + * optionsCatalogAddItemEnum + * Add a new enum option to the catalog + * + * "allowed_values" is a pointer to a NULL-terminated char* array (last item of + * an array should be null). This array contains a list of acceptable values + * for the option. + * + * "default_val" is a number of item in allowed_values array that should be + * set as default value. If you want to handle "option was not set" special + * case, you can set default_val to -1 + */ +void +optionsCatalogAddItemEnum(options_catalog * catalog, char *name, char *desc, + LOCKMODE lockmode, option_definition_flags flags, int struct_offset, + const char **allowed_values, int default_val) +{ + option_definition_enum *catalog_item; + + catalog_item = (option_definition_enum *) + allocateOptionDefinition(OPTION_TYPE_ENUM, name, desc, lockmode, + flags, struct_offset); + + catalog_item->default_val = default_val; + catalog_item->allowed_values = allowed_values; + + optionCatalogAddItem((option_definition_basic *) catalog_item, catalog); +} + +/* + * optionsCatalogAddItemString + * Add a new string option definition to the catalog + * + * "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 +optionsCatalogAddItemString(options_catalog * catalog, char *name, char *desc, + LOCKMODE lockmode, option_definition_flags flags, int struct_offset, + char *default_val, validate_string_relopt validator) +{ + option_definition_string *catalog_item; + + /* make sure the validator/default combination is sane */ + if (validator) + (validator) (default_val); + + catalog_item = (option_definition_string *) + allocateOptionDefinition(OPTION_TYPE_STRING, name, desc, lockmode, + flags, struct_offset); + catalog_item->validate_cb = validator; + + if (default_val) + catalog_item->default_val = MemoryContextStrdup(TopMemoryContext, + default_val); + else + catalog_item->default_val = NULL; + optionCatalogAddItem((option_definition_basic *) catalog_item, catalog); +} + + +/* + * Options transform functions + */ + +/* + * Option values exists in five representations: DefList, TextArray, Values and + * Bytea: + * + * DefList: Is a List of DefElem structures, that comes from syntax analyzer. + * It can be transformed to Values representation for further parsing and + * validating + * + * Values: A List of option_value structures. Is divided into two subclasses: + * RawValues, when values are already transformed from DefList or TextArray, + * but not parsed yet. (In this case you should use raw_name and raw_value + * structure members to see option content). ParsedValues (or just simple + * Values) is crated after finding a definition for this option in a catalog + * and after parsing of the raw value. For ParsedValues content is stored in + * values structure member, and name can be taken from option definition in gen + * structure member. Actually Value list can have both Raw and Parsed values, + * as we do not validate options that came from database, and db option that + * does not exist in catalog is just ignored, and kept as RawValues + * + * TextArray: The representation in which options for existing object comes + * and goes from/to database; for example from pg_class.reloptions. It is a + * plain TEXT[] db object with name=value text inside. This representation can + * be transformed into Values for further processing, using options catalog. + * + * Bytea: Is a binary representation of options. Each object that has code that + * uses options, should create a C-structure for this options, with varlen + * 4-byte header in front of the data; all items of options catalog should have + * an offset of a corresponding binary data in this structure, so transform + * function can put this data in the correct place. One can transform options + * data from values representation into Bytea, using catalog data, and then use + * it as a usual Datum object, when needed. This Datum should be cached + * somewhere (for example in rel->rd_options for relations) when object that + * has option is loaded from db. + */ + + +/* optionsDefListToRawValues + * Converts option values that came from syntax analyzer (DefList) into + * Values List. + * + * No parsing is done here except for checking that RESET syntax is correct + * (syntax analyzer do not see difference between SET and RESET cases, we + * should treat it here manually + */ +List * +optionsDefListToRawValues(List *defList, options_parse_mode parse_mode) +{ + ListCell *cell; + List *result = NIL; + + foreach(cell, defList) + { + option_value *option_dst; + DefElem *def = (DefElem *) lfirst(cell); + char *value; + + option_dst = palloc(sizeof(option_value)); + + if (def->defnamespace) + { + option_dst->namespace = palloc(strlen(def->defnamespace) + 1); + strcpy(option_dst->namespace,def->defnamespace); + } else + { + option_dst->namespace = NULL; + } + option_dst->raw_name = palloc(strlen(def->defname) + 1); + strcpy(option_dst->raw_name, def->defname); + + if (parse_mode & OPTIONS_PARSE_MODE_FOR_RESET) + { + /* + * If this option came from RESET statement we should throw error + * it it brings us name=value data, as syntax analyzer do not + * prevent it + */ + if (def->arg != NULL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("RESET must not include values for parameters"))); + + option_dst->status = OPTION_VALUE_STATUS_FOR_RESET; + } + else + { + /* + * For SET statement we should treat (name) expression as if it + * is actually (name=true) so do it here manually. In other cases + * just use value as we should use it + */ + option_dst->status = OPTION_VALUE_STATUS_RAW; + if (def->arg != NULL) + value = defGetString(def); + else + value = "true"; + option_dst->raw_value = palloc(strlen(value) + 1); + strcpy(option_dst->raw_value,value); + } + + result = lappend(result, option_dst); + } + return result; +} + +/* + * optionsValuesToTextArray + * Converts List of option_values into TextArray + * + * Convertation is made to put options into database (e.g. in + * pg_class.reloptions for all relation options) + */ + +Datum +optionsValuesToTextArray(List *options_values) +{ + ArrayBuildState *astate = NULL; + ListCell *cell; + Datum result; + foreach(cell, options_values) + { + option_value *option = (option_value *) lfirst(cell); + const char *name; + char *value; + text *t; + int len; + + /* + * Raw value were not cleared while parsing, so instead of converting + * it back, just use it to store value as text + */ + value = option->raw_value; + + Assert(option->status != OPTION_VALUE_STATUS_EMPTY); + + /* + * Name will be taken from option definition, if option were parsed or + * from raw_name if option were not parsed for some reason + */ + if (option->status == OPTION_VALUE_STATUS_PARSED) + name = option->gen->name; + else + name = option->raw_name; + + /* + * Now build "name=value" string and append it to the array + */ + len = VARHDRSZ + strlen(name) + strlen(value) + 1; + t = (text *) palloc(len + 1); + SET_VARSIZE(t, len); + sprintf(VARDATA(t), "%s=%s", name, value); + astate = accumArrayResult(astate, PointerGetDatum(t), false, + TEXTOID, CurrentMemoryContext); + } + if (astate) + result = makeArrayResult(astate, CurrentMemoryContext); + else + result = (Datum) 0; + + return result; +} + +/* + * optionsTextArrayToRawValues + * Converts options from TextArray format into RawValues list. + * + * This function is used to convert options data that comes from database to + * List of option_values, for further parsing, and, in the case of ALTER + * command, for merging with new option values. + */ +List * +optionsTextArrayToRawValues(Datum array_datum) +{ + List *result = NIL; + + if (PointerIsValid(DatumGetPointer(array_datum))) + { + ArrayType *array = DatumGetArrayTypeP(array_datum); + Datum *options; + int noptions; + int i; + + deconstruct_array(array, TEXTOID, -1, false, 'i', + &options, NULL, &noptions); + + for (i = 0; i < noptions; i++) + { + text *option_src = DatumGetTextP(options[i]); + option_value * option_dst; + char *text_str = VARDATA(option_src); + int text_len = VARSIZE(option_src) - VARHDRSZ; + int i; + int name_len = -1; + char *name; + int raw_value_len; + char *raw_value; + + /* + * Find position of '=' sign and treat id as a separator + * between name and value in "name=value" item + */ + for(i = 0; i < text_len; i = i + pg_mblen(text_str)) + { + if (text_str[i] == '=') + { + name_len = i; + break; + } + } + Assert(name_len >= 1); /* Just in case */ + + raw_value_len = text_len - name_len - 1; + + /* + * Copy name from src + */ + name = palloc(name_len + 1); + memcpy(name, text_str, name_len); + name[name_len] = '\0'; + + /* + * Copy value from src + */ + raw_value = palloc(raw_value_len + 1); + memcpy(raw_value, text_str + name_len + 1, raw_value_len); + raw_value[raw_value_len] = '\0'; + + /* + * Create new option_value item + */ + option_dst = palloc(sizeof(option_value)); + option_dst->status = OPTION_VALUE_STATUS_RAW; + option_dst->raw_name = name; + option_dst->raw_value = raw_value; + + result = lappend(result, option_dst); + } + } + return result; +} + +/* + * optionsMergeOptionValues + * Merges two lists of option_values into one list + * + * This function is used to merge two Values list into one. It is used for all + * kinds of ALTER commands when existing options are merged|replaced with new + * options list. This function also process RESET variant of ALTER command. It + * merges two lists as usual, and then removes all items with RESET flag on. + * + * Both incoming lists will be destroyed while merging + */ +List * +optionsMergeOptionValues(List *old_options, List *new_options) +{ + List * result = NIL; + ListCell *old_cell; + ListCell *old_prev; + ListCell *old_next; + ListCell *new_cell; + ListCell *new_prev; + ListCell *new_next; + + old_prev = NULL; + + /* + * First try to remove from old options list all values + * that exists in a new options list + */ + for (old_cell = list_head(old_options); old_cell; old_cell = old_next) + { + bool found; + const char *old_name; + option_value *old_option; + + old_next = lnext(old_cell); + old_option = (option_value *) lfirst(old_cell); + if (old_option->status == OPTION_VALUE_STATUS_PARSED) + old_name = old_option->gen->name; + else + old_name = old_option->raw_name; + + /* + * Try to find old_name option among new option + */ + found = false; + foreach(new_cell, new_options) + { + option_value *new_option; + const char *new_name; + new_option = (option_value *) lfirst(new_cell); + if (new_option->status == OPTION_VALUE_STATUS_PARSED) + new_name = new_option->gen->name; + else + new_name = new_option->raw_name; + if (pg_strcasecmp(new_name, old_name) == 0) + { + found = true; + break; + } + } + /* + * If found, delete old option from the list + */ + if (found) + { + old_options = list_delete_cell(old_options,old_cell,old_prev); + } + else + { + old_prev = old_cell; + } + } + + /* + * Remove from new_options all options that are for RESET. In old list all + * this options were already removed in previous block, so all RESET + * options are cleaned now + */ + new_prev = NULL; + for (new_cell = list_head(new_options); new_cell; new_cell = new_next) + { + option_value *new_option = (option_value *) lfirst(new_cell); + new_next = lnext(new_cell); + + if (new_option->status == OPTION_VALUE_STATUS_FOR_RESET) + new_options = list_delete_cell(new_options,new_cell,new_prev); + else + new_prev = new_cell; + } + + /* + * Now merge what remained of both lists + */ + result = list_concat(old_options,new_options); + return result; +} + +/* + * optionsDefListValdateNamespaces + * Function checks that all options represented as DefList has no + * namespaces or have namespaces only from allowed list + * + * Function accept options as DefList and NULL terminated list of allowed + * namespaces. It throws an error if not proper namespace was found. + * + * This function actually used only for tables with it's toast. namespace + */ +void +optionsDefListValdateNamespaces(List *defList, char **allowed_namespaces) +{ + ListCell *cell; + + foreach(cell, defList) + { + DefElem *def = (DefElem *) lfirst(cell); + /* + * Checking namespace only for options that have namespaces. + * Options with no namespaces are always accepted + */ + if (def->defnamespace) + { + bool found = false; + int i = 0; + while (allowed_namespaces[i]) + { + if (pg_strcasecmp(def->defnamespace, + allowed_namespaces[i]) == 0) + { + found = true; + break; + } + i++; + } + if (! found) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unrecognized parameter namespace \"%s\"", + def->defnamespace))); + } + } +} + +/* + * optionsDefListFilterNamespaces + * Iterates over DefList, choose items with specified namespace and adds + * them to a result List + * + * This function does not destroy source DefList but does not create copies + * of List nodes. + * It is actually used only for tables, in order to split toast and heap + * reloptions, so each one can be stored in on it's own pg_class record + */ +List * +optionsDefListFilterNamespaces(List *defList, char *namespace) +{ + ListCell *cell; + List *result = NIL; + + foreach(cell, defList) + { + DefElem *def = (DefElem *) lfirst(cell); + if ((! namespace && ! def->defnamespace) || + (namespace && def->defnamespace && + pg_strcasecmp(namespace, def->defnamespace) == 0)) + { + result = lappend(result, def); + } + } + return result; +} + +/* + * optionsTextArrayToDefList + * Convert the text-array format of reloptions into a List of DefElem. + */ +List * +optionsTextArrayToDefList(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; +} + +/* + * optionsParseRawValues + * Parses and vlaidates (if proper flag is set) option_values. As a result + * caller will get the list of parsed (or partly parsed) option_values + * + * This function is used in cases when caller gets raw values from db or + * syntax and want to parse them. + * This function uses option_catalog to get information about how each option + * should be parsed. + * If validate mode is off, function found an option that do not have proper + * option_catalog entry, this option kept unparsed (if some garbage came from + * the DB, we should put it back there) + * + * This function destroys incoming list. + */ +List * +optionsParseRawValues(List * raw_values, options_catalog *catalog, + options_parse_mode mode) +{ + ListCell *cell; + List *result = NIL; + bool *is_set; + int i; + bool validate = mode & OPTIONS_PARSE_MODE_VALIDATE; + bool for_alter = mode & OPTIONS_PARSE_MODE_FOR_ALTER; + + + is_set = palloc0(sizeof(bool)*catalog->num); + foreach(cell, raw_values) + { + option_value *option = (option_value *) lfirst(cell); + bool found = false; + bool skip = false; + + + if (option->status == OPTION_VALUE_STATUS_PARSED) + { + /* + * This can happen while ALTER, when new values were already + * parsed, but old values merged from DB are still raw + */ + result = lappend(result, option); + continue; + } + if (validate && option->namespace && (! catalog->namespace || + pg_strcasecmp(catalog->namespace, option->namespace) != 0)) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unrecognized parameter namespace \"%s\"", + option->namespace))); + } + + for (i=0;inum;i++) + { + option_definition_basic * definition = catalog->definitions[i]; + + if (pg_strcasecmp(option->raw_name, + definition->name) == 0) + { + /* + * Skip option with "ignore" flag, as it is processed somewhere + * else. (WITH OIDS special case) + */ + if (definition->flags & OPTION_DEFINITION_FLAG_IGNORE) + { + found = true; + skip = true; + break; + } + /* + * Reject option as if it was not in catalog. Needed for cases + * when option should have default value, but should not + * be changed + */ + if (definition->flags & OPTION_DEFINITION_FLAG_REJECT) + { + found = false; + break; + } + + if (validate && is_set[i]) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("parameter \"%s\" specified more than once", + option->raw_name))); + } + if ((for_alter) && + (definition->flags & OPTION_DEFINITION_FLAG_FORBID_ALTER )) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("changing parameter \"%s\" is not allowed", + definition->name))); + } + if (option->status == OPTION_VALUE_STATUS_FOR_RESET) + { + /* + * For RESET options do not need further processing + * so mark it found and stop searching + */ + found = true; + break; + } + pfree(option->raw_name); + option->raw_name = NULL; + option->gen = definition; + parse_one_option(option, NULL, -1, validate); + is_set[i] = true; + found = true; + break; + } + } + if (! found) + { + if (validate) + { + if(option->namespace) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unrecognized parameter \"%s.%s\"", + option->namespace, option->raw_name))); + else + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unrecognized parameter \"%s\"", + option->raw_name))); + } + /* If we are parsing not in validate mode, then we should keep + * unknown node, because non-validate mode is for data that is + * already in the DB and should not be changed after altering + * another entries */ + } + if (! skip) + result = lappend(result, option); + } + return result; +} + +/* + * parse_one_option + * + * Subroutine for optionsParseRawValues, to parse and validate a + * single option's value + */ +static void +parse_one_option(option_value *option, char *text_str, int text_len, + bool validate) +{ + char *value; + bool parsed; + + value = option->raw_value; + + switch (option->gen->type) + { + case OPTION_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 OPTION_TYPE_INT: + { + option_definition_int *optint = + (option_definition_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 OPTION_TYPE_REAL: + { + option_definition_real *optreal = + (option_definition_real *) option->gen; + + parsed = parse_real(value, &option->values.real_val); + 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 OPTION_TYPE_ENUM: + { + option_definition_enum *opt_enum = + (option_definition_enum *) option->gen; + int i = 0; + parsed = false; + while (opt_enum->allowed_values[i]) + { + if (pg_strcasecmp(value, opt_enum->allowed_values[i]) == 0) + { + option->values.enum_val = i; + parsed = true; + break; + } + i++; + } + if (! parsed) + { + int length = 0; + char *str; + char *ptr; + /* Generating list of allowed values: + * "value1", "value2", ... "valueN" */ + i = 0; + while (opt_enum->allowed_values[i]) + { + length += strlen(opt_enum->allowed_values[i]) + 4; + /* +4: two quotes, one comma, one space */ + i++; + } + /* one byte not used for comma after the last item will be + * used for \0; for another byte will do -1 */ + str = palloc((length - 1 ) * sizeof(char)); + i = 0; + ptr = str; + while (opt_enum->allowed_values[i]) + { + if (i != 0) + { + ptr[0] = ','; + ptr[1] = ' '; + ptr += 2; + } + ptr[0]='"'; + ptr++; + sprintf(ptr,"%s",opt_enum->allowed_values[i]); + ptr += strlen(ptr); + ptr[0] = '"'; + ptr++; + i++; + } + *ptr='\0'; + + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid value for \"%s\" option", + option->gen->name), + errdetail("Valid values are %s.", str))); + } + } + break; + case OPTION_TYPE_STRING: + { + option_definition_string *optstring = + (option_definition_string *) option->gen; + + option->values.string_val = value; + 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->status = OPTION_VALUE_STATUS_PARSED; + +} + +/* + * optionsAllocateBytea + * Allocates memory for bytea options representation + * + * Function allocates memory for byrea structure of an option, plus adds space + * for values of string options. We should keep all data including string + * values in the same memory chunk, because Cache code copies bytea option + * data from one MemoryConext to another without knowing about it's internal + * structure, so it would not be able to copy string values if they are outside + * of bytea memory chunk. + */ +void * +optionsAllocateBytea(options_catalog *catalog, List *options) +{ + Size size; + int i; + ListCell *cell; + int length; + void *res; + + Assert(catalog->struct_size > 0); /* Make sure it was initialized */ + size = catalog->struct_size; + + /* Calculate size needed to store all string values for this option */ + for (i=0;inum;i++) + { + option_definition_basic *definition = catalog->definitions[i]; + bool found = false; + option_value *option; + + /* Not interested in non-string options, skipping*/ + if (definition->type != OPTION_TYPE_STRING) + continue; + + /* + * Trying to find option_value that references definition + * catalog entry + */ + foreach(cell, options) + { + option = (option_value *) lfirst(cell); + if (option->status == OPTION_VALUE_STATUS_PARSED && + pg_strcasecmp(option->gen->name, definition->name) ==0) + { + found = true; + break; + } + } + if (found) + /* If found, it'value will be stored */ + length = strlen(option->values.string_val)+1; + else + /* If not found, then there would be default value there */ + if (((option_definition_string*)definition)->default_val) + length = strlen( + ((option_definition_string*)definition)->default_val) + 1; + else + length = 0; + /* Add total length of all string values to basic size */ + size += length; + } + + res = palloc0(size); + SET_VARSIZE(res, size); + return res; +} + +/* + * optionsValuesToBytea + * Converts options from List of option_values to binary bytea structure + * + * Convertation goes according to options_catalog: each catalog item + * has offset value, and option value in binary mode is written to the + * structure with that offset. + * + * More special case is string values. Memory for bytea structure is allocated + * by optionsAllocateBytea which adds some more space for string values to + * the size of original structure. All string values are copied there and + * inside the bytea structure an offset to that value is kept. + * + */ +bytea * +optionsValuesToBytea(List * options, options_catalog *catalog) +{ + char *data; + char *string_values_buffer; + int i; + + data = optionsAllocateBytea(catalog, options); + + /* place for string data starts right after original structure */ + string_values_buffer = data + catalog->struct_size; + + for (i = 0; i < catalog->num; i++) + { + option_value *found = NULL; + ListCell *cell; + char *item_pos; + option_definition_basic *definition = catalog->definitions[i]; + + if (definition->flags & OPTION_DEFINITION_FLAG_IGNORE) + continue; + + /* Calculate the position of the item inside the structure */ + item_pos = data + definition->struct_offset; + + /* Looking for the corresponding option from options list*/ + foreach(cell, options) + { + option_value *option = (option_value *) lfirst(cell); + + if (option->status == OPTION_VALUE_STATUS_RAW) + continue; /* raw can come from db. Just ignore them then*/ + Assert(option->status != OPTION_VALUE_STATUS_EMPTY); + + if (pg_strcasecmp(definition->name,option->gen->name) == 0) + { + found = option; + break; + } + } + /* writing to the proper position either option value or default val */ + switch (definition->type) + { + case OPTION_TYPE_BOOL: + *(bool *) item_pos = found ? + found->values.bool_val : + ((option_definition_bool *) definition)->default_val; + break; + case OPTION_TYPE_INT: + *(int *) item_pos = found ? + found->values.int_val : + ((option_definition_int *) definition)->default_val; + break; + case OPTION_TYPE_REAL: + *(double *) item_pos = found ? + found->values.real_val : + ((option_definition_real *) definition)->default_val; + break; + case OPTION_TYPE_ENUM: + *(int *) item_pos = found ? + found->values.enum_val : + ((option_definition_enum *) definition)->default_val; + break; + + case OPTION_TYPE_STRING: + { + /* + * For string options: writing string value at the string + * buffer after the structure, and storing and offset + * to that value + */ + char *value = NULL; + if (found) + value = found->values.string_val; + else + value = ((option_definition_string *) definition) + ->default_val; + *(int *) item_pos = value ? + string_values_buffer - data : + OPTION_STRING_VALUE_NOT_SET_OFFSET; + if (value) + { + strcpy(string_values_buffer,value); + string_values_buffer += strlen(value) + 1; + } + } + break; + default: + elog(ERROR, "unsupported reloption type %d", + definition->type); + break; + } + } + return (void*) data; +} + + +/* + * transformOptions + * This function is used by src/backend/commands/Xxxx in order to process + * new option values, merge them with existing values (in the case of + * ALTER command) and prepare to put them [back] into DB + */ + +Datum +transformOptions(options_catalog * catalog, Datum oldOptions, + List *defList, options_parse_mode parse_mode) +{ + Datum result; + List *new_values; + List *old_values; + List *merged_values; + + /* + * Parse and validate New values + */ + new_values = optionsDefListToRawValues(defList, parse_mode); + new_values = optionsParseRawValues(new_values, catalog, + parse_mode | OPTIONS_PARSE_MODE_VALIDATE); + + /* + * Old values exists in case of ALTER commands. Transform them to raw + * values and merge them with new_values, and parse it. + */ + if (PointerIsValid(DatumGetPointer(oldOptions))) + { + old_values = optionsTextArrayToRawValues(oldOptions); + merged_values = optionsMergeOptionValues(old_values, new_values); + /* + * Parse options only after merging in order not to parse options + * that would be removed by merging later + */ + merged_values = optionsParseRawValues(merged_values, catalog, 0); + } else + { + merged_values = new_values; + } + + /* + * If we have postprocess_fun function defined in catalog, then there might + * be some custom options checks there, with error throwing. So we should + * do it here to throw these errors while CREATing or ALTERing options + */ + if (catalog->postprocess_fun) + { + bytea *data = optionsValuesToBytea(merged_values, catalog); + catalog->postprocess_fun(data, true); + pfree(data); + } + /* + * Convert options to TextArray format so caller can store them into + * database + */ + result = optionsValuesToTextArray(merged_values); + return result; +} + + +/* + * optionsTextArrayToBytea + * A meta-function that transforms options stored as TextArray into binary + * (bytea) representation. + * + * This function runs other transform functions that leads to the desired + * result in no-validation mode. This function is used by cache mechanism, + * in order to load and cache options when object itself is loaded and cached + */ +bytea * +optionsTextArrayToBytea(options_catalog *catalog, Datum data) +{ + List *values; + bytea *options; + values = optionsTextArrayToRawValues(data); + values = optionsParseRawValues(values, catalog, 0); + options = optionsValuesToBytea(values, catalog); + + if (catalog->postprocess_fun) + { + catalog->postprocess_fun(options, false); + } + return options; +} diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 816483e..c872d6a 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -1,7 +1,10 @@ /*------------------------------------------------------------------------- * * reloptions.c - * Core support for relation options (pg_class.reloptions) + * Support for options relotions (pg_class.reloptions). + * Reloptions for non-Access Metod relations are defined here. + * There is also extractRelOptions function to extract reloptions + * from pg_class table for all kind of relations. * * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California @@ -15,884 +18,16 @@ #include "postgres.h" -#include "access/gist_private.h" -#include "access/hash.h" #include "access/htup_details.h" -#include "access/nbtree.h" +#include "access/options.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" +#include "storage/bufmgr.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__reloption). - * (iii) add it to the appropriate options struct (perhaps StdRdOptions) - * (iv) add it to the appropriate handling routine (perhaps - * default_reloptions) - * (v) don't forget to document the option - * - * Note that we don't handle "oids" in relOpts because it is handled by - * interpretOidsOption(). - */ - -static relopt_bool boolRelOpts[] = -{ - { - { - "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 - }, - /* 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_delay", - "Vacuum cost delay in milliseconds, for autovacuum", - RELOPT_KIND_HEAP | RELOPT_KIND_TOAST, - ShareUpdateExclusiveLock - }, - -1, 0, 100 - }, - { - { - "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 - }, - { - { - "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, - AccessExclusiveLock - }, -#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, - AccessExclusiveLock - }, - -1, 0, 1024 - }, - - /* list terminator */ - {{NULL}} -}; - -static relopt_real realRelOpts[] = -{ - { - { - "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, - AccessExclusiveLock - }, - -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, - AccessExclusiveLock - }, - -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, - AccessExclusiveLock - }, - 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, - AccessExclusiveLock - }, - 0, -1.0, DBL_MAX - }, - /* list terminator */ - {{NULL}} -}; - -static relopt_string stringRelOpts[] = -{ - { - { - "buffering", - "Enables buffering build for this GiST index", - RELOPT_KIND_GIST, - AccessExclusiveLock - }, - 4, - false, - gistValidateBufferingOption, - "auto" - }, - { - { - "check_option", - "View has WITH CHECK OPTION defined (local or cascaded).", - RELOPT_KIND_VIEW, - AccessExclusiveLock - }, - 0, - true, - validateWithCheckOption, - NULL - }, - /* 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; 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; 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, char *name, char *desc) -{ - 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_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; - - MemoryContextSwitchTo(oldcxt); - - return newoption; -} - -/* - * add_bool_reloption - * Add a new boolean reloption - */ -void -add_bool_reloption(bits32 kinds, char *name, char *desc, bool default_val) -{ - relopt_bool *newoption; - - newoption = (relopt_bool *) allocate_reloption(kinds, RELOPT_TYPE_BOOL, - name, desc); - newoption->default_val = default_val; - - add_reloption((relopt_gen *) newoption); -} - -/* - * add_int_reloption - * Add a new integer reloption - */ -void -add_int_reloption(bits32 kinds, char *name, char *desc, int default_val, - int min_val, int max_val) -{ - relopt_int *newoption; - - newoption = (relopt_int *) allocate_reloption(kinds, RELOPT_TYPE_INT, - name, desc); - 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, char *name, char *desc, double default_val, - double min_val, double max_val) -{ - relopt_real *newoption; - - newoption = (relopt_real *) allocate_reloption(kinds, RELOPT_TYPE_REAL, - name, desc); - newoption->default_val = default_val; - newoption->min = min_val; - newoption->max = max_val; - - 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, char *name, char *desc, char *default_val, - validate_string_relopt validator) -{ - 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); - 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 ignoreOids is true, then we should ignore any occurrence of "oids" - * in the list (it will be or has been handled by interpretOidsOption()). - * - * 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, char *namspace, - char *validnsps[], bool ignoreOids, 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++) - { - text *oldoption = DatumGetTextP(oldoptions[i]); - char *text_str = VARDATA(oldoption); - int text_len = VARSIZE(oldoption) - 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 (pg_strcasecmp(def->defnamespace, namspace) != 0) - continue; - - kw_len = strlen(def->defname); - if (text_len > kw_len && text_str[kw_len] == '=' && - pg_strncasecmp(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 (pg_strcasecmp(def->defnamespace, - validnsps[i]) == 0) - { - valid = true; - break; - } - } - } - - if (!valid) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("unrecognized parameter namespace \"%s\"", - def->defnamespace))); - } - - if (ignoreOids && pg_strcasecmp(def->defname, "oids") == 0) - continue; - - /* ignore if not in the same namespace */ - if (namspace == NULL) - { - if (def->defnamespace != NULL) - continue; - } - else if (def->defnamespace == NULL) - continue; - else if (pg_strcasecmp(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"; - 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; -} +void add_autovacuum_options(options_catalog *catalog, int base_offset, + bool for_toast); /* * Extract and parse reloptions from a pg_class tuple. @@ -908,11 +43,12 @@ untransformRelOptions(Datum options) */ bytea * extractRelOptions(HeapTuple tuple, TupleDesc tupdesc, - amoptions_function amoptions) + amrelopt_catalog_function catalog_fun) { - bytea *options; + bytea *options = NULL; bool isnull; Datum datum; + options_catalog *catalog = NULL; Form_pg_class classForm; datum = fastgetattr(tuple, @@ -928,16 +64,19 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc, switch (classForm->relkind) { case RELKIND_RELATION: - case RELKIND_TOASTVALUE: case RELKIND_MATVIEW: case RELKIND_PARTITIONED_TABLE: - options = heap_reloptions(classForm->relkind, datum, false); + catalog = get_heap_relopt_catalog(); + break; + case RELKIND_TOASTVALUE: + catalog = get_toast_relopt_catalog(); break; case RELKIND_VIEW: - options = view_reloptions(datum, false); + catalog = get_view_relopt_catalog(); break; case RELKIND_INDEX: - options = index_reloptions(amoptions, datum, false); + if (catalog_fun) + catalog = catalog_fun(); break; case RELKIND_FOREIGN_TABLE: options = NULL; @@ -948,529 +87,293 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc, break; } + if (catalog) + options = optionsTextArrayToBytea(catalog, datum); 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. + * Here goes functions that build catalogs for non-index relations. Catalogs + * for index relations are available from Access Methods, all other + * relation catalogs are here + */ + +/* + * Both heap and toast relation have almost the same autovacuum set of options. + * These autovacuum options are stored in the same structure (AutoVacOpts) + * that is a part of both HeapOptions and ToastOptions. * - * If there are no options of the given kind, numrelopts is set to 0 and NULL - * is returned. + * Function add_autovacuum_options adds to the "catalog" catalog autovacuum + * options definition. In binary mode this options are saved into AutoVacOpts + * structure, which is located in result bytea chunk with base_offset offset. * - * 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. + * Heap has two options (autovacuum_analyze_threshold and + * autovacuum_analyze_scale_factor) that is not used for toast. So for toast + * case this options are added to catalog with OPTION_DEFINITION_FLAG_REJECT + * flag set, so postgres will reject this toast options from the CREATE or + * ALTER command, but still will save default value in binary representation */ -relopt_value * -parseRelOptions(Datum options, bool validate, relopt_kind kind, - int *numrelopts) -{ - relopt_value *reloptions; - 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) - { - *numrelopts = 0; - return NULL; - } - - 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++) - { - text *optiontext = DatumGetTextP(optiondatums[i]); - char *text_str = VARDATA(optiontext); - int text_len = VARSIZE(optiontext) - 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] == '=' && - pg_strncasecmp(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; +void +add_autovacuum_options(options_catalog *catalog, int base_offset, + bool for_toast) +{ + optionsCatalogAddItemBool(catalog, "autovacuum_enabled", + "Enables autovacuum in this relation", + ShareUpdateExclusiveLock, + 0, base_offset + offsetof(AutoVacOpts, enabled), true); + + optionsCatalogAddItemInt(catalog, "autovacuum_vacuum_threshold", + "Minimum number of tuple updates or deletes prior to vacuum", + ShareUpdateExclusiveLock, + 0, base_offset + offsetof(AutoVacOpts, vacuum_threshold), + -1, 0, INT_MAX); + + optionsCatalogAddItemInt(catalog, "autovacuum_analyze_threshold", + "Minimum number of tuple updates or deletes prior to vacuum", + ShareUpdateExclusiveLock, + for_toast ? OPTION_DEFINITION_FLAG_REJECT : 0, + base_offset + offsetof(AutoVacOpts, analyze_threshold), + -1, 0, INT_MAX); + + optionsCatalogAddItemInt(catalog, "autovacuum_vacuum_cost_delay", + "Vacuum cost delay in milliseconds, for autovacuum", + ShareUpdateExclusiveLock, + 0, base_offset + offsetof(AutoVacOpts, vacuum_cost_delay), + -1, 0, 100); + + optionsCatalogAddItemInt(catalog, "autovacuum_vacuum_cost_limit", + "Vacuum cost amount available before napping, for autovacuum", + ShareUpdateExclusiveLock, + 0, base_offset + offsetof(AutoVacOpts, vacuum_cost_limit), + -1, 0, 10000); + + optionsCatalogAddItemInt(catalog, "autovacuum_freeze_min_age", + "Minimum age at which VACUUM should freeze a table row, for autovacuum", + ShareUpdateExclusiveLock, + 0, base_offset + offsetof(AutoVacOpts, freeze_min_age), + -1, 0, 1000000000); + + optionsCatalogAddItemInt(catalog, "autovacuum_freeze_max_age", + "Age at which to autovacuum a table to prevent transaction ID wraparound", + ShareUpdateExclusiveLock, + 0, base_offset + offsetof(AutoVacOpts, freeze_max_age), + -1, 100000, 2000000000); + + optionsCatalogAddItemInt(catalog, "autovacuum_freeze_table_age", + "Age at which VACUUM should perform a full table sweep to freeze row versions", + ShareUpdateExclusiveLock, + 0, base_offset + offsetof(AutoVacOpts, freeze_table_age), + -1, 0, 2000000000); + + optionsCatalogAddItemInt(catalog, "autovacuum_multixact_freeze_min_age", + "Minimum multixact age at which VACUUM should freeze a row multixact's, for autovacuum", + ShareUpdateExclusiveLock, + 0, base_offset + offsetof(AutoVacOpts, multixact_freeze_min_age), + -1, 0, 1000000000); + + optionsCatalogAddItemInt(catalog, "autovacuum_multixact_freeze_max_age", + "Multixact age at which to autovacuum a table to prevent multixact wraparound", + ShareUpdateExclusiveLock, + 0, base_offset + offsetof(AutoVacOpts, multixact_freeze_max_age), + -1, 10000, 2000000000); + + optionsCatalogAddItemInt(catalog, "autovacuum_multixact_freeze_table_age", + "Age of multixact at which VACUUM should perform a full table sweep to freeze row versions", + ShareUpdateExclusiveLock, + 0, base_offset + offsetof(AutoVacOpts, multixact_freeze_table_age), + -1, 0, 2000000000); + + optionsCatalogAddItemInt(catalog, "log_autovacuum_min_duration", + "Sets the minimum execution time above which autovacuum actions will be logged", + ShareUpdateExclusiveLock, + 0, base_offset + offsetof(AutoVacOpts, log_min_duration), + -1, -1, INT_MAX); + + optionsCatalogAddItemReal(catalog, "autovacuum_vacuum_scale_factor", + "Number of tuple updates or deletes prior to vacuum as a fraction of reltuples", + ShareUpdateExclusiveLock, + 0, base_offset + offsetof(AutoVacOpts, vacuum_scale_factor), + -1, 0.0, 100.0); + + optionsCatalogAddItemReal(catalog, "autovacuum_analyze_scale_factor", + "Number of tuple inserts, updates or deletes prior to analyze as a fraction of reltuples", + ShareUpdateExclusiveLock, + for_toast ? OPTION_DEFINITION_FLAG_REJECT : 0, + base_offset + offsetof(AutoVacOpts, analyze_scale_factor), + -1, 0.0, 100.0); } /* - * Subroutine for parseRelOptions, to parse and validate a single option's - * value + * get_heap_relopt_catalog + * Returns an options catalog for heap relation. */ -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))); +static options_catalog * heap_relopt_catalog = NULL; - 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) +options_catalog * +get_heap_relopt_catalog(void) +{ + if (! heap_relopt_catalog) { - 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; + heap_relopt_catalog = allocateOptionsCatalog(NULL); + heap_relopt_catalog->struct_size = sizeof(HeapOptions); - 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; + optionsCatalogAddItemInt(heap_relopt_catalog, "fillfactor", + "Packs table pages only to this percentag", + ShareUpdateExclusiveLock, /*since it applies only to later inserts*/ + 0, offsetof(HeapOptions, fillfactor), + HEAP_DEFAULT_FILLFACTOR, HEAP_MIN_FILLFACTOR, 100); - parsed = parse_real(value, &option->values.real_val); - 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_STRING: - { - relopt_string *optstring = (relopt_string *) option->gen; + add_autovacuum_options(heap_relopt_catalog, + offsetof(HeapOptions, autovacuum), false); - 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; + optionsCatalogAddItemBool(heap_relopt_catalog, "user_catalog_table", + "Declare a table as an additional catalog table, e.g. for the purpose of logical replication", + AccessExclusiveLock, + 0, offsetof(HeapOptions, user_catalog_table), + false); + + optionsCatalogAddItemInt(heap_relopt_catalog, "parallel_workers", + "Number of parallel processes that can be used per executor node for this relation", + AccessExclusiveLock, + 0, offsetof(HeapOptions, parallel_workers), + -1, 0, 1024); + + /* + * A WITH OID / WITHOUT OIDS expressions are converted by syntax parser + * into "oids" relation option, but this option is processed by + * interpretOidsOption(), that does not use core options code. That is + * why here we should just ignore this option as if it does not exist. + * Do it with OPTION_DEFINITION_FLAG_IGNORE flag + */ + optionsCatalogAddItemBool(heap_relopt_catalog, "oids", + "Option used for WITH OIDS expression. Processed elsewhere", + NoLock, + OPTION_DEFINITION_FLAG_IGNORE, -1, + false); } - - if (parsed) - option->isset = true; - if (!nofree) - pfree(value); + return heap_relopt_catalog; } /* - * 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). + * get_toast_relopt_catalog + * Returns an options catalog for toast relation. */ -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; +static options_catalog * toast_relopt_catalog = NULL; - 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) +options_catalog * +get_toast_relopt_catalog(void) { - int i; - int offset = basesize; - - for (i = 0; i < numoptions; i++) + if (! toast_relopt_catalog) { - int j; - bool found = false; + toast_relopt_catalog = allocateOptionsCatalog("toast"); + toast_relopt_catalog->struct_size = sizeof(ToastOptions); - for (j = 0; j < numelems; j++) - { - if (pg_strcasecmp(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_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); + add_autovacuum_options(toast_relopt_catalog, + offsetof(ToastOptions, autovacuum), true); } - SET_VARSIZE(rdopts, offset); + return toast_relopt_catalog; } - /* - * Option parser for anything that uses StdRdOptions. + * get_view_relopt_catalog + * Returns an options catalog for view relation. */ -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_delay", RELOPT_TYPE_INT, - offsetof(StdRdOptions, autovacuum) +offsetof(AutoVacOpts, vacuum_cost_delay)}, - {"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)}, - {"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)} - }; - - 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)); +static options_catalog * view_relopt_catalog = NULL; - pfree(options); - - return (bytea *) rdopts; -} - -/* - * Option parser for views - */ -bytea * -view_reloptions(Datum reloptions, bool validate) +options_catalog * +get_view_relopt_catalog(void) { - 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_STRING, - offsetof(ViewOptions, check_option_offset)} - }; - - options = parseRelOptions(reloptions, validate, RELOPT_KIND_VIEW, &numoptions); + static const char *enum_names[] = VIEW_OPTION_CHECK_OPTION_VALUE_NAMES; - /* 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; -} + if (! view_relopt_catalog) + { + view_relopt_catalog = allocateOptionsCatalog(NULL); + view_relopt_catalog->struct_size = sizeof(ViewOptions); -/* - * Parse options for heaps, views and toast tables. - */ -bytea * -heap_reloptions(char relkind, Datum reloptions, bool validate) -{ - StdRdOptions *rdopts; + optionsCatalogAddItemBool(view_relopt_catalog, "security_barrier", + "View acts as a row security barrier", + AccessExclusiveLock, + 0, offsetof(ViewOptions, security_barrier), false); - 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: - case RELKIND_PARTITIONED_TABLE: - return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP); - default: - /* other relkinds are not supported */ - return NULL; + optionsCatalogAddItemEnum(view_relopt_catalog, "check_option", + "View has WITH CHECK OPTION defined (local or cascaded)", + AccessExclusiveLock, 0, + offsetof(ViewOptions, check_option), + enum_names, + VIEW_OPTION_CHECK_OPTION_NOT_SET); } + return view_relopt_catalog; } - /* - * Parse options for indexes. - * - * amoptions index AM's option parser function - * reloptions options as text[] datum - * validate error flag + * get_attribute_options_catalog + * Returns an options catalog for heap attributes */ -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); -} +static options_catalog * attribute_options_catalog = NULL; -/* - * Option parser for attribute reloptions - */ -bytea * -attribute_reloptions(Datum reloptions, bool validate) +options_catalog * +get_attribute_options_catalog(void) { - 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)); + if (! attribute_options_catalog) + { + attribute_options_catalog = allocateOptionsCatalog(NULL); + attribute_options_catalog->struct_size = sizeof(AttributeOpts); - pfree(options); + optionsCatalogAddItemReal(attribute_options_catalog, "n_distinct", + "Sets the planner's estimate of the number of distinct values appearing in a column (excluding child relations)", + AccessExclusiveLock, + 0, offsetof(AttributeOpts, n_distinct), 0, -1.0, DBL_MAX); - return (bytea *) aopts; + optionsCatalogAddItemReal(attribute_options_catalog, + "n_distinct_inherited", + "Sets the planner's estimate of the number of distinct values appearing in a column (including child relations", + AccessExclusiveLock, + 0, offsetof(AttributeOpts, n_distinct_inherited), 0, -1.0, DBL_MAX); + } + return attribute_options_catalog; } /* - * Option parser for tablespace reloptions + * get_tablespace_options_catalog + * Returns an options catalog for tablespaces */ -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; +static options_catalog * tablespace_options_catalog = NULL; - tsopts = allocateReloptStruct(sizeof(TableSpaceOpts), options, numoptions); +options_catalog * +get_tablespace_options_catalog(void) +{ + if (! tablespace_options_catalog) + { + tablespace_options_catalog = allocateOptionsCatalog(NULL); + tablespace_options_catalog->struct_size = sizeof(TableSpaceOpts); - fillRelOptions((void *) tsopts, sizeof(TableSpaceOpts), options, numoptions, - validate, tab, lengthof(tab)); + optionsCatalogAddItemReal(tablespace_options_catalog, + "random_page_cost", + "Sets the planner's estimate of the cost of a nonsequentially fetched disk page", + AccessExclusiveLock, + 0, offsetof(TableSpaceOpts, random_page_cost), -1, 0.0, DBL_MAX); - pfree(options); + optionsCatalogAddItemReal(tablespace_options_catalog, "seq_page_cost", + "Sets the planner's estimate of the cost of a sequentially fetched disk page", + AccessExclusiveLock, + 0, offsetof(TableSpaceOpts, seq_page_cost), -1, 0.0, DBL_MAX); - return (bytea *) tsopts; + optionsCatalogAddItemInt(tablespace_options_catalog, + "effective_io_concurrency", + "Number of simultaneous requests that can be handled efficiently by the disk subsystem", + AccessExclusiveLock, + 0, offsetof(TableSpaceOpts, effective_io_concurrency), +#ifdef USE_PREFETCH + -1, 0, MAX_IO_CONCURRENCY +#else + 0, 0, 0 +#endif + ); + } + return tablespace_options_catalog; } /* @@ -1480,33 +383,51 @@ tablespace_reloptions(Datum reloptions, bool validate) * for a longer explanation of how this works. */ LOCKMODE -AlterTableGetRelOptionsLockLevel(List *defList) +AlterTableGetRelOptionsLockLevel(Relation rel, List *defList) { - LOCKMODE lockmode = NoLock; - ListCell *cell; + LOCKMODE lockmode = NoLock; + ListCell *cell; + options_catalog *catalog = NULL; if (defList == NIL) return AccessExclusiveLock; - if (need_initialization) - initialize_reloptions(); + switch (rel->rd_rel->relkind) + { + case RELKIND_TOASTVALUE: + catalog = get_toast_relopt_catalog(); + break; + case RELKIND_RELATION: + case RELKIND_MATVIEW: + catalog = get_heap_relopt_catalog(); + break; + case RELKIND_INDEX: + catalog = rel->rd_amroutine->amrelopt_catalog(); + break; + case RELKIND_VIEW: + catalog = get_view_relopt_catalog(); + break; + default: + Assert(false); /* can't get here */ + break; + } + + Assert(catalog);/* No catalog - no reloption change. Should not get here */ foreach(cell, defList) { - DefElem *def = (DefElem *) lfirst(cell); - int i; + DefElem *def = (DefElem *) lfirst(cell); - for (i = 0; relOpts[i]; i++) + int i; + for (i=0; i< catalog->num; i++) { - if (pg_strncasecmp(relOpts[i]->name, - def->defname, - relOpts[i]->namelen + 1) == 0) - { - if (lockmode < relOpts[i]->lockmode) - lockmode = relOpts[i]->lockmode; - } + option_definition_basic *gen = catalog->definitions[i]; + + if (pg_strcasecmp(gen->name, + def->defname) == 0) + if (lockmode < gen->lockmode) + lockmode = gen->lockmode; } } - return lockmode; } diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c index 02d920b..9d1149b 100644 --- a/src/backend/access/gin/ginutil.c +++ b/src/backend/access/gin/ginutil.c @@ -15,7 +15,7 @@ #include "postgres.h" #include "access/gin_private.h" -#include "access/reloptions.h" +#include "access/options.h" #include "access/xloginsert.h" #include "catalog/pg_collation.h" #include "catalog/pg_type.h" @@ -25,7 +25,8 @@ #include "utils/builtins.h" #include "utils/index_selfuncs.h" #include "utils/typcache.h" - +#include "utils/memutils.h" +#include "utils/guc.h" /* * GIN handler function: return IndexAmRoutine with access method parameters @@ -58,7 +59,6 @@ ginhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = ginvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = gincostestimate; - amroutine->amoptions = ginoptions; amroutine->amproperty = NULL; amroutine->amvalidate = ginvalidate; amroutine->ambeginscan = ginbeginscan; @@ -68,6 +68,7 @@ ginhandler(PG_FUNCTION_ARGS) amroutine->amendscan = ginendscan; amroutine->ammarkpos = NULL; amroutine->amrestrpos = NULL; + amroutine->amrelopt_catalog = gingetreloptcatalog; amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; @@ -591,35 +592,6 @@ ginExtractEntries(GinState *ginstate, OffsetNumber attnum, return entries; } -bytea * -ginoptions(Datum reloptions, bool validate) -{ - relopt_value *options; - GinOptions *rdopts; - int numoptions; - static const relopt_parse_elt tab[] = { - {"fastupdate", RELOPT_TYPE_BOOL, offsetof(GinOptions, useFastUpdate)}, - {"gin_pending_list_limit", RELOPT_TYPE_INT, offsetof(GinOptions, - pendingListCleanupSize)} - }; - - options = parseRelOptions(reloptions, validate, RELOPT_KIND_GIN, - &numoptions); - - /* if none set, we're done */ - if (numoptions == 0) - return NULL; - - rdopts = allocateReloptStruct(sizeof(GinOptions), options, numoptions); - - fillRelOptions((void *) rdopts, sizeof(GinOptions), options, numoptions, - validate, tab, lengthof(tab)); - - pfree(options); - - return (bytea *) rdopts; -} - /* * Fetch index's statistical data into *stats * @@ -696,3 +668,32 @@ ginUpdateStats(Relation index, const GinStatsData *stats) END_CRIT_SECTION(); } + + +static options_catalog * gin_relopt_catalog = NULL; + +void * +gingetreloptcatalog (void) +{ + if (! gin_relopt_catalog) + { + gin_relopt_catalog = allocateOptionsCatalog(NULL); + gin_relopt_catalog->struct_size = sizeof(GinOptions); + + optionsCatalogAddItemBool(gin_relopt_catalog, "fastupdate", + "Enables \"fast update\" feature for this GIN index", + AccessExclusiveLock, + 0, + offsetof(GinOptions, useFastUpdate), + GIN_DEFAULT_USE_FASTUPDATE); + + optionsCatalogAddItemInt(gin_relopt_catalog, "gin_pending_list_limit", + "Maximum size of the pending list for this GIN index, in kilobytes", + AccessExclusiveLock, + 0, + offsetof(GinOptions, pendingListCleanupSize), + -1, 64, MAX_KILOBYTES); + + } + return gin_relopt_catalog; +} diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c index c2247ad..bae95bc 100644 --- a/src/backend/access/gist/gist.c +++ b/src/backend/access/gist/gist.c @@ -79,7 +79,6 @@ gisthandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = gistvacuumcleanup; amroutine->amcanreturn = gistcanreturn; amroutine->amcostestimate = gistcostestimate; - amroutine->amoptions = gistoptions; amroutine->amproperty = gistproperty; amroutine->amvalidate = gistvalidate; amroutine->ambeginscan = gistbeginscan; @@ -89,6 +88,7 @@ gisthandler(PG_FUNCTION_ARGS) amroutine->amendscan = gistendscan; amroutine->ammarkpos = NULL; amroutine->amrestrpos = NULL; + amroutine->amrelopt_catalog = gistgetreloptcatalog; amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; @@ -1579,3 +1579,4 @@ gistvacuumpage(Relation rel, Page page, Buffer buffer) * the page. */ } + diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c index b65926f..ada7488 100644 --- a/src/backend/access/gist/gistbuild.c +++ b/src/backend/access/gist/gistbuild.c @@ -125,11 +125,9 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo) { /* Get buffering mode from the options string */ GiSTOptions *options = (GiSTOptions *) index->rd_options; - char *bufferingMode = (char *) options + options->bufferingModeOffset; - - if (strcmp(bufferingMode, "on") == 0) + if (options->buffering_mode == GIST_OPTION_BUFFERING_ON) buildstate.bufferingMode = GIST_BUFFERING_STATS; - else if (strcmp(bufferingMode, "off") == 0) + else if (options->buffering_mode == GIST_OPTION_BUFFERING_OFF) buildstate.bufferingMode = GIST_BUFFERING_DISABLED; else buildstate.bufferingMode = GIST_BUFFERING_AUTO; @@ -233,25 +231,6 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo) } /* - * Validator for "buffering" reloption on GiST indexes. Allows "on", "off" - * and "auto" values. - */ -void -gistValidateBufferingOption(char *value) -{ - if (value == NULL || - (strcmp(value, "on") != 0 && - strcmp(value, "off") != 0 && - strcmp(value, "auto") != 0)) - { - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("invalid value for \"buffering\" option"), - errdetail("Valid values are \"on\", \"off\", and \"auto\"."))); - } -} - -/* * Attempt to switch to buffering mode. * * If there is not enough memory for buffering build, sets bufferingMode diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c index f92baed..ff8aaf9 100644 --- a/src/backend/access/gist/gistutil.c +++ b/src/backend/access/gist/gistutil.c @@ -17,14 +17,13 @@ #include "access/gist_private.h" #include "access/htup_details.h" -#include "access/reloptions.h" +#include "access/options.h" #include "catalog/pg_opclass.h" #include "storage/indexfsm.h" #include "storage/lmgr.h" #include "utils/builtins.h" #include "utils/syscache.h" - /* * Write itup vector to page, has no control of free space. */ @@ -811,34 +810,6 @@ gistNewBuffer(Relation r) return buffer; } -bytea * -gistoptions(Datum reloptions, bool validate) -{ - relopt_value *options; - GiSTOptions *rdopts; - int numoptions; - static const relopt_parse_elt tab[] = { - {"fillfactor", RELOPT_TYPE_INT, offsetof(GiSTOptions, fillfactor)}, - {"buffering", RELOPT_TYPE_STRING, offsetof(GiSTOptions, bufferingModeOffset)} - }; - - options = parseRelOptions(reloptions, validate, RELOPT_KIND_GIST, - &numoptions); - - /* if none set, we're done */ - if (numoptions == 0) - return NULL; - - rdopts = allocateReloptStruct(sizeof(GiSTOptions), options, numoptions); - - fillRelOptions((void *) rdopts, sizeof(GiSTOptions), options, numoptions, - validate, tab, lengthof(tab)); - - pfree(options); - - return (bytea *) rdopts; -} - /* * gistproperty() -- Check boolean properties of indexes. * @@ -964,3 +935,32 @@ gistGetFakeLSN(Relation rel) return GetFakeLSNForUnloggedRel(); } } + +static options_catalog * gist_relopt_catalog = NULL; + +void * +gistgetreloptcatalog (void) +{ + static const char *enum_names[] = GIST_OPTION_BUFFERING_VALUE_NAMES; + if (! gist_relopt_catalog) + { + gist_relopt_catalog = allocateOptionsCatalog(NULL); + gist_relopt_catalog->struct_size = sizeof(GiSTOptions); + + optionsCatalogAddItemInt(gist_relopt_catalog, "fillfactor", + "Packs gist index pages only to this percentage", + NoLock, /* No ALTER, no lock */ + OPTION_DEFINITION_FLAG_FORBID_ALTER, + offsetof(GiSTOptions, fillfactor), + GIST_DEFAULT_FILLFACTOR, + GIST_MIN_FILLFACTOR, 100); + optionsCatalogAddItemEnum(gist_relopt_catalog, "buffering", + "Enables buffering build for this GiST index", + NoLock, /* No ALTER, no lock */ + OPTION_DEFINITION_FLAG_FORBID_ALTER, + offsetof(GiSTOptions, buffering_mode), + enum_names, + GIST_OPTION_BUFFERING_AUTO); + } + return gist_relopt_catalog; +} diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c index ec8ed33..7ecea95 100644 --- a/src/backend/access/hash/hash.c +++ b/src/backend/access/hash/hash.c @@ -76,7 +76,6 @@ hashhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = hashvacuumcleanup; amroutine->amcanreturn = NULL; amroutine->amcostestimate = hashcostestimate; - amroutine->amoptions = hashoptions; amroutine->amproperty = NULL; amroutine->amvalidate = hashvalidate; amroutine->ambeginscan = hashbeginscan; @@ -86,6 +85,7 @@ hashhandler(PG_FUNCTION_ARGS) amroutine->amendscan = hashendscan; amroutine->ammarkpos = NULL; amroutine->amrestrpos = NULL; + amroutine->amrelopt_catalog = hashgetreloptcatalog; amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; diff --git a/src/backend/access/hash/hashpage.c b/src/backend/access/hash/hashpage.c index 69676eb..1542fd1 100644 --- a/src/backend/access/hash/hashpage.c +++ b/src/backend/access/hash/hashpage.c @@ -332,7 +332,7 @@ _hash_metapinit(Relation rel, double num_tuples, ForkNumber forkNum) data_width = sizeof(uint32); item_width = MAXALIGN(sizeof(IndexTupleData)) + MAXALIGN(data_width) + sizeof(ItemIdData); /* include the line pointer */ - ffactor = RelationGetTargetPageUsage(rel, HASH_DEFAULT_FILLFACTOR) / item_width; + ffactor = (BLCKSZ * HashGetFillFactor(rel) / 100) / item_width; /* keep to a sane range */ if (ffactor < 10) ffactor = 10; diff --git a/src/backend/access/hash/hashutil.c b/src/backend/access/hash/hashutil.c index c705531..478cb44 100644 --- a/src/backend/access/hash/hashutil.c +++ b/src/backend/access/hash/hashutil.c @@ -15,7 +15,7 @@ #include "postgres.h" #include "access/hash.h" -#include "access/reloptions.h" +#include "access/options.h" #include "access/relscan.h" #include "utils/lsyscache.h" #include "utils/rel.h" @@ -219,12 +219,6 @@ _hash_checkpage(Relation rel, Buffer buf, int flags) } } -bytea * -hashoptions(Datum reloptions, bool validate) -{ - return default_reloptions(reloptions, validate, RELOPT_KIND_HASH); -} - /* * _hash_get_indextuple_hashkey - get the hash index tuple's hash key value */ @@ -446,3 +440,26 @@ _hash_get_newbucket_from_oldbucket(Relation rel, Bucket old_bucket, return new_bucket; } + +static options_catalog * hash_relopt_catalog = NULL; + +void * +hashgetreloptcatalog(void) +{ + if (! hash_relopt_catalog) + { + hash_relopt_catalog = allocateOptionsCatalog(NULL); + hash_relopt_catalog->struct_size = sizeof(HashRelOptions); + + optionsCatalogAddItemInt(hash_relopt_catalog, "fillfactor", + "Packs hash index pages only to this percentage", + NoLock, /* No ALTER -- no lock */ + OPTION_DEFINITION_FLAG_FORBID_ALTER, /* fillfactor is actualy stored in metapage + and should not be changed once index is + created */ + offsetof(HashRelOptions, fillfactor), + HASH_DEFAULT_FILLFACTOR, + HASH_MIN_FILLFACTOR, 100); + } + return hash_relopt_catalog; +} diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index 5fd7f1e..81f8984 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -2656,7 +2656,10 @@ heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples, bool need_cids = RelationIsAccessibleInLogicalDecoding(relation); needwal = !(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relation); - saveFreeSpace = RelationGetTargetPageFreeSpace(relation, + if (IsToastRelation(relation)) + saveFreeSpace = ToastGetTargetPageFreeSpace(); + else + saveFreeSpace = HeapGetTargetPageFreeSpace(relation, HEAP_DEFAULT_FILLFACTOR); /* Toast and set header data in all the tuples */ diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c index 6529fe3..78ecc46 100644 --- a/src/backend/access/heap/hio.c +++ b/src/backend/access/heap/hio.c @@ -323,8 +323,11 @@ RelationGetBufferForTuple(Relation relation, Size len, len, MaxHeapTupleSize))); /* Compute desired extra freespace due to fillfactor option */ - saveFreeSpace = RelationGetTargetPageFreeSpace(relation, - HEAP_DEFAULT_FILLFACTOR); + if (IsToastRelation(relation)) + saveFreeSpace = ToastGetTargetPageFreeSpace(); + else + saveFreeSpace = HeapGetTargetPageFreeSpace(relation, + HEAP_DEFAULT_FILLFACTOR); if (otherBuffer != InvalidBuffer) otherBlock = BufferGetBlockNumber(otherBuffer); diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c index d69a266..fef5e40 100644 --- a/src/backend/access/heap/pruneheap.c +++ b/src/backend/access/heap/pruneheap.c @@ -131,8 +131,12 @@ heap_page_prune_opt(Relation relation, Buffer buffer) * important than sometimes getting a wrong answer in what is after all * just a heuristic estimate. */ - minfree = RelationGetTargetPageFreeSpace(relation, - HEAP_DEFAULT_FILLFACTOR); + + if (IsToastRelation(relation)) + minfree = ToastGetTargetPageFreeSpace(); + else + minfree = HeapGetTargetPageFreeSpace(relation, + HEAP_DEFAULT_FILLFACTOR); minfree = Max(minfree, BLCKSZ / 10); if (PageIsFull(page) || PageGetHeapFreeSpace(page) < minfree) diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c index c7b283c..ee859e7 100644 --- a/src/backend/access/heap/rewriteheap.c +++ b/src/backend/access/heap/rewriteheap.c @@ -665,7 +665,10 @@ raw_heap_insert(RewriteState state, HeapTuple tup) len, MaxHeapTupleSize))); /* Compute desired extra freespace due to fillfactor option */ - saveFreeSpace = RelationGetTargetPageFreeSpace(state->rs_new_rel, + if (IsToastRelation(state->rs_new_rel)) + saveFreeSpace = ToastGetTargetPageFreeSpace(); + else + saveFreeSpace = HeapGetTargetPageFreeSpace(state->rs_new_rel, HEAP_DEFAULT_FILLFACTOR); /* Now we can check to see if there's enough free space already. */ diff --git a/src/backend/access/nbtree/nbtinsert.c b/src/backend/access/nbtree/nbtinsert.c index 883d70d..c66cdc8 100644 --- a/src/backend/access/nbtree/nbtinsert.c +++ b/src/backend/access/nbtree/nbtinsert.c @@ -1432,8 +1432,7 @@ _bt_findsplitloc(Relation rel, state.is_rightmost = P_RIGHTMOST(opaque); state.have_split = false; if (state.is_leaf) - state.fillfactor = RelationGetFillFactor(rel, - BTREE_DEFAULT_FILLFACTOR); + state.fillfactor = BTGetFillFactor(rel); else state.fillfactor = BTREE_NONLEAF_FILLFACTOR; state.newitemonleft = false; /* these just to keep compiler quiet */ diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c index 469e7ab..675066e 100644 --- a/src/backend/access/nbtree/nbtree.c +++ b/src/backend/access/nbtree/nbtree.c @@ -21,6 +21,7 @@ #include "access/nbtree.h" #include "access/relscan.h" #include "access/xlog.h" +#include "access/reloptions.h" #include "catalog/index.h" #include "commands/vacuum.h" #include "storage/indexfsm.h" @@ -32,7 +33,6 @@ #include "utils/index_selfuncs.h" #include "utils/memutils.h" - /* Working state for btbuild and its callback */ typedef struct { @@ -75,8 +75,7 @@ static void btvacuumscan(IndexVacuumInfo *info, IndexBulkDeleteResult *stats, BTCycleId cycleid); static void btvacuumpage(BTVacState *vstate, BlockNumber blkno, BlockNumber orig_blkno); - - +void * btgetreloptcatalog (void); /* * Btree handler function: return IndexAmRoutine with access method parameters * and callbacks. @@ -108,7 +107,6 @@ bthandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = btvacuumcleanup; amroutine->amcanreturn = btcanreturn; amroutine->amcostestimate = btcostestimate; - amroutine->amoptions = btoptions; amroutine->amproperty = btproperty; amroutine->amvalidate = btvalidate; amroutine->ambeginscan = btbeginscan; @@ -118,6 +116,7 @@ bthandler(PG_FUNCTION_ARGS) amroutine->amendscan = btendscan; amroutine->ammarkpos = btmarkpos; amroutine->amrestrpos = btrestrpos; + amroutine->amrelopt_catalog = btgetreloptcatalog; amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; @@ -1136,3 +1135,26 @@ btcanreturn(Relation index, int attno) { return true; } + + +static options_catalog * bt_relopt_catalog = NULL; + +void * +btgetreloptcatalog (void) +{ + if (! bt_relopt_catalog) + { + bt_relopt_catalog = allocateOptionsCatalog(NULL); + bt_relopt_catalog->struct_size = sizeof(BTRelOptions); + + optionsCatalogAddItemInt(bt_relopt_catalog, "fillfactor", + "Packs btree index pages only to this percentage", + ShareUpdateExclusiveLock, /* since it applies only to later inserts */ + 0, + offsetof(BTRelOptions, fillfactor), + BTREE_DEFAULT_FILLFACTOR, + BTREE_MIN_FILLFACTOR, 100); + } + return bt_relopt_catalog; +} + diff --git a/src/backend/access/nbtree/nbtsort.c b/src/backend/access/nbtree/nbtsort.c index 3d041c4..692c751 100644 --- a/src/backend/access/nbtree/nbtsort.c +++ b/src/backend/access/nbtree/nbtsort.c @@ -344,8 +344,8 @@ _bt_pagestate(BTWriteState *wstate, uint32 level) if (level > 0) state->btps_full = (BLCKSZ * (100 - BTREE_NONLEAF_FILLFACTOR) / 100); else - state->btps_full = RelationGetTargetPageFreeSpace(wstate->index, - BTREE_DEFAULT_FILLFACTOR); + state->btps_full = (BLCKSZ * (100 - BTGetFillFactor(wstate->index)) / 100); + /* no parent level, yet */ state->btps_next = NULL; diff --git a/src/backend/access/nbtree/nbtutils.c b/src/backend/access/nbtree/nbtutils.c index da0f330..8b3bd08 100644 --- a/src/backend/access/nbtree/nbtutils.c +++ b/src/backend/access/nbtree/nbtutils.c @@ -18,7 +18,7 @@ #include #include "access/nbtree.h" -#include "access/reloptions.h" +#include "access/options.h" #include "access/relscan.h" #include "miscadmin.h" #include "utils/array.h" @@ -2034,12 +2034,6 @@ BTreeShmemInit(void) Assert(found); } -bytea * -btoptions(Datum reloptions, bool validate) -{ - return default_reloptions(reloptions, validate, RELOPT_KIND_BTREE); -} - /* * btproperty() -- Check boolean properties of indexes. * diff --git a/src/backend/access/spgist/spgutils.c b/src/backend/access/spgist/spgutils.c index 78846be..381bbd2 100644 --- a/src/backend/access/spgist/spgutils.c +++ b/src/backend/access/spgist/spgutils.c @@ -15,7 +15,7 @@ #include "postgres.h" -#include "access/reloptions.h" +#include "access/options.h" #include "access/spgist_private.h" #include "access/transam.h" #include "access/xact.h" @@ -58,7 +58,6 @@ spghandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup = spgvacuumcleanup; amroutine->amcanreturn = spgcanreturn; amroutine->amcostestimate = spgcostestimate; - amroutine->amoptions = spgoptions; amroutine->amproperty = NULL; amroutine->amvalidate = spgvalidate; amroutine->ambeginscan = spgbeginscan; @@ -68,6 +67,7 @@ spghandler(PG_FUNCTION_ARGS) amroutine->amendscan = spgendscan; amroutine->ammarkpos = NULL; amroutine->amrestrpos = NULL; + amroutine->amrelopt_catalog = spggetreloptcatalog; amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; @@ -371,8 +371,8 @@ SpGistGetBuffer(Relation index, int flags, int needSpace, bool *isNew) * related to the ones already on it. But fillfactor mustn't cause an * error for requests that would otherwise be legal. */ - needSpace += RelationGetTargetPageFreeSpace(index, - SPGIST_DEFAULT_FILLFACTOR); + needSpace += (BLCKSZ * (100 - SpGistGetFillFactor(index)) / 100); + needSpace = Min(needSpace, SPGIST_PAGE_CAPACITY); /* Get the cache entry for this flags setting */ @@ -536,15 +536,6 @@ SpGistInitMetapage(Page page) } /* - * reloptions processing for SPGiST - */ -bytea * -spgoptions(Datum reloptions, bool validate) -{ - return default_reloptions(reloptions, validate, RELOPT_KIND_SPGIST); -} - -/* * Get the space needed to store a non-null datum of the indicated type. * Note the result is already rounded up to a MAXALIGN boundary. * Also, we follow the SPGiST convention that pass-by-val types are @@ -910,3 +901,26 @@ SpGistPageAddNewItem(SpGistState *state, Page page, Item item, Size size, return offnum; } + + + +static options_catalog * spgist_relopt_catalog = NULL; + +void * +spggetreloptcatalog (void) +{ + if (! spgist_relopt_catalog) + { + spgist_relopt_catalog = allocateOptionsCatalog(NULL); + spgist_relopt_catalog->struct_size = sizeof(SpGistRelOptions); + + optionsCatalogAddItemInt(spgist_relopt_catalog, "fillfactor", + "Packs spgist index pages only to this percentage", + ShareUpdateExclusiveLock, /* since it applies only to later inserts */ + 0, + offsetof(SpGistRelOptions, fillfactor), + SPGIST_DEFAULT_FILLFACTOR, + SPGIST_MIN_FILLFACTOR, 100); + } + return spgist_relopt_catalog; +} diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c index e5f773d..d2cdb21 100644 --- a/src/backend/catalog/toasting.c +++ b/src/backend/catalog/toasting.c @@ -36,7 +36,7 @@ /* Potentially set by pg_upgrade_support functions */ Oid binary_upgrade_next_toast_pg_type_oid = InvalidOid; -static void CheckAndCreateToastTable(Oid relOid, Datum reloptions, +static bool CheckAndCreateToastTable(Oid relOid, Datum reloptions, LOCKMODE lockmode, bool check); static bool create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid, Datum reloptions, LOCKMODE lockmode, bool check); @@ -67,23 +67,26 @@ NewHeapCreateToastTable(Oid relOid, Datum reloptions, LOCKMODE lockmode) CheckAndCreateToastTable(relOid, reloptions, lockmode, false); } -void +bool NewRelationCreateToastTable(Oid relOid, Datum reloptions) { - CheckAndCreateToastTable(relOid, reloptions, AccessExclusiveLock, false); + return CheckAndCreateToastTable(relOid, reloptions, + AccessExclusiveLock, false); } -static void +static bool CheckAndCreateToastTable(Oid relOid, Datum reloptions, LOCKMODE lockmode, bool check) { Relation rel; + bool success; rel = heap_open(relOid, lockmode); /* create_toast_table does all the work */ - (void) create_toast_table(rel, InvalidOid, InvalidOid, reloptions, lockmode, check); + success = create_toast_table(rel, InvalidOid, InvalidOid, reloptions, lockmode, check); heap_close(rel, NoLock); + return success; } /* diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c index 646a884..5020ee1 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -89,6 +89,8 @@ create_ctas_internal(List *attrList, IntoClause *into) Datum toast_options; static char *validnsps[] = HEAP_RELOPT_NAMESPACES; ObjectAddress intoRelationAddr; + List *toastDefList; + bool toast_created; /* This code supports both CREATE TABLE AS and CREATE MATERIALIZED VIEW */ is_matview = (into->viewQuery != NULL); @@ -122,15 +124,20 @@ create_ctas_internal(List *attrList, IntoClause *into) CommandCounterIncrement(); /* parse and validate reloptions for the toast table */ - toast_options = transformRelOptions((Datum) 0, - create->options, - "toast", - validnsps, - true, false); - (void) heap_reloptions(RELKIND_TOASTVALUE, toast_options, true); - NewRelationCreateToastTable(intoRelationAddr.objectId, toast_options); + optionsDefListValdateNamespaces(create->options, validnsps); + toastDefList = optionsDefListFilterNamespaces(create->options, "toast"); + + toast_options = transformOptions(get_toast_relopt_catalog(), (Datum) 0, + toastDefList, 0); + + toast_created = NewRelationCreateToastTable(intoRelationAddr.objectId, + toast_options); + if (! toast_created && toast_options) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("no TOAST relation was created for a table. Can't set toast.* storage parameters"))); /* Create the "view" part of a materialized view. */ if (is_matview) diff --git a/src/backend/commands/foreigncmds.c b/src/backend/commands/foreigncmds.c index d5d40e6..7aa4576 100644 --- a/src/backend/commands/foreigncmds.c +++ b/src/backend/commands/foreigncmds.c @@ -111,7 +111,7 @@ transformGenericOptions(Oid catalogId, List *options, Oid fdwvalidator) { - List *resultOptions = untransformRelOptions(oldOptions); + List *resultOptions = optionsTextArrayToDefList(oldOptions); ListCell *optcell; Datum result; diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c index f4814c0..e434dc4 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -323,7 +323,7 @@ DefineIndex(Oid relationId, Form_pg_am accessMethodForm; IndexAmRoutine *amRoutine; bool amcanorder; - amoptions_function amoptions; + amrelopt_catalog_function amoption_catalog; Datum reloptions; int16 *coloptions; IndexInfo *indexInfo; @@ -525,7 +525,7 @@ DefineIndex(Oid relationId, accessMethodName))); amcanorder = amRoutine->amcanorder; - amoptions = amRoutine->amoptions; + amoption_catalog = amRoutine->amrelopt_catalog; pfree(amRoutine); ReleaseSysCache(tuple); @@ -539,10 +539,18 @@ DefineIndex(Oid relationId, /* * Parse AM-specific options, convert to text array form, validate. */ - reloptions = transformRelOptions((Datum) 0, stmt->options, - NULL, NULL, false, false); - - (void) index_reloptions(amoptions, reloptions, true); + if (amoption_catalog) + { + reloptions = transformOptions(amoption_catalog(), + (Datum) 0,stmt->options, 0); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("access method %s does not support options", + accessMethodName))); + } /* * Prepare arguments for index_create, primarily an IndexInfo structure. diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 37a4c4a..cd52e9d 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -497,7 +497,6 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, Datum reloptions; ListCell *listptr; AttrNumber attnum; - static char *validnsps[] = HEAP_RELOPT_NAMESPACES; Oid ofTypeId; ObjectAddress address; @@ -583,13 +582,23 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, /* * Parse and validate reloptions, if any. */ - reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps, - true, false); - if (relkind == RELKIND_VIEW) - (void) view_reloptions(reloptions, true); + { + reloptions = transformOptions( + get_view_relopt_catalog(), + (Datum) 0, stmt->options, 0); + } else - (void) heap_reloptions(relkind, reloptions, true); + { + /* If it is not view, then it if heap */ + char *namespaces[] = HEAP_RELOPT_NAMESPACES; + List * heapDefList; + + optionsDefListValdateNamespaces(stmt->options, namespaces); + heapDefList = optionsDefListFilterNamespaces(stmt->options, NULL); + reloptions = transformOptions(get_heap_relopt_catalog(), + (Datum) 0, heapDefList, 0); + } if (stmt->ofTypename) { @@ -3058,7 +3067,8 @@ void AlterTableInternal(Oid relid, List *cmds, bool recurse) { Relation rel; - LOCKMODE lockmode = AlterTableGetLockLevel(cmds); + + LOCKMODE lockmode = AlterTableGetLockLevel(relid, cmds); rel = relation_open(relid, lockmode); @@ -3100,13 +3110,15 @@ AlterTableInternal(Oid relid, List *cmds, bool recurse) * otherwise we might end up with an inconsistent dump that can't restore. */ LOCKMODE -AlterTableGetLockLevel(List *cmds) + +AlterTableGetLockLevel(Oid relid, List *cmds) { /* * This only works if we read catalog tables using MVCC snapshots. */ ListCell *lcmd; LOCKMODE lockmode = ShareUpdateExclusiveLock; + Relation rel; foreach(lcmd, cmds) { @@ -3315,7 +3327,10 @@ AlterTableGetLockLevel(List *cmds) * getTables() */ case AT_ResetRelOptions: /* Uses MVCC in getIndexes() and * getTables() */ - cmd_lockmode = AlterTableGetRelOptionsLockLevel((List *) cmd->def); + rel = relation_open(relid, NoLock); + cmd_lockmode = AlterTableGetRelOptionsLockLevel(rel, + castNode(List, cmd->def)); + relation_close(rel,NoLock); break; case AT_AttachPartition: @@ -4071,7 +4086,7 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode) errmsg("cannot rewrite system relation \"%s\"", RelationGetRelationName(OldHeap)))); - if (RelationIsUsedAsCatalogTable(OldHeap)) + if (HeapIsUsedAsCatalogTable(OldHeap)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot rewrite table \"%s\" used as a catalog table", @@ -5910,11 +5925,11 @@ ATExecSetOptions(Relation rel, const char *colName, Node *options, /* Generate new proposed attoptions (text array) */ datum = SysCacheGetAttr(ATTNAME, tuple, Anum_pg_attribute_attoptions, &isnull); - newOptions = transformRelOptions(isnull ? (Datum) 0 : datum, - castNode(List, options), NULL, NULL, - false, isReset); - /* Validate new options */ - (void) attribute_reloptions(newOptions, true); + + newOptions = transformOptions(get_attribute_options_catalog(), + isnull ? (Datum) 0 : datum, + castNode(List, options), OPTIONS_PARSE_MODE_FOR_ALTER | + (isReset ? OPTIONS_PARSE_MODE_FOR_RESET : 0)); /* Build new tuple. */ memset(repl_null, false, sizeof(repl_null)); @@ -9911,7 +9926,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation, Datum repl_val[Natts_pg_class]; bool repl_null[Natts_pg_class]; bool repl_repl[Natts_pg_class]; - static char *validnsps[] = HEAP_RELOPT_NAMESPACES; + options_parse_mode parse_mode; if (defList == NIL && operation != AT_ReplaceRelOptions) return; /* nothing to do */ @@ -9940,25 +9955,52 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation, &isnull); } - /* Generate new proposed reloptions (text array) */ - newOptions = transformRelOptions(isnull ? (Datum) 0 : datum, - defList, NULL, validnsps, false, - operation == AT_ResetRelOptions); - /* Validate */ + parse_mode = OPTIONS_PARSE_MODE_FOR_ALTER; + if (operation == AT_ResetRelOptions) + parse_mode |= OPTIONS_PARSE_MODE_FOR_RESET; + switch (rel->rd_rel->relkind) { case RELKIND_RELATION: case RELKIND_TOASTVALUE: case RELKIND_MATVIEW: case RELKIND_PARTITIONED_TABLE: - (void) heap_reloptions(rel->rd_rel->relkind, newOptions, true); + { + char *namespaces[] = HEAP_RELOPT_NAMESPACES; + List * heapDefList; + + optionsDefListValdateNamespaces(defList, namespaces); + heapDefList = optionsDefListFilterNamespaces( + defList, NULL); + newOptions = transformOptions(get_heap_relopt_catalog(), + isnull ? (Datum) 0 : datum, + heapDefList, parse_mode); + } break; case RELKIND_VIEW: - (void) view_reloptions(newOptions, true); + { + newOptions = transformOptions( + get_view_relopt_catalog(), + isnull ? (Datum) 0 : datum, + defList, parse_mode); + } break; case RELKIND_INDEX: - (void) index_reloptions(rel->rd_amroutine->amoptions, newOptions, true); + if (rel->rd_amroutine->amrelopt_catalog) + { + newOptions = transformOptions( + rel->rd_amroutine->amrelopt_catalog(), + isnull ? (Datum) 0 : datum, + defList, parse_mode); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("index %s does not support options", + RelationGetRelationName(rel)))); + } break; default: ereport(ERROR, @@ -9972,7 +10014,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation, if (rel->rd_rel->relkind == RELKIND_VIEW) { Query *view_query = get_view_query(rel); - List *view_options = untransformRelOptions(newOptions); + List *view_options = optionsTextArrayToDefList(newOptions); ListCell *cell; bool check_option = false; @@ -10032,6 +10074,8 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation, { Relation toastrel; Oid toastid = rel->rd_rel->reltoastrelid; + List *toastDefList; + options_parse_mode parse_mode; toastrel = heap_open(toastid, lockmode); @@ -10055,12 +10099,15 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation, datum = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_reloptions, &isnull); } + parse_mode = OPTIONS_PARSE_MODE_FOR_ALTER; + if (operation == AT_ResetRelOptions) + parse_mode |= OPTIONS_PARSE_MODE_FOR_RESET; - newOptions = transformRelOptions(isnull ? (Datum) 0 : datum, - defList, "toast", validnsps, false, - operation == AT_ResetRelOptions); + toastDefList = optionsDefListFilterNamespaces(defList, "toast"); - (void) heap_reloptions(RELKIND_TOASTVALUE, newOptions, true); + newOptions = transformOptions(get_toast_relopt_catalog(), + isnull ? (Datum) 0 : datum, + toastDefList, parse_mode); memset(repl_val, 0, sizeof(repl_val)); memset(repl_null, false, sizeof(repl_null)); @@ -10088,6 +10135,17 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation, heap_close(toastrel, NoLock); } + else + { + List *toastDefList; + toastDefList = optionsDefListFilterNamespaces(defList, "toast"); + if (toastDefList) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("TOAST relation for this table does not exist. Can't change toast.* storage parameters"))); + } + } heap_close(pgclass, RowExclusiveLock); } diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c index 80515ba..7ee3250 100644 --- a/src/backend/commands/tablespace.c +++ b/src/backend/commands/tablespace.c @@ -333,10 +333,11 @@ CreateTableSpace(CreateTableSpaceStmt *stmt) nulls[Anum_pg_tablespace_spcacl - 1] = true; /* Generate new proposed spcoptions (text array) */ - newOptions = transformRelOptions((Datum) 0, - stmt->options, - NULL, NULL, false, false); - (void) tablespace_reloptions(newOptions, true); + + newOptions = transformOptions( + get_tablespace_options_catalog(), + (Datum) 0, stmt->options, 0); + if (newOptions != (Datum) 0) values[Anum_pg_tablespace_spcoptions - 1] = newOptions; else @@ -1024,10 +1025,13 @@ AlterTableSpaceOptions(AlterTableSpaceOptionsStmt *stmt) /* Generate new proposed spcoptions (text array) */ datum = heap_getattr(tup, Anum_pg_tablespace_spcoptions, RelationGetDescr(rel), &isnull); - newOptions = transformRelOptions(isnull ? (Datum) 0 : datum, - stmt->options, NULL, NULL, false, - stmt->isReset); - (void) tablespace_reloptions(newOptions, true); + + + newOptions = transformOptions(get_tablespace_options_catalog(), + isnull ? (Datum) 0 : datum, + stmt->options, + OPTIONS_PARSE_MODE_FOR_ALTER | + (stmt->isReset? OPTIONS_PARSE_MODE_FOR_RESET : 0)); /* Build new tuple. */ memset(repl_null, false, sizeof(repl_null)); diff --git a/src/backend/foreign/foreign.c b/src/backend/foreign/foreign.c index fdb4f71..a32d376 100644 --- a/src/backend/foreign/foreign.c +++ b/src/backend/foreign/foreign.c @@ -62,7 +62,7 @@ GetForeignDataWrapper(Oid fdwid) if (isnull) fdw->options = NIL; else - fdw->options = untransformRelOptions(datum); + fdw->options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -133,7 +133,7 @@ GetForeignServer(Oid serverid) if (isnull) server->options = NIL; else - server->options = untransformRelOptions(datum); + server->options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -201,7 +201,7 @@ GetUserMapping(Oid userid, Oid serverid) if (isnull) um->options = NIL; else - um->options = untransformRelOptions(datum); + um->options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -238,7 +238,7 @@ GetForeignTable(Oid relid) if (isnull) ft->options = NIL; else - ft->options = untransformRelOptions(datum); + ft->options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -271,7 +271,7 @@ GetForeignColumnOptions(Oid relid, AttrNumber attnum) if (isnull) options = NIL; else - options = untransformRelOptions(datum); + options = optionsTextArrayToDefList(datum); ReleaseSysCache(tp); @@ -540,7 +540,7 @@ pg_options_to_table(PG_FUNCTION_ARGS) Datum array = PG_GETARG_DATUM(0); deflist_to_tuplestore((ReturnSetInfo *) fcinfo->resultinfo, - untransformRelOptions(array)); + optionsTextArrayToDefList(array)); return (Datum) 0; } @@ -611,7 +611,7 @@ is_conninfo_option(const char *option, Oid context) Datum postgresql_fdw_validator(PG_FUNCTION_ARGS) { - List *options_list = untransformRelOptions(PG_GETARG_DATUM(0)); + List *options_list = optionsTextArrayToDefList(PG_GETARG_DATUM(0)); Oid catalog = PG_GETARG_OID(1); ListCell *cell; diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 7836e6b..123fef4 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -135,7 +135,7 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, &rel->pages, &rel->tuples, &rel->allvisfrac); /* Retrieve the parallel_workers reloption, or -1 if not set. */ - rel->rel_parallel_workers = RelationGetParallelWorkers(relation, -1); + rel->rel_parallel_workers = HeapGetParallelWorkers(relation, -1); /* * Make list of indexes. Ignore indexes on system catalogs if told to. diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c index 69f4736..5225c73 100644 --- a/src/backend/parser/parse_clause.c +++ b/src/backend/parser/parse_clause.c @@ -2842,7 +2842,7 @@ transformOnConflictArbiter(ParseState *pstate, exprLocation((Node *) onConflictClause)))); /* Same applies to table used by logical decoding as catalog table */ - if (RelationIsUsedAsCatalogTable(pstate->p_target_relation)) + if (HeapIsUsedAsCatalogTable(pstate->p_target_relation)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("ON CONFLICT is not supported on table \"%s\" used as a catalog table", diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index 8d19394..e550125 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -1394,7 +1394,7 @@ generateClonedIndexStmt(CreateStmtContext *cxt, Relation source_idx, datum = SysCacheGetAttr(RELOID, ht_idxrel, Anum_pg_class_reloptions, &isnull); if (!isnull) - index->options = untransformRelOptions(datum); + index->options = optionsTextArrayToDefList(datum); /* If it's a partial index, decompile and append the predicate */ datum = SysCacheGetAttr(INDEXRELID, ht_idx, diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c index 0c5ffa0..bc21d81 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -2510,6 +2510,7 @@ extract_autovac_opts(HeapTuple tup, TupleDesc pg_class_desc) { bytea *relopts; AutoVacOpts *av; + AutoVacOpts *src; Assert(((Form_pg_class) GETSTRUCT(tup))->relkind == RELKIND_RELATION || ((Form_pg_class) GETSTRUCT(tup))->relkind == RELKIND_MATVIEW || @@ -2519,8 +2520,13 @@ extract_autovac_opts(HeapTuple tup, TupleDesc pg_class_desc) if (relopts == NULL) return NULL; + if (((Form_pg_class) GETSTRUCT(tup))->relkind == RELKIND_TOASTVALUE) + src = &(((ToastOptions *) relopts)->autovacuum); + else + src = &(((HeapOptions *) relopts)->autovacuum); + av = palloc(sizeof(AutoVacOpts)); - memcpy(av, &(((StdRdOptions *) relopts)->autovacuum), sizeof(AutoVacOpts)); + memcpy(av, src, sizeof(AutoVacOpts)); pfree(relopts); return av; diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c index d3e44fb..a677923 100644 --- a/src/backend/rewrite/rewriteHandler.c +++ b/src/backend/rewrite/rewriteHandler.c @@ -1484,7 +1484,7 @@ ApplyRetrieveRule(Query *parsetree, rte->rtekind = RTE_SUBQUERY; rte->relid = InvalidOid; - rte->security_barrier = RelationIsSecurityView(relation); + rte->security_barrier = ViewIsSecurityView(relation); rte->subquery = rule_action; rte->inh = false; /* must not be set for a subquery */ @@ -2988,7 +2988,7 @@ rewriteTargetView(Query *parsetree, Relation view) ChangeVarNodes(viewqual, base_rt_index, new_rt_index, 0); - if (RelationIsSecurityView(view)) + if (ViewIsSecurityView(view)) { /* * The view's quals go in front of existing barrier quals: those @@ -3024,8 +3024,9 @@ rewriteTargetView(Query *parsetree, Relation view) */ if (parsetree->commandType != CMD_DELETE) { - bool has_wco = RelationHasCheckOption(view); - bool cascaded = RelationHasCascadedCheckOption(view); + + bool has_wco = ViewHasCheckOption(view); + bool cascaded = ViewHasCascadedCheckOption(view); /* * If the parent view has a cascaded check option, treat this view as diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 5d3be38..4a2e6d9 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -996,6 +996,8 @@ ProcessUtilitySlow(ParseState *pstate, { Datum toast_options; static char *validnsps[] = HEAP_RELOPT_NAMESPACES; + List *toastDefList; + bool success; /* Create the table itself */ address = DefineRelation((CreateStmt *) stmt, @@ -1016,18 +1018,24 @@ ProcessUtilitySlow(ParseState *pstate, * parse and validate reloptions for the toast * table */ - toast_options = transformRelOptions((Datum) 0, - ((CreateStmt *) stmt)->options, - "toast", - validnsps, - true, - false); - (void) heap_reloptions(RELKIND_TOASTVALUE, - toast_options, - true); - NewRelationCreateToastTable(address.objectId, - toast_options); + optionsDefListValdateNamespaces( + ((CreateStmt *) stmt)->options, + validnsps); + + toastDefList = optionsDefListFilterNamespaces( + ((CreateStmt *) stmt)->options, "toast"); + + toast_options = transformOptions( + get_toast_relopt_catalog(), (Datum) 0, + toastDefList, 0); + + success = NewRelationCreateToastTable( + address.objectId, toast_options); + if (! success && toast_options ) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("no TOAST relation was created for a table. Can't set toast.* storage parameters"))); } else if (IsA(stmt, CreateForeignTableStmt)) { @@ -1093,8 +1101,13 @@ ProcessUtilitySlow(ParseState *pstate, * lock on (for example) a relation on which we have no * permissions. */ - lockmode = AlterTableGetLockLevel(atstmt->cmds); - relid = AlterTableLookupRelation(atstmt, lockmode); + + relid = AlterTableLookupRelation(atstmt, NoLock); + if (OidIsValid(relid)) + { + lockmode = AlterTableGetLockLevel(relid, atstmt->cmds); + relid = AlterTableLookupRelation(atstmt, lockmode); + } if (OidIsValid(relid)) { diff --git a/src/backend/utils/cache/attoptcache.c b/src/backend/utils/cache/attoptcache.c index f7f85b5..b5f3dce 100644 --- a/src/backend/utils/cache/attoptcache.c +++ b/src/backend/utils/cache/attoptcache.c @@ -150,7 +150,10 @@ get_attribute_options(Oid attrelid, int attnum) opts = NULL; else { - bytea *bytea_opts = attribute_reloptions(datum, false); + bytea *bytea_opts; + + bytea_opts = optionsTextArrayToBytea( + get_attribute_options_catalog(), datum); opts = MemoryContextAlloc(CacheMemoryContext, VARSIZE(bytea_opts)); diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 9001e20..19d7b44 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -459,7 +459,8 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple) options = extractRelOptions(tuple, GetPgClassDescriptor(), relation->rd_rel->relkind == RELKIND_INDEX ? - relation->rd_amroutine->amoptions : NULL); + relation->rd_amroutine->amrelopt_catalog : NULL + ); /* * Copy parsed data into CacheMemoryContext. To guard against the diff --git a/src/backend/utils/cache/spccache.c b/src/backend/utils/cache/spccache.c index 9dcb80c..74bf884 100644 --- a/src/backend/utils/cache/spccache.c +++ b/src/backend/utils/cache/spccache.c @@ -149,7 +149,9 @@ get_tablespace(Oid spcid) opts = NULL; else { - bytea *bytea_opts = tablespace_reloptions(datum, false); + bytea *bytea_opts; + bytea_opts = optionsTextArrayToBytea( + get_tablespace_options_catalog(), datum); opts = MemoryContextAlloc(CacheMemoryContext, VARSIZE(bytea_opts)); memcpy(opts, bytea_opts, VARSIZE(bytea_opts)); @@ -190,7 +192,6 @@ get_tablespace_page_costs(Oid spcid, else *spc_random_page_cost = spc->opts->random_page_cost; } - if (spc_seq_page_cost) { if (!spc->opts || spc->opts->seq_page_cost < 0) diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h index e91e41d..5e6551a 100644 --- a/src/include/access/amapi.h +++ b/src/include/access/amapi.h @@ -96,10 +96,6 @@ typedef void (*amcostestimate_function) (struct PlannerInfo *root, Selectivity *indexSelectivity, double *indexCorrelation); -/* parse index reloptions */ -typedef bytea *(*amoptions_function) (Datum reloptions, - bool validate); - /* report AM, index, or index column property */ typedef bool (*amproperty_function) (Oid index_oid, int attno, IndexAMProperty prop, const char *propname, @@ -137,6 +133,9 @@ typedef void (*ammarkpos_function) (IndexScanDesc scan); /* restore marked scan position */ typedef void (*amrestrpos_function) (IndexScanDesc scan); +/* get catalog of reloptions definitions */ +typedef void *(*amrelopt_catalog_function) (); + /* * Callback function signatures - for parallel index scans. */ @@ -198,7 +197,6 @@ typedef struct IndexAmRoutine amvacuumcleanup_function amvacuumcleanup; amcanreturn_function amcanreturn; /* can be NULL */ amcostestimate_function amcostestimate; - amoptions_function amoptions; amproperty_function amproperty; /* can be NULL */ amvalidate_function amvalidate; ambeginscan_function ambeginscan; @@ -208,6 +206,7 @@ typedef struct IndexAmRoutine amendscan_function amendscan; ammarkpos_function ammarkpos; /* can be NULL */ amrestrpos_function amrestrpos; /* can be NULL */ + amrelopt_catalog_function amrelopt_catalog; /* can be NULL */ /* interface functions to support parallel index scans */ amestimateparallelscan_function amestimateparallelscan; /* can be NULL */ diff --git a/src/include/access/brin.h b/src/include/access/brin.h index 896824a..c84d6d4 100644 --- a/src/include/access/brin.h +++ b/src/include/access/brin.h @@ -25,6 +25,9 @@ typedef struct BrinOptions } BrinOptions; #define BRIN_DEFAULT_PAGES_PER_RANGE 128 +#define BRIN_MIN_PAGES_PER_RANGE 1 +#define BRIN_MAX_PAGES_PER_RANGE 131072 + #define BrinGetPagesPerRange(relation) \ ((relation)->rd_options ? \ ((BrinOptions *) (relation)->rd_options)->pagesPerRange : \ diff --git a/src/include/access/brin_internal.h b/src/include/access/brin_internal.h index 4031260..8829bc1 100644 --- a/src/include/access/brin_internal.h +++ b/src/include/access/brin_internal.h @@ -101,7 +101,6 @@ extern IndexBulkDeleteResult *brinbulkdelete(IndexVacuumInfo *info, void *callback_state); extern IndexBulkDeleteResult *brinvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats); -extern bytea *brinoptions(Datum reloptions, bool validate); /* brin_validate.c */ extern bool brinvalidate(Oid opclassoid); diff --git a/src/include/access/gin_private.h b/src/include/access/gin_private.h index d46274e..f1b5a5a 100644 --- a/src/include/access/gin_private.h +++ b/src/include/access/gin_private.h @@ -591,7 +591,7 @@ typedef struct ginxlogDeleteListPages /* ginutil.c */ -extern bytea *ginoptions(Datum reloptions, bool validate); +extern Datum ginhandler(PG_FUNCTION_ARGS); extern void initGinState(GinState *state, Relation index); extern Buffer GinNewBuffer(Relation index); extern void GinInitBuffer(Buffer b, uint32 f); @@ -610,6 +610,7 @@ extern Datum *ginExtractEntries(GinState *ginstate, OffsetNumber attnum, extern OffsetNumber gintuple_get_attrnum(GinState *ginstate, IndexTuple tuple); extern Datum gintuple_get_key(GinState *ginstate, IndexTuple tuple, GinNullCategory *category); +extern void *gingetreloptcatalog (void); /* gininsert.c */ extern IndexBuildResult *ginbuild(Relation heap, Relation index, diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h index 60a770a..be18393 100644 --- a/src/include/access/gist_private.h +++ b/src/include/access/gist_private.h @@ -412,6 +412,24 @@ typedef struct GISTBuildBuffers int rootlevel; } GISTBuildBuffers; + +/* + * Definition of items of enum type. Names and codes. To add or modify item + * edit both lists + */ +#define GIST_OPTION_BUFFERING_VALUE_NAMES { \ + "on", \ + "off", \ + "auto", \ + (const char *) NULL \ +} +typedef enum gist_option_buffering_value_numbers +{ + GIST_OPTION_BUFFERING_ON = 0, + GIST_OPTION_BUFFERING_OFF = 1, + GIST_OPTION_BUFFERING_AUTO = 2, +} gist_option_buffering_value_numbers; + /* * Storage type for GiST's reloptions */ @@ -419,7 +437,7 @@ typedef struct GiSTOptions { int32 vl_len_; /* varlena header (do not touch directly!) */ int fillfactor; /* page fill factor in percent (0..100) */ - int bufferingModeOffset; /* use buffering build? */ + int buffering_mode; /* use buffering build? */ } GiSTOptions; /* gist.c */ @@ -486,7 +504,6 @@ extern bool gistvalidate(Oid opclassoid); #define GIST_MIN_FILLFACTOR 10 #define GIST_DEFAULT_FILLFACTOR 90 -extern bytea *gistoptions(Datum reloptions, bool validate); extern bool gistproperty(Oid index_oid, int attno, IndexAMProperty prop, const char *propname, bool *res, bool *isnull); @@ -536,6 +553,7 @@ extern void gistMakeUnionKey(GISTSTATE *giststate, int attno, Datum *dst, bool *dstisnull); extern XLogRecPtr gistGetFakeLSN(Relation rel); +extern void * gistgetreloptcatalog (void); /* gistvacuum.c */ extern IndexBulkDeleteResult *gistbulkdelete(IndexVacuumInfo *info, @@ -554,7 +572,6 @@ extern void gistSplitByKey(Relation r, Page page, IndexTuple *itup, /* gistbuild.c */ extern IndexBuildResult *gistbuild(Relation heap, Relation index, struct IndexInfo *indexInfo); -extern void gistValidateBufferingOption(char *value); /* gistbuildbuffers.c */ extern GISTBuildBuffers *gistInitBuildBuffers(int pagesPerBuffer, int levelStep, diff --git a/src/include/access/hash.h b/src/include/access/hash.h index 1a9b91f..ad66f0b 100644 --- a/src/include/access/hash.h +++ b/src/include/access/hash.h @@ -185,6 +185,19 @@ typedef struct HashMetaPageData typedef HashMetaPageData *HashMetaPage; + +typedef struct HashRelOptions +{ + int32 varlena_header_;/* varlena header (do not touch directly!) */ + int fillfactor; /* page fill factor in percent (0..100) */ +} HashRelOptions; + + +#define HashGetFillFactor(relation) \ + ((relation)->rd_options ? \ + ((HashRelOptions *) (relation)->rd_options)->fillfactor : \ + HASH_DEFAULT_FILLFACTOR) + /* * Maximum size of a hash index item (it's okay to have only one per page) */ @@ -279,7 +292,6 @@ extern IndexBulkDeleteResult *hashbulkdelete(IndexVacuumInfo *info, void *callback_state); extern IndexBulkDeleteResult *hashvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats); -extern bytea *hashoptions(Datum reloptions, bool validate); extern bool hashvalidate(Oid opclassoid); extern Datum hash_any(register const unsigned char *k, register int keylen); @@ -368,4 +380,6 @@ extern void hashbucketcleanup(Relation rel, Bucket cur_bucket, bool bucket_has_garbage, IndexBulkDeleteCallback callback, void *callback_state); +extern void *hashgetreloptcatalog(void); + #endif /* HASH_H */ diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h index 011a72e..4d95f12 100644 --- a/src/include/access/nbtree.h +++ b/src/include/access/nbtree.h @@ -651,6 +651,17 @@ typedef BTScanOpaqueData *BTScanOpaque; #define SK_BT_DESC (INDOPTION_DESC << SK_BT_INDOPTION_SHIFT) #define SK_BT_NULLS_FIRST (INDOPTION_NULLS_FIRST << SK_BT_INDOPTION_SHIFT) +typedef struct BTRelOptions +{ + int32 varlena_header_;/* varlena header (do not touch directly!) */ + int fillfactor; /* page fill factor in percent (0..100) */ +} BTRelOptions; + +#define BTGetFillFactor(relation) \ + ((relation)->rd_options ? \ + ((BTRelOptions *) (relation)->rd_options)->fillfactor : \ + BTREE_DEFAULT_FILLFACTOR) + /* * prototypes for functions in nbtree.c (external entry points for btree) */ @@ -746,7 +757,6 @@ extern void _bt_end_vacuum(Relation rel); extern void _bt_end_vacuum_callback(int code, Datum arg); extern Size BTreeShmemSize(void); extern void BTreeShmemInit(void); -extern bytea *btoptions(Datum reloptions, bool validate); extern bool btproperty(Oid index_oid, int attno, IndexAMProperty prop, const char *propname, bool *res, bool *isnull); diff --git a/src/include/access/options.h b/src/include/access/options.h new file mode 100644 index 0000000..1be9df0 --- /dev/null +++ b/src/include/access/options.h @@ -0,0 +1,224 @@ +/*------------------------------------------------------------------------- + * + * options.h + * Core support for relation and tablespace options (pg_class.reloptions + * and pg_tablespace.spcoptions) + * + * Note: the functions dealing with text-array options values declare + * them as Datum, not ArrayType *, to avoid needing to include array.h + * into a lot of low-level code. + * + * + * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/access/options.h + * + *------------------------------------------------------------------------- + */ +#ifndef OPTIONS_H +#define OPTIONS_H + +#include "storage/lock.h" +#include "nodes/pg_list.h" + +/* types supported by reloptions */ +typedef enum option_type +{ + OPTION_TYPE_BOOL, + OPTION_TYPE_INT, + OPTION_TYPE_REAL, + OPTION_TYPE_ENUM, + OPTION_TYPE_STRING +} option_type; + +typedef enum option_value_status +{ + OPTION_VALUE_STATUS_EMPTY, /* Option was just initialized */ + OPTION_VALUE_STATUS_RAW, /* Option just came from syntax analyzer + in has name, and raw (unparsed) value */ + OPTION_VALUE_STATUS_PARSED, /* Option was parsed and has link to + catalog entry and proper value */ + OPTION_VALUE_STATUS_FOR_RESET /* This option came from ALTER xxx RESET */ +} option_value_status; + +/* flags for reloptinon definition */ +typedef enum option_definition_flags +{ + OPTION_DEFINITION_FLAG_FORBID_ALTER = (1 << 0), /* Altering this option is forbidden */ + OPTION_DEFINITION_FLAG_IGNORE = (1 << 1), /* Skip this option while parsing. Used for + WITH OIDS special case */ + OPTION_DEFINITION_FLAG_REJECT = (1 << 2) /* Option will be rejected when comes from + syntax analyzer, but still have default + value and offset */ +} option_definition_flags; + +/* flags that tells reloption parser how to parse*/ +typedef enum options_parse_mode +{ + OPTIONS_PARSE_MODE_VALIDATE = (1 << 0), + OPTIONS_PARSE_MODE_FOR_ALTER = (1 << 1), + OPTIONS_PARSE_MODE_FOR_RESET = (1 << 2) +} options_parse_mode; + + +/* generic struct to hold shared data */ +typedef struct option_definition_basic +{ + const char *name; /* must be first (used as list termination + * marker) */ + const char *desc; + LOCKMODE lockmode; + option_definition_flags flags; + option_type type; + int struct_offset; /* offset of the value in Bytea representation */ +} option_definition_basic; + +/* holds a parsed value */ +typedef struct option_value +{ + option_definition_basic *gen; + char *namespace; + option_value_status status; + char *raw_value; /* allocated separately */ + char *raw_name; + union + { + bool bool_val; + int int_val; + double real_val; + int enum_val; + char *string_val; /* allocated separately */ + } values; +} option_value; + +/* reloptions records for specific variable types */ +typedef struct option_definition_bool +{ + option_definition_basic base; + bool default_val; +} option_definition_bool; + +typedef struct option_definition_int +{ + option_definition_basic base; + int default_val; + int min; + int max; +} option_definition_int; + +typedef struct option_definition_real +{ + option_definition_basic base; + double default_val; + double min; + double max; +} option_definition_real; + +typedef struct option_definition_enum +{ + option_definition_basic base; + const char **allowed_values; /* Null terminated array of allowed values for + the option */ + int default_val; /* Number of item of allowed_values array*/ +} option_definition_enum; + +/* validation routines for strings */ +typedef void (*validate_string_relopt) (char *value); + +/* + * When storing sting reloptions, we shoud deal with special case when + * option value is not set. For fixed length options, we just copy default + * option value into the binary structure. For varlen value, there can be + * "not set" special case, with no default value offered. + * In this case we will set offset value to -1, so code that use relptions + * can deal this case. For better readability it was defined as a constant. + */ +#define OPTION_STRING_VALUE_NOT_SET_OFFSET -1 + +typedef struct option_definition_string +{ + option_definition_basic base; + validate_string_relopt validate_cb; + char *default_val; +} option_definition_string; + +typedef void (*postprocess_bytea_options_function) (void *data, bool validate); + +typedef struct options_catalog +{ + option_definition_basic **definitions; + int num; /* Number of catalog items in use */ + int num_allocated; /* Number of catalog items allocated */ + Size struct_size; /* Size of a structure for options in binary + representation */ + postprocess_bytea_options_function postprocess_fun; /* This function is + called after options were + converted in Bytea represenation. + Can be used for extra validation + and so on */ + char *namespace; /* Catalog is used for options from this + namespase */ +} options_catalog; + + +/* + * Options catalog related functions + */ +extern options_catalog *allocateOptionsCatalog(char *namespace); +extern void optionsCatalogAddItemBool(options_catalog * catalog, char *name, + char *desc, LOCKMODE lockmode, option_definition_flags flags, + int struct_offset, bool default_val); +extern void optionsCatalogAddItemInt(options_catalog * catalog, char *name, + char *desc, LOCKMODE lockmode, option_definition_flags flags, + int struct_offset, int default_val, int min_val, int max_val); +extern void optionsCatalogAddItemReal(options_catalog * catalog, char *name, + char *desc, LOCKMODE lockmode, option_definition_flags flags, + int struct_offset, double default_val, double min_val, double max_val); +extern void optionsCatalogAddItemEnum(options_catalog * catalog, + char *name, char *desc, LOCKMODE lockmode, option_definition_flags flags, + int struct_offset, const char **allowed_values, int default_val); +extern void optionsCatalogAddItemString(options_catalog * catalog, char *name, + char *desc, LOCKMODE lockmode, option_definition_flags flags, + int struct_offset, char *default_val, validate_string_relopt validator); + + +/* + * This macro allows to get string option value from bytea representation. + * "optstruct" - is a structure that is stored in bytea options representation + * "member" - member of this structure that has string option value + * (actually string values are stored in bytea after the structure, and + * and "member" will contain an offset to this value. This macro do all + * the math + */ +#define GET_STRING_OPTION(optstruct, member) \ + ((optstruct)->member == OPTION_STRING_VALUE_NOT_SET_OFFSET ? NULL : \ + (char *)(optstruct) + (optstruct)->member) + +/* + * Functions related to option convertation, parsing, manipulation + * and validation + */ +extern List *optionsDefListToRawValues(List *defList, options_parse_mode + parse_mode); +extern Datum optionsValuesToTextArray(List *options_values); +extern List *optionsTextArrayToRawValues(Datum array_datum); +extern List *optionsMergeOptionValues(List *old_options, List *new_options); +extern void optionsDefListValdateNamespaces(List *defList, + char **allowed_namespaces); +extern List *optionsDefListFilterNamespaces(List *defList, char *namespace); +extern List *optionsTextArrayToDefList(Datum options); +extern List *optionsParseRawValues(List * raw_values, options_catalog *catalog, + options_parse_mode mode); +extern bytea *optionsValuesToBytea(List * options, options_catalog *catalog); + +/* + * Meta functions that uses functions above to get options for relations, + * tablespaces, views and so on + */ + +extern bytea *optionsTextArrayToBytea(options_catalog *catalog, Datum data); +extern Datum transformOptions(options_catalog * catalog, Datum oldOptions, + List *defList, options_parse_mode parse_mode); + +#endif /* OPTIONS_H */ diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index 861977a..78c45db 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -1,13 +1,9 @@ /*------------------------------------------------------------------------- * * reloptions.h - * Core support for relation and tablespace options (pg_class.reloptions + * Support for relation and tablespace options (pg_class.reloptions * and pg_tablespace.spcoptions) * - * Note: the functions dealing with text-array reloptions values declare - * them as Datum, not ArrayType *, to avoid needing to include array.h - * into a lot of low-level code. - * * * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California @@ -20,263 +16,27 @@ #define RELOPTIONS_H #include "access/amapi.h" -#include "access/htup.h" -#include "access/tupdesc.h" -#include "nodes/pg_list.h" -#include "storage/lock.h" - -/* types supported by reloptions */ -typedef enum relopt_type -{ - RELOPT_TYPE_BOOL, - RELOPT_TYPE_INT, - RELOPT_TYPE_REAL, - RELOPT_TYPE_STRING -} relopt_type; +#include "access/options.h" -/* kinds supported by reloptions */ -typedef enum relopt_kind -{ - RELOPT_KIND_HEAP = (1 << 0), - RELOPT_KIND_TOAST = (1 << 1), - RELOPT_KIND_BTREE = (1 << 2), - RELOPT_KIND_HASH = (1 << 3), - RELOPT_KIND_GIN = (1 << 4), - RELOPT_KIND_GIST = (1 << 5), - RELOPT_KIND_ATTRIBUTE = (1 << 6), - RELOPT_KIND_TABLESPACE = (1 << 7), - RELOPT_KIND_SPGIST = (1 << 8), - RELOPT_KIND_VIEW = (1 << 9), - RELOPT_KIND_BRIN = (1 << 10), - /* if you add a new kind, make sure you update "last_default" too */ - RELOPT_KIND_LAST_DEFAULT = RELOPT_KIND_BRIN, - /* some compilers treat enums as signed ints, so we can't use 1 << 31 */ - RELOPT_KIND_MAX = (1 << 30) -} relopt_kind; /* reloption namespaces allowed for heaps -- currently only TOAST */ #define HEAP_RELOPT_NAMESPACES { "toast", NULL } -/* generic struct to hold shared data */ -typedef struct relopt_gen -{ - const char *name; /* must be first (used as list termination - * marker) */ - const char *desc; - bits32 kinds; - LOCKMODE lockmode; - int namelen; - relopt_type type; -} relopt_gen; - -/* holds a parsed value */ -typedef struct relopt_value -{ - relopt_gen *gen; - bool isset; - union - { - bool bool_val; - int int_val; - double real_val; - char *string_val; /* allocated separately */ - } values; -} relopt_value; - -/* reloptions records for specific variable types */ -typedef struct relopt_bool -{ - relopt_gen gen; - bool default_val; -} relopt_bool; - -typedef struct relopt_int -{ - relopt_gen gen; - int default_val; - int min; - int max; -} relopt_int; - -typedef struct relopt_real -{ - relopt_gen gen; - double default_val; - double min; - double max; -} relopt_real; - -/* validation routines for strings */ -typedef void (*validate_string_relopt) (char *value); - -typedef struct relopt_string -{ - relopt_gen gen; - int default_len; - bool default_isnull; - validate_string_relopt validate_cb; - char *default_val; -} relopt_string; - -/* This is the table datatype for fillRelOptions */ -typedef struct -{ - const char *optname; /* option's name */ - relopt_type opttype; /* option's datatype */ - int offset; /* offset of field in result struct */ -} relopt_parse_elt; - - -/* - * These macros exist for the convenience of amoptions writers (but consider - * using fillRelOptions, which is a lot simpler). Beware of multiple - * evaluation of arguments! - * - * The last argument in the HANDLE_*_RELOPTION macros allows the caller to - * determine whether the option was set (true), or its value acquired from - * defaults (false); it can be passed as (char *) NULL if the caller does not - * need this information. - * - * optname is the option name (a string), var is the variable - * on which the value should be stored (e.g. StdRdOptions->fillfactor), and - * option is a relopt_value pointer. - * - * The normal way to use this is to loop on the relopt_value array returned by - * parseRelOptions: - * for (i = 0; options[i].gen->name; i++) - * { - * if (HAVE_RELOPTION("fillfactor", options[i]) - * { - * HANDLE_INT_RELOPTION("fillfactor", rdopts->fillfactor, options[i], &isset); - * continue; - * } - * if (HAVE_RELOPTION("default_row_acl", options[i]) - * { - * ... - * } - * ... - * if (validate) - * ereport(ERROR, - * (errmsg("unknown option"))); - * } - * - * Note that this is more or less the same that fillRelOptions does, so only - * use this if you need to do something non-standard within some option's - * code block. - */ -#define HAVE_RELOPTION(optname, option) \ - (pg_strncasecmp(option.gen->name, optname, option.gen->namelen + 1) == 0) - -#define HANDLE_INT_RELOPTION(optname, var, option, wasset) \ - do { \ - if (option.isset) \ - var = option.values.int_val; \ - else \ - var = ((relopt_int *) option.gen)->default_val; \ - (wasset) != NULL ? *(wasset) = option.isset : (dummyret)NULL; \ - } while (0) - -#define HANDLE_BOOL_RELOPTION(optname, var, option, wasset) \ - do { \ - if (option.isset) \ - var = option.values.bool_val; \ - else \ - var = ((relopt_bool *) option.gen)->default_val; \ - (wasset) != NULL ? *(wasset) = option.isset : (dummyret) NULL; \ - } while (0) -#define HANDLE_REAL_RELOPTION(optname, var, option, wasset) \ - do { \ - if (option.isset) \ - var = option.values.real_val; \ - else \ - var = ((relopt_real *) option.gen)->default_val; \ - (wasset) != NULL ? *(wasset) = option.isset : (dummyret) NULL; \ - } while (0) - -/* - * Note that this assumes that the variable is already allocated at the tail of - * reloptions structure (StdRdOptions or equivalent). - * - * "base" is a pointer to the reloptions structure, and "offset" is an integer - * variable that must be initialized to sizeof(reloptions structure). This - * struct must have been allocated with enough space to hold any string option - * present, including terminating \0 for every option. SET_VARSIZE() must be - * called on the struct with this offset as the second argument, after all the - * string options have been processed. - */ -#define HANDLE_STRING_RELOPTION(optname, var, option, base, offset, wasset) \ - do { \ - relopt_string *optstring = (relopt_string *) option.gen;\ - char *string_val; \ - if (option.isset) \ - string_val = option.values.string_val; \ - else if (!optstring->default_isnull) \ - string_val = optstring->default_val; \ - else \ - string_val = NULL; \ - (wasset) != NULL ? *(wasset) = option.isset : (dummyret) NULL; \ - if (string_val == NULL) \ - var = 0; \ - else \ - { \ - strcpy(((char *)(base)) + (offset), string_val); \ - var = (offset); \ - (offset) += strlen(string_val) + 1; \ - } \ - } while (0) - -/* - * For use during amoptions: get the strlen of a string option - * (either default or the user defined value) - */ -#define GET_STRING_RELOPTION_LEN(option) \ - ((option).isset ? strlen((option).values.string_val) : \ - ((relopt_string *) (option).gen)->default_len) +extern bytea *extractRelOptions(HeapTuple tuple, TupleDesc tupdesc, + amrelopt_catalog_function catalog_fun); /* - * For use by code reading options already parsed: get a pointer to the string - * value itself. "optstruct" is the StdRdOption struct or equivalent, "member" - * is the struct member corresponding to the string option + * Functions that creates option catalog instances for heap, toast, view + * and other object types that are part of the postgres core */ -#define GET_STRING_RELOPTION(optstruct, member) \ - ((optstruct)->member == 0 ? NULL : \ - (char *)(optstruct) + (optstruct)->member) +extern options_catalog *get_heap_relopt_catalog(void); +extern options_catalog *get_toast_relopt_catalog(void); -extern relopt_kind add_reloption_kind(void); -extern void add_bool_reloption(bits32 kinds, char *name, char *desc, - bool default_val); -extern void add_int_reloption(bits32 kinds, char *name, char *desc, - int default_val, int min_val, int max_val); -extern void add_real_reloption(bits32 kinds, char *name, char *desc, - double default_val, double min_val, double max_val); -extern void add_string_reloption(bits32 kinds, char *name, char *desc, - char *default_val, validate_string_relopt validator); - -extern Datum transformRelOptions(Datum oldOptions, List *defList, - char *namspace, char *validnsps[], - bool ignoreOids, bool isReset); -extern List *untransformRelOptions(Datum options); -extern bytea *extractRelOptions(HeapTuple tuple, TupleDesc tupdesc, - amoptions_function amoptions); -extern relopt_value *parseRelOptions(Datum options, bool validate, - relopt_kind kind, int *numrelopts); -extern void *allocateReloptStruct(Size base, relopt_value *options, - int numoptions); -extern void fillRelOptions(void *rdopts, Size basesize, - relopt_value *options, int numoptions, - bool validate, - const relopt_parse_elt *elems, int nelems); - -extern bytea *default_reloptions(Datum reloptions, bool validate, - relopt_kind kind); -extern bytea *heap_reloptions(char relkind, Datum reloptions, bool validate); -extern bytea *view_reloptions(Datum reloptions, bool validate); -extern bytea *index_reloptions(amoptions_function amoptions, Datum reloptions, - bool validate); -extern bytea *attribute_reloptions(Datum reloptions, bool validate); -extern bytea *tablespace_reloptions(Datum reloptions, bool validate); -extern LOCKMODE AlterTableGetRelOptionsLockLevel(List *defList); +extern options_catalog *get_view_relopt_catalog(void); +extern options_catalog *get_attribute_options_catalog(void); +extern options_catalog *get_tablespace_options_catalog(void); +extern LOCKMODE AlterTableGetRelOptionsLockLevel(Relation rel, List *defList); #endif /* RELOPTIONS_H */ diff --git a/src/include/access/spgist.h b/src/include/access/spgist.h index aaf78bc..1eb6de4 100644 --- a/src/include/access/spgist.h +++ b/src/include/access/spgist.h @@ -20,10 +20,6 @@ #include "lib/stringinfo.h" -/* reloption parameters */ -#define SPGIST_MIN_FILLFACTOR 10 -#define SPGIST_DEFAULT_FILLFACTOR 80 - /* SPGiST opclass support function numbers */ #define SPGIST_CONFIG_PROC 1 #define SPGIST_CHOOSE_PROC 2 @@ -183,7 +179,7 @@ typedef struct spgLeafConsistentOut /* spgutils.c */ -extern bytea *spgoptions(Datum reloptions, bool validate); +extern Datum spghandler(PG_FUNCTION_ARGS); /* spginsert.c */ extern IndexBuildResult *spgbuild(Relation heap, Relation index, diff --git a/src/include/access/spgist_private.h b/src/include/access/spgist_private.h index b2979a9..62811f0 100644 --- a/src/include/access/spgist_private.h +++ b/src/include/access/spgist_private.h @@ -21,6 +21,19 @@ #include "utils/relcache.h" +typedef struct SpGistRelOptions +{ + int32 varlena_header_;/* varlena header (do not touch directly!) */ + int fillfactor; /* page fill factor in percent (0..100) */ +} SpGistRelOptions; + + +#define SpGistGetFillFactor(relation) \ + ((relation)->rd_options ? \ + ((SpGistRelOptions *) (relation)->rd_options)->fillfactor : \ + SPGIST_DEFAULT_FILLFACTOR) + + /* Page numbers of fixed-location pages */ #define SPGIST_METAPAGE_BLKNO (0) /* metapage */ #define SPGIST_ROOT_BLKNO (1) /* root for normal entries */ @@ -610,6 +623,11 @@ typedef struct spgxlogVacuumRedirect #define GBUF_REQ_NULLS(flags) ((flags) & GBUF_NULLS) /* spgutils.c */ + +/* reloption parameters */ +#define SPGIST_MIN_FILLFACTOR 10 +#define SPGIST_DEFAULT_FILLFACTOR 80 + extern SpGistCache *spgGetCache(Relation index); extern void initSpGistState(SpGistState *state, Relation index); extern Buffer SpGistNewBuffer(Relation index); @@ -637,6 +655,7 @@ extern OffsetNumber SpGistPageAddNewItem(SpGistState *state, Page page, Item item, Size size, OffsetNumber *startOffset, bool errorOK); +extern void * spggetreloptcatalog(void); /* spgdoinsert.c */ extern void spgUpdateNodeLink(SpGistInnerTuple tup, int nodeN, diff --git a/src/include/catalog/toasting.h b/src/include/catalog/toasting.h index db7f145..d6464a7 100644 --- a/src/include/catalog/toasting.h +++ b/src/include/catalog/toasting.h @@ -19,7 +19,7 @@ /* * toasting.c prototypes */ -extern void NewRelationCreateToastTable(Oid relOid, Datum reloptions); +extern bool NewRelationCreateToastTable(Oid relOid, Datum reloptions); extern void NewHeapCreateToastTable(Oid relOid, Datum reloptions, LOCKMODE lockmode); extern void AlterTableCreateToastTable(Oid relOid, Datum reloptions, diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h index f3a9701..33f5782 100644 --- a/src/include/commands/tablecmds.h +++ b/src/include/commands/tablecmds.h @@ -31,7 +31,7 @@ extern Oid AlterTableLookupRelation(AlterTableStmt *stmt, LOCKMODE lockmode); extern void AlterTable(Oid relid, LOCKMODE lockmode, AlterTableStmt *stmt); -extern LOCKMODE AlterTableGetLockLevel(List *cmds); +extern LOCKMODE AlterTableGetLockLevel(Oid relid, List *cmds); extern void ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lockmode); diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index a617a7c..28113ef 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -269,7 +269,7 @@ typedef struct AutoVacOpts float8 analyze_scale_factor; } AutoVacOpts; -typedef struct StdRdOptions +typedef struct HeapOptions { int32 vl_len_; /* varlena header (do not touch directly!) */ int fillfactor; /* page fill factor in percent (0..100) */ @@ -277,107 +277,115 @@ typedef struct StdRdOptions bool user_catalog_table; /* use as an additional catalog * relation */ int parallel_workers; /* max number of parallel workers */ -} StdRdOptions; +} HeapOptions; + +typedef struct ToastOptions +{ + int32 vl_len_; /* varlena header (do not touch directly!) */ + AutoVacOpts autovacuum; /* autovacuum-related options */ +} ToastOptions; #define HEAP_MIN_FILLFACTOR 10 #define HEAP_DEFAULT_FILLFACTOR 100 +#define TOAST_DEFAULT_FILLFACTOR 100 /* Only default is actually used */ + /* - * RelationGetFillFactor + * HeapGetFillFactor * Returns the relation's fillfactor. Note multiple eval of argument! */ -#define RelationGetFillFactor(relation, defaultff) \ +#define HeapGetFillFactor(relation, defaultff) \ ((relation)->rd_options ? \ - ((StdRdOptions *) (relation)->rd_options)->fillfactor : (defaultff)) + ((HeapOptions *) (relation)->rd_options)->fillfactor : (defaultff)) /* - * RelationGetTargetPageUsage - * Returns the relation's desired space usage per page in bytes. - */ -#define RelationGetTargetPageUsage(relation, defaultff) \ - (BLCKSZ * RelationGetFillFactor(relation, defaultff) / 100) - -/* - * RelationGetTargetPageFreeSpace + * HeapGetTargetPageFreeSpace * Returns the relation's desired freespace per page in bytes. */ -#define RelationGetTargetPageFreeSpace(relation, defaultff) \ - (BLCKSZ * (100 - RelationGetFillFactor(relation, defaultff)) / 100) +#define HeapGetTargetPageFreeSpace(relation, defaultff) \ + (BLCKSZ * (100 - HeapGetFillFactor(relation, defaultff)) / 100) /* - * RelationIsUsedAsCatalogTable + * HeapIsUsedAsCatalogTable * Returns whether the relation should be treated as a catalog table * from the pov of logical decoding. Note multiple eval of argument! */ -#define RelationIsUsedAsCatalogTable(relation) \ +#define HeapIsUsedAsCatalogTable(relation) \ ((relation)->rd_options && \ ((relation)->rd_rel->relkind == RELKIND_RELATION || \ (relation)->rd_rel->relkind == RELKIND_MATVIEW) ? \ - ((StdRdOptions *) (relation)->rd_options)->user_catalog_table : false) + ((HeapOptions *) (relation)->rd_options)->user_catalog_table : false) /* - * RelationGetParallelWorkers + * HeapGetParallelWorkers * Returns the relation's parallel_workers reloption setting. * Note multiple eval of argument! */ -#define RelationGetParallelWorkers(relation, defaultpw) \ +#define HeapGetParallelWorkers(relation, defaultpw) \ ((relation)->rd_options ? \ - ((StdRdOptions *) (relation)->rd_options)->parallel_workers : (defaultpw)) + ((HeapOptions *) (relation)->rd_options)->parallel_workers : (defaultpw)) +#define ToastGetTargetPageFreeSpace() \ + (BLCKSZ * (100 - TOAST_DEFAULT_FILLFACTOR) / 100) /* * ViewOptions * Contents of rd_options for views */ + +/* + * Definition of items of enum type. Names and codes. To add or modify item + * edit both lists + */ +#define VIEW_OPTION_CHECK_OPTION_VALUE_NAMES { \ + "local", \ + "cascaded", \ + (const char *) NULL \ +} + +typedef enum view_option_check_option_value_numbers +{ + VIEW_OPTION_CHECK_OPTION_NOT_SET = -1, + VIEW_OPTION_CHECK_OPTION_LOCAL = 0, + VIEW_OPTION_CHECK_OPTION_CASCADED = 1, +} view_option_check_option_value_numbers; + typedef struct ViewOptions { int32 vl_len_; /* varlena header (do not touch directly!) */ bool security_barrier; - int check_option_offset; + int check_option; } ViewOptions; - /* - * RelationIsSecurityView - * Returns whether the relation is security view, or not. Note multiple - * eval of argument! + * ViewIsSecurityView + * Returns whether the view is security view, or not. Note multiple eval + * of argument! */ -#define RelationIsSecurityView(relation) \ +#define ViewIsSecurityView(relation) \ ((relation)->rd_options ? \ - ((ViewOptions *) (relation)->rd_options)->security_barrier : false) + ((ViewOptions *) (relation)->rd_options)->security_barrier : false) /* - * RelationHasCheckOption - * Returns true if the relation is a view defined with either the local - * or the cascaded check option. Note multiple eval of argument! + * ViewHasCheckOption + * Returns true if view is defined with either the local or the cascaded + * check option. Note multiple eval of argument! */ -#define RelationHasCheckOption(relation) \ - ((relation)->rd_options && \ - ((ViewOptions *) (relation)->rd_options)->check_option_offset != 0) -/* - * RelationHasLocalCheckOption - * Returns true if the relation is a view defined with the local check - * option. Note multiple eval of argument! - */ -#define RelationHasLocalCheckOption(relation) \ - ((relation)->rd_options && \ - ((ViewOptions *) (relation)->rd_options)->check_option_offset != 0 ? \ - strcmp((char *) (relation)->rd_options + \ - ((ViewOptions *) (relation)->rd_options)->check_option_offset, \ - "local") == 0 : false) +#define ViewHasCheckOption(relation) \ +((relation)->rd_options && \ + ((ViewOptions *) (relation)->rd_options)->check_option != \ + VIEW_OPTION_CHECK_OPTION_NOT_SET) /* - * RelationHasCascadedCheckOption - * Returns true if the relation is a view defined with the cascaded check - * option. Note multiple eval of argument! + * ViewHasCascadedCheckOption + * Returns true if the view is defined with the cascaded check option. + * Note multiple eval of argument! */ -#define RelationHasCascadedCheckOption(relation) \ - ((relation)->rd_options && \ - ((ViewOptions *) (relation)->rd_options)->check_option_offset != 0 ? \ - strcmp((char *) (relation)->rd_options + \ - ((ViewOptions *) (relation)->rd_options)->check_option_offset, \ - "cascaded") == 0 : false) +#define ViewHasCascadedCheckOption(relation) \ +((relation)->rd_options && \ + ((ViewOptions *) (relation)->rd_options)->check_option == \ + VIEW_OPTION_CHECK_OPTION_CASCADED) /* * RelationIsValid @@ -556,7 +564,7 @@ typedef struct ViewOptions #define RelationIsAccessibleInLogicalDecoding(relation) \ (XLogLogicalInfoActive() && \ RelationNeedsWAL(relation) && \ - (IsCatalogRelation(relation) || RelationIsUsedAsCatalogTable(relation))) + (IsCatalogRelation(relation) || HeapIsUsedAsCatalogTable(relation))) /* * RelationIsLogicallyLogged diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out index d8e7b61..88ecca4 100644 --- a/src/test/regress/expected/alter_table.out +++ b/src/test/regress/expected/alter_table.out @@ -2285,7 +2285,9 @@ ERROR: unrecognized parameter "autovacuum_enabled" alter view my_locks set (autovacuum_enabled = false); ERROR: unrecognized parameter "autovacuum_enabled" alter table my_locks reset (autovacuum_enabled); +ERROR: unrecognized parameter "autovacuum_enabled" alter view my_locks reset (autovacuum_enabled); +ERROR: unrecognized parameter "autovacuum_enabled" begin; alter view my_locks set (security_barrier=off); select * from my_locks order by 1; @@ -3364,3 +3366,10 @@ alter table p attach partition p1 for values from (1, 2) to (1, 10); ERROR: partition constraint is violated by some row -- cleanup: avoid using CASCADE drop table p, p1, p11; +-- test alter column options +CREATE TABLE tmp(i integer); +INSERT INTO tmp VALUES (1); +ALTER TABLE tmp ALTER COLUMN i SET (n_distinct = 1, n_distinct_inherited = 2); +ALTER TABLE tmp ALTER COLUMN i RESET (n_distinct_inherited); +ANALYZE tmp; +DROP TABLE tmp; diff --git a/src/test/regress/expected/brin.out b/src/test/regress/expected/brin.out index 21676e5..36b1cc4 100644 --- a/src/test/regress/expected/brin.out +++ b/src/test/regress/expected/brin.out @@ -418,3 +418,8 @@ SELECT brin_summarize_new_values('brinidx'); -- ok, no change expected 0 (1 row) +-- Check that changing reloptions for brin index is not allowed +ALTER INDEX brinidx SET (pages_per_range = 10); +ERROR: changing parameter "pages_per_range" is not allowed +ALTER INDEX brinidx RESET (pages_per_range); +ERROR: changing parameter "pages_per_range" is not allowed diff --git a/src/test/regress/expected/create_index.out b/src/test/regress/expected/create_index.out index e519fdb..2ff134e 100644 --- a/src/test/regress/expected/create_index.out +++ b/src/test/regress/expected/create_index.out @@ -2340,7 +2340,7 @@ CREATE INDEX hash_name_index ON hash_name_heap USING hash (random name_ops); WARNING: hash indexes are not WAL-logged and their use is discouraged CREATE INDEX hash_txt_index ON hash_txt_heap USING hash (random text_ops); WARNING: hash indexes are not WAL-logged and their use is discouraged -CREATE INDEX hash_f8_index ON hash_f8_heap USING hash (random float8_ops); +CREATE INDEX hash_f8_index ON hash_f8_heap USING hash (random float8_ops) WITH (fillfactor=60); WARNING: hash indexes are not WAL-logged and their use is discouraged CREATE UNLOGGED TABLE unlogged_hash_table (id int4); CREATE INDEX unlogged_hash_index ON unlogged_hash_table USING hash (id int4_ops); diff --git a/src/test/regress/expected/gist.out b/src/test/regress/expected/gist.out index c7181b0..19f1881 100644 --- a/src/test/regress/expected/gist.out +++ b/src/test/regress/expected/gist.out @@ -12,6 +12,20 @@ insert into gist_point_tbl (id, p) select g+100000, point(g*10+1, g*10+1) from generate_series(1, 10000) g; -- To test vacuum, delete some entries from all over the index. delete from gist_point_tbl where id % 2 = 1; +-- Test buffering and fillfactor reloption takes valid values +create index gist_pointidx2 on gist_point_tbl using gist(p) with (buffering = on, fillfactor=50); +create index gist_pointidx3 on gist_point_tbl using gist(p) with (buffering = off); +create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = auto); +--Test buffering and fillfactor for refising invalid values +create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value); +ERROR: invalid value for "buffering" option +DETAIL: Valid values are "on", "off", "auto". +create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9); +ERROR: value 9 out of bounds for option "fillfactor" +DETAIL: Valid values are between "10" and "100". +create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=101); +ERROR: value 101 out of bounds for option "fillfactor" +DETAIL: Valid values are between "10" and "100". -- And also delete some concentration of values. (GiST doesn't currently -- attempt to delete pages even when they become empty, but if it did, this -- would exercise it) diff --git a/src/test/regress/expected/hash_index.out b/src/test/regress/expected/hash_index.out index f8b9f02..9f9352d 100644 --- a/src/test/regress/expected/hash_index.out +++ b/src/test/regress/expected/hash_index.out @@ -232,3 +232,14 @@ INSERT INTO hash_heap_float4 VALUES (1.1,1); CREATE INDEX hash_idx ON hash_heap_float4 USING hash (x); WARNING: hash indexes are not WAL-logged and their use is discouraged DROP TABLE hash_heap_float4 CASCADE; +-- Check reloptions min max values and that it is not allowed to ALTER +CREATE INDEX hash_f8_index2 ON hash_f8_heap USING hash (random float8_ops) WITH (fillfactor=9); +WARNING: hash indexes are not WAL-logged and their use is discouraged +ERROR: value 9 out of bounds for option "fillfactor" +DETAIL: Valid values are between "10" and "100". +CREATE INDEX hash_f8_index2 ON hash_f8_heap USING hash (random float8_ops) WITH (fillfactor=101); +WARNING: hash indexes are not WAL-logged and their use is discouraged +ERROR: value 101 out of bounds for option "fillfactor" +DETAIL: Valid values are between "10" and "100". +ALTER INDEX hash_f8_index SET (fillfactor=99); +ERROR: changing parameter "fillfactor" is not allowed diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/expected/reloptions.out new file mode 100644 index 0000000..835d72f --- /dev/null +++ b/src/test/regress/expected/reloptions.out @@ -0,0 +1,193 @@ +-- Simple create +CREATE TABLE reloptions_test(i INT) WITH (fillfactor=30,autovacuum_enabled = false, autovacuum_analyze_scale_factor = 0.2); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------------------------------------------------------------ + {fillfactor=30,autovacuum_enabled=false,autovacuum_analyze_scale_factor=0.2} +(1 row) + +-- Test сase insensitive +CREATE TABLE reloptions_test3(i INT) WITH (FiLlFaCtoR=40); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test3'::regclass; + reloptions +----------------- + {fillfactor=40} +(1 row) + +-- Fail on min/max values check +CREATE TABLE reloptions_test2(i INT) WITH (fillfactor=2); +ERROR: value 2 out of bounds for option "fillfactor" +DETAIL: Valid values are between "10" and "100". +CREATE TABLE reloptions_test2(i INT) WITH (fillfactor=110); +ERROR: value 110 out of bounds for option "fillfactor" +DETAIL: Valid values are between "10" and "100". +CREATE TABLE reloptions_test2(i INT) WITH (autovacuum_analyze_scale_factor = -10.0); +ERROR: value -10.0 out of bounds for option "autovacuum_analyze_scale_factor" +DETAIL: Valid values are between "0.000000" and "100.000000". +CREATE TABLE reloptions_test2(i INT) WITH (autovacuum_analyze_scale_factor = 110.0); +ERROR: value 110.0 out of bounds for option "autovacuum_analyze_scale_factor" +DETAIL: Valid values are between "0.000000" and "100.000000". +-- Fail when option and namespase do not exist +CREATE TABLE reloptions_test2(i INT) WITH (not_existing_option=2); +ERROR: unrecognized parameter "not_existing_option" +CREATE TABLE reloptions_test2(i INT) WITH (not_existing_namespace.fillfactor=2); +ERROR: unrecognized parameter namespace "not_existing_namespace" +-- Fail while setting unproper value +CREATE TABLE reloptions_test2(i INT) WITH (fillfactor=30.5); +ERROR: invalid value for integer option "fillfactor": 30.5 +CREATE TABLE reloptions_test2(i INT) WITH (fillfactor='string'); +ERROR: invalid value for integer option "fillfactor": string +CREATE TABLE reloptions_test2(i INT) WITH (fillfactor=true); +ERROR: invalid value for integer option "fillfactor": true +CREATE TABLE reloptions_test2(i INT) WITH (autovacuum_enabled=12); +ERROR: invalid value for boolean option "autovacuum_enabled": 12 +CREATE TABLE reloptions_test2(i INT) WITH (autovacuum_enabled=30.5); +ERROR: invalid value for boolean option "autovacuum_enabled": 30.5 +CREATE TABLE reloptions_test2(i INT) WITH (autovacuum_enabled='string'); +ERROR: invalid value for boolean option "autovacuum_enabled": string +CREATE TABLE reloptions_test2(i INT) WITH (autovacuum_analyze_scale_factor='string'); +ERROR: invalid value for floating point option "autovacuum_analyze_scale_factor": string +CREATE TABLE reloptions_test2(i INT) WITH (autovacuum_analyze_scale_factor=true); +ERROR: invalid value for floating point option "autovacuum_analyze_scale_factor": true +-- Specifing name only should fail as if there was =ture after it +CREATE TABLE reloptions_test2(i INT) WITH (fillfactor); +ERROR: invalid value for integer option "fillfactor": true +-- Simple ALTER TABLE +ALTER TABLE reloptions_test SET (fillfactor=31, autovacuum_analyze_scale_factor = 0.3); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------------------------------------------------------------ + {autovacuum_enabled=false,fillfactor=31,autovacuum_analyze_scale_factor=0.3} +(1 row) + +-- Check that we cat set boolean option to true just by mentioning it +ALTER TABLE reloptions_test SET (autovacuum_enabled, fillfactor=32); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------------------------------------------------------------------- + {autovacuum_analyze_scale_factor=0.3,autovacuum_enabled=true,fillfactor=32} +(1 row) + +-- Check that RESET works well +ALTER TABLE reloptions_test RESET (fillfactor); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +--------------------------------------------------------------- + {autovacuum_analyze_scale_factor=0.3,autovacuum_enabled=true} +(1 row) + +-- Check that RESETting all values make NULL reloptions record in pg_class +ALTER TABLE reloptions_test RESET (autovacuum_enabled, autovacuum_analyze_scale_factor); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass AND reloptions IS NULL; + reloptions +------------ + +(1 row) + +-- Check RESET fails on att=value +ALTER TABLE reloptions_test RESET (fillfactor=12); +ERROR: RESET must not include values for parameters +-- Check oids options is ignored +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (fillfactor=20, oids=true); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +----------------- + {fillfactor=20} +(1 row) + +-- Now testing toast.* options +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test (s VARCHAR) WITH (toast.autovacuum_vacuum_cost_delay = 23 ); +SELECT reloptions FROM pg_class WHERE oid = (SELECT reltoastrelid FROM pg_class WHERE oid = 'reloptions_test'::regclass); + reloptions +----------------------------------- + {autovacuum_vacuum_cost_delay=23} +(1 row) + +ALTER TABLE reloptions_test SET (toast.autovacuum_vacuum_cost_delay = 24); +SELECT reloptions FROM pg_class WHERE oid = (SELECT reltoastrelid FROM pg_class WHERE oid = 'reloptions_test'::regclass); + reloptions +----------------------------------- + {autovacuum_vacuum_cost_delay=24} +(1 row) + +ALTER TABLE reloptions_test RESET (toast.autovacuum_vacuum_cost_delay); +SELECT reloptions FROM pg_class WHERE oid = (SELECT reltoastrelid FROM pg_class WHERE oid = 'reloptions_test'::regclass); + reloptions +------------ + +(1 row) + +-- Fail on unexisting options in toast namespace +CREATE TABLE reloptions_test2 (i int) WITH (toast.not_existing_option = 42 ); +ERROR: unrecognized parameter "toast.not_existing_option" +-- Fail on setting reloption to a table that does not have a TOAST relation +CREATE TABLE reloptions_test2 (i int) WITH (toast.autovacuum_vacuum_cost_delay = 23 ); +ERROR: no TOAST relation was created for a table. Can't set toast.* storage parameters +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT); +ALTER TABLE reloptions_test SET (toast.autovacuum_vacuum_cost_delay = 23); +ERROR: TOAST relation for this table does not exist. Can't change toast.* storage parameters +ALTER TABLE reloptions_test RESET (toast.autovacuum_vacuum_cost_delay); +ERROR: TOAST relation for this table does not exist. Can't change toast.* storage parameters +-- autovacuum_analyze_scale_factor and autovacuum_analyze_threshold should be +-- accepted by heap but rejected by toast (special case) +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test (s VARCHAR) WITH (autovacuum_analyze_scale_factor=1, autovacuum_analyze_threshold=1); +CREATE TABLE reloptions_test2 (s VARCHAR) WITH (toast.autovacuum_analyze_scale_factor=1); +ERROR: unrecognized parameter "toast.autovacuum_analyze_scale_factor" +CREATE TABLE reloptions_test2 (s VARCHAR) WITH (toast.autovacuum_analyze_threshold=1); +ERROR: unrecognized parameter "toast.autovacuum_analyze_threshold" +-- And now mixed toast + heap +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test (s VARCHAR) WITH (toast.autovacuum_vacuum_cost_delay = 23, autovacuum_vacuum_cost_delay = 24, fillfactor = 40); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + reloptions +------------------------------------------------- + {autovacuum_vacuum_cost_delay=24,fillfactor=40} +(1 row) + +SELECT reloptions FROM pg_class WHERE oid = (SELECT reltoastrelid FROM pg_class WHERE oid = 'reloptions_test'::regclass); + reloptions +----------------------------------- + {autovacuum_vacuum_cost_delay=23} +(1 row) + +-- Same FOR CREATE and ALTER INDEX for btree indexes +CREATE INDEX reloptions_test_idx ON reloptions_test (s) WITH (fillfactor=30); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test_idx'::regclass; + reloptions +----------------- + {fillfactor=30} +(1 row) + +-- Fail when option and namespase do not exist +CREATE INDEX reloptions_test_idx ON reloptions_test (s) WITH (not_existing_option=2); +ERROR: unrecognized parameter "not_existing_option" +CREATE INDEX reloptions_test_idx ON reloptions_test (s) WITH (not_existing_option.fillfactor=2); +ERROR: unrecognized parameter namespace "not_existing_option" +-- Check ranges +CREATE INDEX reloptions_test_idx2 ON reloptions_test (s) WITH (fillfactor=1); +ERROR: value 1 out of bounds for option "fillfactor" +DETAIL: Valid values are between "10" and "100". +CREATE INDEX reloptions_test_idx2 ON reloptions_test (s) WITH (fillfactor=130); +ERROR: value 130 out of bounds for option "fillfactor" +DETAIL: Valid values are between "10" and "100". +-- Check alter +ALTER INDEX reloptions_test_idx SET (fillfactor=40); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test_idx'::regclass; + reloptions +----------------- + {fillfactor=40} +(1 row) + +-- Check alter on empty relop list +CREATE INDEX reloptions_test_idx3 ON reloptions_test (s); +ALTER INDEX reloptions_test_idx3 SET (fillfactor=40); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test_idx3'::regclass; + reloptions +----------------- + {fillfactor=40} +(1 row) + diff --git a/src/test/regress/expected/spgist.out b/src/test/regress/expected/spgist.out index 0691e91..bb58fb7 100644 --- a/src/test/regress/expected/spgist.out +++ b/src/test/regress/expected/spgist.out @@ -4,7 +4,7 @@ -- There are other tests to test different SP-GiST opclasses. This is for -- testing SP-GiST code itself. create table spgist_point_tbl(id int4, p point); -create index spgist_point_idx on spgist_point_tbl using spgist(p); +create index spgist_point_idx on spgist_point_tbl using spgist(p) with (fillfactor = 75); -- Test vacuum-root operation. It gets invoked when the root is also a leaf, -- i.e. the index is very small. insert into spgist_point_tbl (id, p) @@ -37,3 +37,12 @@ select g, 'baaaaaaaaaaaaaar' || g from generate_series(1, 1000) g; -- tuple to be moved to another page. insert into spgist_text_tbl (id, t) select -g, 'f' || repeat('o', 100-g) || 'surprise' from generate_series(1, 100) g; +-- Check reloptions min, max values +create index spgist_point_idx2 on spgist_point_tbl using spgist(p) with (fillfactor = 9); +ERROR: value 9 out of bounds for option "fillfactor" +DETAIL: Valid values are between "10" and "100". +create index spgist_point_idx2 on spgist_point_tbl using spgist(p) with (fillfactor = 101); +ERROR: value 101 out of bounds for option "fillfactor" +DETAIL: Valid values are between "10" and "100". +-- ALTER reloption should work +alter index spgist_point_idx set (fillfactor = 76); diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out index aa06d1d..287dd84 100644 --- a/src/test/regress/expected/updatable_views.out +++ b/src/test/regress/expected/updatable_views.out @@ -1551,7 +1551,7 @@ SELECT * FROM base_tbl; ALTER VIEW rw_view1 SET (check_option=here); -- invalid ERROR: invalid value for "check_option" option -DETAIL: Valid values are "local" and "cascaded". +DETAIL: Valid values are "local", "cascaded". ALTER VIEW rw_view1 SET (check_option=local); INSERT INTO rw_view2 VALUES (-20); -- should fail ERROR: new row violates check option for view "rw_view1" diff --git a/src/test/regress/input/tablespace.source b/src/test/regress/input/tablespace.source index 743c4b9..0bb5299 100644 --- a/src/test/regress/input/tablespace.source +++ b/src/test/regress/input/tablespace.source @@ -12,10 +12,10 @@ DROP TABLESPACE regress_tblspacewith; CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@'; -- try setting and resetting some properties for the new tablespace -ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0); +ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1); ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail -ALTER TABLESPACE regress_tblspace RESET (random_page_cost, seq_page_cost); -- ok +ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok -- create a schema we can use CREATE SCHEMA testschema; diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/output/tablespace.source index 31f2ac0..ccf4bee 100644 --- a/src/test/regress/output/tablespace.source +++ b/src/test/regress/output/tablespace.source @@ -14,12 +14,12 @@ DROP TABLESPACE regress_tblspacewith; -- create a tablespace we can use CREATE TABLESPACE regress_tblspace LOCATION '@testtablespace@'; -- try setting and resetting some properties for the new tablespace -ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0); +ALTER TABLESPACE regress_tblspace SET (random_page_cost = 1.0, seq_page_cost = 1.1); ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter = true); -- fail ERROR: unrecognized parameter "some_nonexistent_parameter" ALTER TABLESPACE regress_tblspace RESET (random_page_cost = 2.0); -- fail ERROR: RESET must not include values for parameters -ALTER TABLESPACE regress_tblspace RESET (random_page_cost, seq_page_cost); -- ok +ALTER TABLESPACE regress_tblspace RESET (random_page_cost, effective_io_concurrency); -- ok -- create a schema we can use CREATE SCHEMA testschema; -- try a table diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index edeb2d6..f70cdf3 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -109,7 +109,7 @@ test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combo # NB: temp.sql does a reconnect which transiently uses 2 connections, # so keep this parallel group to at most 19 tests # ---------- -test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml +test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml reloptions # event triggers cannot run concurrently with any test that runs DDL test: event_trigger diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule index 27a46d7..b37f3b7 100644 --- a/src/test/regress/serial_schedule +++ b/src/test/regress/serial_schedule @@ -170,5 +170,6 @@ test: returning test: largeobject test: with test: xml +test: reloptions test: event_trigger test: stats diff --git a/src/test/regress/sql/alter_table.sql b/src/test/regress/sql/alter_table.sql index 1f551ec..c7f2d9f 100644 --- a/src/test/regress/sql/alter_table.sql +++ b/src/test/regress/sql/alter_table.sql @@ -2219,3 +2219,11 @@ alter table p attach partition p1 for values from (1, 2) to (1, 10); -- cleanup: avoid using CASCADE drop table p, p1, p11; + +-- test alter column options +CREATE TABLE tmp(i integer); +INSERT INTO tmp VALUES (1); +ALTER TABLE tmp ALTER COLUMN i SET (n_distinct = 1, n_distinct_inherited = 2); +ALTER TABLE tmp ALTER COLUMN i RESET (n_distinct_inherited); +ANALYZE tmp; +DROP TABLE tmp; diff --git a/src/test/regress/sql/brin.sql b/src/test/regress/sql/brin.sql index e7f6f77..2701e76 100644 --- a/src/test/regress/sql/brin.sql +++ b/src/test/regress/sql/brin.sql @@ -421,3 +421,7 @@ UPDATE brintest SET textcol = '' WHERE textcol IS NOT NULL; SELECT brin_summarize_new_values('brintest'); -- error, not an index SELECT brin_summarize_new_values('tenk1_unique1'); -- error, not a BRIN index SELECT brin_summarize_new_values('brinidx'); -- ok, no change expected + +-- Check that changing reloptions for brin index is not allowed +ALTER INDEX brinidx SET (pages_per_range = 10); +ALTER INDEX brinidx RESET (pages_per_range); diff --git a/src/test/regress/sql/create_index.sql b/src/test/regress/sql/create_index.sql index 1648072..1f8d101 100644 --- a/src/test/regress/sql/create_index.sql +++ b/src/test/regress/sql/create_index.sql @@ -682,7 +682,7 @@ CREATE INDEX hash_name_index ON hash_name_heap USING hash (random name_ops); CREATE INDEX hash_txt_index ON hash_txt_heap USING hash (random text_ops); -CREATE INDEX hash_f8_index ON hash_f8_heap USING hash (random float8_ops); +CREATE INDEX hash_f8_index ON hash_f8_heap USING hash (random float8_ops) WITH (fillfactor=60); CREATE UNLOGGED TABLE unlogged_hash_table (id int4); CREATE INDEX unlogged_hash_index ON unlogged_hash_table USING hash (id int4_ops); diff --git a/src/test/regress/sql/gist.sql b/src/test/regress/sql/gist.sql index 9d4ff1e..ce1ac1a 100644 --- a/src/test/regress/sql/gist.sql +++ b/src/test/regress/sql/gist.sql @@ -17,6 +17,17 @@ select g+100000, point(g*10+1, g*10+1) from generate_series(1, 10000) g; -- To test vacuum, delete some entries from all over the index. delete from gist_point_tbl where id % 2 = 1; +-- Test buffering and fillfactor reloption takes valid values +create index gist_pointidx2 on gist_point_tbl using gist(p) with (buffering = on, fillfactor=50); +create index gist_pointidx3 on gist_point_tbl using gist(p) with (buffering = off); +create index gist_pointidx4 on gist_point_tbl using gist(p) with (buffering = auto); +--Test buffering and fillfactor for refising invalid values +create index gist_pointidx5 on gist_point_tbl using gist(p) with (buffering = invalid_value); +create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=9); +create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfactor=101); + + + -- And also delete some concentration of values. (GiST doesn't currently -- attempt to delete pages even when they become empty, but if it did, this -- would exercise it) diff --git a/src/test/regress/sql/hash_index.sql b/src/test/regress/sql/hash_index.sql index 15a3b06..b034efb 100644 --- a/src/test/regress/sql/hash_index.sql +++ b/src/test/regress/sql/hash_index.sql @@ -194,3 +194,8 @@ CREATE TABLE hash_heap_float4 (x float4, y int); INSERT INTO hash_heap_float4 VALUES (1.1,1); CREATE INDEX hash_idx ON hash_heap_float4 USING hash (x); DROP TABLE hash_heap_float4 CASCADE; + +-- Check reloptions min max values and that it is not allowed to ALTER +CREATE INDEX hash_f8_index2 ON hash_f8_heap USING hash (random float8_ops) WITH (fillfactor=9); +CREATE INDEX hash_f8_index2 ON hash_f8_heap USING hash (random float8_ops) WITH (fillfactor=101); +ALTER INDEX hash_f8_index SET (fillfactor=99); \ No newline at end of file diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/reloptions.sql new file mode 100644 index 0000000..0bfec28 --- /dev/null +++ b/src/test/regress/sql/reloptions.sql @@ -0,0 +1,153 @@ + +-- Simple create +CREATE TABLE reloptions_test(i INT) WITH (fillfactor=30,autovacuum_enabled = false, autovacuum_analyze_scale_factor = 0.2); + +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- Test сase insensitive +CREATE TABLE reloptions_test3(i INT) WITH (FiLlFaCtoR=40); + +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test3'::regclass; + +-- Fail on min/max values check +CREATE TABLE reloptions_test2(i INT) WITH (fillfactor=2); + +CREATE TABLE reloptions_test2(i INT) WITH (fillfactor=110); + +CREATE TABLE reloptions_test2(i INT) WITH (autovacuum_analyze_scale_factor = -10.0); + +CREATE TABLE reloptions_test2(i INT) WITH (autovacuum_analyze_scale_factor = 110.0); + +-- Fail when option and namespase do not exist + +CREATE TABLE reloptions_test2(i INT) WITH (not_existing_option=2); + +CREATE TABLE reloptions_test2(i INT) WITH (not_existing_namespace.fillfactor=2); + +-- Fail while setting unproper value + +CREATE TABLE reloptions_test2(i INT) WITH (fillfactor=30.5); + +CREATE TABLE reloptions_test2(i INT) WITH (fillfactor='string'); + +CREATE TABLE reloptions_test2(i INT) WITH (fillfactor=true); + +CREATE TABLE reloptions_test2(i INT) WITH (autovacuum_enabled=12); + +CREATE TABLE reloptions_test2(i INT) WITH (autovacuum_enabled=30.5); + +CREATE TABLE reloptions_test2(i INT) WITH (autovacuum_enabled='string'); + +CREATE TABLE reloptions_test2(i INT) WITH (autovacuum_analyze_scale_factor='string'); + +CREATE TABLE reloptions_test2(i INT) WITH (autovacuum_analyze_scale_factor=true); + +-- Specifing name only should fail as if there was =ture after it +CREATE TABLE reloptions_test2(i INT) WITH (fillfactor); + +-- Simple ALTER TABLE + +ALTER TABLE reloptions_test SET (fillfactor=31, autovacuum_analyze_scale_factor = 0.3); + +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- Check that we cat set boolean option to true just by mentioning it + +ALTER TABLE reloptions_test SET (autovacuum_enabled, fillfactor=32); + +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- Check that RESET works well + +ALTER TABLE reloptions_test RESET (fillfactor); + +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- Check that RESETting all values make NULL reloptions record in pg_class +ALTER TABLE reloptions_test RESET (autovacuum_enabled, autovacuum_analyze_scale_factor); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass AND reloptions IS NULL; + +-- Check RESET fails on att=value + +ALTER TABLE reloptions_test RESET (fillfactor=12); + +-- Check oids options is ignored +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (fillfactor=20, oids=true); +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; + +-- Now testing toast.* options +DROP TABLE reloptions_test; + +CREATE TABLE reloptions_test (s VARCHAR) WITH (toast.autovacuum_vacuum_cost_delay = 23 ); + +SELECT reloptions FROM pg_class WHERE oid = (SELECT reltoastrelid FROM pg_class WHERE oid = 'reloptions_test'::regclass); + +ALTER TABLE reloptions_test SET (toast.autovacuum_vacuum_cost_delay = 24); + +SELECT reloptions FROM pg_class WHERE oid = (SELECT reltoastrelid FROM pg_class WHERE oid = 'reloptions_test'::regclass); + +ALTER TABLE reloptions_test RESET (toast.autovacuum_vacuum_cost_delay); + +SELECT reloptions FROM pg_class WHERE oid = (SELECT reltoastrelid FROM pg_class WHERE oid = 'reloptions_test'::regclass); + +-- Fail on unexisting options in toast namespace +CREATE TABLE reloptions_test2 (i int) WITH (toast.not_existing_option = 42 ); + +-- Fail on setting reloption to a table that does not have a TOAST relation +CREATE TABLE reloptions_test2 (i int) WITH (toast.autovacuum_vacuum_cost_delay = 23 ); +DROP TABLE reloptions_test; + +CREATE TABLE reloptions_test(i INT); +ALTER TABLE reloptions_test SET (toast.autovacuum_vacuum_cost_delay = 23); +ALTER TABLE reloptions_test RESET (toast.autovacuum_vacuum_cost_delay); + +-- autovacuum_analyze_scale_factor and autovacuum_analyze_threshold should be +-- accepted by heap but rejected by toast (special case) +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test (s VARCHAR) WITH (autovacuum_analyze_scale_factor=1, autovacuum_analyze_threshold=1); + +CREATE TABLE reloptions_test2 (s VARCHAR) WITH (toast.autovacuum_analyze_scale_factor=1); +CREATE TABLE reloptions_test2 (s VARCHAR) WITH (toast.autovacuum_analyze_threshold=1); + +-- And now mixed toast + heap +DROP TABLE reloptions_test; + +CREATE TABLE reloptions_test (s VARCHAR) WITH (toast.autovacuum_vacuum_cost_delay = 23, autovacuum_vacuum_cost_delay = 24, fillfactor = 40); + +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test'::regclass; +SELECT reloptions FROM pg_class WHERE oid = (SELECT reltoastrelid FROM pg_class WHERE oid = 'reloptions_test'::regclass); + +-- Same FOR CREATE and ALTER INDEX for btree indexes + +CREATE INDEX reloptions_test_idx ON reloptions_test (s) WITH (fillfactor=30); + +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test_idx'::regclass; + +-- Fail when option and namespase do not exist + +CREATE INDEX reloptions_test_idx ON reloptions_test (s) WITH (not_existing_option=2); + +CREATE INDEX reloptions_test_idx ON reloptions_test (s) WITH (not_existing_option.fillfactor=2); + +-- Check ranges + +CREATE INDEX reloptions_test_idx2 ON reloptions_test (s) WITH (fillfactor=1); + +CREATE INDEX reloptions_test_idx2 ON reloptions_test (s) WITH (fillfactor=130); + +-- Check alter + +ALTER INDEX reloptions_test_idx SET (fillfactor=40); + +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test_idx'::regclass; + +-- Check alter on empty relop list + +CREATE INDEX reloptions_test_idx3 ON reloptions_test (s); + +ALTER INDEX reloptions_test_idx3 SET (fillfactor=40); + +SELECT reloptions FROM pg_class WHERE oid = 'reloptions_test_idx3'::regclass; + + diff --git a/src/test/regress/sql/spgist.sql b/src/test/regress/sql/spgist.sql index 5896b50..8d26e49 100644 --- a/src/test/regress/sql/spgist.sql +++ b/src/test/regress/sql/spgist.sql @@ -5,7 +5,7 @@ -- testing SP-GiST code itself. create table spgist_point_tbl(id int4, p point); -create index spgist_point_idx on spgist_point_tbl using spgist(p); +create index spgist_point_idx on spgist_point_tbl using spgist(p) with (fillfactor = 75); -- Test vacuum-root operation. It gets invoked when the root is also a leaf, -- i.e. the index is very small. @@ -48,3 +48,10 @@ select g, 'baaaaaaaaaaaaaar' || g from generate_series(1, 1000) g; -- tuple to be moved to another page. insert into spgist_text_tbl (id, t) select -g, 'f' || repeat('o', 100-g) || 'surprise' from generate_series(1, 100) g; + +-- Check reloptions min, max values +create index spgist_point_idx2 on spgist_point_tbl using spgist(p) with (fillfactor = 9); +create index spgist_point_idx2 on spgist_point_tbl using spgist(p) with (fillfactor = 101); + +-- ALTER reloption should work +alter index spgist_point_idx set (fillfactor = 76);