ITAGAKI Takahiro wrote: > Alvaro Herrera <alvhe...@commandprompt.com> wrote: > > > Here's a patch for improving the general reloptions mechanism. What > > this patch does is add a table-based option parser. This allows adding > > new options very easily, and stops the business of having to pass the > > minimum and default fillfactor each time you want the reloptions > > processed. > > You use struct relopt_gen (and its subclasses) for the purpose of > both "definition of options" and "parsed result". But I think > it is cleaner to separete parsed results into another struct > something like:
Thanks for the suggestion -- yes, it is better as you suggest. I think putting the default on the same struct was just out of laziness at first, and inertia later. Here's the next version, which also fixes some particularly embarrasing bugs. -- Alvaro Herrera http://www.CommandPrompt.com/ The PostgreSQL Company - Command Prompt, Inc.
Index: src/backend/access/common/reloptions.c =================================================================== RCS file: /home/alvherre/Code/cvs/pgsql/src/backend/access/common/reloptions.c,v retrieving revision 1.11 diff -c -p -r1.11 reloptions.c *** src/backend/access/common/reloptions.c 23 Jul 2008 17:29:53 -0000 1.11 --- src/backend/access/common/reloptions.c 22 Dec 2008 16:14:40 -0000 *************** *** 15,20 **** --- 15,23 ---- #include "postgres.h" + #include "access/gist_private.h" + #include "access/hash.h" + #include "access/nbtree.h" #include "access/reloptions.h" #include "catalog/pg_type.h" #include "commands/defrem.h" *************** *** 22,29 **** --- 25,157 ---- #include "utils/array.h" #include "utils/builtins.h" #include "utils/guc.h" + #include "utils/memutils.h" #include "utils/rel.h" + /* + * Contents of pg_class.reloptions + * + * To add an option: + * + * (i) decide on a class (integer, real, bool), name, default value, upper + * and lower bounds (if applicable). + * (ii) add a record below. + * (iii) add it to StdRdOptions if appropriate + * (iv) add a block to the appropriate handling routine (probably + * 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[] = + { + /* list terminator */ + { { NULL } } + }; + + static relopt_int intRelOpts[] = + { + { + { + "fillfactor", + "Packs table pages only to this percentage", + RELOPT_KIND_HEAP + }, + HEAP_DEFAULT_FILLFACTOR, HEAP_MIN_FILLFACTOR, 100 + }, + { + { + "fillfactor", + "Packs btree index pages only to this percentage", + RELOPT_KIND_BTREE + }, + BTREE_DEFAULT_FILLFACTOR, BTREE_MIN_FILLFACTOR, 100 + }, + { + { + "fillfactor", + "Packs hash index pages only to this percentage", + RELOPT_KIND_HASH + }, + HASH_DEFAULT_FILLFACTOR, HASH_MIN_FILLFACTOR, 100 + }, + { + { + "fillfactor", + "Packs gist index pages only to this percentage", + RELOPT_KIND_GIST + }, + GIST_DEFAULT_FILLFACTOR, GIST_MIN_FILLFACTOR, 100 + }, + /* list terminator */ + { { NULL } } + }; + + static relopt_real realRelOpts[] = + { + /* list terminator */ + { { NULL } } + }; + + static relopt_gen **relOpts = NULL; + + static void parse_one_reloption(relopt_value *option, char *text_str, + int text_len, bool validate); + + /* + * initialize_reloptions + * initialization routine, must be called at backend start + * + * Initialize the relOpts array and fill each variable's type and name length. + */ + void + initialize_reloptions(void) + { + int i; + int j = 0; + + for (i = 0; boolRelOpts[i].gen.name; i++) + j++; + for (i = 0; intRelOpts[i].gen.name; i++) + j++; + for (i = 0; realRelOpts[i].gen.name; i++) + j++; + + 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++; + } + + /* add a list terminator */ + relOpts[j] = NULL; + } /* * Transform a relation options list (list of DefElem) into the text array *************** transformRelOptions(Datum oldOptions, Li *** 73,81 **** 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) --- 201,208 ---- for (i = 0; i < noldoptions; i++) { ! char *text_str = TextDatumGetCString(oldoptions[i]); ! int text_len = strlen(text_str); /* Search for a match in defList */ foreach(cell, defList) *************** untransformRelOptions(Datum options) *** 198,344 **** /* * Interpret reloptions that are given in text-array format. * ! * options: array of "keyword=value" strings, as built by transformRelOptions ! * numkeywords: number of legal keywords ! * keywords: the allowed keywords ! * values: output area ! * validate: if true, throw error for unrecognized keywords. ! * ! * The keywords and values arrays must both be of length numkeywords. ! * The values entry corresponding to a keyword is set to a palloc'd string ! * containing the corresponding value, or NULL if the keyword does not appear. */ ! void ! parseRelOptions(Datum options, int numkeywords, const char *const * keywords, ! char **values, bool validate) { ! ArrayType *array; ! Datum *optiondatums; ! int noptions; int i; ! /* Initialize to "all defaulted" */ ! MemSet(values, 0, numkeywords * sizeof(char *)); ! /* Done if no options */ ! if (!PointerIsValid(DatumGetPointer(options))) ! return; ! array = DatumGetArrayTypeP(options); ! ! Assert(ARR_ELEMTYPE(array) == TEXTOID); ! 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 keywords */ ! for (j = 0; j < numkeywords; j++) { ! int kw_len = strlen(keywords[j]); ! if (text_len > kw_len && text_str[kw_len] == '=' && ! pg_strncasecmp(text_str, keywords[j], kw_len) == 0) { ! char *value; ! int value_len; ! if (values[j] && validate) ! ereport(ERROR, ! (errcode(ERRCODE_INVALID_PARAMETER_VALUE), ! errmsg("parameter \"%s\" specified more than once", ! keywords[j]))); ! value_len = text_len - kw_len - 1; ! value = (char *) palloc(value_len + 1); ! memcpy(value, text_str + kw_len + 1, value_len); ! value[value_len] = '\0'; ! values[j] = value; ! break; } - } - if (j >= numkeywords && 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))); } } } /* ! * Parse reloptions for anything using StdRdOptions (ie, fillfactor only) */ bytea * ! default_reloptions(Datum reloptions, bool validate, ! int minFillfactor, int defaultFillfactor) { ! static const char *const default_keywords[1] = {"fillfactor"}; ! char *values[1]; ! int fillfactor; ! StdRdOptions *result; ! parseRelOptions(reloptions, 1, default_keywords, values, validate); ! /* ! * If no options, we can just return NULL rather than doing anything. ! * (defaultFillfactor is thus not used, but we require callers to pass it ! * anyway since we would need it if more options were added.) ! */ ! if (values[0] == NULL) return NULL; ! if (!parse_int(values[0], &fillfactor, 0, NULL)) ! { ! if (validate) ! ereport(ERROR, ! (errcode(ERRCODE_INVALID_PARAMETER_VALUE), ! errmsg("fillfactor must be an integer: \"%s\"", ! values[0]))); ! return NULL; ! } ! if (fillfactor < minFillfactor || fillfactor > 100) { ! if (validate) ! ereport(ERROR, ! (errcode(ERRCODE_INVALID_PARAMETER_VALUE), ! errmsg("fillfactor=%d is out of range (should be between %d and 100)", ! fillfactor, minFillfactor))); ! return NULL; } ! result = (StdRdOptions *) palloc(sizeof(StdRdOptions)); ! SET_VARSIZE(result, sizeof(StdRdOptions)); ! result->fillfactor = fillfactor; ! return (bytea *) result; } - /* * Parse options for heaps (and perhaps someday toast tables). */ bytea * heap_reloptions(char relkind, Datum reloptions, bool validate) { ! return default_reloptions(reloptions, validate, ! HEAP_MIN_FILLFACTOR, ! HEAP_DEFAULT_FILLFACTOR); } --- 325,548 ---- /* * Interpret reloptions that are given in text-array format. * ! * 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 is the array as constructed by transformRelOptions. ! * kind specifies the family of options to be processed. */ ! relopt_value * ! parseRelOptions(Datum options, bool validate, relopt_kind kind, ! int *numrelopts) { ! relopt_value *reloptions; ! int numoptions = 0; int i; + int j; ! /* Build a list of expected options, based on kind */ ! for (i = 0; relOpts[i]; i++) ! if (relOpts[i]->kind == kind) ! numoptions++; ! reloptions = palloc(numoptions * sizeof(relopt_value)); ! for (i = 0, j = 0; relOpts[i]; i++) ! { ! if (relOpts[i]->kind == kind) ! { ! reloptions[j].gen = relOpts[i]; ! reloptions[j].isset = false; ! j++; ! } ! } ! /* Done if no options */ ! if (PointerIsValid(DatumGetPointer(options))) { ! ArrayType *array; ! Datum *optiondatums; ! int noptions; ! ! array = DatumGetArrayTypeP(options); ! Assert(ARR_ELEMTYPE(array) == TEXTOID); ! ! 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))); ! } } } + + *numrelopts = numoptions; + return reloptions; } + /* + * Subroutine for parseRelOptions, to parse and validate a single option's + * value + */ + static void + parse_one_reloption(relopt_value *option, char *text_str, int text_len, + bool validate) + { + char *value; + int value_len; + bool parsed; + + if (option->isset && validate) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("parameter \"%s\" specified more than once", + option->gen->name))); + + value_len = text_len - option->gen->namelen - 1; + value = (char *) palloc(value_len + 1); + memcpy(value, text_str + option->gen->namelen + 1, value_len); + value[value_len] = '\0'; + + switch (option->gen->type) + { + case RELOPT_TYPE_BOOL: + { + parsed = parse_bool(value, &option->values.bool_val); + if (validate && !parsed) + ereport(ERROR, + (errmsg("invalid value for boolean option \"%s\": %s", + option->gen->name, value))); + } + break; + case RELOPT_TYPE_INT: + { + relopt_int *optint = (relopt_int *) option->gen; + + parsed = parse_int(value, &option->values.int_val, 0, NULL); + if (validate && !parsed) + ereport(ERROR, + (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, + (errmsg("value %s out of bounds for option \"%s\"", + value, option->gen->name), + errdetail("Valid values are between \"%d\" and \"%d\".", + optint->min, optint->max))); + } + break; + case RELOPT_TYPE_REAL: + { + relopt_real *optreal = (relopt_real *) option->gen; + + parsed = parse_real(value, &option->values.real_val); + if (validate && !parsed) + ereport(ERROR, + (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, + (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; + } + + if (parsed) + option->isset = true; + pfree(value); + } /* ! * Option parser for anything that uses StdRdOptions (i.e. fillfactor only) */ bytea * ! default_reloptions(Datum reloptions, bool validate, relopt_kind kind) { ! relopt_value *options; ! StdRdOptions *rdopts; ! StdRdOptions lopts; ! int numoptions; ! int len; ! int i; ! options = parseRelOptions(reloptions, validate, kind, &numoptions); ! /* if none set, we're done */ ! if (numoptions == 0) return NULL; ! MemSet(&lopts, 0, sizeof(StdRdOptions)); ! for (i = 0; i < numoptions; i++) { ! if (pg_strncasecmp(options[i].gen->name, "fillfactor", ! options[i].gen->namelen) == 0) ! { ! if (options[i].isset) ! lopts.fillfactor = options[i].values.int_val; ! else ! lopts.fillfactor = ((relopt_int *) options[i].gen)->default_val; ! break; ! } } ! pfree(options); ! len = offsetof(StdRdOptions, fillfactor) + sizeof(int); ! rdopts = palloc(len); ! memcpy(rdopts, &lopts, len); ! SET_VARSIZE(rdopts, len); ! return (bytea *) rdopts; } /* * Parse options for heaps (and perhaps someday toast tables). */ bytea * heap_reloptions(char relkind, Datum reloptions, bool validate) { ! return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP); } Index: src/backend/access/gin/ginutil.c =================================================================== RCS file: /home/alvherre/Code/cvs/pgsql/src/backend/access/gin/ginutil.c,v retrieving revision 1.18 diff -c -p -r1.18 ginutil.c *** src/backend/access/gin/ginutil.c 3 Nov 2008 20:47:48 -0000 1.18 --- src/backend/access/gin/ginutil.c 22 Dec 2008 16:23:59 -0000 *************** ginoptions(PG_FUNCTION_ARGS) *** 317,332 **** bool validate = PG_GETARG_BOOL(1); bytea *result; ! /* ! * It's not clear that fillfactor is useful for GIN, but for the moment ! * we'll accept it anyway. (It won't do anything...) ! */ ! #define GIN_MIN_FILLFACTOR 10 ! #define GIN_DEFAULT_FILLFACTOR 100 ! ! result = default_reloptions(reloptions, validate, ! GIN_MIN_FILLFACTOR, ! GIN_DEFAULT_FILLFACTOR); if (result) PG_RETURN_BYTEA_P(result); PG_RETURN_NULL(); --- 317,323 ---- bool validate = PG_GETARG_BOOL(1); bytea *result; ! result = default_reloptions(reloptions, validate, RELOPT_KIND_GIN); if (result) PG_RETURN_BYTEA_P(result); PG_RETURN_NULL(); Index: src/backend/access/gist/gistutil.c =================================================================== RCS file: /home/alvherre/Code/cvs/pgsql/src/backend/access/gist/gistutil.c,v retrieving revision 1.31 diff -c -p -r1.31 gistutil.c *** src/backend/access/gist/gistutil.c 30 Sep 2008 10:52:10 -0000 1.31 --- src/backend/access/gist/gistutil.c 19 Dec 2008 23:39:48 -0000 *************** gistoptions(PG_FUNCTION_ARGS) *** 670,678 **** bool validate = PG_GETARG_BOOL(1); bytea *result; ! result = default_reloptions(reloptions, validate, ! GIST_MIN_FILLFACTOR, ! GIST_DEFAULT_FILLFACTOR); if (result) PG_RETURN_BYTEA_P(result); PG_RETURN_NULL(); --- 670,677 ---- bool validate = PG_GETARG_BOOL(1); bytea *result; ! result = default_reloptions(reloptions, validate, RELOPT_KIND_GIST); ! if (result) PG_RETURN_BYTEA_P(result); PG_RETURN_NULL(); Index: src/backend/access/hash/hashutil.c =================================================================== RCS file: /home/alvherre/Code/cvs/pgsql/src/backend/access/hash/hashutil.c,v retrieving revision 1.57 diff -c -p -r1.57 hashutil.c *** src/backend/access/hash/hashutil.c 15 Sep 2008 18:43:41 -0000 1.57 --- src/backend/access/hash/hashutil.c 19 Dec 2008 23:39:48 -0000 *************** hashoptions(PG_FUNCTION_ARGS) *** 224,232 **** bool validate = PG_GETARG_BOOL(1); bytea *result; ! result = default_reloptions(reloptions, validate, ! HASH_MIN_FILLFACTOR, ! HASH_DEFAULT_FILLFACTOR); if (result) PG_RETURN_BYTEA_P(result); PG_RETURN_NULL(); --- 224,231 ---- bool validate = PG_GETARG_BOOL(1); bytea *result; ! result = default_reloptions(reloptions, validate, RELOPT_KIND_HASH); ! if (result) PG_RETURN_BYTEA_P(result); PG_RETURN_NULL(); Index: src/backend/access/nbtree/nbtutils.c =================================================================== RCS file: /home/alvherre/Code/cvs/pgsql/src/backend/access/nbtree/nbtutils.c,v retrieving revision 1.91 diff -c -p -r1.91 nbtutils.c *** src/backend/access/nbtree/nbtutils.c 19 Jun 2008 00:46:03 -0000 1.91 --- src/backend/access/nbtree/nbtutils.c 19 Dec 2008 23:39:48 -0000 *************** btoptions(PG_FUNCTION_ARGS) *** 1402,1410 **** bool validate = PG_GETARG_BOOL(1); bytea *result; ! result = default_reloptions(reloptions, validate, ! BTREE_MIN_FILLFACTOR, ! BTREE_DEFAULT_FILLFACTOR); if (result) PG_RETURN_BYTEA_P(result); PG_RETURN_NULL(); --- 1402,1408 ---- bool validate = PG_GETARG_BOOL(1); bytea *result; ! result = default_reloptions(reloptions, validate, RELOPT_KIND_BTREE); if (result) PG_RETURN_BYTEA_P(result); PG_RETURN_NULL(); Index: src/backend/utils/init/postinit.c =================================================================== RCS file: /home/alvherre/Code/cvs/pgsql/src/backend/utils/init/postinit.c,v retrieving revision 1.186 diff -c -p -r1.186 postinit.c *** src/backend/utils/init/postinit.c 23 Sep 2008 09:20:36 -0000 1.186 --- src/backend/utils/init/postinit.c 19 Dec 2008 23:39:48 -0000 *************** *** 19,24 **** --- 19,25 ---- #include <unistd.h> #include "access/heapam.h" + #include "access/reloptions.h" #include "access/xact.h" #include "catalog/catalog.h" #include "catalog/namespace.h" *************** InitPostgres(const char *in_dbname, Oid *** 645,650 **** --- 646,655 ---- if (!bootstrap) pgstat_bestart(); + /* initialize the relation options subsystem */ + if (!bootstrap) + initialize_reloptions(); + /* close the transaction we started above */ if (!bootstrap) CommitTransactionCommand(); Index: src/include/access/reloptions.h =================================================================== RCS file: /home/alvherre/Code/cvs/pgsql/src/include/access/reloptions.h,v retrieving revision 1.5 diff -c -p -r1.5 reloptions.h *** src/include/access/reloptions.h 1 Jan 2008 19:45:56 -0000 1.5 --- src/include/access/reloptions.h 22 Dec 2008 16:05:16 -0000 *************** *** 20,40 **** #include "nodes/pg_list.h" ! extern Datum transformRelOptions(Datum oldOptions, List *defList, ! bool ignoreOids, bool isReset); extern List *untransformRelOptions(Datum options); ! ! extern void parseRelOptions(Datum options, int numkeywords, ! const char *const * keywords, ! char **values, bool validate); extern bytea *default_reloptions(Datum reloptions, bool validate, ! int minFillfactor, int defaultFillfactor); ! extern bytea *heap_reloptions(char relkind, Datum reloptions, bool validate); - extern bytea *index_reloptions(RegProcedure amoptions, Datum reloptions, ! bool validate); #endif /* RELOPTIONS_H */ --- 20,101 ---- #include "nodes/pg_list.h" ! /* types supported by reloptions */ ! typedef enum relopt_type ! { ! RELOPT_TYPE_BOOL, ! RELOPT_TYPE_INT, ! RELOPT_TYPE_REAL ! } relopt_type; ! ! /* kinds supported by reloptions */ ! typedef enum relopt_kind ! { ! RELOPT_KIND_HEAP, ! /* XXX do we need a separate kind for TOAST tables? */ ! RELOPT_KIND_BTREE, ! RELOPT_KIND_HASH, ! RELOPT_KIND_GIN, ! RELOPT_KIND_GIST ! } relopt_kind; ! ! /* generic struct to hold shared data */ ! typedef struct relopt_gen ! { ! const char *name; /* must be first (used as list termination marker) */ ! const char *desc; ! relopt_kind kind; ! 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; ! } 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; + extern void initialize_reloptions(void); + extern Datum transformRelOptions(Datum oldOptions, List *defList, + bool ignoreOids, bool isReset); extern List *untransformRelOptions(Datum options); ! extern relopt_value *parseRelOptions(Datum options, bool validate, ! relopt_kind kind, int *numrelopts); extern bytea *default_reloptions(Datum reloptions, bool validate, ! relopt_kind kind); extern bytea *heap_reloptions(char relkind, Datum reloptions, bool validate); extern bytea *index_reloptions(RegProcedure amoptions, Datum reloptions, ! bool validate); #endif /* RELOPTIONS_H */
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers