Index: src/backend/access/common/reloptions.c =================================================================== RCS file: /a/pgsql/dev/anoncvs/pgsql/src/backend/access/common/reloptions.c,v retrieving revision 1.11 diff -c -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 10 Oct 2008 13:55:15 -0000 *************** *** 24,29 **** --- 24,182 ---- #include "utils/guc.h" #include "utils/rel.h" + /* + * Contents of pg_class.reloptions + * + * To add an option: + * + * (i) decide on a class (integer, double, bool), name, default value, upper + * and lower bounds (if applicable). + * + * (ii) add a record below. + * + * (iii) don't forget to document the option + */ + + static struct relopt_bool relOptBools[] = + { + { + { + "autovacuum_enabled", + "Enables autovacuum in this relation", + RO_BOOL + }, + false + }, + /* End-of-list marker */ + { + { + NULL, + NULL, + RO_BOOL + }, + false + } + }; + + static struct relopt_int relOptInts[] = + { + { + { + "fillfactor", + "Packs table pages only to this percentage", + RO_INT + }, + 100, + 10, + 100 + }, + { + { + "autovacuum_vac_base_thresh", + "Minimum number of tuple updates or deletes prior to vacuum", + RO_INT + }, + 50, + 0, + INT_MAX + }, + { + { + "autovacuum_anl_base_thresh", + "Minimum number of tuple inserts, updates or deletes prior to analyze", + RO_INT + }, + 50, + 0, + INT_MAX + }, + { + { + "autovacuum_vac_cost_delay", + "Vacuum cost delay in milliseconds, for autovacuum", + RO_INT + }, + 20, + -1, + 1000 + }, + { + { + "autovacuum_vac_cost_limit", + "Vacuum cost ammount available before napping, for autovacuum", + RO_INT + }, + -1, + -1, + 10000 + }, + { + { + "autovacuum_freeze_min_age", + "Minimum age at which VACUUM should freeze a table row, for autovacuum", + RO_INT + }, + 100000000, + 0, + 1000000000 + }, + { + { + "autovacuum_freeze_max_age", + "Age at which to autovacuum a table to prevent transaction ID wraparound", + RO_INT + }, + 200000000, + 100000000, + 2000000000 + }, + /* End-of-list marker */ + { + { + NULL, + NULL, + RO_INT + }, + 0, + 0, + 0 + } + }; + + struct relopt_real relOptReals[] = + { + { + { + "autovacuum_vac_scale_factor", + "Number of tuples inserts, updates or deletes prior to vacuum as a fraction of reltuples", + RO_REAL + }, + 0.2, + 0.0, + 100.0 + }, + { + { + "autovacuum_anl_scale_factor", + "Number of tuples inserts, updates or deletes prior to analyze as a fraction of reltuples", + RO_REAL + }, + 0.1, + 0.0, + 100.0 + }, + /* End-of-list marker */ + { + { + NULL, + NULL, + RO_REAL + }, + 0.0, + 0.0, + 0.0 + } + }; /* * Transform a relation options list (list of DefElem) into the text array *************** *** 51,56 **** --- 204,212 ---- ArrayBuildState *astate; ListCell *cell; + ereport(DEBUG2, + (errmsg("starting transformRelOptions() ..."))); + /* no change if empty list */ if (defList == NIL) return oldOptions; *************** *** 77,82 **** --- 233,243 ---- char *text_str = VARDATA(oldoption); int text_len = VARSIZE(oldoption) - VARHDRSZ; + char *tmp = text_str; + tmp[text_len] = '\0'; + ereport(DEBUG1, + (errmsg("old reloption: %s", text_str))); + /* Search for a match in defList */ foreach(cell, defList) { *************** *** 93,98 **** --- 254,261 ---- astate = accumArrayResult(astate, oldoptions[i], false, TEXTOID, CurrentMemoryContext); + ereport(DEBUG1, + (errmsg("added old reloption: %s", tmp))); } } } *************** *** 136,141 **** --- 299,307 ---- SET_VARSIZE(t, len); sprintf(VARDATA(t), "%s=%s", def->defname, value); + ereport(DEBUG1, + (errmsg("added new reloption: %s=%s", def->defname, value))); + astate = accumArrayResult(astate, PointerGetDatum(t), false, TEXTOID, CurrentMemoryContext); *************** *** 147,152 **** --- 313,321 ---- else result = (Datum) 0; + ereport(DEBUG2, + (errmsg("ending transformRelOptions() ..."))); + return result; } *************** *** 164,169 **** --- 333,341 ---- int noptions; int i; + ereport(DEBUG2, + (errmsg("starting untransformRelOptions() ..."))); + /* Nothing to do if no options */ if (!PointerIsValid(DatumGetPointer(options))) return result; *************** *** 188,196 **** --- 360,375 ---- *p++ = '\0'; val = (Node *) makeString(pstrdup(p)); } + + ereport(DEBUG1, + (errmsg("added reloption: %s=%s", s, p))); + result = lappend(result, makeDefElem(pstrdup(s), val)); } + ereport(DEBUG2, + (errmsg("ending untransformRelOptions() ..."))); + return result; } *************** *** 199,206 **** * 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. * --- 378,383 ---- *************** *** 209,221 **** * 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 *)); --- 386,410 ---- * containing the corresponding value, or NULL if the keyword does not appear. */ void ! parseRelOptions(Datum options, char **values, bool validate, int minFillfactor) { ArrayType *array; Datum *optiondatums; int noptions; int i; + int numkeywords = 0; + + ereport(DEBUG2, + (errmsg("starting parseRelOptions() ..."))); + + for (i = 0; relOptBools[i].gen.name; i++) + numkeywords++; + for (i = 0; relOptInts[i].gen.name; i++) + numkeywords++; + for (i = 0; relOptReals[i].gen.name; i++) + numkeywords++; + ereport(DEBUG1, + (errmsg("number of keywords: %d", numkeywords))); /* Initialize to "all defaulted" */ MemSet(values, 0, numkeywords * sizeof(char *)); *************** *** 236,267 **** 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; --- 425,574 ---- text *optiontext = DatumGetTextP(optiondatums[i]); char *text_str = VARDATA(optiontext); int text_len = VARSIZE(optiontext) - VARHDRSZ; ! int j = 0; ! bool found = false; ! ! char *tmp = text_str; ! tmp[text_len] = '\0'; ! ereport(DEBUG1, ! (errmsg("parsing reloption %s ...", tmp))); /* Search for a match in keywords */ ! while (relOptBools[j].gen.name) { ! int kw_len = strlen(relOptBools[j].gen.name); if (text_len > kw_len && text_str[kw_len] == '=' && ! pg_strcasecmp(text_str, relOptBools[j].gen.name, 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", ! relOptBools[j].gen.name))); ! 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'; + + if (parse_bool(value, NULL) == false && validate) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid input value for parameter %s: \"%s\"", + relOptBools[j].gen.name, value), + errhint("value must be a boolean"))); + values[j] = value; + + found = true; break; } + + j++; } + + while (relOptInts[j].gen.name || !found) + { + int kw_len = strlen(relOptInts[j].gen.name); + + if (text_len > kw_len && text_str[kw_len] == '=' && + pg_strcasecmp(text_str, relOptInts[j].gen.name, 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", + relOptInts[j].gen.name))); + + 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'; + + if (parse_int(value, NULL, 0, NULL) == false && validate) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid input value for parameter %s: \"%s\"", + relOptBools[j].gen.name, value), + errhint("value must be an integer"))); + + if ((value < relOptInts[j].min || value > relOptInts[j].max) && validate) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("%s=%s is out of range (should be between %d and %d)", + relOptInts[j].gen.name, value, relOptInts[j].min, relOptInts[j].max))); + + /* + * Especial check for fillfactor because minimum fillfactor values + * are AMs and/or heap dependant. We don't need to check upper + * limit because we already did it above. + */ + if (pg_strcasecmp(relOptInts[j].gen.name, "fillfactor") == 0 && + value < minFillfactor && validate) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("fillfactor=%s is out of range (should be between %d and 100)", + value, minFillfactor))); + + values[j] = value; + + found = true; + break; + } + + j++; + } + + while (relOptReals[j].gen.name || !found) + { + int kw_len = strlen(relOptReals[j].gen.name); + + if (text_len > kw_len && text_str[kw_len] == '=' && + pg_strcasecmp(text_str, relOptReals[j].gen.name, 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", + relOptReals[j].gen.name))); + + 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'; + + if (parse_real(value, NULL) == false && validate) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("invalid input value for parameter %s: \"%s\"", + relOptBools[j].gen.name, value), + errhint("value must be a float"))); + + if ((value < relOptReals[j].min || value > relOptReals[j].max) && validate) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("%s=%s is out of range (should be between %d and %d)", + relOptReals[j].gen.name, value, relOptReals[j].min, relOptReals[j].max))); + + values[j] = value; + + found = true; + break; + } + + j++; + } + if (j >= numkeywords && validate) { char *s; *************** *** 276,281 **** --- 583,591 ---- errmsg("unrecognized parameter \"%s\"", s))); } } + + ereport(DEBUG2, + (errmsg("ending parseRelOptions() ..."))); } *************** *** 286,297 **** 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. --- 596,609 ---- default_reloptions(Datum reloptions, bool validate, int minFillfactor, int defaultFillfactor) { char *values[1]; int fillfactor; StdRdOptions *result; ! ereport(DEBUG2, ! (errmsg("starting default_options() ..."))); ! ! parseRelOptions(reloptions, values, validate, minFillfactor); /* * If no options, we can just return NULL rather than doing anything. *************** *** 301,331 **** 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; } --- 613,626 ---- if (values[0] == NULL) return NULL; result = (StdRdOptions *) palloc(sizeof(StdRdOptions)); SET_VARSIZE(result, sizeof(StdRdOptions)); result->fillfactor = fillfactor; + ereport(DEBUG2, + (errmsg("ending default_reloptions() ..."))); + return (bytea *) result; } *************** *** 336,344 **** bytea * heap_reloptions(char relkind, Datum reloptions, bool validate) { ! return default_reloptions(reloptions, validate, ! HEAP_MIN_FILLFACTOR, ! HEAP_DEFAULT_FILLFACTOR); } --- 631,642 ---- bytea * heap_reloptions(char relkind, Datum reloptions, bool validate) { ! ereport(DEBUG2, ! (errmsg("starting heap_reloptions() ..."))); ! ! return default_reloptions(reloptions, validate, ! HEAP_MIN_FILLFACTOR, ! HEAP_DEFAULT_FILLFACTOR); } *************** *** 356,361 **** --- 654,662 ---- FunctionCallInfoData fcinfo; Datum result; + ereport(DEBUG2, + (errmsg("starting index_reloptions() ..."))); + Assert(RegProcedureIsValid(amoptions)); /* Assume function is strict */ *************** *** 377,381 **** --- 678,685 ---- if (fcinfo.isnull || DatumGetPointer(result) == NULL) return NULL; + ereport(DEBUG2, + (errmsg("ending index_reloptions() ..."))); + return DatumGetByteaP(result); } Index: src/backend/access/gin/ginutil.c =================================================================== RCS file: /a/pgsql/dev/anoncvs/pgsql/src/backend/access/gin/ginutil.c,v retrieving revision 1.17 diff -c -r1.17 ginutil.c *** src/backend/access/gin/ginutil.c 30 Sep 2008 10:52:10 -0000 1.17 --- src/backend/access/gin/ginutil.c 2 Oct 2008 20:33:13 -0000 *************** *** 339,347 **** #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(); --- 339,350 ---- #define GIN_MIN_FILLFACTOR 10 #define GIN_DEFAULT_FILLFACTOR 100 ! /* TODO how can we pass the min|default fillfactor to reloptions? */ ! /* XXX different AM has different values! */ ! result = default_reloptions(reloptions, validate, ! GIN_MIN_FILLFACTOR, GIN_DEFAULT_FILLFACTOR); + if (result) PG_RETURN_BYTEA_P(result); PG_RETURN_NULL(); Index: src/backend/access/gist/gistutil.c =================================================================== RCS file: /a/pgsql/dev/anoncvs/pgsql/src/backend/access/gist/gistutil.c,v retrieving revision 1.31 diff -c -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 2 Oct 2008 20:33:28 -0000 *************** *** 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,681 ---- bool validate = PG_GETARG_BOOL(1); bytea *result; ! /* TODO how can we pass the min|default fillfactor to reloptions? */ ! /* XXX different AM has different values! */ ! result = default_reloptions(reloptions, validate, ! GIST_MIN_FILLFACTOR, GIST_DEFAULT_FILLFACTOR); + if (result) PG_RETURN_BYTEA_P(result); PG_RETURN_NULL(); Index: src/backend/access/hash/hashutil.c =================================================================== RCS file: /a/pgsql/dev/anoncvs/pgsql/src/backend/access/hash/hashutil.c,v retrieving revision 1.57 diff -c -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 2 Oct 2008 20:32:48 -0000 *************** *** 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,235 ---- bool validate = PG_GETARG_BOOL(1); bytea *result; ! /* TODO how can we pass the min|default fillfactor to reloptions? */ ! /* XXX different AM has different values! */ ! result = default_reloptions(reloptions, validate, ! HASH_MIN_FILLFACTOR, HASH_DEFAULT_FILLFACTOR); + if (result) PG_RETURN_BYTEA_P(result); PG_RETURN_NULL(); Index: src/backend/access/nbtree/nbtutils.c =================================================================== RCS file: /a/pgsql/dev/anoncvs/pgsql/src/backend/access/nbtree/nbtutils.c,v retrieving revision 1.91 diff -c -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 2 Oct 2008 20:32:35 -0000 *************** *** 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,1413 ---- bool validate = PG_GETARG_BOOL(1); bytea *result; ! /* TODO how can we pass the min|default fillfactor to reloptions? */ ! /* XXX different AM has different values! */ ! result = default_reloptions(reloptions, validate, ! BTREE_MIN_FILLFACTOR, BTREE_DEFAULT_FILLFACTOR); + if (result) PG_RETURN_BYTEA_P(result); PG_RETURN_NULL(); Index: src/include/access/reloptions.h =================================================================== RCS file: /a/pgsql/dev/anoncvs/pgsql/src/include/access/reloptions.h,v retrieving revision 1.5 diff -c -r1.5 reloptions.h *** src/include/access/reloptions.h 1 Jan 2008 19:45:56 -0000 1.5 --- src/include/access/reloptions.h 7 Oct 2008 04:57:59 -0000 *************** *** 20,36 **** #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); --- 20,85 ---- #include "nodes/pg_list.h" + /* types supported by reloptions */ + enum ro_type + { + RO_BOOL, + RO_INT, + RO_REAL + }; + + /* kind supported by reloptions */ + enum ro_kind + { + RO_INDEX, + RO_HEAP + }; + + /* generic struct to hold shared data */ + struct relopt_gen + { + const char *name; + const char *desc; + enum ro_type type; /* type of variable */ + enum ro_kind kind; /* index or heap? */ + }; + + /* reloptions records for specific variable types */ + struct relopt_bool + { + struct relopt_gen gen; + bool value; + bool reset_value; /* XXX useful? */ + }; + + struct relopt_int + { + struct relopt_gen gen; + int value; + int min; + int max; + int reset_value; /* XXX useful? */ + }; + + struct relopt_real + { + struct relopt_gen gen; + double value; + double min; + double max; + double reset_value; /* XXX useful? */ + }; + extern Datum transformRelOptions(Datum oldOptions, List *defList, bool ignoreOids, bool isReset); extern List *untransformRelOptions(Datum options); ! extern void parseRelOptions(Datum options, char **values, bool validate, ! int minFillfactor); ! extern bytea *default_reloptions(Datum reloptions, bool validate, ! int minFillfactor, int defaultFillfactor); extern bytea *heap_reloptions(char relkind, Datum reloptions, bool validate);