Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtp (Exim 4.84_2) (envelope-from ) id 1capvg-0001nU-PH for pgsql-hackers@arkaria.postgresql.org; Mon, 06 Feb 2017 20:30:29 +0000 Received: from localhost ([127.0.0.1] helo=postgresql.org) by malur.postgresql.org with smtp (Exim 4.84_2) (envelope-from ) id 1capvg-0003nB-9Q for pgsql-hackers@arkaria.postgresql.org; Mon, 06 Feb 2017 20:30:28 +0000 Received: from makus.postgresql.org ([2001:4800:1501:1::229]) by malur.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA384:256) (Exim 4.84_2) (envelope-from ) id 1capvd-0003n5-O5 for pgsql-hackers@postgresql.org; Mon, 06 Feb 2017 20:30:26 +0000 Received: from enot.nataraj.su ([195.54.209.170]) by makus.postgresql.org with esmtp (Exim 4.84_2) (envelope-from ) id 1capvS-0006sI-OI for pgsql-hackers@postgresql.org; Mon, 06 Feb 2017 20:30:24 +0000 Received: from x200m.localnet (unknown [193.107.1.21]) by enot.nataraj.su (Postfix) with ESMTPSA id 56BE2511E48A; Mon, 6 Feb 2017 15:30:11 -0500 (EST) From: Nikolay Shaplov To: Alvaro Herrera , PostgreSQL Developers Subject: [PATCH] Move all am-related reloption code into src/backend/access/[am-name] and get rid of relopt_kind for custom AM Date: Mon, 06 Feb 2017 23:30:03 +0300 Message-ID: <2146419.veIEZdk4E4@x200m> User-Agent: KMail/4.14.1 (Linux/3.16.0-4-amd64; KDE/4.14.2; x86_64; ; ) MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="nextPart2916058.REV3beB8aF" Content-Transfer-Encoding: 7Bit X-Pg-Spam-Score: -1.9 (-) List-Archive: List-Help: List-ID: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: X-Mailing-List: pgsql-hackers Precedence: bulk Sender: pgsql-hackers-owner@postgresql.org This is a multi-part message in MIME format. --nextPart2916058.REV3beB8aF Content-Transfer-Encoding: 7Bit Content-Type: text/plain; charset="us-ascii" (Please see Attribution notice at the bottom of the letter) Hi! Some time ago I've send a proposal, about removing all am-related code from src/backend/access/common/reloptions.c and move it into somewhere inside am code. It was in https://www.postgresql.org/message-id/4636505.Bu9AKW1Kzc%40nataraj-amd64 thread. Now I have a patch that is ready Before explaining what have been done, I need to define some concepts, so it would be easy to speak about them in the further explanation. Reloptions values in postgres actually exists in four representations: DefList*, TextArray[], Bytea and so called Values. - DefList representation is a list of DefElem, it is the representation in which reloptions comes from syntax analyzer, when relation is created or altered, and also this representation that is needed for pg_dump to dump a create statement that creates this relation. - TextArray[] representation is a way, how reloptions are stored in pg_catalog. It is TEXT[] attribute in the pg_catalog table and from the poinf o view of postgres code it is Datum array of TEXT Datums, when read into the memory - Bytea (or I will also call it Binary) representation, is a representation with which relation code really works. It has C-structure inside in which fixed-length options are sored, some space is reserved for storing varlen values after the structure, and certainly it has BYTEA header in front of all of it. It is cached, it is fast to access. It is a good stuff. - Values (or in some cases RawValues) -- internal representation of option parser. Technically it is List of option_value structures (old name for it is relopt_value). This representation is used for parsing and validating options. Actually all the convertations between representations listed above are dove through Values representation. Values are called RawValues when Values List were already created, but it was not parsed yet, and all option values are in raw state: represented as a text. DefList and TextArray representations are converted in both ways(always through Values representation): we need DefList -> TextArray to create or change entry in the pg_catalog, and need TextArray -> DefList for pg_dump Bytea representation is converted from TextArray representation (also through Values representation). When relation code, tries to get cached Bytea representation for certain relation, and none is actually cached, then cache code calls extractRelOptions function that fetches entry from pg_catalog for that relation, gets reloptions TextArray[], converts it into Bytea and cache it. Each reloption has it's own definition. This definitions tells how this option should be parsed, validated and converted. Before my patch this information were stored in relopt_gen and relopt_parse_elt structures. In my patch all this information were moved into option_definition_basic structure (that is actually old relopt_gen + relopt_parse_elt) The set of options definition I would call a options catalog. Before my patch there was one global options catalog (actually it was divided into parts by data types and has another part for dynamically added options, but nevertheless, it was global). After my patch there would be a separate options catalog for each relation type. For AM-relation this catalog would be available via AM-handler. for other relation types it would be still kept in reloption.c Now I am ready to tell what actually have been done in this patch: 1. I moved options definitions from global lists from reloptions.c [type name]RelOpts to lists that are kept inside AM code. One list per AM. heap options, toast options and options for all other relation types that are not accessible through AccessMethod, all were kept inside reloptions.c, but now it is one list for each relation type, not a global one 2. Each AccessMethod had amoptions function that was responsible for converting reloptions from TextArray[] representation into Bytea representaions. This function used functions from reloptions.c and also had an array of relopt_parse_elt that defines how parsed reloption data should be packed inside Bytea chunk. I've moved data from relopt_parse_elt array into option definitions that are stored in the catalog (since catalog are now defined inside the AccessMethod we have access to a structure of Bytea representation at the place where we define option catalog) 3. Speaking of amoptions function, I've completely removed it, since we do not need it. For developers of Access Methods who still want to do some custom action with just parsed data, I've added postprocess_fun to option catalog. If this function is defined, it is called by convertation function right after Bytea representation were created. In postprocess_fun developer can do some custom validation, or change just parsed data. This feature is used in bloom index. 4. Instead of amoptions function I've added amrelopt_catalog function to the Access Method. This function returns option definition catalog for an Access Method. The catalog is used for processing options. 5. As before, relation cache calls extractRelOptions when it needs options that were not previously cached. Before my patch, it created Bytea representations for non-AM relations, and called amoptions AM function to get Bytea representation for AM relation. In by patch, it now gets option catalog, (using local functions for non-AM relations, and using amrelopt_catalog function for AM-relations), and then use this catalog to convert options from TextArray into Bytea representation. 6. I've added some more featues: - Now you can set OPTION_DEFINITION_FLAG_FORBID_ALTER flag in definition of the option, and then postgres will refuse to change option value using ALTER command. In many indexes, some options are used only for index creation. After this it's value is saved in MetaPage, and used from there. Nevertheless user is allowed to change option value, though it affects nothing, one need to recreate index to really change such value. Now using this flag we can prevent user from getting an illusion that he successfully changed option value. - After applying my patch if you try to set toast. option for table that does not have toast, you will get an error. Before postgres will accept toast option for a table without a toast, but this value will be lost. This is bad behavior, so now postgres will throw an error in this case. - I've noticed that all string relation options in postgres are technically enum options. So I've added enum option type. This will save some bytes of the memory and some tacts of the CPU. I also left string option type (through it is not used now). A. Korotkov said that it might be needed later for storing patterns, xml/json paths or something like that. - Added some more tests that will trigger more options code. Though I am dreaming about more advanced test system, that will allow to check that certain value is received somewhere deep in the code. 7. And now the most controversial change. I've got rid of StdRdOptions structure, in which options for heap, toast, nbtree, hash and spgist were stored in Bytea representation. In indexes only fillfactor value were actually used from the whole big structure. So I've created a separate structure for each relation type. HeapOptions and ToastOptions are very similar to each other. So common part of creating these catalogs were moved to add_autovacuum_options function that are called while creating each catalog. Now to the controversial part: in src/include/utils/rel.h had a lot of Relation* macroses that used StdRdOptions structure. I've changed them into View* Heap* and Toast* analogues, and changed the code to use these macroses instead of Relation* one. But this part of code I least sure of. I'd like to ask experienced developers to double check it. 8. I've moved all options-abstract code into options.c and options.h file, and left in reloptions.c and reloptions.h only the code that concerns relation options. Actually the main idea of this patch was to get this abstract code in order to use it for custom attribute options later. All the rest just a side effects :-) So, before adding this to commitfest I want somebody to give a general look at it, if the code is OK. Alvaro Herrera, you once said that you can review the patch... The patch is available in the attachment. It can be applied to current master You can also see latest version on my github https://github.com/dhyannataraj/postgres/tree/reloption_fix at the reloption_fix branch. ATTRIBUTION NOTICE: I wrote this patch, when I was an employee in Postgres Professional company. So this patch should be attributed as patch from Postgres Pro (or something like that). For some reasons I did not manage to commit this patch while I left that job. But I got a permission to commit it even if I left the company. So my contribution to this patch as a independent developer was final code cleanup, and writing some more comments. All the rest was a work of Postgres Pro employee Nikolay Shaplov. -- Nikolay Shaplov, independent Perl & C/C++ developer. Available for hire. --nextPart2916058.REV3beB8aF Content-Disposition: attachment; filename="reloptions6.diff" Content-Transfer-Encoding: quoted-printable Content-Type: text/x-patch; charset="UTF-8"; name="reloptions6.diff" 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(IndexVacuumI= nfo *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 @@ =20 PG_FUNCTION_INFO_V1(blhandler); =20 -/* 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; =20 static int32 myRand(void); static void mySrand(uint32 seed); - -/* - * Module initialize function: initialize info about Bloom relation option= s. - * - * Note: keep this in sync with makeDefaultBloomOptions(). - */ -void -_PG_init(void) -{ - int i; - char buf[16]; - - bl_relopt_kind =3D 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 =3D "length"; - bl_relopt_tab[0].opttype =3D RELOPT_TYPE_INT; - bl_relopt_tab[0].offset =3D offsetof(BloomOptions, bloomLength); - - /* Number of bits for each possible index column: col1, col2, ... */ - for (i =3D 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 =3D MemoryContextStrdup(TopMemoryContext, - buf); - bl_relopt_tab[i + 1].opttype =3D RELOPT_TYPE_INT; - bl_relopt_tab[i + 1].offset =3D offsetof(BloomOptions, bitSize[0]) + siz= eof(int) * i; - } -} +static void *blrelopt_catalog(void); +static void blReloptionPostprocess(void *, bool validate); =20 /* * Construct a default set of Bloom options. @@ -128,7 +92,7 @@ blhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup =3D blvacuumcleanup; amroutine->amcanreturn =3D NULL; amroutine->amcostestimate =3D blcostestimate; - amroutine->amoptions =3D bloptions; + amroutine->amrelopt_catalog =3D blrelopt_catalog; amroutine->amproperty =3D NULL; amroutine->amvalidate =3D blvalidate; amroutine->ambeginscan =3D blbeginscan; @@ -145,6 +109,27 @@ blhandler(PG_FUNCTION_ARGS) PG_RETURN_POINTER(amroutine); } =20 +void blReloptionPostprocess(void *data, bool validate) +{ + BloomOptions *opts =3D (BloomOptions*) data; + int i; + + if (validate) + for (i =3D 0; i < INDEX_MAX_KEYS; i++) + { + if (opts->bitSize[i] >=3D 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 =3D (opts->bloomLength + SIGNWORDBITS - 1) / SIGNWORDBI= TS; +} + + /* * Fill BloomState structure for particular index. */ @@ -465,24 +450,38 @@ BloomInitMetapage(Relation index) UnlockReleaseBuffer(metaBuffer); } =20 -/* - * 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) + { =20 - /* Parse the user-given reloptions */ - options =3D parseRelOptions(reloptions, validate, bl_relopt_kind, &numopt= ions); - rdopts =3D 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]; =20 - /* Convert signature length from # of bits to # to words, rounding up */ - rdopts->bloomLength =3D (rdopts->bloomLength + SIGNWORDBITS - 1) / SIGNWO= RDBITS; + bl_relopt_catalog =3D allocateOptionsCatalog(NULL); + bl_relopt_catalog->struct_size =3D sizeof(BloomOptions); + bl_relopt_catalog->postprocess_fun =3D 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),=20 + DEFAULT_BLOOM_LENGTH, 1, MAX_BLOOM_LENGTH); =20 - return (bytea *) rdopts; + /* Number of bits for each possible index column: col1, col2, ... */ + for (i =3D 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/bloo= m.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) =20 +-- reloptions test +DROP INDEX bloomidx; +CREATE INDEX bloomidx ON tst USING bloom (i, t) WITH (length=3D7, col1 =3D= 4); +SELECT reloptions FROM pg_class WHERE oid =3D 'bloomidx'::regclass; + reloptions=20=20=20=20=20 +------------------- + {length=3D7,col1=3D4} +(1 row) + +-- check for min and max values +CREATE INDEX bloomidx2 ON tst USING bloom (i, t) WITH (length=3D0); +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=3D4097); +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=3D0); +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=3D4096); +ERROR: value 4096 out of bounds for option "col1" +DETAIL: Valid values are between "1" and "4095". +-- check post_validate for colNamvacuumcleanup =3D brinvacuumcleanup; amroutine->amcanreturn =3D NULL; amroutine->amcostestimate =3D brincostestimate; - amroutine->amoptions =3D brinoptions; amroutine->amproperty =3D NULL; amroutine->amvalidate =3D brinvalidate; amroutine->ambeginscan =3D brinbeginscan; @@ -112,6 +112,7 @@ brinhandler(PG_FUNCTION_ARGS) amroutine->amendscan =3D brinendscan; amroutine->ammarkpos =3D NULL; amroutine->amrestrpos =3D NULL; + amroutine->amrelopt_catalog =3D bringetreloptcatalog; amroutine->amestimateparallelscan =3D NULL; amroutine->aminitparallelscan =3D NULL; amroutine->amparallelrescan =3D NULL; @@ -750,36 +751,6 @@ brinvacuumcleanup(IndexVacuumInfo *info, IndexBulkDele= teResult *stats) } =20 /* - * reloptions processor for BRIN indexes - */ -bytea * -brinoptions(Datum reloptions, bool validate) -{ - relopt_value *options; - BrinOptions *rdopts; - int numoptions; - static const relopt_parse_elt tab[] =3D { - {"pages_per_range", RELOPT_TYPE_INT, offsetof(BrinOptions, pagesPerRange= )} - }; - - options =3D parseRelOptions(reloptions, validate, RELOPT_KIND_BRIN, - &numoptions); - - /* if none set, we're done */ - if (numoptions =3D=3D 0) - return NULL; - - rdopts =3D 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, BufferAccessStrate= gy strategy) if (vacuum_fsm) FreeSpaceMapVacuum(idxrel); } + +static options_catalog * brin_relopt_catalog =3D NULL; + +void * +bringetreloptcatalog (void) +{ + if (! brin_relopt_catalog) + { + brin_relopt_catalog =3D allocateOptionsCatalog(NULL); + brin_relopt_catalog->struct_size =3D 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 =3D src/backend/access/common top_builddir =3D ../../../.. include $(top_builddir)/src/Makefile.global =20 -OBJS =3D heaptuple.o indextuple.o printsimple.o printtup.o reloptions.o \ +OBJS =3D heaptuple.o indextuple.o options.o printsimple.o printtup.o relop= tions.o \ scankey.o tupconvert.o tupdesc.o =20 include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/access/common/options.c b/src/backend/access/commo= n/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 option= s. + * 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=20 + * pg_catalog. + * + * Now options are mainly used for all kind or relations (pg_class.relopti= on) + * 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 optio= ns + * for an object. All parsing/validation/transformation functions should n= ot + * 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. Opt= ions + * 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 (o= r 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=3Dvalue). For now postgres uses only toa= st. + * namespace for tables. + */ + +options_catalog * +allocateOptionsCatalog(char *namespace) +{ + MemoryContext oldcxt; + options_catalog *catalog; + + oldcxt =3D MemoryContextSwitchTo(TopMemoryContext); + catalog =3D palloc(sizeof(options_catalog)); + if (namespace) + { + catalog->namespace=3Dpalloc(strlen(namespace) + 1); + strcpy(catalog->namespace, namespace); + } else + catalog->namespace =3D NULL; + + catalog->definitions =3D NULL; + catalog->num =3D 0; + catalog->num_allocated =3D 0; + catalog->struct_size =3D -1; /* Will Assert if not properly changed */ + catalog->postprocess_fun =3D 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 >=3D catalog->num_allocated) + { + MemoryContext oldcxt; + + oldcxt =3D MemoryContextSwitchTo(TopMemoryContext); + + if (catalog->num_allocated =3D=3D 0) + { + catalog->num_allocated =3D 8; + catalog->definitions =3D palloc( + catalog->num_allocated * sizeof(option_definition_basic *)); + } + else + { + catalog->num_allocated *=3D 2; + catalog->definitions =3D repalloc(catalog->definitions, + catalog->num_allocated * sizeof(option_definition_basic *)); + } + MemoryContextSwitchTo(oldcxt); + } + catalog->definitions[catalog->num] =3D newoption; + catalog->definitions[catalog->num + 1] =3D 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 lockmo= de, + option_definition_flags flags, int struct_offset) +{ + MemoryContext oldcxt; + size_t size; + option_definition_basic *newoption; + + oldcxt =3D MemoryContextSwitchTo(TopMemoryContext); + + switch (type) + { + case OPTION_TYPE_BOOL: + size =3D sizeof(option_definition_bool); + break; + case OPTION_TYPE_INT: + size =3D sizeof(option_definition_int); + break; + case OPTION_TYPE_REAL: + size =3D sizeof(option_definition_real); + break; + case OPTION_TYPE_ENUM: + size =3D sizeof(option_definition_enum); + break; + case OPTION_TYPE_STRING: + size =3D sizeof(option_definition_string); + break; + default: + elog(ERROR, "unsupported reloption type %d", type); + return NULL; /* keep compiler quiet */ + } + + newoption =3D palloc(size); + + newoption->name =3D pstrdup(name); + if (desc) + newoption->desc =3D pstrdup(desc); + else + newoption->desc =3D NULL; + newoption->type =3D type; + newoption->lockmode =3D lockmode; + newoption->flags =3D flags; + newoption->struct_offset =3D struct_offset; + + MemoryContextSwitchTo(oldcxt); + + return newoption; +} + +/* + * optionsCatalogAddItemBool + * Add a new boolean option definition to the catalog + */ +void +optionsCatalogAddItemBool(options_catalog * catalog, char *name, char *des= c, + LOCKMODE lockmode, option_definition_flags flags, + int struct_offset, bool default_val) +{ + option_definition_bool *catalog_item; + + catalog_item =3D (option_definition_bool *) + allocateOptionDefinition(OPTION_TYPE_BOOL, name, desc, lockmode, + flags, struct_offset); + + catalog_item->default_val =3D 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 =3D (option_definition_int *) + allocateOptionDefinition(OPTION_TYPE_INT, name, desc, lockmode, + flags, struct_offset); + + catalog_item->default_val =3D default_val; + catalog_item->min =3D min_val; + catalog_item->max =3D 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 *des= c, + 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 =3D (option_definition_real *) + allocateOptionDefinition(OPTION_TYPE_REAL, name, desc, lockmode, + flags, struct_offset); + + catalog_item->default_val =3D default_val; + catalog_item->min =3D min_val; + catalog_item->max =3D 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 it= em of + * an array should be null). This array contains a list of acceptable valu= es + * 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 *des= c, + LOCKMODE lockmode, option_definition_flags flags, int struct_offset, + const char **allowed_values, int default_val) +{ + option_definition_enum *catalog_item; + + catalog_item =3D (option_definition_enum *) + allocateOptionDefinition(OPTION_TYPE_ENUM, name, desc, lockmode, + flags, struct_offset); + + catalog_item->default_val =3D default_val; + catalog_item->allowed_values =3D 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 *d= esc, + 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 =3D (option_definition_string *) + allocateOptionDefinition(OPTION_TYPE_STRING, name, desc, lockmode, + flags, struct_offset); + catalog_item->validate_cb =3D validator; + + if (default_val) + catalog_item->default_val =3D MemoryContextStrdup(TopMemoryContext, + default_val); + else + catalog_item->default_val =3D NULL; + optionCatalogAddItem((option_definition_basic *) catalog_item, catalog); +} + + +/* + * Options transform functions + */ + +/* + * Option values exists in five representations: DefList, TextArray, Value= s and + * Bytea: + * + * DefList: Is a List of DefElem structures, that comes from syntax analyz= er. + * It can be transformed to Values representation for further parsing and + * validating + * + * Values: A List of option_value structures. Is divided into two subclass= es: + * RawValues, when values are already transformed from DefList or TextArra= y, + * 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 catal= og + * and after parsing of the raw value. For ParsedValues content is stored = in + * values structure member, and name can be taken from option definition i= n gen + * structure member. Actually Value list can have both Raw and Parsed val= ues, + * as we do not validate options that came from database, and db option th= at + * does not exist in catalog is just ignored, and kept as RawValues + * + * TextArray: The representation in which options for existing object com= es + * and goes from/to database; for example from pg_class.reloptions. It is a + * plain TEXT[] db object with name=3Dvalue text inside. This representati= on can + * be transformed into Values for further processing, using options catalo= g. + * + * 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 opti= ons + * data from values representation into Bytea, using catalog data, and the= n 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 th= at + * 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 =3D NIL; + + foreach(cell, defList) + { + option_value *option_dst; + DefElem *def =3D (DefElem *) lfirst(cell); + char *value; + + option_dst =3D palloc(sizeof(option_value)); + + if (def->defnamespace) + { + option_dst->namespace =3D palloc(strlen(def->defnamespace) + 1); + strcpy(option_dst->namespace,def->defnamespace); + } else + { + option_dst->namespace =3D NULL; + } + option_dst->raw_name =3D 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=3Dvalue data, as syntax analyzer do not + * prevent it + */ + if (def->arg !=3D NULL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("RESET must not include values for parameters"))); + + option_dst->status =3D OPTION_VALUE_STATUS_FOR_RESET; + } + else + { + /* + * For SET statement we should treat (name) expression as if it + * is actually (name=3Dtrue) so do it here manually. In other cases + * just use value as we should use it + */ + option_dst->status =3D OPTION_VALUE_STATUS_RAW; + if (def->arg !=3D NULL) + value =3D defGetString(def); + else + value =3D "true"; + option_dst->raw_value =3D palloc(strlen(value) + 1); + strcpy(option_dst->raw_value,value); + } + + result =3D 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 =3D NULL; + ListCell *cell; + Datum result; + foreach(cell, options_values) + { + option_value *option =3D (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 =3D option->raw_value; + + Assert(option->status !=3D 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 =3D=3D OPTION_VALUE_STATUS_PARSED) + name =3D option->gen->name; + else + name =3D option->raw_name; + + /* + * Now build "name=3Dvalue" string and append it to the array + */ + len =3D VARHDRSZ + strlen(name) + strlen(value) + 1; + t =3D (text *) palloc(len + 1); + SET_VARSIZE(t, len); + sprintf(VARDATA(t), "%s=3D%s", name, value); + astate =3D accumArrayResult(astate, PointerGetDatum(t), false, + TEXTOID, CurrentMemoryContext); + } + if (astate) + result =3D makeArrayResult(astate, CurrentMemoryContext); + else + result =3D (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 =3D NIL; + + if (PointerIsValid(DatumGetPointer(array_datum))) + { + ArrayType *array =3D DatumGetArrayTypeP(array_datum); + Datum *options; + int noptions; + int i; + + deconstruct_array(array, TEXTOID, -1, false, 'i', + &options, NULL, &noptions); + + for (i =3D 0; i < noptions; i++) + { + text *option_src =3D DatumGetTextP(options[i]); + option_value * option_dst; + char *text_str =3D VARDATA(option_src); + int text_len =3D VARSIZE(option_src) - VARHDRSZ; + int i; + int name_len =3D -1; + char *name; + int raw_value_len; + char *raw_value; + + /* + * Find position of '=3D' sign and treat id as a separator + * between name and value in "name=3Dvalue" item + */ + for(i =3D 0; i < text_len; i =3D i + pg_mblen(text_str)) + { + if (text_str[i] =3D=3D '=3D') + { + name_len =3D i; + break; + } + } + Assert(name_len >=3D 1); /* Just in case */ + + raw_value_len =3D text_len - name_len - 1; + + /* + * Copy name from src + */ + name =3D palloc(name_len + 1); + memcpy(name, text_str, name_len); + name[name_len] =3D '\0'; + + /* + * Copy value from src + */ + raw_value =3D palloc(raw_value_len + 1); + memcpy(raw_value, text_str + name_len + 1, raw_value_len); + raw_value[raw_value_len] =3D '\0'; + + /* + * Create new option_value item + */ + option_dst =3D palloc(sizeof(option_value)); + option_dst->status =3D OPTION_VALUE_STATUS_RAW; + option_dst->raw_name =3D name; + option_dst->raw_value =3D raw_value; + + result =3D 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 o= n. + * + * Both incoming lists will be destroyed while merging + */ +List * +optionsMergeOptionValues(List *old_options, List *new_options) +{ + List * result =3D NIL; + ListCell *old_cell; + ListCell *old_prev; + ListCell *old_next; + ListCell *new_cell; + ListCell *new_prev; + ListCell *new_next; + + old_prev =3D NULL; + + /* + * First try to remove from old options list all values + * that exists in a new options list + */ + for (old_cell =3D list_head(old_options); old_cell; old_cell =3D old_next) + { + bool found; + const char *old_name; + option_value *old_option; + + old_next =3D lnext(old_cell); + old_option =3D (option_value *) lfirst(old_cell); + if (old_option->status =3D=3D OPTION_VALUE_STATUS_PARSED) + old_name =3D old_option->gen->name; + else + old_name =3D old_option->raw_name; + + /* + * Try to find old_name option among new option + */ + found =3D false; + foreach(new_cell, new_options) + { + option_value *new_option; + const char *new_name; + new_option =3D (option_value *) lfirst(new_cell); + if (new_option->status =3D=3D OPTION_VALUE_STATUS_PARSED) + new_name =3D new_option->gen->name; + else + new_name =3D new_option->raw_name; + if (pg_strcasecmp(new_name, old_name) =3D=3D 0) + { + found =3D true; + break; + } + } + /* + * If found, delete old option from the list + */ + if (found) + { + old_options =3D list_delete_cell(old_options,old_cell,old_prev); + } + else + { + old_prev =3D 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 =3D NULL; + for (new_cell =3D list_head(new_options); new_cell; new_cell =3D new_next) + { + option_value *new_option =3D (option_value *) lfirst(new_cell); + new_next =3D lnext(new_cell); + + if (new_option->status =3D=3D OPTION_VALUE_STATUS_FOR_RESET) + new_options =3D list_delete_cell(new_options,new_cell,new_prev); + else + new_prev =3D new_cell; + } + + /* + * Now merge what remained of both lists + */ + result =3D 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 =3D (DefElem *) lfirst(cell); + /* + * Checking namespace only for options that have namespaces. + * Options with no namespaces are always accepted + */ + if (def->defnamespace) + { + bool found =3D false; + int i =3D 0; + while (allowed_namespaces[i]) + { + if (pg_strcasecmp(def->defnamespace, + allowed_namespaces[i]) =3D=3D 0) + { + found =3D 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 =3D NIL; + + foreach(cell, defList) + { + DefElem *def =3D (DefElem *) lfirst(cell); + if ((! namespace && ! def->defnamespace) || + (namespace && def->defnamespace && + pg_strcasecmp(namespace, def->defnamespace) =3D=3D 0)) + { + result =3D lappend(result, def); + } + } + return result; +} + +/* + * optionsTextArrayToDefList + * Convert the text-array format of reloptions into a List of DefElem. + */ +List * +optionsTextArrayToDefList(Datum options) +{ + List *result =3D NIL; + ArrayType *array; + Datum *optiondatums; + int noptions; + int i; + + /* Nothing to do if no options */ + if (!PointerIsValid(DatumGetPointer(options))) + return result; + + array =3D DatumGetArrayTypeP(options); + + deconstruct_array(array, TEXTOID, -1, false, 'i', + &optiondatums, NULL, &noptions); + + for (i =3D 0; i < noptions; i++) + { + char *s; + char *p; + Node *val =3D NULL; + + s =3D TextDatumGetCString(optiondatums[i]); + p =3D strchr(s, '=3D'); + if (p) + { + *p++ =3D '\0'; + val =3D (Node *) makeString(pstrdup(p)); + } + result =3D lappend(result, makeDefElem(pstrdup(s), val, -1)); + } + + return result; +} + +/* + * optionsParseRawValues + * Parses and vlaidates (if proper flag is set) option_values. As a resu= lt + * 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 opt= ion + * should be parsed. + * If validate mode is off, function found an option that do not have prop= er + * option_catalog entry, this option kept unparsed (if some garbage came f= rom + * 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 =3D NIL; + bool *is_set; + int i; + bool validate =3D mode & OPTIONS_PARSE_MODE_VALIDATE; + bool for_alter =3D mode & OPTIONS_PARSE_MODE_FOR_ALTER; + + + is_set =3D palloc0(sizeof(bool)*catalog->num); + foreach(cell, raw_values) + { + option_value *option =3D (option_value *) lfirst(cell); + bool found =3D false; + bool skip =3D false; + + + if (option->status =3D=3D 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 =3D lappend(result, option); + continue; + } + if (validate && option->namespace && (! catalog->namespace || + pg_strcasecmp(catalog->namespace, option->namespace) !=3D 0)) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unrecognized parameter namespace \"%s\"", + option->namespace))); + } + + for (i=3D0;inum;i++) + { + option_definition_basic * definition =3D catalog->definitions[i]; + + if (pg_strcasecmp(option->raw_name, + definition->name) =3D=3D 0) + { + /* + * Skip option with "ignore" flag, as it is processed somewhere + * else. (WITH OIDS special case) + */ + if (definition->flags & OPTION_DEFINITION_FLAG_IGNORE) + { + found =3D true; + skip =3D 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 =3D 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 =3D=3D OPTION_VALUE_STATUS_FOR_RESET) + { + /* + * For RESET options do not need further processing + * so mark it found and stop searching + */ + found =3D true; + break; + } + pfree(option->raw_name); + option->raw_name =3D NULL; + option->gen =3D definition; + parse_one_option(option, NULL, -1, validate); + is_set[i] =3D true; + found =3D 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 =3D 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 =3D option->raw_value; + + switch (option->gen->type) + { + case OPTION_TYPE_BOOL: + { + parsed =3D 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 =3D + (option_definition_int *) option->gen; + + parsed =3D 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 =3D + (option_definition_real *) option->gen; + + parsed =3D 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 =3D + (option_definition_enum *) option->gen; + int i =3D 0; + parsed =3D false; + while (opt_enum->allowed_values[i]) + { + if (pg_strcasecmp(value, opt_enum->allowed_values[i]) =3D=3D 0) + { + option->values.enum_val =3D i; + parsed =3D true; + break; + } + i++; + } + if (! parsed) + { + int length =3D 0; + char *str; + char *ptr; + /* Generating list of allowed values: + * "value1", "value2", ... "valueN" */ + i =3D 0; + while (opt_enum->allowed_values[i]) + { + length +=3D 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 =3D palloc((length - 1 ) * sizeof(char)); + i =3D 0; + ptr =3D str; + while (opt_enum->allowed_values[i]) + { + if (i !=3D 0) + { + ptr[0] =3D ','; + ptr[1] =3D ' '; + ptr +=3D 2; + } + ptr[0]=3D'"'; + ptr++; + sprintf(ptr,"%s",opt_enum->allowed_values[i]); + ptr +=3D strlen(ptr); + ptr[0] =3D '"'; + ptr++; + i++; + } + *ptr=3D'\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 =3D + (option_definition_string *) option->gen; + + option->values.string_val =3D value; + if (validate && optstring->validate_cb) + (optstring->validate_cb) (value); + parsed =3D true; + } + break; + default: + elog(ERROR, "unsupported reloption type %d", option->gen->type); + parsed =3D true; /* quiet compiler */ + break; + } + + if (parsed) + option->status =3D OPTION_VALUE_STATUS_PARSED; + +} + +/* + * optionsAllocateBytea + * Allocates memory for bytea options representation + * + * Function allocates memory for byrea structure of an option, plus adds s= pace + * 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 intern= al + * structure, so it would not be able to copy string values if they are ou= tside + * 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 =3D catalog->struct_size; + + /* Calculate size needed to store all string values for this option */ + for (i=3D0;inum;i++) + { + option_definition_basic *definition =3D catalog->definitions[i]; + bool found =3D false; + option_value *option; + + /* Not interested in non-string options, skipping*/ + if (definition->type !=3D OPTION_TYPE_STRING) + continue; + + /* + * Trying to find option_value that references definition + * catalog entry + */ + foreach(cell, options) + { + option =3D (option_value *) lfirst(cell); + if (option->status =3D=3D OPTION_VALUE_STATUS_PARSED && + pg_strcasecmp(option->gen->name, definition->name) =3D=3D0) + { + found =3D true; + break; + } + } + if (found) + /* If found, it'value will be stored */ + length =3D 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 =3D strlen( + ((option_definition_string*)definition)->default_val) + 1; + else + length =3D 0; + /* Add total length of all string values to basic size */ + size +=3D length; + } + + res =3D 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 alloc= ated + * 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 =3D optionsAllocateBytea(catalog, options); + + /* place for string data starts right after original structure */ + string_values_buffer =3D data + catalog->struct_size; + + for (i =3D 0; i < catalog->num; i++) + { + option_value *found =3D NULL; + ListCell *cell; + char *item_pos; + option_definition_basic *definition =3D catalog->definitions[i]; + + if (definition->flags & OPTION_DEFINITION_FLAG_IGNORE) + continue; + + /* Calculate the position of the item inside the structure */ + item_pos =3D data + definition->struct_offset; + + /* Looking for the corresponding option from options list*/ + foreach(cell, options) + { + option_value *option =3D (option_value *) lfirst(cell); + + if (option->status =3D=3D OPTION_VALUE_STATUS_RAW) + continue; /* raw can come from db. Just ignore them then*/ + Assert(option->status !=3D OPTION_VALUE_STATUS_EMPTY); + + if (pg_strcasecmp(definition->name,option->gen->name) =3D=3D 0) + { + found =3D option; + break; + } + } + /* writing to the proper position either option value or default val */ + switch (definition->type) + { + case OPTION_TYPE_BOOL: + *(bool *) item_pos =3D found ? + found->values.bool_val : + ((option_definition_bool *) definition)->default_val; + break; + case OPTION_TYPE_INT: + *(int *) item_pos =3D found ? + found->values.int_val : + ((option_definition_int *) definition)->default_val; + break; + case OPTION_TYPE_REAL: + *(double *) item_pos =3D found ? + found->values.real_val : + ((option_definition_real *) definition)->default_val; + break; + case OPTION_TYPE_ENUM: + *(int *) item_pos =3D 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 =3D NULL; + if (found) + value =3D found->values.string_val; + else + value =3D ((option_definition_string *) definition) + ->default_val; + *(int *) item_pos =3D value ? + string_values_buffer - data : + OPTION_STRING_VALUE_NOT_SET_OFFSET; + if (value) + { + strcpy(string_values_buffer,value); + string_values_buffer +=3D 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 =3D optionsDefListToRawValues(defList, parse_mode); + new_values =3D 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 =3D optionsTextArrayToRawValues(oldOptions); + merged_values =3D 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 =3D optionsParseRawValues(merged_values, catalog, 0); + } else + { + merged_values =3D new_values; + } + + /* + * If we have postprocess_fun function defined in catalog, then there mig= ht + * 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 =3D optionsValuesToBytea(merged_values, catalog); + catalog->postprocess_fun(data, true); + pfree(data); + } + /* + * Convert options to TextArray format so caller can store them into + * database + */ + result =3D optionsValuesToTextArray(merged_values); + return result; +} + + +/* + * optionsTextArrayToBytea + * A meta-function that transforms options stored as TextArray into bina= ry + * (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 ca= ched + */ +bytea * +optionsTextArrayToBytea(options_catalog *catalog, Datum data) +{ + List *values; + bytea *options; + values =3D optionsTextArrayToRawValues(data); + values =3D optionsParseRawValues(values, catalog, 0); + options =3D 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/co= mmon/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 @@ =20 #include "postgres.h" =20 -#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" =20 -/* - * 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 validat= ion - * 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[] =3D -{ - { - { - "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 o= f 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[] =3D -{ - { - { - "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 wraparoun= d", - 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 wrap= around", - 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 ve= rsions", - 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 th= is relation.", - RELOPT_KIND_HEAP, - AccessExclusiveLock - }, - -1, 0, 1024 - }, - - /* list terminator */ - {{NULL}} -}; - -static relopt_real realRelOpts[] =3D -{ - { - { - "autovacuum_vacuum_scale_factor", - "Number of tuple updates or deletes prior to vacuum as a fraction of re= ltuples", - 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 frac= tion 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 d= isk 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[] =3D -{ - { - { - "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 =3D NULL; -static bits32 last_assigned_kind =3D RELOPT_KIND_LAST_DEFAULT; - -static int num_custom_options =3D 0; -static relopt_gen **custom_options =3D NULL; -static bool need_initialization =3D 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 len= gth. - */ -static void -initialize_reloptions(void) -{ - int i; - int j; - - j =3D 0; - for (i =3D 0; boolRelOpts[i].gen.name; i++) - { - Assert(DoLockModesConflict(boolRelOpts[i].gen.lockmode, - boolRelOpts[i].gen.lockmode)); - j++; - } - for (i =3D 0; intRelOpts[i].gen.name; i++) - { - Assert(DoLockModesConflict(intRelOpts[i].gen.lockmode, - intRelOpts[i].gen.lockmode)); - j++; - } - for (i =3D 0; realRelOpts[i].gen.name; i++) - { - Assert(DoLockModesConflict(realRelOpts[i].gen.lockmode, - realRelOpts[i].gen.lockmode)); - j++; - } - for (i =3D 0; stringRelOpts[i].gen.name; i++) - { - Assert(DoLockModesConflict(stringRelOpts[i].gen.lockmode, - stringRelOpts[i].gen.lockmode)); - j++; - } - j +=3D num_custom_options; - - if (relOpts) - pfree(relOpts); - relOpts =3D MemoryContextAlloc(TopMemoryContext, - (j + 1) * sizeof(relopt_gen *)); - - j =3D 0; - for (i =3D 0; boolRelOpts[i].gen.name; i++) - { - relOpts[j] =3D &boolRelOpts[i].gen; - relOpts[j]->type =3D RELOPT_TYPE_BOOL; - relOpts[j]->namelen =3D strlen(relOpts[j]->name); - j++; - } - - for (i =3D 0; intRelOpts[i].gen.name; i++) - { - relOpts[j] =3D &intRelOpts[i].gen; - relOpts[j]->type =3D RELOPT_TYPE_INT; - relOpts[j]->namelen =3D strlen(relOpts[j]->name); - j++; - } - - for (i =3D 0; realRelOpts[i].gen.name; i++) - { - relOpts[j] =3D &realRelOpts[i].gen; - relOpts[j]->type =3D RELOPT_TYPE_REAL; - relOpts[j]->namelen =3D strlen(relOpts[j]->name); - j++; - } - - for (i =3D 0; stringRelOpts[i].gen.name; i++) - { - relOpts[j] =3D &stringRelOpts[i].gen; - relOpts[j]->type =3D RELOPT_TYPE_STRING; - relOpts[j]->namelen =3D strlen(relOpts[j]->name); - j++; - } - - for (i =3D 0; i < num_custom_options; i++) - { - relOpts[j] =3D custom_options[i]; - j++; - } - - /* add a list terminator */ - relOpts[j] =3D NULL; - - /* flag the work is complete */ - need_initialization =3D 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 >=3D RELOPT_KIND_MAX) - ereport(ERROR, - (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), - errmsg("user-defined relation parameter types limit exceeded"))); - last_assigned_kind <<=3D 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 =3D 0; - - if (num_custom_options >=3D max_custom_options) - { - MemoryContext oldcxt; - - oldcxt =3D MemoryContextSwitchTo(TopMemoryContext); - - if (max_custom_options =3D=3D 0) - { - max_custom_options =3D 8; - custom_options =3D palloc(max_custom_options * sizeof(relopt_gen *)); - } - else - { - max_custom_options *=3D 2; - custom_options =3D repalloc(custom_options, - max_custom_options * sizeof(relopt_gen *)); - } - MemoryContextSwitchTo(oldcxt); - } - custom_options[num_custom_options++] =3D newoption; - - need_initialization =3D 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 =3D MemoryContextSwitchTo(TopMemoryContext); - - switch (type) - { - case RELOPT_TYPE_BOOL: - size =3D sizeof(relopt_bool); - break; - case RELOPT_TYPE_INT: - size =3D sizeof(relopt_int); - break; - case RELOPT_TYPE_REAL: - size =3D sizeof(relopt_real); - break; - case RELOPT_TYPE_STRING: - size =3D sizeof(relopt_string); - break; - default: - elog(ERROR, "unsupported reloption type %d", type); - return NULL; /* keep compiler quiet */ - } - - newoption =3D palloc(size); - - newoption->name =3D pstrdup(name); - if (desc) - newoption->desc =3D pstrdup(desc); - else - newoption->desc =3D NULL; - newoption->kinds =3D kinds; - newoption->namelen =3D strlen(name); - newoption->type =3D 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 =3D (relopt_bool *) allocate_reloption(kinds, RELOPT_TYPE_BOOL, - name, desc); - newoption->default_val =3D 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 =3D (relopt_int *) allocate_reloption(kinds, RELOPT_TYPE_INT, - name, desc); - newoption->default_val =3D default_val; - newoption->min =3D min_val; - newoption->max =3D 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_va= l, - double min_val, double max_val) -{ - relopt_real *newoption; - - newoption =3D (relopt_real *) allocate_reloption(kinds, RELOPT_TYPE_REAL, - name, desc); - newoption->default_val =3D default_val; - newoption->min =3D min_val; - newoption->max =3D 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_v= al, - validate_string_relopt validator) -{ - relopt_string *newoption; - - /* make sure the validator/default combination is sane */ - if (validator) - (validator) (default_val); - - newoption =3D (relopt_string *) allocate_reloption(kinds, RELOPT_TYPE_STR= ING, - name, desc); - newoption->validate_cb =3D validator; - if (default_val) - { - newoption->default_val =3D MemoryContextStrdup(TopMemoryContext, - default_val); - newoption->default_len =3D strlen(default_val); - newoption->default_isnull =3D false; - } - else - { - newoption->default_val =3D ""; - newoption->default_len =3D 0; - newoption->default_isnull =3D 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 =3D=3D NIL) - return oldOptions; - - /* We build new array using accumArrayResult */ - astate =3D NULL; - - /* Copy any oldOptions that aren't to be replaced */ - if (PointerIsValid(DatumGetPointer(oldOptions))) - { - ArrayType *array =3D DatumGetArrayTypeP(oldOptions); - Datum *oldoptions; - int noldoptions; - int i; - - deconstruct_array(array, TEXTOID, -1, false, 'i', - &oldoptions, NULL, &noldoptions); - - for (i =3D 0; i < noldoptions; i++) - { - text *oldoption =3D DatumGetTextP(oldoptions[i]); - char *text_str =3D VARDATA(oldoption); - int text_len =3D VARSIZE(oldoption) - VARHDRSZ; - - /* Search for a match in defList */ - foreach(cell, defList) - { - DefElem *def =3D (DefElem *) lfirst(cell); - int kw_len; - - /* ignore if not in the same namespace */ - if (namspace =3D=3D NULL) - { - if (def->defnamespace !=3D NULL) - continue; - } - else if (def->defnamespace =3D=3D NULL) - continue; - else if (pg_strcasecmp(def->defnamespace, namspace) !=3D 0) - continue; - - kw_len =3D strlen(def->defname); - if (text_len > kw_len && text_str[kw_len] =3D=3D '=3D' && - pg_strncasecmp(text_str, def->defname, kw_len) =3D=3D 0) - break; - } - if (!cell) - { - /* No match, so keep old option */ - astate =3D 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=3Dval). (Must do this because the gramm= ar - * doesn't enforce it.) - */ - foreach(cell, defList) - { - DefElem *def =3D (DefElem *) lfirst(cell); - - if (isReset) - { - if (def->arg !=3D 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 !=3D NULL) - { - bool valid =3D false; - int i; - - if (validnsps) - { - for (i =3D 0; validnsps[i]; i++) - { - if (pg_strcasecmp(def->defnamespace, - validnsps[i]) =3D=3D 0) - { - valid =3D 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") =3D=3D 0) - continue; - - /* ignore if not in the same namespace */ - if (namspace =3D=3D NULL) - { - if (def->defnamespace !=3D NULL) - continue; - } - else if (def->defnamespace =3D=3D NULL) - continue; - else if (pg_strcasecmp(def->defnamespace, namspace) !=3D 0) - continue; - - /* - * Flatten the DefElem into a text string like "name=3Darg". If we - * have just "name", assume "name=3Dtrue" is meant. Note: the - * namespace is not output. - */ - if (def->arg !=3D NULL) - value =3D defGetString(def); - else - value =3D "true"; - len =3D VARHDRSZ + strlen(def->defname) + 1 + strlen(value); - /* +1 leaves room for sprintf's trailing null */ - t =3D (text *) palloc(len + 1); - SET_VARSIZE(t, len); - sprintf(VARDATA(t), "%s=3D%s", def->defname, value); - - astate =3D accumArrayResult(astate, PointerGetDatum(t), - false, TEXTOID, - CurrentMemoryContext); - } - } - - if (astate) - result =3D makeArrayResult(astate, CurrentMemoryContext); - else - result =3D (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 =3D NIL; - ArrayType *array; - Datum *optiondatums; - int noptions; - int i; - - /* Nothing to do if no options */ - if (!PointerIsValid(DatumGetPointer(options))) - return result; - - array =3D DatumGetArrayTypeP(options); - - deconstruct_array(array, TEXTOID, -1, false, 'i', - &optiondatums, NULL, &noptions); - - for (i =3D 0; i < noptions; i++) - { - char *s; - char *p; - Node *val =3D NULL; - - s =3D TextDatumGetCString(optiondatums[i]); - p =3D strchr(s, '=3D'); - if (p) - { - *p++ =3D '\0'; - val =3D (Node *) makeString(pstrdup(p)); - } - result =3D lappend(result, makeDefElem(pstrdup(s), val, -1)); - } - - return result; -} +void add_autovacuum_options(options_catalog *catalog, int base_offset, + bool for_toast); =20 /* * 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 =3D NULL; bool isnull; Datum datum; + options_catalog *catalog =3D NULL; Form_pg_class classForm; =20 datum =3D 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 =3D heap_reloptions(classForm->relkind, datum, false); + catalog =3D get_heap_relopt_catalog(); + break; + case RELKIND_TOASTVALUE: + catalog =3D get_toast_relopt_catalog(); break; case RELKIND_VIEW: - options =3D view_reloptions(datum, false); + catalog =3D get_view_relopt_catalog(); break; case RELKIND_INDEX: - options =3D index_reloptions(amoptions, datum, false); + if (catalog_fun) + catalog =3D catalog_fun(); break; case RELKIND_FOREIGN_TABLE: options =3D NULL; @@ -948,529 +87,293 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc, break; } =20 + if (catalog) + options =3D optionsTextArrayToBytea(catalog, datum); return options; } =20 + /* - * 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=3Dtrue. The length of t= his - * 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. Catalo= gs + * 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 opt= ions. + * 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 N= ULL - * is returned. + * Function add_autovacuum_options adds to the "catalog" catalog autovacuum + * options definition. In binary mode this options are saved into AutoVacO= pts + * structure, which is located in result bytea chunk with base_offset offs= et. * - * 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 toa= st + * case this options are added to catalog with OPTION_DEFINITION_FLAG_REJE= CT + * flag set, so postgres will reject this toast options from the CREATE or + * ALTER command, but still will save default value in binary representati= on */ -relopt_value * -parseRelOptions(Datum options, bool validate, relopt_kind kind, - int *numrelopts) -{ - relopt_value *reloptions; - int numoptions =3D 0; - int i; - int j; - - if (need_initialization) - initialize_reloptions(); - - /* Build a list of expected options, based on kind */ - - for (i =3D 0; relOpts[i]; i++) - if (relOpts[i]->kinds & kind) - numoptions++; - - if (numoptions =3D=3D 0) - { - *numrelopts =3D 0; - return NULL; - } - - reloptions =3D palloc(numoptions * sizeof(relopt_value)); - - for (i =3D 0, j =3D 0; relOpts[i]; i++) - { - if (relOpts[i]->kinds & kind) - { - reloptions[j].gen =3D relOpts[i]; - reloptions[j].isset =3D false; - j++; - } - } - - /* Done if no options */ - if (PointerIsValid(DatumGetPointer(options))) - { - ArrayType *array =3D DatumGetArrayTypeP(options); - Datum *optiondatums; - int noptions; - - deconstruct_array(array, TEXTOID, -1, false, 'i', - &optiondatums, NULL, &noptions); - - for (i =3D 0; i < noptions; i++) - { - text *optiontext =3D DatumGetTextP(optiondatums[i]); - char *text_str =3D VARDATA(optiontext); - int text_len =3D VARSIZE(optiontext) - VARHDRSZ; - int j; - - /* Search for a match in reloptions */ - for (j =3D 0; j < numoptions; j++) - { - int kw_len =3D reloptions[j].gen->namelen; - - if (text_len > kw_len && text_str[kw_len] =3D=3D '=3D' && - pg_strncasecmp(text_str, reloptions[j].gen->name, - kw_len) =3D=3D 0) - { - parse_one_reloption(&reloptions[j], text_str, text_len, - validate); - break; - } - } - - if (j >=3D numoptions && validate) - { - char *s; - char *p; - - s =3D TextDatumGetCString(optiondatums[i]); - p =3D strchr(s, '=3D'); - if (p) - *p =3D '\0'; - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("unrecognized parameter \"%s\"", s))); - } - } =20 - /* It's worth avoiding memory leaks in this function */ - pfree(optiondatums); - if (((void *) array) !=3D DatumGetPointer(options)) - pfree(array); - } - - *numrelopts =3D 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 ver= sions", + 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 wrapa= round", + 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 f= reeze 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 rel= tuples", + 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 fract= ion of reltuples", + ShareUpdateExclusiveLock, + for_toast ? OPTION_DEFINITION_FLAG_REJECT : 0, + base_offset + offsetof(AutoVacOpts, analyze_scale_factor), + -1, 0.0, 100.0); } =20 /* - * 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 =3D 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 =3D NULL; =20 - value_len =3D text_len - option->gen->namelen - 1; - value =3D (char *) palloc(value_len + 1); - memcpy(value, text_str + option->gen->namelen + 1, value_len); - value[value_len] =3D '\0'; - - switch (option->gen->type) +options_catalog * +get_heap_relopt_catalog(void) +{ + if (! heap_relopt_catalog) { - case RELOPT_TYPE_BOOL: - { - parsed =3D 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 =3D (relopt_int *) option->gen; + heap_relopt_catalog =3D allocateOptionsCatalog(NULL); + heap_relopt_catalog->struct_size =3D sizeof(HeapOptions); =20 - parsed =3D 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 =3D (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); =20 - parsed =3D 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 =3D (relopt_string *) option->gen; + add_autovacuum_options(heap_relopt_catalog, + offsetof(HeapOptions, autovacuum), false); =20 - option->values.string_val =3D value; - nofree =3D true; - if (validate && optstring->validate_cb) - (optstring->validate_cb) (value); - parsed =3D true; - } - break; - default: - elog(ERROR, "unsupported reloption type %d", option->gen->type); - parsed =3D true; /* quiet compiler */ - break; + optionsCatalogAddItemBool(heap_relopt_catalog, "user_catalog_table", + "Declare a table as an additional catalog table, e.g. for the purpose o= f 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 th= is 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 =3D true; - if (!nofree) - pfree(value); + return heap_relopt_catalog; } =20 /* - * Given the result from parseRelOptions, allocate a struct that's of the - * specified base size plus any extra space that's needed for string varia= bles. - * - * "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 =3D base; - int i; =20 - for (i =3D 0; i < numoptions; i++) - if (options[i].gen->type =3D=3D RELOPT_TYPE_STRING) - size +=3D GET_STRING_RELOPTION_LEN(options[i]) + 1; +static options_catalog * toast_relopt_catalog =3D NULL; =20 - 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 =3D basesize; - - for (i =3D 0; i < numoptions; i++) + if (! toast_relopt_catalog) { - int j; - bool found =3D false; + toast_relopt_catalog =3D allocateOptionsCatalog("toast"); + toast_relopt_catalog->struct_size =3D sizeof(ToastOptions); =20 - for (j =3D 0; j < numelems; j++) - { - if (pg_strcasecmp(options[i].gen->name, elems[j].optname) =3D=3D 0) - { - relopt_string *optstring; - char *itempos =3D ((char *) rdopts) + elems[j].offset; - char *string_val; - - switch (options[i].gen->type) - { - case RELOPT_TYPE_BOOL: - *(bool *) itempos =3D options[i].isset ? - options[i].values.bool_val : - ((relopt_bool *) options[i].gen)->default_val; - break; - case RELOPT_TYPE_INT: - *(int *) itempos =3D options[i].isset ? - options[i].values.int_val : - ((relopt_int *) options[i].gen)->default_val; - break; - case RELOPT_TYPE_REAL: - *(double *) itempos =3D options[i].isset ? - options[i].values.real_val : - ((relopt_real *) options[i].gen)->default_val; - break; - case RELOPT_TYPE_STRING: - optstring =3D (relopt_string *) options[i].gen; - if (options[i].isset) - string_val =3D options[i].values.string_val; - else if (!optstring->default_isnull) - string_val =3D optstring->default_val; - else - string_val =3D NULL; - - if (string_val =3D=3D NULL) - *(int *) itempos =3D 0; - else - { - strcpy((char *) rdopts + offset, string_val); - *(int *) itempos =3D offset; - offset +=3D strlen(string_val) + 1; - } - break; - default: - elog(ERROR, "unsupported reloption type %d", - options[i].gen->type); - break; - } - found =3D 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; } =20 - /* - * 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[] =3D { - {"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_thresho= ld)}, - {"autovacuum_analyze_threshold", RELOPT_TYPE_INT, - offsetof(StdRdOptions, autovacuum) +offsetof(AutoVacOpts, analyze_thresh= old)}, - {"autovacuum_vacuum_cost_delay", RELOPT_TYPE_INT, - offsetof(StdRdOptions, autovacuum) +offsetof(AutoVacOpts, vacuum_cost_de= lay)}, - {"autovacuum_vacuum_cost_limit", RELOPT_TYPE_INT, - offsetof(StdRdOptions, autovacuum) +offsetof(AutoVacOpts, vacuum_cost_li= mit)}, - {"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_a= ge)}, - {"autovacuum_multixact_freeze_min_age", RELOPT_TYPE_INT, - offsetof(StdRdOptions, autovacuum) +offsetof(AutoVacOpts, multixact_free= ze_min_age)}, - {"autovacuum_multixact_freeze_max_age", RELOPT_TYPE_INT, - offsetof(StdRdOptions, autovacuum) +offsetof(AutoVacOpts, multixact_free= ze_max_age)}, - {"autovacuum_multixact_freeze_table_age", RELOPT_TYPE_INT, - offsetof(StdRdOptions, autovacuum) +offsetof(AutoVacOpts, multixact_free= ze_table_age)}, - {"log_autovacuum_min_duration", RELOPT_TYPE_INT, - offsetof(StdRdOptions, autovacuum) +offsetof(AutoVacOpts, log_min_durati= on)}, - {"autovacuum_vacuum_scale_factor", RELOPT_TYPE_REAL, - offsetof(StdRdOptions, autovacuum) +offsetof(AutoVacOpts, vacuum_scale_f= actor)}, - {"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 =3D parseRelOptions(reloptions, validate, kind, &numoptions); - - /* if none set, we're done */ - if (numoptions =3D=3D 0) - return NULL; - - rdopts =3D allocateReloptStruct(sizeof(StdRdOptions), options, numoptions= ); - - fillRelOptions((void *) rdopts, sizeof(StdRdOptions), options, numoptions, - validate, tab, lengthof(tab)); +static options_catalog * view_relopt_catalog =3D NULL; =20 - 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[] =3D { - {"security_barrier", RELOPT_TYPE_BOOL, - offsetof(ViewOptions, security_barrier)}, - {"check_option", RELOPT_TYPE_STRING, - offsetof(ViewOptions, check_option_offset)} - }; - - options =3D parseRelOptions(reloptions, validate, RELOPT_KIND_VIEW, &numo= ptions); + static const char *enum_names[] =3D VIEW_OPTION_CHECK_OPTION_VALUE_NAMES; =20 - /* if none set, we're done */ - if (numoptions =3D=3D 0) - return NULL; - - vopts =3D 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 =3D allocateOptionsCatalog(NULL); + view_relopt_catalog->struct_size =3D sizeof(ViewOptions); =20 -/* - * 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); =20 - switch (relkind) - { - case RELKIND_TOASTVALUE: - rdopts =3D (StdRdOptions *) - default_reloptions(reloptions, validate, RELOPT_KIND_TOAST); - if (rdopts !=3D NULL) - { - /* adjust default-only parameters for TOAST relations */ - rdopts->fillfactor =3D 100; - rdopts->autovacuum.analyze_threshold =3D -1; - rdopts->autovacuum.analyze_scale_factor =3D -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; } =20 - /* - * 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 vali= date) -{ - Assert(amoptions !=3D NULL); - - /* Assume function is strict */ - if (!PointerIsValid(DatumGetPointer(reloptions))) - return NULL; - - return amoptions(reloptions, validate); -} +static options_catalog * attribute_options_catalog =3D NULL; =20 -/* - * 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[] =3D { - {"n_distinct", RELOPT_TYPE_REAL, offsetof(AttributeOpts, n_distinct)}, - {"n_distinct_inherited", RELOPT_TYPE_REAL, offsetof(AttributeOpts, n_dis= tinct_inherited)} - }; - - options =3D parseRelOptions(reloptions, validate, RELOPT_KIND_ATTRIBUTE, - &numoptions); - - /* if none set, we're done */ - if (numoptions =3D=3D 0) - return NULL; - - aopts =3D allocateReloptStruct(sizeof(AttributeOpts), options, numoptions= ); - - fillRelOptions((void *) aopts, sizeof(AttributeOpts), options, numoptions, - validate, tab, lengthof(tab)); + if (! attribute_options_catalog) + { + attribute_options_catalog =3D allocateOptionsCatalog(NULL); + attribute_options_catalog->struct_size =3D sizeof(AttributeOpts); =20 - 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); =20 - 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; } =20 /* - * 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[] =3D { - {"random_page_cost", RELOPT_TYPE_REAL, offsetof(TableSpaceOpts, random_p= age_cost)}, - {"seq_page_cost", RELOPT_TYPE_REAL, offsetof(TableSpaceOpts, seq_page_co= st)}, - {"effective_io_concurrency", RELOPT_TYPE_INT, offsetof(TableSpaceOpts, e= ffective_io_concurrency)} - }; - - options =3D parseRelOptions(reloptions, validate, RELOPT_KIND_TABLESPACE, - &numoptions); - - /* if none set, we're done */ - if (numoptions =3D=3D 0) - return NULL; +static options_catalog * tablespace_options_catalog =3D NULL; =20 - tsopts =3D allocateReloptStruct(sizeof(TableSpaceOpts), options, numoptio= ns); +options_catalog * +get_tablespace_options_catalog(void) +{ + if (! tablespace_options_catalog) + { + tablespace_options_catalog =3D allocateOptionsCatalog(NULL); + tablespace_options_catalog->struct_size =3D sizeof(TableSpaceOpts); =20 - fillRelOptions((void *) tsopts, sizeof(TableSpaceOpts), options, numoptio= ns, - validate, tab, lengthof(tab)); + optionsCatalogAddItemReal(tablespace_options_catalog, + "random_page_cost", + "Sets the planner's estimate of the cost of a nonsequentially fetched d= isk page", + AccessExclusiveLock, + 0, offsetof(TableSpaceOpts, random_page_cost), -1, 0.0, DBL_MAX); =20 - 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); =20 - 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; } =20 /* @@ -1480,33 +383,51 @@ tablespace_reloptions(Datum reloptions, bool validat= e) * for a longer explanation of how this works. */ LOCKMODE -AlterTableGetRelOptionsLockLevel(List *defList) +AlterTableGetRelOptionsLockLevel(Relation rel, List *defList) { - LOCKMODE lockmode =3D NoLock; - ListCell *cell; + LOCKMODE lockmode =3D NoLock; + ListCell *cell; + options_catalog *catalog =3D NULL; =20 if (defList =3D=3D NIL) return AccessExclusiveLock; =20 - if (need_initialization) - initialize_reloptions(); + switch (rel->rd_rel->relkind) + { + case RELKIND_TOASTVALUE: + catalog =3D get_toast_relopt_catalog(); + break; + case RELKIND_RELATION: + case RELKIND_MATVIEW: + catalog =3D get_heap_relopt_catalog(); + break; + case RELKIND_INDEX: + catalog =3D rel->rd_amroutine->amrelopt_catalog(); + break; + case RELKIND_VIEW: + catalog =3D get_view_relopt_catalog(); + break; + default: + Assert(false); /* can't get here */ + break; + } + + Assert(catalog);/* No catalog - no reloption change. Should not get here = */ =20 foreach(cell, defList) { - DefElem *def =3D (DefElem *) lfirst(cell); - int i; + DefElem *def =3D (DefElem *) lfirst(cell); =20 - for (i =3D 0; relOpts[i]; i++) + int i; + for (i=3D0; i< catalog->num; i++) { - if (pg_strncasecmp(relOpts[i]->name, - def->defname, - relOpts[i]->namelen + 1) =3D=3D 0) - { - if (lockmode < relOpts[i]->lockmode) - lockmode =3D relOpts[i]->lockmode; - } + option_definition_basic *gen =3D catalog->definitions[i]; + + if (pg_strcasecmp(gen->name, + def->defname) =3D=3D 0) + if (lockmode < gen->lockmode) + lockmode =3D gen->lockmode; } } - return lockmode; } diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginu= til.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" =20 #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" =20 /* * GIN handler function: return IndexAmRoutine with access method paramete= rs @@ -58,7 +59,6 @@ ginhandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup =3D ginvacuumcleanup; amroutine->amcanreturn =3D NULL; amroutine->amcostestimate =3D gincostestimate; - amroutine->amoptions =3D ginoptions; amroutine->amproperty =3D NULL; amroutine->amvalidate =3D ginvalidate; amroutine->ambeginscan =3D ginbeginscan; @@ -68,6 +68,7 @@ ginhandler(PG_FUNCTION_ARGS) amroutine->amendscan =3D ginendscan; amroutine->ammarkpos =3D NULL; amroutine->amrestrpos =3D NULL; + amroutine->amrelopt_catalog =3D gingetreloptcatalog; amroutine->amestimateparallelscan =3D NULL; amroutine->aminitparallelscan =3D NULL; amroutine->amparallelrescan =3D NULL; @@ -591,35 +592,6 @@ ginExtractEntries(GinState *ginstate, OffsetNumber att= num, return entries; } =20 -bytea * -ginoptions(Datum reloptions, bool validate) -{ - relopt_value *options; - GinOptions *rdopts; - int numoptions; - static const relopt_parse_elt tab[] =3D { - {"fastupdate", RELOPT_TYPE_BOOL, offsetof(GinOptions, useFastUpdate)}, - {"gin_pending_list_limit", RELOPT_TYPE_INT, offsetof(GinOptions, - pendingListCleanupSize)} - }; - - options =3D parseRelOptions(reloptions, validate, RELOPT_KIND_GIN, - &numoptions); - - /* if none set, we're done */ - if (numoptions =3D=3D 0) - return NULL; - - rdopts =3D 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 *sta= ts) =20 END_CRIT_SECTION(); } + + +static options_catalog * gin_relopt_catalog =3D NULL; + +void * +gingetreloptcatalog (void) +{ + if (! gin_relopt_catalog) + { + gin_relopt_catalog =3D allocateOptionsCatalog(NULL); + gin_relopt_catalog->struct_size =3D 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 =3D gistvacuumcleanup; amroutine->amcanreturn =3D gistcanreturn; amroutine->amcostestimate =3D gistcostestimate; - amroutine->amoptions =3D gistoptions; amroutine->amproperty =3D gistproperty; amroutine->amvalidate =3D gistvalidate; amroutine->ambeginscan =3D gistbeginscan; @@ -89,6 +88,7 @@ gisthandler(PG_FUNCTION_ARGS) amroutine->amendscan =3D gistendscan; amroutine->ammarkpos =3D NULL; amroutine->amrestrpos =3D NULL; + amroutine->amrelopt_catalog =3D gistgetreloptcatalog; amroutine->amestimateparallelscan =3D NULL; amroutine->aminitparallelscan =3D NULL; amroutine->amparallelrescan =3D 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 *in= dexInfo) { /* Get buffering mode from the options string */ GiSTOptions *options =3D (GiSTOptions *) index->rd_options; - char *bufferingMode =3D (char *) options + options->bufferingModeOffs= et; - - if (strcmp(bufferingMode, "on") =3D=3D 0) + if (options->buffering_mode =3D=3D GIST_OPTION_BUFFERING_ON) buildstate.bufferingMode =3D GIST_BUFFERING_STATS; - else if (strcmp(bufferingMode, "off") =3D=3D 0) + else if (options->buffering_mode =3D=3D GIST_OPTION_BUFFERING_OFF) buildstate.bufferingMode =3D GIST_BUFFERING_DISABLED; else buildstate.bufferingMode =3D GIST_BUFFERING_AUTO; @@ -233,25 +231,6 @@ gistbuild(Relation heap, Relation index, IndexInfo *in= dexInfo) } =20 /* - * Validator for "buffering" reloption on GiST indexes. Allows "on", "off" - * and "auto" values. - */ -void -gistValidateBufferingOption(char *value) -{ - if (value =3D=3D NULL || - (strcmp(value, "on") !=3D 0 && - strcmp(value, "off") !=3D 0 && - strcmp(value, "auto") !=3D 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/g= istutil.c index f92baed..ff8aaf9 100644 --- a/src/backend/access/gist/gistutil.c +++ b/src/backend/access/gist/gistutil.c @@ -17,14 +17,13 @@ =20 #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" =20 - /* * Write itup vector to page, has no control of free space. */ @@ -811,34 +810,6 @@ gistNewBuffer(Relation r) return buffer; } =20 -bytea * -gistoptions(Datum reloptions, bool validate) -{ - relopt_value *options; - GiSTOptions *rdopts; - int numoptions; - static const relopt_parse_elt tab[] =3D { - {"fillfactor", RELOPT_TYPE_INT, offsetof(GiSTOptions, fillfactor)}, - {"buffering", RELOPT_TYPE_STRING, offsetof(GiSTOptions, bufferingModeOff= set)} - }; - - options =3D parseRelOptions(reloptions, validate, RELOPT_KIND_GIST, - &numoptions); - - /* if none set, we're done */ - if (numoptions =3D=3D 0) - return NULL; - - rdopts =3D 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 =3D NULL; + +void * +gistgetreloptcatalog (void) +{ + static const char *enum_names[] =3D GIST_OPTION_BUFFERING_VALUE_NAMES; + if (! gist_relopt_catalog) + { + gist_relopt_catalog =3D allocateOptionsCatalog(NULL); + gist_relopt_catalog->struct_size =3D 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 =3D hashvacuumcleanup; amroutine->amcanreturn =3D NULL; amroutine->amcostestimate =3D hashcostestimate; - amroutine->amoptions =3D hashoptions; amroutine->amproperty =3D NULL; amroutine->amvalidate =3D hashvalidate; amroutine->ambeginscan =3D hashbeginscan; @@ -86,6 +85,7 @@ hashhandler(PG_FUNCTION_ARGS) amroutine->amendscan =3D hashendscan; amroutine->ammarkpos =3D NULL; amroutine->amrestrpos =3D NULL; + amroutine->amrelopt_catalog =3D hashgetreloptcatalog; amroutine->amestimateparallelscan =3D NULL; amroutine->aminitparallelscan =3D NULL; amroutine->amparallelrescan =3D NULL; diff --git a/src/backend/access/hash/hashpage.c b/src/backend/access/hash/h= ashpage.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, ForkNu= mber forkNum) data_width =3D sizeof(uint32); item_width =3D MAXALIGN(sizeof(IndexTupleData)) + MAXALIGN(data_width) + sizeof(ItemIdData); /* include the line pointer */ - ffactor =3D RelationGetTargetPageUsage(rel, HASH_DEFAULT_FILLFACTOR) / it= em_width; + ffactor =3D (BLCKSZ * HashGetFillFactor(rel) / 100) / item_width; /* keep to a sane range */ if (ffactor < 10) ffactor =3D 10; diff --git a/src/backend/access/hash/hashutil.c b/src/backend/access/hash/h= ashutil.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" =20 #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) } } =20 -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, Bucke= t old_bucket, =20 return new_bucket; } + +static options_catalog * hash_relopt_catalog =3D NULL; + +void * +hashgetreloptcatalog(void) +{ + if (! hash_relopt_catalog) + { + hash_relopt_catalog =3D allocateOptionsCatalog(NULL); + hash_relopt_catalog->struct_size =3D 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/hea= pam.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 *tupl= es, int ntuples, bool need_cids =3D RelationIsAccessibleInLogicalDecoding(relation); =20 needwal =3D !(options & HEAP_INSERT_SKIP_WAL) && RelationNeedsWAL(relatio= n); - saveFreeSpace =3D RelationGetTargetPageFreeSpace(relation, + if (IsToastRelation(relation)) + saveFreeSpace =3D ToastGetTargetPageFreeSpace(); + else + saveFreeSpace =3D HeapGetTargetPageFreeSpace(relation, HEAP_DEFAULT_FILLFACTOR); =20 /* 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))); =20 /* Compute desired extra freespace due to fillfactor option */ - saveFreeSpace =3D RelationGetTargetPageFreeSpace(relation, - HEAP_DEFAULT_FILLFACTOR); + if (IsToastRelation(relation)) + saveFreeSpace =3D ToastGetTargetPageFreeSpace(); + else + saveFreeSpace =3D HeapGetTargetPageFreeSpace(relation, + HEAP_DEFAULT_FILLFACTOR); =20 if (otherBuffer !=3D InvalidBuffer) otherBlock =3D 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 =3D RelationGetTargetPageFreeSpace(relation, - HEAP_DEFAULT_FILLFACTOR); + + if (IsToastRelation(relation)) + minfree =3D ToastGetTargetPageFreeSpace(); + else + minfree =3D HeapGetTargetPageFreeSpace(relation, + HEAP_DEFAULT_FILLFACTOR); minfree =3D Max(minfree, BLCKSZ / 10); =20 if (PageIsFull(page) || PageGetHeapFreeSpace(page) < minfree) diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/hea= p/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))); =20 /* Compute desired extra freespace due to fillfactor option */ - saveFreeSpace =3D RelationGetTargetPageFreeSpace(state->rs_new_rel, + if (IsToastRelation(state->rs_new_rel)) + saveFreeSpace =3D ToastGetTargetPageFreeSpace(); + else + saveFreeSpace =3D HeapGetTargetPageFreeSpace(state->rs_new_rel, HEAP_DEFAULT_FILLFACTOR); =20 /* 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/nbt= ree/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 =3D P_RIGHTMOST(opaque); state.have_split =3D false; if (state.is_leaf) - state.fillfactor =3D RelationGetFillFactor(rel, - BTREE_DEFAULT_FILLFACTOR); + state.fillfactor =3D BTGetFillFactor(rel); else state.fillfactor =3D BTREE_NONLEAF_FILLFACTOR; state.newitemonleft =3D 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" =20 - /* Working state for btbuild and its callback */ typedef struct { @@ -75,8 +75,7 @@ static void btvacuumscan(IndexVacuumInfo *info, IndexBulk= DeleteResult *stats, BTCycleId cycleid); static void btvacuumpage(BTVacState *vstate, BlockNumber blkno, BlockNumber orig_blkno); - - +void * btgetreloptcatalog (void); /* * Btree handler function: return IndexAmRoutine with access method parame= ters * and callbacks. @@ -108,7 +107,6 @@ bthandler(PG_FUNCTION_ARGS) amroutine->amvacuumcleanup =3D btvacuumcleanup; amroutine->amcanreturn =3D btcanreturn; amroutine->amcostestimate =3D btcostestimate; - amroutine->amoptions =3D btoptions; amroutine->amproperty =3D btproperty; amroutine->amvalidate =3D btvalidate; amroutine->ambeginscan =3D btbeginscan; @@ -118,6 +116,7 @@ bthandler(PG_FUNCTION_ARGS) amroutine->amendscan =3D btendscan; amroutine->ammarkpos =3D btmarkpos; amroutine->amrestrpos =3D btrestrpos; + amroutine->amrelopt_catalog =3D btgetreloptcatalog; amroutine->amestimateparallelscan =3D NULL; amroutine->aminitparallelscan =3D NULL; amroutine->amparallelrescan =3D NULL; @@ -1136,3 +1135,26 @@ btcanreturn(Relation index, int attno) { return true; } + + +static options_catalog * bt_relopt_catalog =3D NULL; + +void * +btgetreloptcatalog (void) +{ + if (! bt_relopt_catalog) + { + bt_relopt_catalog =3D allocateOptionsCatalog(NULL); + bt_relopt_catalog->struct_size =3D 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/nbtre= e/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 =3D (BLCKSZ * (100 - BTREE_NONLEAF_FILLFACTOR) / 100); else - state->btps_full =3D RelationGetTargetPageFreeSpace(wstate->index, - BTREE_DEFAULT_FILLFACTOR); + state->btps_full =3D (BLCKSZ * (100 - BTGetFillFactor(wstate->index)) /= 100); +=09 /* no parent level, yet */ state->btps_next =3D NULL; =20 diff --git a/src/backend/access/nbtree/nbtutils.c b/src/backend/access/nbtr= ee/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 =20 #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); } =20 -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/spgi= st/spgutils.c index 78846be..381bbd2 100644 --- a/src/backend/access/spgist/spgutils.c +++ b/src/backend/access/spgist/spgutils.c @@ -15,7 +15,7 @@ =20 #include "postgres.h" =20 -#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 =3D spgvacuumcleanup; amroutine->amcanreturn =3D spgcanreturn; amroutine->amcostestimate =3D spgcostestimate; - amroutine->amoptions =3D spgoptions; amroutine->amproperty =3D NULL; amroutine->amvalidate =3D spgvalidate; amroutine->ambeginscan =3D spgbeginscan; @@ -68,6 +67,7 @@ spghandler(PG_FUNCTION_ARGS) amroutine->amendscan =3D spgendscan; amroutine->ammarkpos =3D NULL; amroutine->amrestrpos =3D NULL; + amroutine->amrelopt_catalog =3D spggetreloptcatalog; amroutine->amestimateparallelscan =3D NULL; amroutine->aminitparallelscan =3D NULL; amroutine->amparallelrescan =3D NULL; @@ -371,8 +371,8 @@ SpGistGetBuffer(Relation index, int flags, int needSpac= e, bool *isNew) * related to the ones already on it. But fillfactor mustn't cause an * error for requests that would otherwise be legal. */ - needSpace +=3D RelationGetTargetPageFreeSpace(index, - SPGIST_DEFAULT_FILLFACTOR); + needSpace +=3D (BLCKSZ * (100 - SpGistGetFillFactor(index)) / 100); + needSpace =3D Min(needSpace, SPGIST_PAGE_CAPACITY); =20 /* Get the cache entry for this flags setting */ @@ -536,15 +536,6 @@ SpGistInitMetapage(Page page) } =20 /* - * 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, It= em item, Size size, =20 return offnum; } + + + +static options_catalog * spgist_relopt_catalog =3D NULL; + +void * +spggetreloptcatalog (void) +{ + if (! spgist_relopt_catalog) + { + spgist_relopt_catalog =3D allocateOptionsCatalog(NULL); + spgist_relopt_catalog->struct_size =3D 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 =3D InvalidOid; =20 -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 toastIndexO= id, Datum reloptions, LOCKMODE lockmode, bool check); @@ -67,23 +67,26 @@ NewHeapCreateToastTable(Oid relOid, Datum reloptions, L= OCKMODE lockmode) CheckAndCreateToastTable(relOid, reloptions, lockmode, false); } =20 -void +bool NewRelationCreateToastTable(Oid relOid, Datum reloptions) { - CheckAndCreateToastTable(relOid, reloptions, AccessExclusiveLock, false); + return CheckAndCreateToastTable(relOid, reloptions, + AccessExclusiveLock, false); } =20 -static void +static bool=20 CheckAndCreateToastTable(Oid relOid, Datum reloptions, LOCKMODE lockmode, = bool check) { Relation rel; + bool success; =20 rel =3D heap_open(relOid, lockmode); =20 /* create_toast_table does all the work */ - (void) create_toast_table(rel, InvalidOid, InvalidOid, reloptions, lockmo= de, check); + success =3D create_toast_table(rel, InvalidOid, InvalidOid, reloptions, l= ockmode, check); =20 heap_close(rel, NoLock); + return success; } =20 /* diff --git a/src/backend/commands/createas.c b/src/backend/commands/createa= s.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[] =3D HEAP_RELOPT_NAMESPACES; ObjectAddress intoRelationAddr; + List *toastDefList; + bool toast_created; =20 /* This code supports both CREATE TABLE AS and CREATE MATERIALIZED VIEW */ is_matview =3D (into->viewQuery !=3D NULL); @@ -122,15 +124,20 @@ create_ctas_internal(List *attrList, IntoClause *into) CommandCounterIncrement(); =20 /* parse and validate reloptions for the toast table */ - toast_options =3D transformRelOptions((Datum) 0, - create->options, - "toast", - validnsps, - true, false); =20 - (void) heap_reloptions(RELKIND_TOASTVALUE, toast_options, true); =20 - NewRelationCreateToastTable(intoRelationAddr.objectId, toast_options); + optionsDefListValdateNamespaces(create->options, validnsps); + toastDefList =3D optionsDefListFilterNamespaces(create->options, "toast"); + + toast_options =3D transformOptions(get_toast_relopt_catalog(), (Datum) 0, + toastDefList, 0); + + toast_created =3D 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.* st= orage parameters"))); =20 /* Create the "view" part of a materialized view. */ if (is_matview) diff --git a/src/backend/commands/foreigncmds.c b/src/backend/commands/fore= igncmds.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 =3D untransformRelOptions(oldOptions); + List *resultOptions =3D optionsTextArrayToDefList(oldOptions); ListCell *optcell; Datum result; =20 diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexc= mds.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))); =20 amcanorder =3D amRoutine->amcanorder; - amoptions =3D amRoutine->amoptions; + amoption_catalog =3D amRoutine->amrelopt_catalog; =20 pfree(amRoutine); ReleaseSysCache(tuple); @@ -539,10 +539,18 @@ DefineIndex(Oid relationId, /* * Parse AM-specific options, convert to text array form, validate. */ - reloptions =3D transformRelOptions((Datum) 0, stmt->options, - NULL, NULL, false, false); - - (void) index_reloptions(amoptions, reloptions, true); + if (amoption_catalog) + { + reloptions =3D 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))); + } =20 /* * Prepare arguments for index_create, primarily an IndexInfo structure. diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablec= mds.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 owne= rId, Datum reloptions; ListCell *listptr; AttrNumber attnum; - static char *validnsps[] =3D HEAP_RELOPT_NAMESPACES; Oid ofTypeId; ObjectAddress address; =20 @@ -583,13 +582,23 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ow= nerId, /* * Parse and validate reloptions, if any. */ - reloptions =3D transformRelOptions((Datum) 0, stmt->options, NULL, validn= sps, - true, false); - if (relkind =3D=3D RELKIND_VIEW) - (void) view_reloptions(reloptions, true); + { + reloptions =3D 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[] =3D HEAP_RELOPT_NAMESPACES; + List * heapDefList; + + optionsDefListValdateNamespaces(stmt->options, namespaces); + heapDefList =3D optionsDefListFilterNamespaces(stmt->options, NULL); + reloptions =3D transformOptions(get_heap_relopt_catalog(), + (Datum) 0, heapDefList, 0); + } =20 if (stmt->ofTypename) { @@ -3058,7 +3067,8 @@ void AlterTableInternal(Oid relid, List *cmds, bool recurse) { Relation rel; - LOCKMODE lockmode =3D AlterTableGetLockLevel(cmds); + + LOCKMODE lockmode =3D AlterTableGetLockLevel(relid, cmds); =20 rel =3D relation_open(relid, lockmode); =20 @@ -3100,13 +3110,15 @@ AlterTableInternal(Oid relid, List *cmds, bool recu= rse) * 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 =3D ShareUpdateExclusiveLock; + Relation rel; =20 foreach(lcmd, cmds) { @@ -3315,7 +3327,10 @@ AlterTableGetLockLevel(List *cmds) * getTables() */ case AT_ResetRelOptions: /* Uses MVCC in getIndexes() and * getTables() */ - cmd_lockmode =3D AlterTableGetRelOptionsLockLevel((List *) cmd->def); + rel =3D relation_open(relid, NoLock); + cmd_lockmode =3D AlterTableGetRelOptionsLockLevel(rel, + castNode(List, cmd->def)); + relation_close(rel,NoLock); break; =20 case AT_AttachPartition: @@ -4071,7 +4086,7 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqu= eue, LOCKMODE lockmode) errmsg("cannot rewrite system relation \"%s\"", RelationGetRelationName(OldHeap)))); =20 - 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 =3D SysCacheGetAttr(ATTNAME, tuple, Anum_pg_attribute_attoptions, &isnull); - newOptions =3D transformRelOptions(isnull ? (Datum) 0 : datum, - castNode(List, options), NULL, NULL, - false, isReset); - /* Validate new options */ - (void) attribute_reloptions(newOptions, true); + + newOptions =3D transformOptions(get_attribute_options_catalog(), + isnull ? (Datum) 0 : datum, + castNode(List, options), OPTIONS_PARSE_MODE_FOR_ALTER | + (isReset ? OPTIONS_PARSE_MODE_FOR_RESET : 0)); =20 /* Build new tuple. */ memset(repl_null, false, sizeof(repl_null)); @@ -9911,7 +9926,7 @@ ATExecSetRelOptions(Relation rel, List *defList, Alte= rTableType operation, Datum repl_val[Natts_pg_class]; bool repl_null[Natts_pg_class]; bool repl_repl[Natts_pg_class]; - static char *validnsps[] =3D HEAP_RELOPT_NAMESPACES; + options_parse_mode parse_mode; =20 if (defList =3D=3D NIL && operation !=3D AT_ReplaceRelOptions) return; /* nothing to do */ @@ -9940,25 +9955,52 @@ ATExecSetRelOptions(Relation rel, List *defList, Al= terTableType operation, &isnull); } =20 - /* Generate new proposed reloptions (text array) */ - newOptions =3D transformRelOptions(isnull ? (Datum) 0 : datum, - defList, NULL, validnsps, false, - operation =3D=3D AT_ResetRelOptions); - /* Validate */ + parse_mode =3D OPTIONS_PARSE_MODE_FOR_ALTER; + if (operation =3D=3D AT_ResetRelOptions) + parse_mode |=3D 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[] =3D HEAP_RELOPT_NAMESPACES; + List * heapDefList; + + optionsDefListValdateNamespaces(defList, namespaces); + heapDefList =3D optionsDefListFilterNamespaces( + defList, NULL); + newOptions =3D transformOptions(get_heap_relopt_catalog(), + isnull ? (Datum) 0 : datum, + heapDefList, parse_mode); + } break; case RELKIND_VIEW: - (void) view_reloptions(newOptions, true); + { + newOptions =3D 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 =3D 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, Alt= erTableType operation, if (rel->rd_rel->relkind =3D=3D RELKIND_VIEW) { Query *view_query =3D get_view_query(rel); - List *view_options =3D untransformRelOptions(newOptions); + List *view_options =3D optionsTextArrayToDefList(newOptions); ListCell *cell; bool check_option =3D false; =20 @@ -10032,6 +10074,8 @@ ATExecSetRelOptions(Relation rel, List *defList, Al= terTableType operation, { Relation toastrel; Oid toastid =3D rel->rd_rel->reltoastrelid; + List *toastDefList; + options_parse_mode parse_mode; =20 toastrel =3D heap_open(toastid, lockmode); =20 @@ -10055,12 +10099,15 @@ ATExecSetRelOptions(Relation rel, List *defList, = AlterTableType operation, datum =3D SysCacheGetAttr(RELOID, tuple, Anum_pg_class_reloptions, &isnull); } + parse_mode =3D OPTIONS_PARSE_MODE_FOR_ALTER; + if (operation =3D=3D AT_ResetRelOptions) + parse_mode |=3D OPTIONS_PARSE_MODE_FOR_RESET; =20 - newOptions =3D transformRelOptions(isnull ? (Datum) 0 : datum, - defList, "toast", validnsps, false, - operation =3D=3D AT_ResetRelOptions); + toastDefList =3D optionsDefListFilterNamespaces(defList, "toast"); =20 - (void) heap_reloptions(RELKIND_TOASTVALUE, newOptions, true); + newOptions =3D transformOptions(get_toast_relopt_catalog(), + isnull ? (Datum) 0 : datum, + toastDefList, parse_mode); =20 memset(repl_val, 0, sizeof(repl_val)); memset(repl_null, false, sizeof(repl_null)); @@ -10088,6 +10135,17 @@ ATExecSetRelOptions(Relation rel, List *defList, A= lterTableType operation, =20 heap_close(toastrel, NoLock); } + else + { + List *toastDefList; + toastDefList =3D optionsDefListFilterNamespaces(defList, "toast"); + if (toastDefList) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("TOAST relation for this table does not exist. Can't change toa= st.* storage parameters"))); + } + } =20 heap_close(pgclass, RowExclusiveLock); } diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/table= space.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] =3D true; =20 /* Generate new proposed spcoptions (text array) */ - newOptions =3D transformRelOptions((Datum) 0, - stmt->options, - NULL, NULL, false, false); - (void) tablespace_reloptions(newOptions, true); + + newOptions =3D transformOptions( + get_tablespace_options_catalog(), + (Datum) 0, stmt->options, 0); + if (newOptions !=3D (Datum) 0) values[Anum_pg_tablespace_spcoptions - 1] =3D newOptions; else @@ -1024,10 +1025,13 @@ AlterTableSpaceOptions(AlterTableSpaceOptionsStmt *= stmt) /* Generate new proposed spcoptions (text array) */ datum =3D heap_getattr(tup, Anum_pg_tablespace_spcoptions, RelationGetDescr(rel), &isnull); - newOptions =3D transformRelOptions(isnull ? (Datum) 0 : datum, - stmt->options, NULL, NULL, false, - stmt->isReset); - (void) tablespace_reloptions(newOptions, true); + + + newOptions =3D transformOptions(get_tablespace_options_catalog(), + isnull ? (Datum) 0 : datum, + stmt->options, + OPTIONS_PARSE_MODE_FOR_ALTER | + (stmt->isReset? OPTIONS_PARSE_MODE_FOR_RESET : 0)); =20 /* 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 =3D NIL; else - fdw->options =3D untransformRelOptions(datum); + fdw->options =3D optionsTextArrayToDefList(datum); =20 ReleaseSysCache(tp); =20 @@ -133,7 +133,7 @@ GetForeignServer(Oid serverid) if (isnull) server->options =3D NIL; else - server->options =3D untransformRelOptions(datum); + server->options =3D optionsTextArrayToDefList(datum); =20 ReleaseSysCache(tp); =20 @@ -201,7 +201,7 @@ GetUserMapping(Oid userid, Oid serverid) if (isnull) um->options =3D NIL; else - um->options =3D untransformRelOptions(datum); + um->options =3D optionsTextArrayToDefList(datum); =20 ReleaseSysCache(tp); =20 @@ -238,7 +238,7 @@ GetForeignTable(Oid relid) if (isnull) ft->options =3D NIL; else - ft->options =3D untransformRelOptions(datum); + ft->options =3D optionsTextArrayToDefList(datum); =20 ReleaseSysCache(tp); =20 @@ -271,7 +271,7 @@ GetForeignColumnOptions(Oid relid, AttrNumber attnum) if (isnull) options =3D NIL; else - options =3D untransformRelOptions(datum); + options =3D optionsTextArrayToDefList(datum); =20 ReleaseSysCache(tp); =20 @@ -540,7 +540,7 @@ pg_options_to_table(PG_FUNCTION_ARGS) Datum array =3D PG_GETARG_DATUM(0); =20 deflist_to_tuplestore((ReturnSetInfo *) fcinfo->resultinfo, - untransformRelOptions(array)); + optionsTextArrayToDefList(array)); =20 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 =3D untransformRelOptions(PG_GETARG_DATUM(0)); + List *options_list =3D optionsTextArrayToDefList(PG_GETARG_DATUM(0)); Oid catalog =3D PG_GETARG_OID(1); =20 ListCell *cell; diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/u= til/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 relationObject= Id, bool inhparent, &rel->pages, &rel->tuples, &rel->allvisfrac); =20 /* Retrieve the parallel_workers reloption, or -1 if not set. */ - rel->rel_parallel_workers =3D RelationGetParallelWorkers(relation, -1); + rel->rel_parallel_workers =3D HeapGetParallelWorkers(relation, -1); =20 /* * 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_c= lause.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)))); =20 /* 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 catalo= g 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, Relat= ion source_idx, datum =3D SysCacheGetAttr(RELOID, ht_idxrel, Anum_pg_class_reloptions, &isnull); if (!isnull) - index->options =3D untransformRelOptions(datum); + index->options =3D optionsTextArrayToDefList(datum); =20 /* If it's a partial index, decompile and append the predicate */ datum =3D SysCacheGetAttr(INDEXRELID, ht_idx, diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/a= utovacuum.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_clas= s_desc) { bytea *relopts; AutoVacOpts *av; + AutoVacOpts *src; =20 Assert(((Form_pg_class) GETSTRUCT(tup))->relkind =3D=3D RELKIND_RELATION = || ((Form_pg_class) GETSTRUCT(tup))->relkind =3D=3D RELKIND_MATVIEW || @@ -2519,8 +2520,13 @@ extract_autovac_opts(HeapTuple tup, TupleDesc pg_cla= ss_desc) if (relopts =3D=3D NULL) return NULL; =20 + if (((Form_pg_class) GETSTRUCT(tup))->relkind =3D=3D RELKIND_TOASTVALUE) + src =3D &(((ToastOptions *) relopts)->autovacuum); + else + src =3D &(((HeapOptions *) relopts)->autovacuum); + av =3D palloc(sizeof(AutoVacOpts)); - memcpy(av, &(((StdRdOptions *) relopts)->autovacuum), sizeof(AutoVacOpts)= ); + memcpy(av, src, sizeof(AutoVacOpts)); pfree(relopts); =20 return av; diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rew= riteHandler.c index d3e44fb..a677923 100644 --- a/src/backend/rewrite/rewriteHandler.c +++ b/src/backend/rewrite/rewriteHandler.c @@ -1484,7 +1484,7 @@ ApplyRetrieveRule(Query *parsetree, =20 rte->rtekind =3D RTE_SUBQUERY; rte->relid =3D InvalidOid; - rte->security_barrier =3D RelationIsSecurityView(relation); + rte->security_barrier =3D ViewIsSecurityView(relation); rte->subquery =3D rule_action; rte->inh =3D false; /* must not be set for a subquery */ =20 @@ -2988,7 +2988,7 @@ rewriteTargetView(Query *parsetree, Relation view) =20 ChangeVarNodes(viewqual, base_rt_index, new_rt_index, 0); =20 - 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 !=3D CMD_DELETE) { - bool has_wco =3D RelationHasCheckOption(view); - bool cascaded =3D RelationHasCascadedCheckOption(view); + + bool has_wco =3D ViewHasCheckOption(view); + bool cascaded =3D ViewHasCascadedCheckOption(view); =20 /* * 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[] =3D HEAP_RELOPT_NAMESPACES; + List *toastDefList; + bool success; =20 /* Create the table itself */ address =3D DefineRelation((CreateStmt *) stmt, @@ -1016,18 +1018,24 @@ ProcessUtilitySlow(ParseState *pstate, * parse and validate reloptions for the toast * table */ - toast_options =3D transformRelOptions((Datum) 0, - ((CreateStmt *) stmt)->options, - "toast", - validnsps, - true, - false); - (void) heap_reloptions(RELKIND_TOASTVALUE, - toast_options, - true); =20 - NewRelationCreateToastTable(address.objectId, - toast_options); + optionsDefListValdateNamespaces( + ((CreateStmt *) stmt)->options, + validnsps); + + toastDefList =3D optionsDefListFilterNamespaces( + ((CreateStmt *) stmt)->options, "toast"); + + toast_options =3D transformOptions( + get_toast_relopt_catalog(), (Datum) 0, + toastDefList, 0); + + success =3D 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 =3D AlterTableGetLockLevel(atstmt->cmds); - relid =3D AlterTableLookupRelation(atstmt, lockmode); + + relid =3D AlterTableLookupRelation(atstmt, NoLock); + if (OidIsValid(relid)) + { + lockmode =3D AlterTableGetLockLevel(relid, atstmt->cmds); + relid =3D AlterTableLookupRelation(atstmt, lockmode); + } =20 if (OidIsValid(relid)) { diff --git a/src/backend/utils/cache/attoptcache.c b/src/backend/utils/cach= e/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 =3D NULL; else { - bytea *bytea_opts =3D attribute_reloptions(datum, false); + bytea *bytea_opts; + + bytea_opts =3D optionsTextArrayToBytea( + get_attribute_options_catalog(), datum); =20 opts =3D MemoryContextAlloc(CacheMemoryContext, VARSIZE(bytea_opts)); diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/r= elcache.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 tu= ple) options =3D extractRelOptions(tuple, GetPgClassDescriptor(), relation->rd_rel->relkind =3D=3D RELKIND_INDEX ? - relation->rd_amroutine->amoptions : NULL); + relation->rd_amroutine->amrelopt_catalog : NULL + ); =20 /* * Copy parsed data into CacheMemoryContext. To guard against the diff --git a/src/backend/utils/cache/spccache.c b/src/backend/utils/cache/s= pccache.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 =3D NULL; else { - bytea *bytea_opts =3D tablespace_reloptions(datum, false); + bytea *bytea_opts; + bytea_opts =3D optionsTextArrayToBytea( + get_tablespace_options_catalog(), datum); =20 opts =3D 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 =3D 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 PlannerI= nfo *root, Selectivity *indexSelectivity, double *indexCorrelation); =20 -/* 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); =20 +/* 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 */ =20 /* 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; =20 #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_i= nternal.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(IndexVacuu= mInfo *info, void *callback_state); extern IndexBulkDeleteResult *brinvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats); -extern bytea *brinoptions(Datum reloptions, bool validate); =20 /* brin_validate.c */ extern bool brinvalidate(Oid opclassoid); diff --git a/src/include/access/gin_private.h b/src/include/access/gin_priv= ate.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 =20 =20 /* 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, Off= setNumber attnum, extern OffsetNumber gintuple_get_attrnum(GinState *ginstate, IndexTuple tu= ple); extern Datum gintuple_get_key(GinState *ginstate, IndexTuple tuple, GinNullCategory *category); +extern void *gingetreloptcatalog (void); =20 /* gininsert.c */ extern IndexBuildResult *ginbuild(Relation heap, Relation index, diff --git a/src/include/access/gist_private.h b/src/include/access/gist_pr= ivate.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; =20 + +/* + * 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 =3D 0, + GIST_OPTION_BUFFERING_OFF =3D 1, + GIST_OPTION_BUFFERING_AUTO =3D 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; =20 /* gist.c */ @@ -486,7 +504,6 @@ extern bool gistvalidate(Oid opclassoid); #define GIST_MIN_FILLFACTOR 10 #define GIST_DEFAULT_FILLFACTOR 90 =20 -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); =20 extern XLogRecPtr gistGetFakeLSN(Relation rel); +extern void * gistgetreloptcatalog (void); =20 /* gistvacuum.c */ extern IndexBulkDeleteResult *gistbulkdelete(IndexVacuumInfo *info, @@ -554,7 +572,6 @@ extern void gistSplitByKey(Relation r, Page page, Index= Tuple *itup, /* gistbuild.c */ extern IndexBuildResult *gistbuild(Relation heap, Relation index, struct IndexInfo *indexInfo); -extern void gistValidateBufferingOption(char *value); =20 /* gistbuildbuffers.c */ extern GISTBuildBuffers *gistInitBuildBuffers(int pagesPerBuffer, int leve= lStep, 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 =20 typedef HashMetaPageData *HashMetaPage; =20 + +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(IndexVacuu= mInfo *info, void *callback_state); extern IndexBulkDeleteResult *hashvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats); -extern bytea *hashoptions(Datum reloptions, bool validate); extern bool hashvalidate(Oid opclassoid); =20 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); =20 +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) =20 +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 =3D (1 << 0), /* Altering this option= is forbidden */ + OPTION_DEFINITION_FLAG_IGNORE =3D (1 << 1), /* Skip this option while par= sing. Used for + WITH OIDS special case */ + OPTION_DEFINITION_FLAG_REJECT =3D (1 << 2) /* Option will be rejected wh= en 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 =3D (1 << 0), + OPTIONS_PARSE_MODE_FOR_ALTER =3D (1 << 1), + OPTIONS_PARSE_MODE_FOR_RESET =3D (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 f= or + 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 valid= ate); + +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 *nam= e, + 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 *nam= e, + 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 *n= ame, + 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 representa= tion + * "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 =3D=3D 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 *cat= alog, + options_parse_mode mode); +extern bytea *optionsValuesToBytea(List * options, options_catalog *catalo= g); + +/* + * 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/reloption= s.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 =20 #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" =20 -/* kinds supported by reloptions */ -typedef enum relopt_kind -{ - RELOPT_KIND_HEAP =3D (1 << 0), - RELOPT_KIND_TOAST =3D (1 << 1), - RELOPT_KIND_BTREE =3D (1 << 2), - RELOPT_KIND_HASH =3D (1 << 3), - RELOPT_KIND_GIN =3D (1 << 4), - RELOPT_KIND_GIST =3D (1 << 5), - RELOPT_KIND_ATTRIBUTE =3D (1 << 6), - RELOPT_KIND_TABLESPACE =3D (1 << 7), - RELOPT_KIND_SPGIST =3D (1 << 8), - RELOPT_KIND_VIEW =3D (1 << 9), - RELOPT_KIND_BRIN =3D (1 << 10), - /* if you add a new kind, make sure you update "last_default" too */ - RELOPT_KIND_LAST_DEFAULT =3D RELOPT_KIND_BRIN, - /* some compilers treat enums as signed ints, so we can't use 1 << 31 */ - RELOPT_KIND_MAX =3D (1 << 30) -} relopt_kind; =20 /* reloption namespaces allowed for heaps -- currently only TOAST */ #define HEAP_RELOPT_NAMESPACES { "toast", NULL } =20 -/* 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 consid= er - * 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 returne= d by - * parseRelOptions: - * for (i =3D 0; options[i].gen->name; i++) - * { - * if (HAVE_RELOPTION("fillfactor", options[i]) - * { - * HANDLE_INT_RELOPTION("fillfactor", rdopts->fillfactor, options[i], &i= sset); - * 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 on= ly - * 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) =3D= =3D 0) - -#define HANDLE_INT_RELOPTION(optname, var, option, wasset) \ - do { \ - if (option.isset) \ - var =3D option.values.int_val; \ - else \ - var =3D ((relopt_int *) option.gen)->default_val; \ - (wasset) !=3D NULL ? *(wasset) =3D option.isset : (dummyret)NULL; \ - } while (0) - -#define HANDLE_BOOL_RELOPTION(optname, var, option, wasset) \ - do { \ - if (option.isset) \ - var =3D option.values.bool_val; \ - else \ - var =3D ((relopt_bool *) option.gen)->default_val; \ - (wasset) !=3D NULL ? *(wasset) =3D option.isset : (dummyret) NULL; \ - } while (0) =20 -#define HANDLE_REAL_RELOPTION(optname, var, option, wasset) \ - do { \ - if (option.isset) \ - var =3D option.values.real_val; \ - else \ - var =3D ((relopt_real *) option.gen)->default_val; \ - (wasset) !=3D NULL ? *(wasset) =3D option.isset : (dummyret) NULL; \ - } while (0) - -/* - * Note that this assumes that the variable is already allocated at the ta= il of - * reloptions structure (StdRdOptions or equivalent). - * - * "base" is a pointer to the reloptions structure, and "offset" is an int= eger - * variable that must be initialized to sizeof(reloptions structure). This - * struct must have been allocated with enough space to hold any string op= tion - * 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 =3D (relopt_string *) option.gen;\ - char *string_val; \ - if (option.isset) \ - string_val =3D option.values.string_val; \ - else if (!optstring->default_isnull) \ - string_val =3D optstring->default_val; \ - else \ - string_val =3D NULL; \ - (wasset) !=3D NULL ? *(wasset) =3D option.isset : (dummyret) NULL; \ - if (string_val =3D=3D NULL) \ - var =3D 0; \ - else \ - { \ - strcpy(((char *)(base)) + (offset), string_val); \ - var =3D (offset); \ - (offset) +=3D 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); =20 /* - * For use by code reading options already parsed: get a pointer to the st= ring - * value itself. "optstruct" is the StdRdOption struct or equivalent, "me= mber" - * 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 =3D=3D 0 ? NULL : \ - (char *)(optstruct) + (optstruct)->member) =20 +extern options_catalog *get_heap_relopt_catalog(void); +extern options_catalog *get_toast_relopt_catalog(void); =20 -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 validat= e); -extern bytea *view_reloptions(Datum reloptions, bool validate); -extern bytea *index_reloptions(amoptions_function amoptions, Datum relopti= ons, - 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 *defLi= st); =20 #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" =20 =20 -/* 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 =20 =20 /* spgutils.c */ -extern bytea *spgoptions(Datum reloptions, bool validate); +extern Datum spghandler(PG_FUNCTION_ARGS); =20 /* spginsert.c */ extern IndexBuildResult *spgbuild(Relation heap, Relation index, diff --git a/src/include/access/spgist_private.h b/src/include/access/spgis= t_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" =20 =20 +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) =20 /* 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 *s= tate, Page page, Item item, Size size, OffsetNumber *startOffset, bool errorOK); +extern void * spggetreloptcatalog(void); =20 /* 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/tablec= mds.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); =20 extern void AlterTable(Oid relid, LOCKMODE lockmode, AlterTableStmt *stmt); =20 -extern LOCKMODE AlterTableGetLockLevel(List *cmds); +extern LOCKMODE AlterTableGetLockLevel(Oid relid, List *cmds); =20 extern void ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recurs= ing, LOCKMODE lockmode); =20 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; =20 -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; =20 #define HEAP_MIN_FILLFACTOR 10 #define HEAP_DEFAULT_FILLFACTOR 100 =20 +#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)) =20 /* - * 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) =20 /* - * 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 =3D=3D RELKIND_RELATION || \ (relation)->rd_rel->relkind =3D=3D RELKIND_MATVIEW) ? \ - ((StdRdOptions *) (relation)->rd_options)->user_catalog_table : false) + ((HeapOptions *) (relation)->rd_options)->user_catalog_table : false) =20 /* - * 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)) =20 +#define ToastGetTargetPageFreeSpace() \ + (BLCKSZ * (100 - TOAST_DEFAULT_FILLFACTOR) / 100) =20 /* * 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 =3D -1, + VIEW_OPTION_CHECK_OPTION_LOCAL =3D 0, + VIEW_OPTION_CHECK_OPTION_CASCADED =3D 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) =20 /* - * 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 !=3D 0) =20 -/* - * 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 !=3D 0 ? \ - strcmp((char *) (relation)->rd_options + \ - ((ViewOptions *) (relation)->rd_options)->check_option_offset, \ - "local") =3D=3D 0 : false) +#define ViewHasCheckOption(relation) \ +((relation)->rd_options && \ + ((ViewOptions *) (relation)->rd_options)->check_option !=3D = \ + VIEW_OPTION_CHECK_OPTION_NOT_SET) =20 /* - * 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 !=3D 0 ? \ - strcmp((char *) (relation)->rd_options + \ - ((ViewOptions *) (relation)->rd_options)->check_option_offset, \ - "cascaded") =3D=3D 0 : false) =20 +#define ViewHasCascadedCheckOption(relation) \ +((relation)->rd_options && \ + ((ViewOptions *) (relation)->rd_options)->check_option =3D=3D = \ + VIEW_OPTION_CHECK_OPTION_CASCADED) =20 /* * RelationIsValid @@ -556,7 +564,7 @@ typedef struct ViewOptions #define RelationIsAccessibleInLogicalDecoding(relation) \ (XLogLogicalInfoActive() && \ RelationNeedsWAL(relation) && \ - (IsCatalogRelation(relation) || RelationIsUsedAsCatalogTable(relation))) + (IsCatalogRelation(relation) || HeapIsUsedAsCatalogTable(relation))) =20 /* * RelationIsLogicallyLogged diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/e= xpected/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 =3D 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=3Doff); 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 =3D 1, n_distinct_inherited= =3D 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) =20 +-- Check that changing reloptions for brin index is not allowed +ALTER INDEX brinidx SET (pages_per_range =3D 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=3D60); 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 int= 4_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 =3D 1; +-- Test buffering and fillfactor reloption takes valid values +create index gist_pointidx2 on gist_point_tbl using gist(p) with (bufferin= g =3D on, fillfactor=3D50); +create index gist_pointidx3 on gist_point_tbl using gist(p) with (bufferin= g =3D off); +create index gist_pointidx4 on gist_point_tbl using gist(p) with (bufferin= g =3D auto); +--Test buffering and fillfactor for refising invalid values +create index gist_pointidx5 on gist_point_tbl using gist(p) with (bufferin= g =3D 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 (fillfact= or=3D9); +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 (fillfact= or=3D101); +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/ex= pected/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=3D9); +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=3D101); +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=3D99); +ERROR: changing parameter "fillfactor" is not allowed diff --git a/src/test/regress/expected/reloptions.out b/src/test/regress/ex= pected/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=3D30,autovacuum_enabl= ed =3D false, autovacuum_analyze_scale_factor =3D 0.2); +SELECT reloptions FROM pg_class WHERE oid =3D 'reloptions_test'::regclass; + reloptions=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20 +--------------------------------------------------------------------------= ---- + {fillfactor=3D30,autovacuum_enabled=3Dfalse,autovacuum_analyze_scale_fact= or=3D0.2} +(1 row) + +-- Test =D1=81ase insensitive +CREATE TABLE reloptions_test3(i INT) WITH (FiLlFaCtoR=3D40); +SELECT reloptions FROM pg_class WHERE oid =3D 'reloptions_test3'::regclass; + reloptions=20=20=20=20 +----------------- + {fillfactor=3D40} +(1 row) + +-- Fail on min/max values check +CREATE TABLE reloptions_test2(i INT) WITH (fillfactor=3D2); +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=3D110); +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= =3D -10.0); +ERROR: value -10.0 out of bounds for option "autovacuum_analyze_scale_fac= tor" +DETAIL: Valid values are between "0.000000" and "100.000000". +CREATE TABLE reloptions_test2(i INT) WITH (autovacuum_analyze_scale_factor= =3D 110.0); +ERROR: value 110.0 out of bounds for option "autovacuum_analyze_scale_fac= tor" +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=3D2); +ERROR: unrecognized parameter "not_existing_option" +CREATE TABLE reloptions_test2(i INT) WITH (not_existing_namespace.fillfact= or=3D2); +ERROR: unrecognized parameter namespace "not_existing_namespace" +-- Fail while setting unproper value +CREATE TABLE reloptions_test2(i INT) WITH (fillfactor=3D30.5); +ERROR: invalid value for integer option "fillfactor": 30.5 +CREATE TABLE reloptions_test2(i INT) WITH (fillfactor=3D'string'); +ERROR: invalid value for integer option "fillfactor": string +CREATE TABLE reloptions_test2(i INT) WITH (fillfactor=3Dtrue); +ERROR: invalid value for integer option "fillfactor": true +CREATE TABLE reloptions_test2(i INT) WITH (autovacuum_enabled=3D12); +ERROR: invalid value for boolean option "autovacuum_enabled": 12 +CREATE TABLE reloptions_test2(i INT) WITH (autovacuum_enabled=3D30.5); +ERROR: invalid value for boolean option "autovacuum_enabled": 30.5 +CREATE TABLE reloptions_test2(i INT) WITH (autovacuum_enabled=3D'string'); +ERROR: invalid value for boolean option "autovacuum_enabled": string +CREATE TABLE reloptions_test2(i INT) WITH (autovacuum_analyze_scale_factor= =3D'string'); +ERROR: invalid value for floating point option "autovacuum_analyze_scale_= factor": string +CREATE TABLE reloptions_test2(i INT) WITH (autovacuum_analyze_scale_factor= =3Dtrue); +ERROR: invalid value for floating point option "autovacuum_analyze_scale_= factor": true +-- Specifing name only should fail as if there was =3Dture after it +CREATE TABLE reloptions_test2(i INT) WITH (fillfactor);=20 +ERROR: invalid value for integer option "fillfactor": true +-- Simple ALTER TABLE +ALTER TABLE reloptions_test SET (fillfactor=3D31, autovacuum_analyze_scale= _factor =3D 0.3); +SELECT reloptions FROM pg_class WHERE oid =3D 'reloptions_test'::regclass; + reloptions=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20 +--------------------------------------------------------------------------= ---- + {autovacuum_enabled=3Dfalse,fillfactor=3D31,autovacuum_analyze_scale_fact= or=3D0.3} +(1 row) + +-- Check that we cat set boolean option to true just by mentioning it +ALTER TABLE reloptions_test SET (autovacuum_enabled, fillfactor=3D32); +SELECT reloptions FROM pg_class WHERE oid =3D 'reloptions_test'::regclass; + reloptions=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20 +--------------------------------------------------------------------------= --- + {autovacuum_analyze_scale_factor=3D0.3,autovacuum_enabled=3Dtrue,fillfact= or=3D32} +(1 row) + +-- Check that RESET works well +ALTER TABLE reloptions_test RESET (fillfactor); +SELECT reloptions FROM pg_class WHERE oid =3D 'reloptions_test'::regclass; + reloptions=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20 +--------------------------------------------------------------- + {autovacuum_analyze_scale_factor=3D0.3,autovacuum_enabled=3Dtrue} +(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 =3D 'reloptions_test'::regclass = AND reloptions IS NULL; + reloptions=20 +------------ +=20 +(1 row) + +-- Check RESET fails on att=3Dvalue +ALTER TABLE reloptions_test RESET (fillfactor=3D12); +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=3D20, oids=3Dtrue); +SELECT reloptions FROM pg_class WHERE oid =3D 'reloptions_test'::regclass; + reloptions=20=20=20=20 +----------------- + {fillfactor=3D20} +(1 row) + +-- Now testing toast.* options +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test (s VARCHAR) WITH (toast.autovacuum_vacuum_cos= t_delay =3D 23 ); +SELECT reloptions FROM pg_class WHERE oid =3D (SELECT reltoastrelid FROM p= g_class WHERE oid =3D 'reloptions_test'::regclass); + reloptions=20=20=20=20=20=20=20=20=20=20=20=20=20 +----------------------------------- + {autovacuum_vacuum_cost_delay=3D23} +(1 row) + +ALTER TABLE reloptions_test SET (toast.autovacuum_vacuum_cost_delay =3D 24= ); +SELECT reloptions FROM pg_class WHERE oid =3D (SELECT reltoastrelid FROM p= g_class WHERE oid =3D 'reloptions_test'::regclass); + reloptions=20=20=20=20=20=20=20=20=20=20=20=20=20 +----------------------------------- + {autovacuum_vacuum_cost_delay=3D24} +(1 row) + +ALTER TABLE reloptions_test RESET (toast.autovacuum_vacuum_cost_delay); +SELECT reloptions FROM pg_class WHERE oid =3D (SELECT reltoastrelid FROM p= g_class WHERE oid =3D 'reloptions_test'::regclass); + reloptions=20 +------------ +=20 +(1 row) + +-- Fail on unexisting options in toast namespace +CREATE TABLE reloptions_test2 (i int) WITH (toast.not_existing_option =3D = 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_d= elay =3D 23 ); +ERROR: no TOAST relation was created for a table. Can't set toast.* stora= ge parameters +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT); +ALTER TABLE reloptions_test SET (toast.autovacuum_vacuum_cost_delay =3D 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_fa= ctor=3D1, autovacuum_analyze_threshold=3D1); +CREATE TABLE reloptions_test2 (s VARCHAR) WITH (toast.autovacuum_analyze_s= cale_factor=3D1); +ERROR: unrecognized parameter "toast.autovacuum_analyze_scale_factor" +CREATE TABLE reloptions_test2 (s VARCHAR) WITH (toast.autovacuum_analyze_t= hreshold=3D1); +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_cos= t_delay =3D 23, autovacuum_vacuum_cost_delay =3D 24, fillfactor =3D 40); +SELECT reloptions FROM pg_class WHERE oid =3D 'reloptions_test'::regclass; + reloptions=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20 +------------------------------------------------- + {autovacuum_vacuum_cost_delay=3D24,fillfactor=3D40} +(1 row) + +SELECT reloptions FROM pg_class WHERE oid =3D (SELECT reltoastrelid FROM p= g_class WHERE oid =3D 'reloptions_test'::regclass); + reloptions=20=20=20=20=20=20=20=20=20=20=20=20=20 +----------------------------------- + {autovacuum_vacuum_cost_delay=3D23} +(1 row) + +-- Same FOR CREATE and ALTER INDEX for btree indexes +CREATE INDEX reloptions_test_idx ON reloptions_test (s) WITH (fillfactor= =3D30); +SELECT reloptions FROM pg_class WHERE oid =3D 'reloptions_test_idx'::regcl= ass; + reloptions=20=20=20=20 +----------------- + {fillfactor=3D30} +(1 row) + +-- Fail when option and namespase do not exist +CREATE INDEX reloptions_test_idx ON reloptions_test (s) WITH (not_existing= _option=3D2); +ERROR: unrecognized parameter "not_existing_option" +CREATE INDEX reloptions_test_idx ON reloptions_test (s) WITH (not_existing= _option.fillfactor=3D2); +ERROR: unrecognized parameter namespace "not_existing_option" +-- Check ranges +CREATE INDEX reloptions_test_idx2 ON reloptions_test (s) WITH (fillfactor= =3D1); +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= =3D130); +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=3D40); +SELECT reloptions FROM pg_class WHERE oid =3D 'reloptions_test_idx'::regcl= ass; + reloptions=20=20=20=20 +----------------- + {fillfactor=3D40} +(1 row) + +-- Check alter on empty relop list +CREATE INDEX reloptions_test_idx3 ON reloptions_test (s); +ALTER INDEX reloptions_test_idx3 SET (fillfactor=3D40); +SELECT reloptions FROM pg_class WHERE oid =3D 'reloptions_test_idx3'::regc= lass; + reloptions=20=20=20=20 +----------------- + {fillfactor=3D40} +(1 row) + diff --git a/src/test/regress/expected/spgist.out b/src/test/regress/expect= ed/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 (fi= llfactor =3D 75); -- Test vacuum-root operation. It gets invoked when the root is also a lea= f, -- 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 (f= illfactor =3D 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 (f= illfactor =3D 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 =3D 76); diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regre= ss/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; =20 ALTER VIEW rw_view1 SET (check_option=3Dhere); -- 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=3Dlocal); 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/in= put/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@'; =20 -- try setting and resetting some properties for the new tablespace -ALTER TABLESPACE regress_tblspace SET (random_page_cost =3D 1.0); +ALTER TABLESPACE regress_tblspace SET (random_page_cost =3D 1.0, seq_page_= cost =3D 1.1); ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter =3D true= ); -- fail ALTER TABLESPACE regress_tblspace RESET (random_page_cost =3D 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_co= ncurrency); -- ok =20 -- create a schema we can use CREATE SCHEMA testschema; diff --git a/src/test/regress/output/tablespace.source b/src/test/regress/o= utput/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 =3D 1.0); +ALTER TABLESPACE regress_tblspace SET (random_page_cost =3D 1.0, seq_page_= cost =3D 1.1); ALTER TABLESPACE regress_tblspace SET (some_nonexistent_parameter =3D true= ); -- fail ERROR: unrecognized parameter "some_nonexistent_parameter" ALTER TABLESPACE regress_tblspace RESET (random_page_cost =3D 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_co= ncurrency); -- 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 depen= dency 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 returni= ng largeobject with xml +test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without= _oid conversion truncate alter_table sequence polymorphism rowtypes returni= ng largeobject with xml reloptions =20 # 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_sch= edule 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/al= ter_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); =20 -- 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 =3D 1, n_distinct_inherited= =3D 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 =3D '' 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 in= dex 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 =3D 10); +ALTER INDEX brinidx RESET (pages_per_range); diff --git a/src/test/regress/sql/create_index.sql b/src/test/regress/sql/c= reate_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 ha= sh (random name_ops); =20 CREATE INDEX hash_txt_index ON hash_txt_heap USING hash (random text_ops); =20 -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=3D60); =20 CREATE UNLOGGED TABLE unlogged_hash_table (id int4); CREATE INDEX unlogged_hash_index ON unlogged_hash_table USING hash (id int= 4_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_ser= ies(1, 10000) g; -- To test vacuum, delete some entries from all over the index. delete from gist_point_tbl where id % 2 =3D 1; =20 +-- Test buffering and fillfactor reloption takes valid values +create index gist_pointidx2 on gist_point_tbl using gist(p) with (bufferin= g =3D on, fillfactor=3D50); +create index gist_pointidx3 on gist_point_tbl using gist(p) with (bufferin= g =3D off); +create index gist_pointidx4 on gist_point_tbl using gist(p) with (bufferin= g =3D auto); +--Test buffering and fillfactor for refising invalid values +create index gist_pointidx5 on gist_point_tbl using gist(p) with (bufferin= g =3D invalid_value); +create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfact= or=3D9); +create index gist_pointidx5 on gist_point_tbl using gist(p) with (fillfact= or=3D101); + + + -- 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/has= h_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=3D9); +CREATE INDEX hash_f8_index2 ON hash_f8_heap USING hash (random float8_ops)= WITH (fillfactor=3D101); +ALTER INDEX hash_f8_index SET (fillfactor=3D99); \ No newline at end of file diff --git a/src/test/regress/sql/reloptions.sql b/src/test/regress/sql/rel= options.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=3D30,autovacuum_enabl= ed =3D false, autovacuum_analyze_scale_factor =3D 0.2); + +SELECT reloptions FROM pg_class WHERE oid =3D 'reloptions_test'::regclass; + +-- Test =D1=81ase insensitive +CREATE TABLE reloptions_test3(i INT) WITH (FiLlFaCtoR=3D40); + +SELECT reloptions FROM pg_class WHERE oid =3D 'reloptions_test3'::regclass; + +-- Fail on min/max values check +CREATE TABLE reloptions_test2(i INT) WITH (fillfactor=3D2); + +CREATE TABLE reloptions_test2(i INT) WITH (fillfactor=3D110); + +CREATE TABLE reloptions_test2(i INT) WITH (autovacuum_analyze_scale_factor= =3D -10.0); + +CREATE TABLE reloptions_test2(i INT) WITH (autovacuum_analyze_scale_factor= =3D 110.0); + +-- Fail when option and namespase do not exist + +CREATE TABLE reloptions_test2(i INT) WITH (not_existing_option=3D2); + +CREATE TABLE reloptions_test2(i INT) WITH (not_existing_namespace.fillfact= or=3D2); + +-- Fail while setting unproper value + +CREATE TABLE reloptions_test2(i INT) WITH (fillfactor=3D30.5); + +CREATE TABLE reloptions_test2(i INT) WITH (fillfactor=3D'string'); + +CREATE TABLE reloptions_test2(i INT) WITH (fillfactor=3Dtrue); + +CREATE TABLE reloptions_test2(i INT) WITH (autovacuum_enabled=3D12); + +CREATE TABLE reloptions_test2(i INT) WITH (autovacuum_enabled=3D30.5); + +CREATE TABLE reloptions_test2(i INT) WITH (autovacuum_enabled=3D'string'); + +CREATE TABLE reloptions_test2(i INT) WITH (autovacuum_analyze_scale_factor= =3D'string'); + +CREATE TABLE reloptions_test2(i INT) WITH (autovacuum_analyze_scale_factor= =3Dtrue); + +-- Specifing name only should fail as if there was =3Dture after it +CREATE TABLE reloptions_test2(i INT) WITH (fillfactor);=20 + +-- Simple ALTER TABLE + +ALTER TABLE reloptions_test SET (fillfactor=3D31, autovacuum_analyze_scale= _factor =3D 0.3); + +SELECT reloptions FROM pg_class WHERE oid =3D 'reloptions_test'::regclass; + +-- Check that we cat set boolean option to true just by mentioning it + +ALTER TABLE reloptions_test SET (autovacuum_enabled, fillfactor=3D32); + +SELECT reloptions FROM pg_class WHERE oid =3D 'reloptions_test'::regclass; + +-- Check that RESET works well + +ALTER TABLE reloptions_test RESET (fillfactor); + +SELECT reloptions FROM pg_class WHERE oid =3D '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 =3D 'reloptions_test'::regclass = AND reloptions IS NULL; + +-- Check RESET fails on att=3Dvalue + +ALTER TABLE reloptions_test RESET (fillfactor=3D12); + +-- Check oids options is ignored +DROP TABLE reloptions_test; +CREATE TABLE reloptions_test(i INT) WITH (fillfactor=3D20, oids=3Dtrue); +SELECT reloptions FROM pg_class WHERE oid =3D 'reloptions_test'::regclass; + +-- Now testing toast.* options +DROP TABLE reloptions_test; + +CREATE TABLE reloptions_test (s VARCHAR) WITH (toast.autovacuum_vacuum_cos= t_delay =3D 23 ); + +SELECT reloptions FROM pg_class WHERE oid =3D (SELECT reltoastrelid FROM p= g_class WHERE oid =3D 'reloptions_test'::regclass); + +ALTER TABLE reloptions_test SET (toast.autovacuum_vacuum_cost_delay =3D 24= ); + +SELECT reloptions FROM pg_class WHERE oid =3D (SELECT reltoastrelid FROM p= g_class WHERE oid =3D 'reloptions_test'::regclass); + +ALTER TABLE reloptions_test RESET (toast.autovacuum_vacuum_cost_delay); + +SELECT reloptions FROM pg_class WHERE oid =3D (SELECT reltoastrelid FROM p= g_class WHERE oid =3D 'reloptions_test'::regclass); + +-- Fail on unexisting options in toast namespace +CREATE TABLE reloptions_test2 (i int) WITH (toast.not_existing_option =3D = 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_d= elay =3D 23 ); +DROP TABLE reloptions_test; + +CREATE TABLE reloptions_test(i INT); +ALTER TABLE reloptions_test SET (toast.autovacuum_vacuum_cost_delay =3D 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_fa= ctor=3D1, autovacuum_analyze_threshold=3D1); + +CREATE TABLE reloptions_test2 (s VARCHAR) WITH (toast.autovacuum_analyze_s= cale_factor=3D1); +CREATE TABLE reloptions_test2 (s VARCHAR) WITH (toast.autovacuum_analyze_t= hreshold=3D1); + +-- And now mixed toast + heap +DROP TABLE reloptions_test; + +CREATE TABLE reloptions_test (s VARCHAR) WITH (toast.autovacuum_vacuum_cos= t_delay =3D 23, autovacuum_vacuum_cost_delay =3D 24, fillfactor =3D 40); + +SELECT reloptions FROM pg_class WHERE oid =3D 'reloptions_test'::regclass; +SELECT reloptions FROM pg_class WHERE oid =3D (SELECT reltoastrelid FROM p= g_class WHERE oid =3D 'reloptions_test'::regclass); + +-- Same FOR CREATE and ALTER INDEX for btree indexes + +CREATE INDEX reloptions_test_idx ON reloptions_test (s) WITH (fillfactor= =3D30); + +SELECT reloptions FROM pg_class WHERE oid =3D 'reloptions_test_idx'::regcl= ass; + +-- Fail when option and namespase do not exist + +CREATE INDEX reloptions_test_idx ON reloptions_test (s) WITH (not_existing= _option=3D2); + +CREATE INDEX reloptions_test_idx ON reloptions_test (s) WITH (not_existing= _option.fillfactor=3D2); + +-- Check ranges + +CREATE INDEX reloptions_test_idx2 ON reloptions_test (s) WITH (fillfactor= =3D1); + +CREATE INDEX reloptions_test_idx2 ON reloptions_test (s) WITH (fillfactor= =3D130); + +-- Check alter + +ALTER INDEX reloptions_test_idx SET (fillfactor=3D40); + +SELECT reloptions FROM pg_class WHERE oid =3D 'reloptions_test_idx'::regcl= ass; + +-- Check alter on empty relop list + +CREATE INDEX reloptions_test_idx3 ON reloptions_test (s); + +ALTER INDEX reloptions_test_idx3 SET (fillfactor=3D40); + +SELECT reloptions FROM pg_class WHERE oid =3D 'reloptions_test_idx3'::regc= lass; + + 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. =20 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 (fi= llfactor =3D 75); =20 -- Test vacuum-root operation. It gets invoked when the root is also a lea= f, -- 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 (f= illfactor =3D 9); +create index spgist_point_idx2 on spgist_point_tbl using spgist(p) with (f= illfactor =3D 101); + +-- ALTER reloption should work +alter index spgist_point_idx set (fillfactor =3D 76); --nextPart2916058.REV3beB8aF Content-Type: text/plain Content-Disposition: inline Content-Transfer-Encoding: 8bit MIME-Version: 1.0 -- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers --nextPart2916058.REV3beB8aF--