From 782e7d98320c6e7d9bf5c0f9aa33fa1c6047101f Mon Sep 17 00:00:00 2001 From: B Sadhu Prasad Patro Date: Mon, 21 Mar 2022 20:23:22 -0700 Subject: [PATCH v4] [PATCH V4] Per-table storage parameters for TableAM extensions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently all the storage options for a table are very much specific to the heap but a different AM might need some user defined AM specific parameters to help tune the AM. So here is a patch which provides an AM level routine so that instead of getting parameters validated using “heap_reloptions” it will call the registered AM routine. I have added new test module, which shows way to register a new table access method. New Command supported as: ALTER TABLE name SET ACCESS METHOD amname [ OPTIONS ( ADD | DROP option 'value' [, ... ] ) ]; --- doc/src/sgml/ref/create_table.sgml | 3 +- src/backend/access/common/reloptions.c | 30 ++++- src/backend/access/heap/heapam_handler.c | 1 + src/backend/commands/tablecmds.c | 214 ++++++++++++++++++++++++++++--- src/backend/parser/gram.y | 43 ++++++- src/backend/postmaster/autovacuum.c | 18 ++- src/backend/utils/cache/relcache.c | 11 +- src/include/access/reloptions.h | 6 +- src/include/access/tableam.h | 8 +- 9 files changed, 298 insertions(+), 36 deletions(-) diff --git a/doc/src/sgml/ref/create_table.sgml b/doc/src/sgml/ref/create_table.sgml index 7e4ef31..615bcad 100644 --- a/doc/src/sgml/ref/create_table.sgml +++ b/doc/src/sgml/ref/create_table.sgml @@ -1375,7 +1375,8 @@ WITH ( MODULUS numeric_literal, REM Storage parameters for indexes are documented in . The storage parameters currently - available for tables are listed below. For many of these parameters, as + available for tables are listed below. Each table may have different set of storage + parameters through different access methods. For many of these parameters, as shown, there is an additional parameter with the same name prefixed with toast., which controls the behavior of the table's secondary TOAST table, if any diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index d592655..bcb08d7 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -1372,7 +1372,8 @@ untransformRelOptions(Datum options) */ bytea * extractRelOptions(HeapTuple tuple, TupleDesc tupdesc, - amoptions_function amoptions) + amoptions_function amoptions, + reloptions_function reloptions) { bytea *options; bool isnull; @@ -1394,7 +1395,9 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc, case RELKIND_RELATION: case RELKIND_TOASTVALUE: case RELKIND_MATVIEW: - options = heap_reloptions(classForm->relkind, datum, false); + options = table_reloptions(reloptions, + classForm->relkind, + datum, false); break; case RELKIND_PARTITIONED_TABLE: options = partitioned_table_reloptions(datum, false); @@ -2007,7 +2010,8 @@ view_reloptions(Datum reloptions, bool validate) } /* - * Parse options for heaps, views and toast tables. + * Parse options for heaps, views and toast tables. This is + * implementation of relOptions for access method heap. */ bytea * heap_reloptions(char relkind, Datum reloptions, bool validate) @@ -2038,6 +2042,26 @@ heap_reloptions(char relkind, Datum reloptions, bool validate) /* + * Parse options for tables. + * + * reloptions tables AM's option parser function + * reloptions options as text[] datum + * validate error flag + */ +bytea * +table_reloptions(reloptions_function reloptsfun, char relkind, + Datum reloptions, bool validate) +{ + Assert(reloptsfun != NULL); + + /* Assume function is strict */ + if (!PointerIsValid(DatumGetPointer(reloptions))) + return NULL; + + return reloptsfun(relkind, reloptions, validate); +} + +/* * Parse options for indexes. * * amoptions index AM's option parser function diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 39ef8a0..4f7f110 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -2581,6 +2581,7 @@ static const TableAmRoutine heapam_methods = { .index_build_range_scan = heapam_index_build_range_scan, .index_validate_scan = heapam_index_validate_scan, + .relation_options = heap_reloptions, .relation_size = table_block_relation_size, .relation_needs_toast_table = heapam_relation_needs_toast_table, .relation_toast_am = heapam_relation_toast_am, diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 80faae9..e2d5172 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -574,6 +574,7 @@ static void ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel, const char *tablespacename, LOCKMODE lockmode); static void ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode); static void ATExecSetTableSpaceNoStorage(Relation rel, Oid newTableSpace); +static void ATExecSetAMOptions(Relation rel, List *defList, AlterTableType operation, LOCKMODE lockmode); static void ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation, LOCKMODE lockmode); @@ -816,24 +817,6 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, if (!OidIsValid(ownerId)) ownerId = GetUserId(); - /* - * Parse and validate reloptions, if any. - */ - reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps, - true, false); - - switch (relkind) - { - case RELKIND_VIEW: - (void) view_reloptions(reloptions, true); - break; - case RELKIND_PARTITIONED_TABLE: - (void) partitioned_table_reloptions(reloptions, true); - break; - default: - (void) heap_reloptions(relkind, reloptions, true); - } - if (stmt->ofTypename) { AclResult aclresult; @@ -954,6 +937,52 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, accessMethodId = get_table_am_oid(accessMethod, false); /* + * Parse and validate reloptions, if any. + */ + reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps, + true, false); + switch (relkind) + { + case RELKIND_VIEW: + (void) view_reloptions(reloptions, true); + break; + case RELKIND_PARTITIONED_TABLE: + (void) partitioned_table_reloptions(reloptions, true); + break; + case RELKIND_RELATION: + case RELKIND_TOASTVALUE: + case RELKIND_MATVIEW: + { + const TableAmRoutine *routine; + HeapTuple tuple; + Form_pg_am aform; + + tuple = SearchSysCache1(AMOID, ObjectIdGetDatum(accessMethodId)); + if (!HeapTupleIsValid(tuple)) + { + elog(ERROR, "cache lookup failed for access method %u", + accessMethodId); + } + + aform = (Form_pg_am) GETSTRUCT(tuple); + routine = GetTableAmRoutine(aform->amhandler); + if (routine->relation_options == NULL) + { + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("specifying a table access method is not supported"))); + } + + (void) routine->relation_options(relkind, reloptions, true); + ReleaseSysCache(tuple); + break; + } + + default: + (void) heap_reloptions(relkind, reloptions, true); + } + + /* * Create the relation. Inherited defaults and constraints are passed in * for immediate handling --- since they don't need parsing, they can be * stored immediately. @@ -5087,6 +5116,7 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, break; case AT_SetAccessMethod: /* SET ACCESS METHOD */ /* handled specially in Phase 3 */ + ATExecSetAMOptions(rel, (List *) cmd->def, cmd->subtype, lockmode); break; case AT_SetTableSpace: /* SET TABLESPACE */ @@ -14103,6 +14133,152 @@ ATPrepSetTableSpace(AlteredTableInfo *tab, Relation rel, const char *tablespacen tab->newTableSpace = tablespaceId; } +/*TODO: This needs to be removed. Same function is already present in foreigncmds.c*/ +static Datum +optionListToArray(List *options) +{ + ArrayBuildState *astate = NULL; + ListCell *cell; + + foreach(cell, options) + { + DefElem *def = lfirst(cell); + const char *value; + Size len; + text *t; + + value = defGetString(def); + len = VARHDRSZ + strlen(def->defname) + 1 + strlen(value); + t = 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) + return makeArrayResult(astate, CurrentMemoryContext); + + return PointerGetDatum(NULL); +} + +/* ADD or DROP reloptions in SET ACCESS METHOD.*/ +static void +ATExecSetAMOptions(Relation rel, List *defList, AlterTableType operation, + LOCKMODE lockmode) +{ + Oid relid; + Relation pgclass; + HeapTuple tuple; + HeapTuple newtuple; + Datum datum; + bool isnull; + Datum newOptions; + Datum repl_val[Natts_pg_class]; + bool repl_null[Natts_pg_class]; + bool repl_repl[Natts_pg_class]; + List *resultOptions; + ListCell *optcell; + + if (defList == NIL) + return; /* nothing to do */ + + pgclass = table_open(RelationRelationId, RowExclusiveLock); + + /* Fetch heap tuple */ + relid = RelationGetRelid(rel); + tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for relation %u", relid); + + /* Get the old reloptions */ + datum = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_reloptions, + &isnull); + resultOptions = untransformRelOptions(datum); + + foreach(optcell, defList) + { + DefElem *od = lfirst(optcell); + ListCell *cell; + + /* Search in existing options */ + foreach(cell, resultOptions) + { + DefElem *def = lfirst(cell); + + if (strcmp(def->defname, od->defname) == 0) + break; + } + + /* + * It is possible to perform multiple ADD/DROP actions on the same + * option. The standard permits this, as long as the options to be + * added are unique. + */ + switch (od->defaction) + { + case DEFELEM_ADD: + if (cell) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("option \"%s\" provided more than once", + od->defname))); + resultOptions = lappend(resultOptions, od); + break; + + case DEFELEM_DROP: + if (!cell) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("option \"%s\" not found", + od->defname))); + resultOptions = list_delete_cell(resultOptions, cell); + break; + + default: + elog(ERROR, "unrecognized action %d on option \"%s\"", + (int) od->defaction, od->defname); + break; + } + } + + newOptions = optionListToArray(resultOptions); + + /* DO need to call the handler function to validate ?*/ + + /* + * All we need do here is update the pg_class row; the new options will be + * propagated into relcaches during post-commit cache inval. + */ + memset(repl_val, 0, sizeof(repl_val)); + memset(repl_null, false, sizeof(repl_null)); + memset(repl_repl, false, sizeof(repl_repl)); + + if (newOptions != (Datum) 0) + repl_val[Anum_pg_class_reloptions - 1] = newOptions; + else + repl_null[Anum_pg_class_reloptions - 1] = true; + + repl_repl[Anum_pg_class_reloptions - 1] = true; + + newtuple = heap_modify_tuple(tuple, RelationGetDescr(pgclass), + repl_val, repl_null, repl_repl); + + CatalogTupleUpdate(pgclass, &newtuple->t_self, newtuple); + + + InvokeObjectPostAlterHook(RelationRelationId, RelationGetRelid(rel), + InvalidOid); + + heap_freetuple(newtuple); + + ReleaseSysCache(tuple); + + table_close(pgclass, RowExclusiveLock); +} + /* * Set, reset, or replace reloptions. */ @@ -14160,7 +14336,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation, case RELKIND_RELATION: case RELKIND_TOASTVALUE: case RELKIND_MATVIEW: - (void) heap_reloptions(rel->rd_rel->relkind, newOptions, true); + rel->rd_tableam->relation_options(rel->rd_rel->relkind, newOptions, true); break; case RELKIND_PARTITIONED_TABLE: (void) partitioned_table_reloptions(newOptions, true); diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 0036c2f..2b1d870 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -441,7 +441,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); prep_type_clause execute_param_clause using_clause returning_clause opt_enum_val_list enum_val_list table_func_column_list - create_generic_options alter_generic_options + create_generic_options alter_generic_options alter_am_options relation_expr_list dostmt_opt_list transform_element_list transform_type_list TriggerTransitions TriggerReferencing @@ -544,8 +544,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type generic_option_name %type generic_option_arg -%type generic_option_elem alter_generic_option_elem -%type generic_option_list alter_generic_option_list +%type generic_option_elem alter_generic_option_elem alter_am_option_elem +%type generic_option_list alter_generic_option_list alter_am_option_list %type reindex_target_type reindex_target_multitable @@ -2655,6 +2655,14 @@ alter_table_cmd: n->name = $4; $$ = (Node *)n; } + /* ALTER TABLE SET ACCESS METHOD [OPTIONS]*/ + | SET ACCESS METHOD name alter_am_options + { + AlterTableCmd *n = makeNode(AlterTableCmd); + n->subtype = AT_SetAccessMethod; + n->name = $4; + $$ = (Node *)n; + } /* ALTER TABLE SET TABLESPACE */ | SET TABLESPACE name { @@ -2724,6 +2732,35 @@ alter_table_cmd: } ; +/* Options definition for ALTER TABLE SET ACCESS METHOD OPTIONS (...) */ +alter_am_options: + OPTIONS '(' alter_am_option_list ')' { $$ = $3; } + ; + +alter_am_option_list: + alter_am_option_elem + { + $$ = list_make1($1); + } + | alter_am_option_list ',' alter_am_option_elem + { + $$ = lappend($1, $3); + } + ; + +alter_am_option_elem: + ADD_P generic_option_elem + { + $$ = $2; + $$->defaction = DEFELEM_ADD; + } + | DROP generic_option_elem + { + $$ = makeDefElemExtended(NULL, $2, NULL, DEFELEM_DROP, @2); + } + ; + + alter_column_default: SET DEFAULT a_expr { $$ = $3; } | DROP DEFAULT { $$ = NULL; } diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c index 681ef91..cd9c0bb 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -327,6 +327,7 @@ static void FreeWorkerInfo(int code, Datum arg); static autovac_table *table_recheck_autovac(Oid relid, HTAB *table_toast_map, TupleDesc pg_class_desc, + reloptions_function reloptions, int effective_multixact_freeze_max_age); static void recheck_relation_needs_vacanalyze(Oid relid, AutoVacOpts *avopts, Form_pg_class classForm, @@ -341,7 +342,7 @@ static void relation_needs_vacanalyze(Oid relid, AutoVacOpts *relopts, static void autovacuum_do_vac_analyze(autovac_table *tab, BufferAccessStrategy bstrategy); static AutoVacOpts *extract_autovac_opts(HeapTuple tup, - TupleDesc pg_class_desc); + TupleDesc pg_class_desc, reloptions_function reloptions); static PgStat_StatTabEntry *get_pgstat_tabentry_relid(Oid relid, bool isshared, PgStat_StatDBEntry *shared, PgStat_StatDBEntry *dbentry); @@ -2118,7 +2119,8 @@ do_autovacuum(void) } /* Fetch reloptions and the pgstat entry for this table */ - relopts = extract_autovac_opts(tuple, pg_class_desc); + relopts = extract_autovac_opts(tuple, pg_class_desc, + classRel->rd_tableam->relation_options); tabentry = get_pgstat_tabentry_relid(relid, classForm->relisshared, shared, dbentry); @@ -2191,7 +2193,8 @@ do_autovacuum(void) * fetch reloptions -- if this toast table does not have them, try the * main rel */ - relopts = extract_autovac_opts(tuple, pg_class_desc); + relopts = extract_autovac_opts(tuple, pg_class_desc, + classRel->rd_tableam->relation_options); if (relopts == NULL) { av_relation *hentry; @@ -2427,6 +2430,7 @@ do_autovacuum(void) */ MemoryContextSwitchTo(AutovacMemCxt); tab = table_recheck_autovac(relid, table_toast_map, pg_class_desc, + classRel->rd_tableam->relation_options, effective_multixact_freeze_max_age); if (tab == NULL) { @@ -2748,7 +2752,8 @@ deleted2: * be a risk; fortunately, it doesn't. */ static AutoVacOpts * -extract_autovac_opts(HeapTuple tup, TupleDesc pg_class_desc) +extract_autovac_opts(HeapTuple tup, TupleDesc pg_class_desc, + reloptions_function reloptions) { bytea *relopts; AutoVacOpts *av; @@ -2757,7 +2762,7 @@ extract_autovac_opts(HeapTuple tup, TupleDesc pg_class_desc) ((Form_pg_class) GETSTRUCT(tup))->relkind == RELKIND_MATVIEW || ((Form_pg_class) GETSTRUCT(tup))->relkind == RELKIND_TOASTVALUE); - relopts = extractRelOptions(tup, pg_class_desc, NULL); + relopts = extractRelOptions(tup, pg_class_desc, NULL, reloptions); if (relopts == NULL) return NULL; @@ -2803,6 +2808,7 @@ get_pgstat_tabentry_relid(Oid relid, bool isshared, PgStat_StatDBEntry *shared, static autovac_table * table_recheck_autovac(Oid relid, HTAB *table_toast_map, TupleDesc pg_class_desc, + reloptions_function reloptions, int effective_multixact_freeze_max_age) { Form_pg_class classForm; @@ -2824,7 +2830,7 @@ table_recheck_autovac(Oid relid, HTAB *table_toast_map, * Get the applicable reloptions. If it is a TOAST table, try to get the * main table reloptions if the toast table itself doesn't have. */ - avopts = extract_autovac_opts(classTup, pg_class_desc); + avopts = extract_autovac_opts(classTup, pg_class_desc, reloptions); if (classForm->relkind == RELKIND_TOASTVALUE && avopts == NULL && table_toast_map != NULL) { diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index fccffce..91ec1b7 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -461,6 +461,7 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple) { bytea *options; amoptions_function amoptsfn; + reloptions_function reloptsfn; relation->rd_options = NULL; @@ -472,13 +473,18 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple) { case RELKIND_RELATION: case RELKIND_TOASTVALUE: - case RELKIND_VIEW: case RELKIND_MATVIEW: + reloptsfn = relation->rd_tableam->relation_options; + amoptsfn = NULL; + break; + case RELKIND_VIEW: case RELKIND_PARTITIONED_TABLE: + reloptsfn = NULL; amoptsfn = NULL; break; case RELKIND_INDEX: case RELKIND_PARTITIONED_INDEX: + reloptsfn = NULL; amoptsfn = relation->rd_indam->amoptions; break; default: @@ -490,7 +496,8 @@ RelationParseRelOptions(Relation relation, HeapTuple tuple) * we might not have any other for pg_class yet (consider executing this * code for pg_class itself) */ - options = extractRelOptions(tuple, GetPgClassDescriptor(), amoptsfn); + options = extractRelOptions(tuple, GetPgClassDescriptor(), + amoptsfn, reloptsfn); /* * Copy parsed data into CacheMemoryContext. To guard against the diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index f740513..c42d5e9 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -21,6 +21,7 @@ #include "access/amapi.h" #include "access/htup.h" +#include "access/tableam.h" #include "access/tupdesc.h" #include "nodes/pg_list.h" #include "storage/lock.h" @@ -224,7 +225,8 @@ extern Datum transformRelOptions(Datum oldOptions, List *defList, bool acceptOidsOff, bool isReset); extern List *untransformRelOptions(Datum options); extern bytea *extractRelOptions(HeapTuple tuple, TupleDesc tupdesc, - amoptions_function amoptions); + amoptions_function amoptions, + reloptions_function reloptions); extern void *build_reloptions(Datum reloptions, bool validate, relopt_kind kind, Size relopt_struct_size, @@ -238,6 +240,8 @@ extern bytea *default_reloptions(Datum reloptions, bool validate, extern bytea *heap_reloptions(char relkind, Datum reloptions, bool validate); extern bytea *view_reloptions(Datum reloptions, bool validate); extern bytea *partitioned_table_reloptions(Datum reloptions, bool validate); +extern bytea *table_reloptions(reloptions_function reloptsfun, char relkind, + Datum reloptions, bool validate); extern bytea *index_reloptions(amoptions_function amoptions, Datum reloptions, bool validate); extern bytea *attribute_reloptions(Datum reloptions, bool validate); diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h index bb36573..e110085 100644 --- a/src/include/access/tableam.h +++ b/src/include/access/tableam.h @@ -252,6 +252,10 @@ typedef void (*IndexBuildCallback) (Relation index, bool tupleIsAlive, void *state); +/* This callback parse the table reloptions and returns in bytea format */ +typedef bytea *(*reloptions_function) (char relkind, + Datum reloptions, bool validate); + /* * API struct for a table AM. Note this must be allocated in a * server-lifetime manner, typically as a static const struct, which then gets @@ -692,6 +696,8 @@ typedef struct TableAmRoutine * ------------------------------------------------------------------------ */ + reloptions_function relation_options; + /* * See table_relation_size(). * @@ -702,7 +708,6 @@ typedef struct TableAmRoutine */ uint64 (*relation_size) (Relation rel, ForkNumber forkNumber); - /* * This callback should return true if the relation requires a TOAST table * and false if it does not. It may wish to examine the relation's tuple @@ -2073,5 +2078,6 @@ extern const TableAmRoutine *GetTableAmRoutine(Oid amhandler); extern const TableAmRoutine *GetHeapamTableAmRoutine(void); extern bool check_default_table_access_method(char **newval, void **extra, GucSource source); +extern bytea *heap_reloptions(char relkind, Datum reloptions, bool validate); #endif /* TABLEAM_H */ -- 1.8.3.1