pg.ddx.io  pgsql-hackers@postgresql.org mailing list archive  
help / color / mirror / Atom feed
From: Nikolay Shaplov <dhyan@nataraj.su>
To: Alvaro Herrera <alvherre@2ndquadrant.com>
To: PostgreSQL Developers <pgsql-hackers@postgresql.org>
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> (raw)
List-Unsubscribe: <mailto:majordomo@postgresql.org?body=unsub%20pgsql-hackers>


(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.

-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Attachments:

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

view thread (46+ messages)  latest in thread

Message-ID: <2146419.veIEZdk4E4@x200m>
Permalink:  ../2146419.veIEZdk4E4@x200m/
Also on:    postgresql.org/message-id/2146419.veIEZdk4E4@x200m

 ·  · 

reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Reply to all the recipients using the --to and --cc options:
  reply via email

  To: pgsql-hackers@postgresql.org
  Cc: dhyan@nataraj.su, alvherre@2ndquadrant.com
  Subject: Re: [PATCH] Move all am-related reloption code into src/backend/access/[am-name] and get rid of relopt_kind for custom AM
  In-Reply-To: <2146419.veIEZdk4E4@x200m>

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

This inbox is served by DDX for PostgreSQL; see mirroring instructions
for how to clone and mirror all data and code used for this inbox