1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-14 08:21:07 +03:00

Change the reloptions machinery to use a table-based parser, and provide

a more complete framework for writing custom option processing routines
by user-defined access methods.

Catalog version bumped due to the general API changes, which are going to
affect user-defined "amoptions" routines.
This commit is contained in:
Alvaro Herrera
2009-01-05 17:14:28 +00:00
parent b0a6ad70a1
commit ba748f7a11
7 changed files with 724 additions and 141 deletions

View File

@ -8,13 +8,16 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/access/common/reloptions.c,v 1.12 2009/01/01 17:23:34 momjian Exp $
* $PostgreSQL: pgsql/src/backend/access/common/reloptions.c,v 1.13 2009/01/05 17:14:28 alvherre Exp $
*
*-------------------------------------------------------------------------
*/
#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,8 +25,357 @@
#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, string), 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_string stringRelOpts[] =
{
/* list terminator */
{ { NULL } }
};
static relopt_gen **relOpts = NULL;
static int last_assigned_kind = RELOPT_KIND_LAST_DEFAULT + 1;
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 = 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++;
for (i = 0; stringRelOpts[i].gen.name; i++)
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;
}
/*
* add_reloption_kind
* Create a new relopt_kind value, to be used in custom reloptions by
* user-defined AMs.
*/
int
add_reloption_kind(void)
{
if (last_assigned_kind >= RELOPT_KIND_MAX)
ereport(ERROR,
(errmsg("user-defined relation parameter types limit exceeded")));
return 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(int kind, int type, char *name, char *desc)
{
MemoryContext oldcxt;
size_t size;
relopt_gen *newoption;
Assert(type != RELOPT_TYPE_STRING);
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;
default:
elog(ERROR, "unsupported option type");
return NULL; /* keep compiler quiet */
}
newoption = palloc(size);
newoption->name = pstrdup(name);
if (desc)
newoption->desc = pstrdup(desc);
else
newoption->desc = NULL;
newoption->kind = kind;
newoption->namelen = strlen(name);
newoption->type = type;
MemoryContextSwitchTo(oldcxt);
return newoption;
}
/*
* add_bool_reloption
* Add a new boolean reloption
*/
void
add_bool_reloption(int kind, char *name, char *desc, bool default_val)
{
relopt_bool *newoption;
newoption = (relopt_bool *) allocate_reloption(kind, 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(int kind, char *name, char *desc, int default_val,
int min_val, int max_val)
{
relopt_int *newoption;
newoption = (relopt_int *) allocate_reloption(kind, 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(int kind, char *name, char *desc, double default_val,
double min_val, double max_val)
{
relopt_real *newoption;
newoption = (relopt_real *) allocate_reloption(kind, 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
*/
void
add_string_reloption(int kind, char *name, char *desc, char *default_val)
{
MemoryContext oldcxt;
relopt_string *newoption;
int default_len = 0;
oldcxt = MemoryContextSwitchTo(TopMemoryContext);
if (default_val)
default_len = strlen(default_val);
newoption = palloc0(sizeof(relopt_string) + default_len);
newoption->gen.name = pstrdup(name);
if (desc)
newoption->gen.desc = pstrdup(desc);
else
newoption->gen.desc = NULL;
newoption->gen.kind = kind;
newoption->gen.namelen = strlen(name);
newoption->gen.type = RELOPT_TYPE_STRING;
if (default_val)
{
strcpy(newoption->default_val, default_val);
newoption->default_len = default_len;
newoption->default_isnull = false;
}
else
{
newoption->default_val[0] = '\0';
newoption->default_len = 0;
newoption->default_isnull = true;
}
MemoryContextSwitchTo(oldcxt);
add_reloption((relopt_gen *) newoption);
}
/*
* Transform a relation options list (list of DefElem) into the text array
@ -198,137 +550,236 @@ untransformRelOptions(Datum options)
/*
* 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.
* options is a reloption text array as constructed by transformRelOptions.
* kind specifies the family of options to be processed.
*
* 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.
* 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.
*
* If there are no options of the given kind, numrelopts is set to 0 and NULL
* is returned.
*
* 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.
*/
void
parseRelOptions(Datum options, int numkeywords, const char *const * keywords,
char **values, bool validate)
relopt_value *
parseRelOptions(Datum options, bool validate, relopt_kind kind,
int *numrelopts)
{
ArrayType *array;
Datum *optiondatums;
int noptions;
relopt_value *reloptions;
int numoptions = 0;
int i;
int j;
/* Initialize to "all defaulted" */
MemSet(values, 0, numkeywords * sizeof(char *));
if (need_initialization)
initialize_reloptions();
/* Build a list of expected options, based on kind */
for (i = 0; relOpts[i]; i++)
if (relOpts[i]->kind == 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]->kind == kind)
{
reloptions[j].gen = relOpts[i];
reloptions[j].isset = false;
j++;
}
}
/* 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++)
if (PointerIsValid(DatumGetPointer(options)))
{
text *optiontext = DatumGetTextP(optiondatums[i]);
char *text_str = VARDATA(optiontext);
int text_len = VARSIZE(optiontext) - VARHDRSZ;
int j;
ArrayType *array;
Datum *optiondatums;
int noptions;
/* Search for a match in keywords */
for (j = 0; j < numkeywords; j++)
array = DatumGetArrayTypeP(options);
Assert(ARR_ELEMTYPE(array) == TEXTOID);
deconstruct_array(array, TEXTOID, -1, false, 'i',
&optiondatums, NULL, &noptions);
for (i = 0; i < noptions; i++)
{
int kw_len = strlen(keywords[j]);
text *optiontext = DatumGetTextP(optiondatums[i]);
char *text_str = VARDATA(optiontext);
int text_len = VARSIZE(optiontext) - VARHDRSZ;
int j;
if (text_len > kw_len && text_str[kw_len] == '=' &&
pg_strncasecmp(text_str, keywords[j], kw_len) == 0)
/* Search for a match in reloptions */
for (j = 0; j < numoptions; j++)
{
char *value;
int value_len;
int kw_len = reloptions[j].gen->namelen;
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 (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)));
}
}
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)));
}
}
}
*numrelopts = numoptions;
return reloptions;
}
/*
* Parse reloptions for anything using StdRdOptions (ie, fillfactor only)
* Subroutine for parseRelOptions, to parse and validate a single option's
* value
*/
bytea *
default_reloptions(Datum reloptions, bool validate,
int minFillfactor, int defaultFillfactor)
static void
parse_one_reloption(relopt_value *option, char *text_str, int text_len,
bool validate)
{
static const char *const default_keywords[1] = {"fillfactor"};
char *values[1];
int fillfactor;
StdRdOptions *result;
char *value;
int value_len;
bool parsed;
bool nofree = false;
parseRelOptions(reloptions, 1, default_keywords, values, validate);
if (option->isset && validate)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("parameter \"%s\" specified more than once",
option->gen->name)));
/*
* 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;
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';
if (!parse_int(values[0], &fillfactor, 0, NULL))
switch (option->gen->type)
{
if (validate)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("fillfactor must be an integer: \"%s\"",
values[0])));
return NULL;
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;
case RELOPT_TYPE_STRING:
option->values.string_val = value;
nofree = true;
parsed = true;
/* no validation possible */
break;
default:
elog(ERROR, "unsupported reloption type %d", option->gen->type);
break;
}
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;
if (parsed)
option->isset = true;
if (!nofree)
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++)
{
HANDLE_INT_RELOPTION("fillfactor", lopts.fillfactor, options[i]);
}
pfree(options);
len = sizeof(StdRdOptions);
rdopts = palloc(len);
memcpy(rdopts, &lopts, len);
SET_VARSIZE(rdopts, len);
return (bytea *) rdopts;
}
/*
* Parse options for heaps (and perhaps someday toast tables).
@ -336,9 +787,7 @@ default_reloptions(Datum reloptions, bool validate,
bytea *
heap_reloptions(char relkind, Datum reloptions, bool validate)
{
return default_reloptions(reloptions, validate,
HEAP_MIN_FILLFACTOR,
HEAP_DEFAULT_FILLFACTOR);
return default_reloptions(reloptions, validate, RELOPT_KIND_HEAP);
}