From 436eca7a1dd45b78b50c3868fd4be518b814cecd Mon Sep 17 00:00:00 2001 From: Tomas Vondra Date: Sun, 9 Jun 2019 20:59:01 +0200 Subject: [PATCH 01/10] Add opclass parameters --- doc/src/sgml/indices.sgml | 2 +- doc/src/sgml/ref/create_index.sgml | 16 ++- src/backend/access/common/reloptions.c | 142 +++++++++++++++------- src/backend/access/index/indexam.c | 81 ++++++++++++ src/backend/catalog/heap.c | 8 +- src/backend/catalog/index.c | 23 +++- src/backend/catalog/toasting.c | 1 + src/backend/commands/indexcmds.c | 17 ++- src/backend/commands/tablecmds.c | 2 +- src/backend/nodes/copyfuncs.c | 1 + src/backend/nodes/equalfuncs.c | 1 + src/backend/nodes/outfuncs.c | 1 + src/backend/optimizer/util/plancat.c | 4 + src/backend/parser/gram.y | 72 +++++++---- src/backend/utils/adt/ruleutils.c | 128 +++++++++++-------- src/backend/utils/cache/relcache.c | 99 +++++++++++++++ src/include/access/amapi.h | 7 ++ src/include/access/genam.h | 5 + src/include/access/reloptions.h | 5 + src/include/catalog/heap.h | 1 + src/include/nodes/execnodes.h | 2 + src/include/nodes/parsenodes.h | 1 + src/include/nodes/pathnodes.h | 1 + src/include/utils/rel.h | 1 + src/include/utils/relcache.h | 3 + src/include/utils/ruleutils.h | 2 + src/test/regress/expected/btree_index.out | 5 + src/test/regress/sql/btree_index.sql | 4 + 28 files changed, 503 insertions(+), 132 deletions(-) diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml index 95c0a1926c..ea3acea88e 100644 --- a/doc/src/sgml/indices.sgml +++ b/doc/src/sgml/indices.sgml @@ -1253,7 +1253,7 @@ SELECT target FROM tests WHERE subject = 'some-subject' AND success; An index definition can specify an operator class for each column of an index. -CREATE INDEX name ON table (column opclass sort options , ...); +CREATE INDEX name ON table (column opclass [ ( opclass_options ) ] sort options , ...); The operator class identifies the operators to be used by the index for that column. For example, a B-tree index on the type int4 diff --git a/doc/src/sgml/ref/create_index.sgml b/doc/src/sgml/ref/create_index.sgml index 629a31ef79..61401f3645 100644 --- a/doc/src/sgml/ref/create_index.sgml +++ b/doc/src/sgml/ref/create_index.sgml @@ -22,7 +22,7 @@ PostgreSQL documentation CREATE [ UNIQUE ] INDEX [ CONCURRENTLY ] [ [ IF NOT EXISTS ] name ] ON [ ONLY ] table_name [ USING method ] - ( { column_name | ( expression ) } [ COLLATE collation ] [ opclass ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ] [, ...] ) + ( { column_name | ( expression ) } [ COLLATE collation ] { opclass | DEFAULT } [ ( opclass_parameter = value [, ... ] ) ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ] [, ...] ) [ INCLUDE ( column_name [, ...] ) ] [ WITH ( storage_parameter = value [, ... ] ) ] [ TABLESPACE tablespace_name ] @@ -278,6 +278,15 @@ CREATE [ UNIQUE ] INDEX [ CONCURRENTLY ] [ [ IF NOT EXISTS ] + + opclass_parameter + + + The name of an operator class parameter. See below for details. + + + + ASC @@ -646,8 +655,9 @@ Indexes: - An operator class can be specified for each - column of an index. The operator class identifies the operators to be + An operator class with its optional parameters + can be specified for each column of an index. + The operator class identifies the operators to be used by the index for that column. For example, a B-tree index on four-byte integers would use the int4_ops class; this operator class includes comparison functions for four-byte diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index de06c92574..abc0082ce7 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -1051,6 +1051,60 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc, return options; } +static void +parseRelOptionsInternal(Datum options, bool validate, + relopt_value *reloptions, int numoptions) +{ + ArrayType *array = DatumGetArrayTypeP(options); + Datum *optiondatums; + int noptions; + int i; + + deconstruct_array(array, TEXTOID, -1, false, 'i', + &optiondatums, NULL, &noptions); + + for (i = 0; i < noptions; i++) + { + char *text_str = VARDATA(optiondatums[i]); + int text_len = VARSIZE(optiondatums[i]) - 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] == '=' && + strncmp(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); +} + /* * Interpret reloptions that are given in text-array format. * @@ -1105,57 +1159,61 @@ parseRelOptions(Datum options, bool validate, relopt_kind kind, /* Done if no options */ if (PointerIsValid(DatumGetPointer(options))) - { - ArrayType *array = DatumGetArrayTypeP(options); - Datum *optiondatums; - int noptions; + parseRelOptionsInternal(options, validate, reloptions, numoptions); - deconstruct_array(array, TEXTOID, -1, false, 'i', - &optiondatums, NULL, &noptions); + *numrelopts = numoptions; + return reloptions; +} - for (i = 0; i < noptions; i++) - { - char *text_str = VARDATA(optiondatums[i]); - int text_len = VARSIZE(optiondatums[i]) - VARHDRSZ; - int j; +/* Parse local unregistered options. */ +relopt_value * +parseLocalRelOptions(Datum options, bool validate, + relopt_gen *optgen[], int nopts) +{ + relopt_value *values = palloc(sizeof(*values) * nopts); + int i; - /* Search for a match in reloptions */ - for (j = 0; j < numoptions; j++) - { - int kw_len = reloptions[j].gen->namelen; + for (i = 0; i < nopts; i++) + { + values[i].gen = optgen[i]; + values[i].isset = false; + } - if (text_len > kw_len && text_str[kw_len] == '=' && - strncmp(text_str, reloptions[j].gen->name, kw_len) == 0) - { - parse_one_reloption(&reloptions[j], text_str, text_len, - validate); - break; - } - } + if (options != (Datum) 0) + parseRelOptionsInternal(options, validate, values, nopts); - if (j >= numoptions && validate) - { - char *s; - char *p; + return values; +} - s = TextDatumGetCString(optiondatums[i]); - p = strchr(s, '='); - if (p) - *p = '\0'; - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("unrecognized parameter \"%s\"", s))); - } - } +/* + * Parse local options, allocate a bytea struct that's of the specified + * 'base_size' plus any extra space that's needed for string variables, + * fill its option's fields located at the given offsets and return it. + */ +void * +parseAndFillLocalRelOptions(Datum options, relopt_gen *optgen[], int offsets[], + int noptions, size_t base_size, bool validate) +{ + relopt_parse_elt *elems = palloc(sizeof(*elems) * noptions); + relopt_value *vals; + void *opts; + int i; - /* It's worth avoiding memory leaks in this function */ - pfree(optiondatums); - if (((void *) array) != DatumGetPointer(options)) - pfree(array); + for (i = 0; i < noptions; i++) + { + elems[i].optname = optgen[i]->name; + elems[i].opttype = optgen[i]->type; + elems[i].offset = offsets[i]; } - *numrelopts = numoptions; - return reloptions; + vals = parseLocalRelOptions(options, validate, optgen, noptions); + opts = allocateReloptStruct(base_size, vals, noptions); + fillRelOptions(opts, base_size, vals, noptions, validate, elems, noptions); + + if (elems) + pfree(elems); + + return opts; } /* diff --git a/src/backend/access/index/indexam.c b/src/backend/access/index/indexam.c index aefdd2916d..6cc76914de 100644 --- a/src/backend/access/index/indexam.c +++ b/src/backend/access/index/indexam.c @@ -51,11 +51,14 @@ #include "access/xlog.h" #include "catalog/index.h" #include "catalog/pg_type.h" +#include "commands/defrem.h" #include "pgstat.h" #include "storage/bufmgr.h" #include "storage/lmgr.h" #include "storage/predicate.h" +#include "utils/ruleutils.h" #include "utils/snapmgr.h" +#include "utils/syscache.h" /* ---------------------------------------------------------------- @@ -905,3 +908,81 @@ index_store_float8_orderby_distances(IndexScanDesc scan, Oid *orderByTypes, } } } + +/* ---------------- + * index_opclass_options + * + * Parse opclass-specific options for index column. + * ---------------- + */ +bytea * +index_opclass_options(Relation relation, AttrNumber attnum, Datum attoptions, + bool validate) +{ + amopclassoptions_function amopclassoptions = + relation->rd_indam->amopclassoptions; + + if (!amopclassoptions) + { + if (validate && PointerIsValid(DatumGetPointer(attoptions))) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("access method \"%s\" does not support opclass options ", + get_am_name(relation->rd_rel->relam)))); + + return NULL; + } + + return amopclassoptions(relation, attnum, attoptions, validate); +} + +/* ---------------- + * index_opclass_options_generic + * + * Parse opclass options for index column using the specified support + * function 'procnum' of column's opclass. + * ---------------- + */ +bytea * +index_opclass_options_generic(Relation indrel, AttrNumber attnum, + uint16 procnum, Datum attoptions, bool validate) +{ + Oid procid = index_getprocid(indrel, attnum, procnum); + FmgrInfo *procinfo; + + if (!OidIsValid(procid)) + { + StringInfoData opclassname; + Oid opclass; + Datum indclassDatum; + oidvector *indclass; + bool isnull; + + if (!DatumGetPointer(attoptions)) + return NULL; /* ok, no options, no procedure */ + + /* + * Report an error if the opclass's options-parsing procedure does not + * exist but the opclass options are specified. + */ + indclassDatum = SysCacheGetAttr(INDEXRELID, indrel->rd_indextuple, + Anum_pg_index_indclass, &isnull); + Assert(!isnull); + indclass = (oidvector *) DatumGetPointer(indclassDatum); + opclass = indclass->values[attnum - 1]; + + initStringInfo(&opclassname); + get_opclass_name(opclass, InvalidOid, &opclassname); + + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("operator class \"%s\" has no options", + opclassname.data))); + } + + procinfo = index_getprocinfo(indrel, attnum, procnum); + + return (bytea *) DatumGetPointer(FunctionCall2(procinfo, + attoptions, + BoolGetDatum(validate))); +} diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index 86820eecfc..4a42cb1523 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -694,6 +694,7 @@ CheckAttributeType(const char *attname, void InsertPgAttributeTuple(Relation pg_attribute_rel, Form_pg_attribute new_attribute, + Datum attoptions, CatalogIndexState indstate) { Datum values[Natts_pg_attribute]; @@ -725,10 +726,11 @@ InsertPgAttributeTuple(Relation pg_attribute_rel, values[Anum_pg_attribute_attislocal - 1] = BoolGetDatum(new_attribute->attislocal); values[Anum_pg_attribute_attinhcount - 1] = Int32GetDatum(new_attribute->attinhcount); values[Anum_pg_attribute_attcollation - 1] = ObjectIdGetDatum(new_attribute->attcollation); + values[Anum_pg_attribute_attoptions - 1] = attoptions; /* start out with empty permissions and empty options */ nulls[Anum_pg_attribute_attacl - 1] = true; - nulls[Anum_pg_attribute_attoptions - 1] = true; + nulls[Anum_pg_attribute_attoptions - 1] = attoptions == (Datum) 0; nulls[Anum_pg_attribute_attfdwoptions - 1] = true; nulls[Anum_pg_attribute_attmissingval - 1] = true; @@ -782,7 +784,7 @@ AddNewAttributeTuples(Oid new_rel_oid, /* Make sure this is OK, too */ attr->attstattarget = -1; - InsertPgAttributeTuple(rel, attr, indstate); + InsertPgAttributeTuple(rel, attr, (Datum) 0, indstate); /* Add dependency info */ myself.classId = RelationRelationId; @@ -820,7 +822,7 @@ AddNewAttributeTuples(Oid new_rel_oid, /* Fill in the correct relation OID in the copied tuple */ attStruct.attrelid = new_rel_oid; - InsertPgAttributeTuple(rel, &attStruct, indstate); + InsertPgAttributeTuple(rel, &attStruct, (Datum) 0, indstate); } } diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index d2e4f53a80..125174852b 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -26,6 +26,7 @@ #include "access/amapi.h" #include "access/heapam.h" #include "access/multixact.h" +#include "access/reloptions.h" #include "access/relscan.h" #include "access/sysattr.h" #include "access/tableam.h" @@ -106,7 +107,8 @@ static TupleDesc ConstructTupleDescriptor(Relation heapRelation, Oid *classObjectId); static void InitializeAttributeOids(Relation indexRelation, int numatts, Oid indexoid); -static void AppendAttributeTuples(Relation indexRelation, int numatts); +static void AppendAttributeTuples(Relation indexRelation, int numatts, + Datum *attopts); static void UpdateIndexRelation(Oid indexoid, Oid heapoid, Oid parentIndexId, IndexInfo *indexInfo, @@ -486,7 +488,7 @@ InitializeAttributeOids(Relation indexRelation, * ---------------------------------------------------------------- */ static void -AppendAttributeTuples(Relation indexRelation, int numatts) +AppendAttributeTuples(Relation indexRelation, int numatts, Datum *attopts) { Relation pg_attribute; CatalogIndexState indstate; @@ -508,10 +510,11 @@ AppendAttributeTuples(Relation indexRelation, int numatts) for (i = 0; i < numatts; i++) { Form_pg_attribute attr = TupleDescAttr(indexTupDesc, i); + Datum attoptions = attopts ? attopts[i] : (Datum) 0; Assert(attr->attnum == i + 1); - InsertPgAttributeTuple(pg_attribute, attr, indstate); + InsertPgAttributeTuple(pg_attribute, attr, attoptions, indstate); } CatalogCloseIndexes(indstate); @@ -591,6 +594,7 @@ UpdateIndexRelation(Oid indexoid, else predDatum = (Datum) 0; + /* * open the system catalog index relation */ @@ -933,7 +937,8 @@ index_create(Relation heapRelation, /* * append ATTRIBUTE tuples for the index */ - AppendAttributeTuples(indexRelation, indexInfo->ii_NumIndexAttrs); + AppendAttributeTuples(indexRelation, indexInfo->ii_NumIndexAttrs, + indexInfo->ii_OpclassOptions); /* ---------------- * update pg_index @@ -1146,6 +1151,12 @@ index_create(Relation heapRelation, indexRelation->rd_index->indnkeyatts = indexInfo->ii_NumIndexKeyAttrs; + /* Validate opclass-specific options */ + if (indexInfo->ii_OpclassOptions) + for (i = 0; i < indexInfo->ii_NumIndexKeyAttrs; i++) + (void) index_opclass_options(indexRelation, i + 1, + indexInfo->ii_OpclassOptions[i], true); + /* * If this is bootstrap (initdb) time, then we don't actually fill in the * index yet. We'll be creating more indexes and classes later, so we @@ -2208,6 +2219,10 @@ BuildIndexInfo(Relation index) ii->ii_ExclusionStrats = NULL; } + ii->ii_OpclassOptions = + RelationGetRawOpclassOptions(RelationGetRelid(index), + RelationGetNumberOfAttributes(index)); + /* other info */ ii->ii_Unique = indexStruct->indisunique; ii->ii_ReadyForInserts = indexStruct->indisready; diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c index de6282a667..7290731b3d 100644 --- a/src/backend/catalog/toasting.c +++ b/src/backend/catalog/toasting.c @@ -304,6 +304,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid, indexInfo->ii_ExclusionOps = NULL; indexInfo->ii_ExclusionProcs = NULL; indexInfo->ii_ExclusionStrats = NULL; + indexInfo->ii_OpclassOptions = NULL; indexInfo->ii_Unique = true; indexInfo->ii_ReadyForInserts = true; indexInfo->ii_Concurrent = false; diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c index d05d2fd3d5..23a57be749 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -792,6 +792,7 @@ DefineIndex(Oid relationId, indexInfo->ii_ExclusionOps = NULL; indexInfo->ii_ExclusionProcs = NULL; indexInfo->ii_ExclusionStrats = NULL; + indexInfo->ii_OpclassOptions = NULL; /* for now */ indexInfo->ii_Unique = stmt->unique; /* In a concurrent build, mark it not-ready-for-inserts */ indexInfo->ii_ReadyForInserts = !stmt->concurrent; @@ -1513,7 +1514,7 @@ CheckPredicate(Expr *predicate) /* * Compute per-index-column information, including indexed column numbers - * or index expressions, opclasses, and indoptions. Note, all output vectors + * or index expressions, opclasses and their options. Note, all output vectors * should be allocated for all columns, including "including" ones. */ static void @@ -1814,6 +1815,20 @@ ComputeIndexAttrs(IndexInfo *indexInfo, accessMethodName))); } + /* Set up the per-column opclass options (attoptions field). */ + if (attribute->opclassopts) + { + Assert(attn < nkeycols); + + if (!indexInfo->ii_OpclassOptions) + indexInfo->ii_OpclassOptions = + palloc0(sizeof(Datum) * indexInfo->ii_NumIndexAttrs); + + indexInfo->ii_OpclassOptions[attn] = + transformRelOptions((Datum) 0, attribute->opclassopts, + NULL, NULL, false, false); + } + attn++; } } diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 98519ef836..a7c8fa38a0 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -5688,7 +5688,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, ReleaseSysCache(typeTuple); - InsertPgAttributeTuple(attrdesc, &attribute, NULL); + InsertPgAttributeTuple(attrdesc, &attribute, (Datum) 0, NULL); table_close(attrdesc, RowExclusiveLock); diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 78deade89b..f5fad43eba 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -2864,6 +2864,7 @@ _copyIndexElem(const IndexElem *from) COPY_STRING_FIELD(indexcolname); COPY_NODE_FIELD(collation); COPY_NODE_FIELD(opclass); + COPY_NODE_FIELD(opclassopts); COPY_SCALAR_FIELD(ordering); COPY_SCALAR_FIELD(nulls_ordering); diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 4f2ebe5118..6b14095c82 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -2546,6 +2546,7 @@ _equalIndexElem(const IndexElem *a, const IndexElem *b) COMPARE_STRING_FIELD(indexcolname); COMPARE_NODE_FIELD(collation); COMPARE_NODE_FIELD(opclass); + COMPARE_NODE_FIELD(opclassopts); COMPARE_SCALAR_FIELD(ordering); COMPARE_SCALAR_FIELD(nulls_ordering); diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 237598e110..35806c0681 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2844,6 +2844,7 @@ _outIndexElem(StringInfo str, const IndexElem *node) WRITE_STRING_FIELD(indexcolname); WRITE_NODE_FIELD(collation); WRITE_NODE_FIELD(opclass); + WRITE_NODE_FIELD(opclassopts); WRITE_ENUM_FIELD(ordering, SortByDir); WRITE_ENUM_FIELD(nulls_ordering, SortByNulls); } diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 2405acbf6f..5c6be745c0 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -362,6 +362,10 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, info->nulls_first = NULL; } + /* Fetch index opclass options */ + info->opclassoptions = + RelationGetParsedOpclassOptions(indexRelation); + /* * Fetch the index expressions and predicate, if any. We must * modify the copies we obtain from the relcache to have the diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 8311b1dd46..2feade5327 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -491,7 +491,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type alias_clause opt_alias_clause %type func_alias_clause %type sortby -%type index_elem +%type index_elem index_elem_options %type table_ref %type joined_table %type relation_expr @@ -7420,43 +7420,65 @@ index_params: index_elem { $$ = list_make1($1); } | index_params ',' index_elem { $$ = lappend($1, $3); } ; + +index_elem_options: + opt_collate opt_class opt_asc_desc opt_nulls_order + { + $$ = makeNode(IndexElem); + $$->name = NULL; + $$->expr = NULL; + $$->indexcolname = NULL; + $$->collation = $1; + $$->opclass = $2; + $$->opclassopts = NIL; + $$->ordering = $3; + $$->nulls_ordering = $4; + } + | opt_collate any_name reloptions opt_asc_desc opt_nulls_order + { + $$ = makeNode(IndexElem); + $$->name = NULL; + $$->expr = NULL; + $$->indexcolname = NULL; + $$->collation = $1; + $$->opclass = $2; + $$->opclassopts = $3; + $$->ordering = $4; + $$->nulls_ordering = $5; + } + | opt_collate DEFAULT reloptions opt_asc_desc opt_nulls_order + { + $$ = makeNode(IndexElem); + $$->name = NULL; + $$->expr = NULL; + $$->indexcolname = NULL; + $$->collation = $1; + $$->opclass = NIL; + $$->opclassopts = $3; + $$->ordering = $4; + $$->nulls_ordering = $5; + } + ; + /* * Index attributes can be either simple column references, or arbitrary * expressions in parens. For backwards-compatibility reasons, we allow * an expression that's just a function call to be written without parens. */ -index_elem: ColId opt_collate opt_class opt_asc_desc opt_nulls_order +index_elem: ColId index_elem_options { - $$ = makeNode(IndexElem); + $$ = $2; $$->name = $1; - $$->expr = NULL; - $$->indexcolname = NULL; - $$->collation = $2; - $$->opclass = $3; - $$->ordering = $4; - $$->nulls_ordering = $5; } - | func_expr_windowless opt_collate opt_class opt_asc_desc opt_nulls_order + | func_expr_windowless index_elem_options { - $$ = makeNode(IndexElem); - $$->name = NULL; + $$ = $2; $$->expr = $1; - $$->indexcolname = NULL; - $$->collation = $2; - $$->opclass = $3; - $$->ordering = $4; - $$->nulls_ordering = $5; } - | '(' a_expr ')' opt_collate opt_class opt_asc_desc opt_nulls_order + | '(' a_expr ')' index_elem_options { - $$ = makeNode(IndexElem); - $$->name = NULL; + $$ = $4; $$->expr = $2; - $$->indexcolname = NULL; - $$->collation = $4; - $$->opclass = $5; - $$->ordering = $6; - $$->nulls_ordering = $7; } ; diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 9dda4820af..ea49bef807 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -453,8 +453,6 @@ static void get_from_clause_coldeflist(RangeTblFunction *rtfunc, deparse_context *context); static void get_tablesample_def(TableSampleClause *tablesample, deparse_context *context); -static void get_opclass_name(Oid opclass, Oid actual_datatype, - StringInfo buf); static Node *processIndirection(Node *node, deparse_context *context); static void printSubscripts(SubscriptingRef *sbsref, deparse_context *context); static char *get_relation_name(Oid relid); @@ -469,6 +467,7 @@ static void add_cast_to(StringInfo buf, Oid typid); static char *generate_qualified_type_name(Oid typid); static text *string_to_text(char *str); static char *flatten_reloptions(Oid relid); +static void get_reloptions(StringInfo buf, Datum reloptions); #define only_marker(rte) ((rte)->inh ? "" : "ONLY ") @@ -1198,6 +1197,7 @@ pg_get_indexdef_worker(Oid indexrelid, int colno, oidvector *indcollation; oidvector *indclass; int2vector *indoption; + Datum *opcoptions = NULL; StringInfoData buf; char *str; char *sep; @@ -1233,6 +1233,9 @@ pg_get_indexdef_worker(Oid indexrelid, int colno, Assert(!isnull); indoption = (int2vector *) DatumGetPointer(indoptionDatum); + if (!attrsOnly) + opcoptions = RelationGetRawOpclassOptions(indexrelid, idxrec->indnatts); + /* * Fetch the pg_class tuple of the index relation */ @@ -1369,16 +1372,28 @@ pg_get_indexdef_worker(Oid indexrelid, int colno, if (!attrsOnly && keyno < idxrec->indnkeyatts && (!colno || colno == keyno + 1)) { + bool has_options = false; int16 opt = indoption->values[keyno]; Oid indcoll = indcollation->values[keyno]; + if (opcoptions) + has_options = (opcoptions[keyno] != (Datum) 0); + /* Add collation, if not default for column */ if (OidIsValid(indcoll) && indcoll != keycolcollation) appendStringInfo(&buf, " COLLATE %s", generate_collation_name((indcoll))); /* Add the operator class name, if not default */ - get_opclass_name(indclass->values[keyno], keycoltype, &buf); + get_opclass_name(indclass->values[keyno], + has_options ? InvalidOid : keycoltype, &buf); + + if (has_options) + { + appendStringInfoString(&buf, " ("); + get_reloptions(&buf, opcoptions[keyno]); + appendStringInfoChar(&buf, ')'); + } /* Add options if relevant */ if (amroutine->amcanorder) @@ -10459,7 +10474,7 @@ get_tablesample_def(TableSampleClause *tablesample, deparse_context *context) * actual_datatype. (If you don't want this behavior, just pass * InvalidOid for actual_datatype.) */ -static void +void get_opclass_name(Oid opclass, Oid actual_datatype, StringInfo buf) { @@ -11168,6 +11183,62 @@ string_to_text(char *str) return result; } +/* + * Generate a C string representing a relation options from text[] datum. + */ +static void +get_reloptions(StringInfo buf, Datum reloptions) +{ + Datum *options; + int noptions; + int i; + + deconstruct_array(DatumGetArrayTypeP(reloptions), + TEXTOID, -1, false, 'i', + &options, NULL, &noptions); + + for (i = 0; i < noptions; i++) + { + char *option = TextDatumGetCString(options[i]); + char *name; + char *separator; + char *value; + + /* + * Each array element should have the form name=value. If the "=" + * is missing for some reason, treat it like an empty value. + */ + name = option; + separator = strchr(option, '='); + if (separator) + { + *separator = '\0'; + value = separator + 1; + } + else + value = ""; + + if (i > 0) + appendStringInfoString(buf, ", "); + appendStringInfo(buf, "%s=", quote_identifier(name)); + + /* + * In general we need to quote the value; but to avoid unnecessary + * clutter, do not quote if it is an identifier that would not + * need quoting. (We could also allow numbers, but that is a bit + * trickier than it looks --- for example, are leading zeroes + * significant? We don't want to assume very much here about what + * custom reloptions might mean.) + */ + if (quote_identifier(value) == value) + appendStringInfoString(buf, value); + else + simple_quote_literal(buf, value); + + pfree(option); + } +} + /* * Generate a C string representing a relation's reloptions, or NULL if none. */ @@ -11188,56 +11259,9 @@ flatten_reloptions(Oid relid) if (!isnull) { StringInfoData buf; - Datum *options; - int noptions; - int i; initStringInfo(&buf); - - deconstruct_array(DatumGetArrayTypeP(reloptions), - TEXTOID, -1, false, 'i', - &options, NULL, &noptions); - - for (i = 0; i < noptions; i++) - { - char *option = TextDatumGetCString(options[i]); - char *name; - char *separator; - char *value; - - /* - * Each array element should have the form name=value. If the "=" - * is missing for some reason, treat it like an empty value. - */ - name = option; - separator = strchr(option, '='); - if (separator) - { - *separator = '\0'; - value = separator + 1; - } - else - value = ""; - - if (i > 0) - appendStringInfoString(&buf, ", "); - appendStringInfo(&buf, "%s=", quote_identifier(name)); - - /* - * In general we need to quote the value; but to avoid unnecessary - * clutter, do not quote if it is an identifier that would not - * need quoting. (We could also allow numbers, but that is a bit - * trickier than it looks --- for example, are leading zeroes - * significant? We don't want to assume very much here about what - * custom reloptions might mean.) - */ - if (quote_identifier(value) == value) - appendStringInfoString(&buf, value); - else - simple_quote_literal(&buf, value); - - pfree(option); - } + get_reloptions(&buf, reloptions); result = buf.data; } diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 2b992d7832..62caa4cfaf 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -5173,6 +5173,105 @@ GetRelationPublicationActions(Relation relation) return pubactions; } +/* + * RelationGetIndexOpclassOptions -- get opclass-specific options for the index + */ +Datum * +RelationGetRawOpclassOptions(Oid indexrelid, int16 natts) +{ + Datum *options = NULL; + int16 attnum; + + for (attnum = 1; attnum <= natts; attnum++) + { + HeapTuple tuple; + Datum attopts; + bool isnull; + + tuple = SearchSysCache2(ATTNUM, ObjectIdGetDatum(indexrelid), + Int16GetDatum(attnum)); + + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for attribute %d of relation %u", + attnum, indexrelid); + + attopts = SysCacheGetAttr(ATTNAME, tuple, Anum_pg_attribute_attoptions, + &isnull); + + if (!isnull) + { + if (!options) + options = palloc0(sizeof(Datum) * natts); + + options[attnum - 1] = datumCopy(attopts, false, -1); /* text */ + } + + ReleaseSysCache(tuple); + } + + return options; +} + +/* + * RelationGetOpclassOptions -- get parsed opclass-specific options for an index + */ +bytea ** +RelationGetParsedOpclassOptions(Relation relation) +{ + MemoryContext oldcxt; + bytea **opts; + Datum *rawopts; + int natts = RelationGetNumberOfAttributes(relation); + int i; + + /* Try to copy cached options. */ + if (relation->rd_opcoptions) + { + opts = palloc(sizeof(*opts) * natts); + + for (i = 0; i < natts; i++) + { + bytea *opt = relation->rd_opcoptions[i]; + + opts[i] = !opt ? NULL : (bytea *) + DatumGetPointer(datumCopy(PointerGetDatum(opt), false, -1)); + } + + return opts; + } + + /* Get and parse opclass options. */ + opts = palloc0(sizeof(*opts) * natts); + + rawopts = RelationGetRawOpclassOptions(RelationGetRelid(relation), natts); + + for (i = 0; i < natts; i++) + { + Datum options = rawopts ? rawopts[i] : (Datum) 0; + + opts[i] = index_opclass_options(relation, i + 1, options, false); + + if (options != (Datum) 0) + pfree(DatumGetPointer(options)); + } + + if (rawopts) + pfree(rawopts); + + /* Copy parsed options to the cache. */ + oldcxt = MemoryContextSwitchTo(CacheMemoryContext); + + relation->rd_opcoptions = palloc(sizeof(*opts) * natts); + + for (i = 0; i < natts; i++) + relation->rd_opcoptions[i] = !opts[i] ? NULL : (bytea *) + DatumGetPointer(datumCopy(PointerGetDatum(opts[i]), false, -1)); + + MemoryContextSwitchTo(oldcxt); + + return opts; +} + /* * Routines to support ereport() reports of relation-related errors * diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h index 6e3db06eed..9c06d1a094 100644 --- a/src/include/access/amapi.h +++ b/src/include/access/amapi.h @@ -103,6 +103,12 @@ typedef void (*amcostestimate_function) (struct PlannerInfo *root, typedef bytea *(*amoptions_function) (Datum reloptions, bool validate); +/* parse column opclass-specific options */ +typedef bytea *(*amopclassoptions_function) (Relation index, + AttrNumber colno, + Datum attoptions, + bool validate); + /* report AM, index, or index column property */ typedef bool (*amproperty_function) (Oid index_oid, int attno, IndexAMProperty prop, const char *propname, @@ -215,6 +221,7 @@ typedef struct IndexAmRoutine amcanreturn_function amcanreturn; /* can be NULL */ amcostestimate_function amcostestimate; amoptions_function amoptions; + amopclassoptions_function amopclassoptions; amproperty_function amproperty; /* can be NULL */ ambuildphasename_function ambuildphasename; /* can be NULL */ amvalidate_function amvalidate; diff --git a/src/include/access/genam.h b/src/include/access/genam.h index 8c053be2ca..aa170f6de6 100644 --- a/src/include/access/genam.h +++ b/src/include/access/genam.h @@ -180,6 +180,11 @@ extern FmgrInfo *index_getprocinfo(Relation irel, AttrNumber attnum, extern void index_store_float8_orderby_distances(IndexScanDesc scan, Oid *orderByTypes, double *distances, bool recheckOrderBy); +extern bytea *index_opclass_options(Relation relation, AttrNumber attnum, + Datum attoptions, bool validate); +extern bytea *index_opclass_options_generic(Relation relation, + AttrNumber attnum, uint16 procnum, + Datum attoptions, bool validate); /* * index access method support routines (in genam.c) diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a1912f41e6..8dea2d8e69 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -263,12 +263,17 @@ extern bytea *extractRelOptions(HeapTuple tuple, TupleDesc tupdesc, amoptions_function amoptions); extern relopt_value *parseRelOptions(Datum options, bool validate, relopt_kind kind, int *numrelopts); +extern relopt_value *parseLocalRelOptions(Datum options, bool validate, + relopt_gen **gen, int nelems); 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 void *parseAndFillLocalRelOptions(Datum options, relopt_gen *optgen[], + int offsets[], int noptions, size_t base_size, + bool validate); extern bytea *default_reloptions(Datum reloptions, bool validate, relopt_kind kind); diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h index eec71c29d5..866e94d04f 100644 --- a/src/include/catalog/heap.h +++ b/src/include/catalog/heap.h @@ -94,6 +94,7 @@ extern List *heap_truncate_find_FKs(List *relationIds); extern void InsertPgAttributeTuple(Relation pg_attribute_rel, Form_pg_attribute new_attribute, + Datum attoptions, CatalogIndexState indstate); extern void InsertPgClassTuple(Relation pg_class_desc, diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 99b9fa414f..e0d6c6c375 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -138,6 +138,7 @@ typedef struct ExprState * UniqueProcs * UniqueStrats * Unique is it a unique index? + * OpclassOptions opclass-specific options, or NULL if none * ReadyForInserts is it valid for inserts? * Concurrent are we doing a concurrent index build? * BrokenHotChain did we detect any broken HOT chains? @@ -166,6 +167,7 @@ typedef struct IndexInfo Oid *ii_UniqueOps; /* array with one entry per column */ Oid *ii_UniqueProcs; /* array with one entry per column */ uint16 *ii_UniqueStrats; /* array with one entry per column */ + Datum *ii_OpclassOptions; /* array with one entry per column */ bool ii_Unique; bool ii_ReadyForInserts; bool ii_Concurrent; diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 2a8edf934f..15699971a7 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -701,6 +701,7 @@ typedef struct IndexElem char *indexcolname; /* name for index column; NULL = default */ List *collation; /* name of collation; NIL = default */ List *opclass; /* name of desired opclass; NIL = default */ + List *opclassopts; /* opclass-specific options, or NIL */ SortByDir ordering; /* ASC/DESC/default */ SortByNulls nulls_ordering; /* FIRST/LAST/default */ } IndexElem; diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index 4b7703d478..f8a935f501 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -802,6 +802,7 @@ struct IndexOptInfo Oid *sortopfamily; /* OIDs of btree opfamilies, if orderable */ bool *reverse_sort; /* is sort order descending? */ bool *nulls_first; /* do NULLs come first in the sort order? */ + bytea **opclassoptions; /* opclass-specific options for columns */ bool *canreturn; /* which index cols can be returned in an * index-only scan? */ Oid relam; /* OID of the access method (in pg_am) */ diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d7f33abce3..8440e225ee 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -175,6 +175,7 @@ typedef struct RelationData uint16 *rd_exclstrats; /* exclusion ops' strategy numbers, if any */ void *rd_amcache; /* available for use by index AM */ Oid *rd_indcollation; /* OIDs of index collations */ + bytea **rd_opcoptions; /* parsed opclass-specific options */ /* * foreign-table support diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h index d9c10ffcba..562929c058 100644 --- a/src/include/utils/relcache.h +++ b/src/include/utils/relcache.h @@ -14,6 +14,7 @@ #ifndef RELCACHE_H #define RELCACHE_H +#include "postgres.h" #include "access/tupdesc.h" #include "nodes/bitmapset.h" @@ -49,6 +50,8 @@ extern Oid RelationGetPrimaryKeyIndex(Relation relation); extern Oid RelationGetReplicaIndex(Relation relation); extern List *RelationGetIndexExpressions(Relation relation); extern List *RelationGetIndexPredicate(Relation relation); +extern bytea **RelationGetParsedOpclassOptions(Relation relation); +extern Datum *RelationGetRawOpclassOptions(Oid indexrelid, int16 natts); typedef enum IndexAttrBitmapKind { diff --git a/src/include/utils/ruleutils.h b/src/include/utils/ruleutils.h index d34cad2f4b..68d7ca892e 100644 --- a/src/include/utils/ruleutils.h +++ b/src/include/utils/ruleutils.h @@ -35,5 +35,7 @@ extern List *select_rtable_names_for_explain(List *rtable, Bitmapset *rels_used); extern char *generate_collation_name(Oid collid); extern char *get_range_partbound_string(List *bound_datums); +extern void get_opclass_name(Oid opclass, Oid actual_datatype, StringInfo buf); + #endif /* RULEUTILS_H */ diff --git a/src/test/regress/expected/btree_index.out b/src/test/regress/expected/btree_index.out index acab8e0b11..33f2bf0e3f 100644 --- a/src/test/regress/expected/btree_index.out +++ b/src/test/regress/expected/btree_index.out @@ -244,6 +244,11 @@ select reloptions from pg_class WHERE oid = 'btree_idx1'::regclass; {vacuum_cleanup_index_scale_factor=70.0} (1 row) +-- Test unsupported btree opclass parameters +create index on btree_tall_tbl (id int4_ops(foo=1)); +ERROR: access method "btree" does not support opclass options +create index on btree_tall_tbl (id default(foo=1)); +ERROR: access method "btree" does not support opclass options -- -- Test for multilevel page deletion -- diff --git a/src/test/regress/sql/btree_index.sql b/src/test/regress/sql/btree_index.sql index 48eaf4fe42..aecd690a01 100644 --- a/src/test/regress/sql/btree_index.sql +++ b/src/test/regress/sql/btree_index.sql @@ -121,6 +121,10 @@ create index btree_idx_err on btree_test(a) with (vacuum_cleanup_index_scale_fac alter index btree_idx1 set (vacuum_cleanup_index_scale_factor = 70.0); select reloptions from pg_class WHERE oid = 'btree_idx1'::regclass; +-- Test unsupported btree opclass parameters +create index on btree_tall_tbl (id int4_ops(foo=1)); +create index on btree_tall_tbl (id default(foo=1)); + -- -- Test for multilevel page deletion -- -- 2.20.1