Hi,
while rebasing the patch series [1] adding bloom/multi-minmax BRIN
opclasses, I've decided to also rebase it on top of this patch, because it
needs the opclass parameters. So I had to rebase this too - it went mostly
fine, with reasonably limited bitrot. The rebased patch series is attached.
Using this patch series in [1] was mostly smooth, I only have two minor
comments at this point:
1) We need a better infrastructure to parse opclass parameters. For
example the gtsvector_options does this:
Datum
gtsvector_options(PG_FUNCTION_ARGS)
{
Datum raw_options = PG_GETARG_DATUM(0);
bool validate = PG_GETARG_BOOL(1);
relopt_int siglen =
{ {"siglen", "signature length", 0, 0, 6, RELOPT_TYPE_INT },
SIGLEN_DEFAULT, 1, SIGLEN_MAX };
relopt_gen *optgen[] = { &siglen.gen };
int offsets[] = { offsetof(GistTsVectorOptions, siglen) };
GistTsVectorOptions *options =
parseAndFillLocalRelOptions(raw_options, optgen, offsets, 1,
sizeof(GistTsVectorOptions), validate);
PG_RETURN_POINTER(options);
}
So in other words, it builds all the various pieces (relopts, optgen,
offsets, lengths etc.) manually, which is really error-prone and difficult
to maintain. We need to make it simpler - ideally as simple as defining a
custom GUC, or just an array of relopt_* structs.
2) The 0001 part does this in index_opclass_options_generic:
get_opclass_name(opclass, InvalidOid, &str);
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("operator class \"%s\" has no options",
opclassname.data)));
But that's a bit broken, because get_opclass_name() appends the opclass
name to 'str', but with a space at the beginning. So this produces
messages like
ERROR: operator class " int4_bloom_ops" has no options
which is not right. I haven't checked if a better function already exists,
or whether we need to implement it.
regards
https://www.postgresql.org/message-id/flat/c1138ead-7668-f0e1-0638-c3be3237e812%402ndquadrant.com
--
Tomas Vondra http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
>From 436eca7a1dd45b78b50c3868fd4be518b814cecd Mon Sep 17 00:00:00 2001
From: Tomas Vondra <to...@2ndquadrant.com>
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 <firstterm>operator
class</firstterm> for each column of an index.
<synopsis>
-CREATE INDEX <replaceable>name</replaceable> ON
<replaceable>table</replaceable> (<replaceable>column</replaceable>
<replaceable>opclass</replaceable> <optional><replaceable>sort
options</replaceable></optional> <optional>, ...</optional>);
+CREATE INDEX <replaceable>name</replaceable> ON
<replaceable>table</replaceable> (<replaceable>column</replaceable>
<replaceable>opclass</replaceable> [ (
<replaceable>opclass_options</replaceable> ) ] <optional><replaceable>sort
options</replaceable></optional> <optional>, ...</optional>);
</synopsis>
The operator class identifies the operators to be used by the index
for that column. For example, a B-tree index on the type <type>int4</type>
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
<refsynopsisdiv>
<synopsis>
CREATE [ UNIQUE ] INDEX [ CONCURRENTLY ] [ [ IF NOT EXISTS ] <replaceable
class="parameter">name</replaceable> ] ON [ ONLY ] <replaceable
class="parameter">table_name</replaceable> [ USING <replaceable
class="parameter">method</replaceable> ]
- ( { <replaceable class="parameter">column_name</replaceable> | (
<replaceable class="parameter">expression</replaceable> ) } [ COLLATE
<replaceable class="parameter">collation</replaceable> ] [ <replaceable
class="parameter">opclass</replaceable> ] [ ASC | DESC ] [ NULLS { FIRST | LAST
} ] [, ...] )
+ ( { <replaceable class="parameter">column_name</replaceable> | (
<replaceable class="parameter">expression</replaceable> ) } [ COLLATE
<replaceable class="parameter">collation</replaceable> ] { <replaceable
class="parameter">opclass</replaceable> | DEFAULT } [ ( <replaceable
class="parameter">opclass_parameter</replaceable> = <replaceable
class="parameter">value</replaceable> [, ... ] ) ] [ ASC | DESC ] [ NULLS {
FIRST | LAST } ] [, ...] )
[ INCLUDE ( <replaceable class="parameter">column_name</replaceable> [,
...] ) ]
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> =
<replaceable class="parameter">value</replaceable> [, ... ] ) ]
[ TABLESPACE <replaceable class="parameter">tablespace_name</replaceable> ]
@@ -278,6 +278,15 @@ CREATE [ UNIQUE ] INDEX [ CONCURRENTLY ] [ [ IF NOT EXISTS
] <replaceable class=
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><replaceable
class="parameter">opclass_parameter</replaceable></term>
+ <listitem>
+ <para>
+ The name of an operator class parameter. See below for details.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>ASC</literal></term>
<listitem>
@@ -646,8 +655,9 @@ Indexes:
</para>
<para>
- An <firstterm>operator class</firstterm> can be specified for each
- column of an index. The operator class identifies the operators to be
+ An <firstterm>operator class</firstterm> 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 <literal>int4_ops</literal> 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> alias_clause opt_alias_clause
%type <list> func_alias_clause
%type <sortby> sortby
-%type <ielem> index_elem
+%type <ielem> index_elem index_elem_options
%type <node> table_ref
%type <jexpr> joined_table
%type <range> 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
>From ddd99a8fb48774cfaf9ca151ebb8a5238bc1d2b3 Mon Sep 17 00:00:00 2001
From: Tomas Vondra <to...@2ndquadrant.com>
Date: Sun, 9 Jun 2019 21:17:42 +0200
Subject: [PATCH 02/10] Add opclass parameters to GiST
---
doc/src/sgml/xindex.sgml | 5 +++
src/backend/access/gist/gist.c | 2 ++
src/backend/access/gist/gistget.c | 12 ++++---
src/backend/access/gist/gistsplit.c | 15 +++++----
src/backend/access/gist/gistutil.c | 44 ++++++++++++++++++--------
src/backend/access/gist/gistvalidate.c | 34 +++++++++++++-------
src/include/access/gist.h | 3 +-
src/include/access/gist_private.h | 4 +++
8 files changed, 82 insertions(+), 37 deletions(-)
diff --git a/doc/src/sgml/xindex.sgml b/doc/src/sgml/xindex.sgml
index 9446f8b836..8c5b5289d7 100644
--- a/doc/src/sgml/xindex.sgml
+++ b/doc/src/sgml/xindex.sgml
@@ -546,6 +546,11 @@
index-only scans (optional)</entry>
<entry>9</entry>
</row>
+ <row>
+ <entry><function>options</function></entry>
+ <entry>parse opclass-specific options (optional)</entry>
+ <entry>10</entry>
+ </row>
</tbody>
</tgroup>
</table>
diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c
index 470b121e7d..8e3460f456 100644
--- a/src/backend/access/gist/gist.c
+++ b/src/backend/access/gist/gist.c
@@ -98,6 +98,7 @@ gisthandler(PG_FUNCTION_ARGS)
amroutine->amestimateparallelscan = NULL;
amroutine->aminitparallelscan = NULL;
amroutine->amparallelrescan = NULL;
+ amroutine->amopclassoptions = gistopclassoptions;
PG_RETURN_POINTER(amroutine);
}
@@ -1528,6 +1529,7 @@ initGISTstate(Relation index)
giststate->scanCxt = scanCxt;
giststate->tempCxt = scanCxt; /* caller must change this if needed */
giststate->leafTupdesc = index->rd_att;
+ giststate->opclassoptions = RelationGetParsedOpclassOptions(index);
/*
* The truncated tupdesc for non-leaf index tuples, which doesn't
contain
diff --git a/src/backend/access/gist/gistget.c
b/src/backend/access/gist/gistget.c
index 8108fbb7d8..abfe659ca5 100644
--- a/src/backend/access/gist/gistget.c
+++ b/src/backend/access/gist/gistget.c
@@ -196,6 +196,7 @@ gistindex_keytest(IndexScanDesc scan,
Datum test;
bool recheck;
GISTENTRY de;
+ bytea *options =
giststate->opclassoptions[key->sk_attno - 1];
gistdentryinit(giststate, key->sk_attno - 1, &de,
datum, r, page, offset,
@@ -216,13 +217,14 @@ gistindex_keytest(IndexScanDesc scan,
*/
recheck = true;
- test = FunctionCall5Coll(&key->sk_func,
+ test = FunctionCall6Coll(&key->sk_func,
key->sk_collation,
PointerGetDatum(&de),
key->sk_argument,
Int16GetDatum(key->sk_strategy),
ObjectIdGetDatum(key->sk_subtype),
-
PointerGetDatum(&recheck));
+
PointerGetDatum(&recheck),
+
PointerGetDatum(options));
if (!DatumGetBool(test))
return false;
@@ -257,6 +259,7 @@ gistindex_keytest(IndexScanDesc scan,
Datum dist;
bool recheck;
GISTENTRY de;
+ bytea *options =
giststate->opclassoptions[key->sk_attno - 1];
gistdentryinit(giststate, key->sk_attno - 1, &de,
datum, r, page, offset,
@@ -279,13 +282,14 @@ gistindex_keytest(IndexScanDesc scan,
* about the flag, but are expected to never be lossy.
*/
recheck = false;
- dist = FunctionCall5Coll(&key->sk_func,
+ dist = FunctionCall6Coll(&key->sk_func,
key->sk_collation,
PointerGetDatum(&de),
key->sk_argument,
Int16GetDatum(key->sk_strategy),
ObjectIdGetDatum(key->sk_subtype),
-
PointerGetDatum(&recheck));
+
PointerGetDatum(&recheck),
+
PointerGetDatum(options));
*recheck_distances_p |= recheck;
*distance_p = DatumGetFloat8(dist);
}
diff --git a/src/backend/access/gist/gistsplit.c
b/src/backend/access/gist/gistsplit.c
index 6a9c54d86c..2b3cb967e1 100644
--- a/src/backend/access/gist/gistsplit.c
+++ b/src/backend/access/gist/gistsplit.c
@@ -378,18 +378,20 @@ genericPickSplit(GISTSTATE *giststate, GistEntryVector
*entryvec, GIST_SPLITVEC
evec->n = v->spl_nleft;
memcpy(evec->vector, entryvec->vector + FirstOffsetNumber,
sizeof(GISTENTRY) * evec->n);
- v->spl_ldatum = FunctionCall2Coll(&giststate->unionFn[attno],
+ v->spl_ldatum = FunctionCall3Coll(&giststate->unionFn[attno],
giststate->supportCollation[attno],
PointerGetDatum(evec),
-
PointerGetDatum(&nbytes));
+
PointerGetDatum(&nbytes),
+
PointerGetDatum(giststate->opclassoptions[attno]));
evec->n = v->spl_nright;
memcpy(evec->vector, entryvec->vector + FirstOffsetNumber +
v->spl_nleft,
sizeof(GISTENTRY) * evec->n);
- v->spl_rdatum = FunctionCall2Coll(&giststate->unionFn[attno],
+ v->spl_rdatum = FunctionCall3Coll(&giststate->unionFn[attno],
giststate->supportCollation[attno],
PointerGetDatum(evec),
-
PointerGetDatum(&nbytes));
+
PointerGetDatum(&nbytes),
+
PointerGetDatum(giststate->opclassoptions[attno]));
}
/*
@@ -430,10 +432,11 @@ gistUserPicksplit(Relation r, GistEntryVector *entryvec,
int attno, GistSplitVec
* Let the opclass-specific PickSplit method do its thing. Note that at
* this point we know there are no null keys in the entryvec.
*/
- FunctionCall2Coll(&giststate->picksplitFn[attno],
+ FunctionCall3Coll(&giststate->picksplitFn[attno],
giststate->supportCollation[attno],
PointerGetDatum(entryvec),
- PointerGetDatum(sv));
+ PointerGetDatum(sv),
+
PointerGetDatum(giststate->opclassoptions[attno]));
if (sv->spl_nleft == 0 || sv->spl_nright == 0)
{
diff --git a/src/backend/access/gist/gistutil.c
b/src/backend/access/gist/gistutil.c
index 49df05653b..000f10103c 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -201,10 +201,11 @@ gistMakeUnionItVec(GISTSTATE *giststate, IndexTuple
*itvec, int len,
}
/* Make union and store in attr array */
- attr[i] = FunctionCall2Coll(&giststate->unionFn[i],
+ attr[i] = FunctionCall3Coll(&giststate->unionFn[i],
giststate->supportCollation[i],
PointerGetDatum(evec),
-
PointerGetDatum(&attrsize));
+
PointerGetDatum(&attrsize),
+
PointerGetDatum(giststate->opclassoptions[i]));
isnull[i] = false;
}
@@ -270,10 +271,11 @@ gistMakeUnionKey(GISTSTATE *giststate, int attno,
}
*dstisnull = false;
- *dst = FunctionCall2Coll(&giststate->unionFn[attno],
+ *dst = FunctionCall3Coll(&giststate->unionFn[attno],
giststate->supportCollation[attno],
PointerGetDatum(evec),
-
PointerGetDatum(&dstsize));
+
PointerGetDatum(&dstsize),
+
PointerGetDatum(giststate->opclassoptions[attno]));
}
}
@@ -282,10 +284,11 @@ gistKeyIsEQ(GISTSTATE *giststate, int attno, Datum a,
Datum b)
{
bool result;
- FunctionCall3Coll(&giststate->equalFn[attno],
+ FunctionCall4Coll(&giststate->equalFn[attno],
giststate->supportCollation[attno],
a, b,
- PointerGetDatum(&result));
+ PointerGetDatum(&result),
+
PointerGetDatum(giststate->opclassoptions[attno]));
return result;
}
@@ -559,9 +562,10 @@ gistdentryinit(GISTSTATE *giststate, int nkey, GISTENTRY
*e,
return;
dep = (GISTENTRY *)
-
DatumGetPointer(FunctionCall1Coll(&giststate->decompressFn[nkey],
+
DatumGetPointer(FunctionCall2Coll(&giststate->decompressFn[nkey],
giststate->supportCollation[nkey],
-
PointerGetDatum(e)));
+
PointerGetDatum(e),
+
PointerGetDatum(giststate->opclassoptions[nkey])));
/* decompressFn may just return the given pointer */
if (dep != e)
gistentryinit(*e, dep->key, dep->rel, dep->page,
dep->offset,
@@ -596,9 +600,10 @@ gistFormTuple(GISTSTATE *giststate, Relation r,
/* there may not be a compress function in opclass */
if (OidIsValid(giststate->compressFn[i].fn_oid))
cep = (GISTENTRY *)
-
DatumGetPointer(FunctionCall1Coll(&giststate->compressFn[i],
+
DatumGetPointer(FunctionCall2Coll(&giststate->compressFn[i],
giststate->supportCollation[i],
-
PointerGetDatum(¢ry)));
+
PointerGetDatum(¢ry),
+
PointerGetDatum(giststate->opclassoptions[i])));
else
cep = ¢ry;
compatt[i] = cep->key;
@@ -643,9 +648,10 @@ gistFetchAtt(GISTSTATE *giststate, int nkey, Datum k,
Relation r)
gistentryinit(fentry, k, r, NULL, (OffsetNumber) 0, false);
fep = (GISTENTRY *)
- DatumGetPointer(FunctionCall1Coll(&giststate->fetchFn[nkey],
+ DatumGetPointer(FunctionCall2Coll(&giststate->fetchFn[nkey],
giststate->supportCollation[nkey],
-
PointerGetDatum(&fentry)));
+
PointerGetDatum(&fentry),
+
PointerGetDatum(giststate->opclassoptions[nkey])));
/* fetchFn set 'key', return it to the caller */
return fep->key;
@@ -722,11 +728,12 @@ gistpenalty(GISTSTATE *giststate, int attno,
if (giststate->penaltyFn[attno].fn_strict == false ||
(isNullOrig == false && isNullAdd == false))
{
- FunctionCall3Coll(&giststate->penaltyFn[attno],
+ FunctionCall4Coll(&giststate->penaltyFn[attno],
giststate->supportCollation[attno],
PointerGetDatum(orig),
PointerGetDatum(add),
- PointerGetDatum(&penalty));
+ PointerGetDatum(&penalty),
+
PointerGetDatum(giststate->opclassoptions[attno]));
/* disallow negative or NaN penalty */
if (isnan(penalty) || penalty < 0.0)
penalty = 0.0;
@@ -915,6 +922,15 @@ gistoptions(Datum reloptions, bool validate)
return (bytea *) rdopts;
}
+bytea *
+gistopclassoptions(Relation index, AttrNumber attnum, Datum attoptions,
+ bool validate)
+{
+ return index_opclass_options_generic(index, attnum,
GIST_OPCLASSOPT_PROC,
+
attoptions, validate);
+}
+
+
/*
* gistproperty() -- Check boolean properties of indexes.
*
diff --git a/src/backend/access/gist/gistvalidate.c
b/src/backend/access/gist/gistvalidate.c
index dfc1a87a75..7ae820070b 100644
--- a/src/backend/access/gist/gistvalidate.c
+++ b/src/backend/access/gist/gistvalidate.c
@@ -108,37 +108,46 @@ gistvalidate(Oid opclassoid)
{
case GIST_CONSISTENT_PROC:
ok = check_amproc_signature(procform->amproc,
BOOLOID, false,
-
5, 5, INTERNALOID, opcintype,
-
INT2OID, OIDOID, INTERNALOID);
+
5, 6, INTERNALOID, opcintype,
+
INT2OID, OIDOID, INTERNALOID,
+
INTERNALOID);
break;
case GIST_UNION_PROC:
ok = check_amproc_signature(procform->amproc,
opckeytype, false,
-
2, 2, INTERNALOID, INTERNALOID);
+
2, 3, INTERNALOID, INTERNALOID,
+
INTERNALOID);
break;
case GIST_COMPRESS_PROC:
case GIST_DECOMPRESS_PROC:
case GIST_FETCH_PROC:
ok = check_amproc_signature(procform->amproc,
INTERNALOID, true,
-
1, 1, INTERNALOID);
+
1, 2, INTERNALOID, INTERNALOID);
break;
case GIST_PENALTY_PROC:
ok = check_amproc_signature(procform->amproc,
INTERNALOID, true,
-
3, 3, INTERNALOID,
-
INTERNALOID, INTERNALOID);
+
3, 4, INTERNALOID,
+
INTERNALOID, INTERNALOID,
+
INTERNALOID);
break;
case GIST_PICKSPLIT_PROC:
ok = check_amproc_signature(procform->amproc,
INTERNALOID, true,
-
2, 2, INTERNALOID, INTERNALOID);
+
2, 3, INTERNALOID, INTERNALOID,
+
INTERNALOID);
break;
case GIST_EQUAL_PROC:
ok = check_amproc_signature(procform->amproc,
INTERNALOID, false,
-
3, 3, opckeytype, opckeytype,
-
INTERNALOID);
+
3, 4, opckeytype, opckeytype,
+
INTERNALOID, INTERNALOID);
break;
case GIST_DISTANCE_PROC:
ok = check_amproc_signature(procform->amproc,
FLOAT8OID, false,
-
5, 5, INTERNALOID, opcintype,
-
INT2OID, OIDOID, INTERNALOID);
+
5, 6, INTERNALOID, opcintype,
+
INT2OID, OIDOID, INTERNALOID,
+
INTERNALOID);
+ break;
+ case GIST_OPCLASSOPT_PROC:
+ ok = check_amproc_signature(procform->amproc,
INTERNALOID, false,
+
2, 2, INTERNALOID, BOOLOID);
break;
default:
ereport(INFO,
@@ -259,7 +268,8 @@ gistvalidate(Oid opclassoid)
(opclassgroup->functionset & (((uint64) 1) << i)) != 0)
continue; /* got it */
if (i == GIST_DISTANCE_PROC || i == GIST_FETCH_PROC ||
- i == GIST_COMPRESS_PROC || i == GIST_DECOMPRESS_PROC)
+ i == GIST_COMPRESS_PROC || i == GIST_DECOMPRESS_PROC ||
+ i == GIST_OPCLASSOPT_PROC)
continue; /* optional methods */
ereport(INFO,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
diff --git a/src/include/access/gist.h b/src/include/access/gist.h
index 6902f4115b..0c57a64369 100644
--- a/src/include/access/gist.h
+++ b/src/include/access/gist.h
@@ -34,7 +34,8 @@
#define GIST_EQUAL_PROC 7
#define GIST_DISTANCE_PROC 8
#define GIST_FETCH_PROC 9
-#define GISTNProcs 9
+#define GIST_OPCLASSOPT_PROC 10
+#define GISTNProcs 10
/*
* Page opaque data in a GiST index page.
diff --git a/src/include/access/gist_private.h
b/src/include/access/gist_private.h
index f80694bf9a..d46b6b89f9 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -96,6 +96,8 @@ typedef struct GISTSTATE
/* Collations to pass to the support functions */
Oid supportCollation[INDEX_MAX_KEYS];
+
+ bytea **opclassoptions; /* parsed opclass-specific options */
} GISTSTATE;
@@ -462,6 +464,8 @@ extern bool gistvalidate(Oid opclassoid);
#define GIST_DEFAULT_FILLFACTOR 90
extern bytea *gistoptions(Datum reloptions, bool validate);
+extern bytea *gistopclassoptions(Relation index, AttrNumber colno,
+ Datum options, bool validate);
extern bool gistproperty(Oid index_oid, int attno,
IndexAMProperty prop, const
char *propname,
bool *res, bool *isnull);
--
2.20.1
>From f056cb2deddde2ff0cbef894be2699710720507b Mon Sep 17 00:00:00 2001
From: Tomas Vondra <to...@2ndquadrant.com>
Date: Sun, 9 Jun 2019 21:18:18 +0200
Subject: [PATCH 03/10] Add opclass parameters to GIN
---
doc/src/sgml/xindex.sgml | 7 ++++++
src/backend/access/gin/ginget.c | 11 +++++-----
src/backend/access/gin/ginlogic.c | 15 +++++++------
src/backend/access/gin/ginscan.c | 6 ++++--
src/backend/access/gin/ginutil.c | 27 +++++++++++++++++------
src/backend/access/gin/ginvalidate.c | 32 +++++++++++++++++-----------
src/backend/utils/adt/selfuncs.c | 6 ++++--
src/include/access/gin.h | 3 ++-
src/include/access/gin_private.h | 5 +++++
9 files changed, 78 insertions(+), 34 deletions(-)
diff --git a/doc/src/sgml/xindex.sgml b/doc/src/sgml/xindex.sgml
index 8c5b5289d7..658ec9bc5a 100644
--- a/doc/src/sgml/xindex.sgml
+++ b/doc/src/sgml/xindex.sgml
@@ -665,6 +665,13 @@
</entry>
<entry>6</entry>
</row>
+ <row>
+ <entry><function>options</function></entry>
+ <entry>
+ parse opclass-specific options (optional)
+ </entry>
+ <entry>7</entry>
+ </row>
</tbody>
</tgroup>
</table>
diff --git a/src/backend/access/gin/ginget.c b/src/backend/access/gin/ginget.c
index b18ae2b3ed..547b7b4762 100644
--- a/src/backend/access/gin/ginget.c
+++ b/src/backend/access/gin/ginget.c
@@ -188,13 +188,13 @@ collectMatchBitmap(GinBtreeData *btree, GinBtreeStack
*stack,
* case cmp < 0 => not match and continue scan
*----------
*/
- cmp =
DatumGetInt32(FunctionCall4Coll(&btree->ginstate->comparePartialFn[attnum - 1],
+ cmp =
DatumGetInt32(FunctionCall5Coll(&btree->ginstate->comparePartialFn[attnum - 1],
btree->ginstate->supportCollation[attnum - 1],
scanEntry->queryKey,
idatum,
UInt16GetDatum(scanEntry->strategy),
-
PointerGetDatum(scanEntry->extra_data)));
-
+
PointerGetDatum(scanEntry->extra_data),
+
PointerGetDatum(btree->ginstate->opclassOptions[attnum -
1])));
if (cmp > 0)
return true;
else if (cmp < 0)
@@ -1508,12 +1508,13 @@ matchPartialInPendingList(GinState *ginstate, Page page,
* case cmp < 0 => not match and continue scan
*----------
*/
- cmp =
DatumGetInt32(FunctionCall4Coll(&ginstate->comparePartialFn[entry->attnum - 1],
+ cmp =
DatumGetInt32(FunctionCall5Coll(&ginstate->comparePartialFn[entry->attnum - 1],
ginstate->supportCollation[entry->attnum - 1],
entry->queryKey,
datum[off - 1],
UInt16GetDatum(entry->strategy),
-
PointerGetDatum(entry->extra_data)));
+
PointerGetDatum(entry->extra_data),
+
PointerGetDatum(ginstate->opclassOptions[entry->attnum - 1])));
if (cmp == 0)
return true;
else if (cmp > 0)
diff --git a/src/backend/access/gin/ginlogic.c
b/src/backend/access/gin/ginlogic.c
index 8f85978972..028b29a8c0 100644
--- a/src/backend/access/gin/ginlogic.c
+++ b/src/backend/access/gin/ginlogic.c
@@ -76,7 +76,7 @@ directBoolConsistentFn(GinScanKey key)
*/
key->recheckCurItem = true;
- return DatumGetBool(FunctionCall8Coll(key->consistentFmgrInfo,
+ return DatumGetBool(FunctionCall9Coll(key->consistentFmgrInfo,
key->collation,
PointerGetDatum(key->entryRes),
UInt16GetDatum(key->strategy),
@@ -85,7 +85,8 @@ directBoolConsistentFn(GinScanKey key)
PointerGetDatum(key->extra_data),
PointerGetDatum(&key->recheckCurItem),
PointerGetDatum(key->queryValues),
-
PointerGetDatum(key->queryCategories)));
+
PointerGetDatum(key->queryCategories),
+
PointerGetDatum(key->opclassOptions)));
}
/*
@@ -94,7 +95,7 @@ directBoolConsistentFn(GinScanKey key)
static GinTernaryValue
directTriConsistentFn(GinScanKey key)
{
- return DatumGetGinTernaryValue(FunctionCall7Coll(
+ return DatumGetGinTernaryValue(FunctionCall8Coll(
key->triConsistentFmgrInfo,
key->collation,
PointerGetDatum(key->entryRes),
@@ -103,7 +104,8 @@ directTriConsistentFn(GinScanKey key)
UInt32GetDatum(key->nuserentries),
PointerGetDatum(key->extra_data),
PointerGetDatum(key->queryValues),
-
PointerGetDatum(key->queryCategories)));
+
PointerGetDatum(key->queryCategories),
+
PointerGetDatum(key->opclassOptions)));
}
/*
@@ -116,7 +118,7 @@ shimBoolConsistentFn(GinScanKey key)
{
GinTernaryValue result;
- result = DatumGetGinTernaryValue(FunctionCall7Coll(
+ result = DatumGetGinTernaryValue(FunctionCall8Coll(
key->triConsistentFmgrInfo,
key->collation,
PointerGetDatum(key->entryRes),
@@ -125,7 +127,8 @@ shimBoolConsistentFn(GinScanKey key)
UInt32GetDatum(key->nuserentries),
PointerGetDatum(key->extra_data),
PointerGetDatum(key->queryValues),
-
PointerGetDatum(key->queryCategories)));
+
PointerGetDatum(key->queryCategories),
+
PointerGetDatum(key->opclassOptions)));
if (result == GIN_MAYBE)
{
key->recheckCurItem = true;
diff --git a/src/backend/access/gin/ginscan.c b/src/backend/access/gin/ginscan.c
index 74d9821ac1..d1384a33d4 100644
--- a/src/backend/access/gin/ginscan.c
+++ b/src/backend/access/gin/ginscan.c
@@ -156,6 +156,7 @@ ginFillScanKey(GinScanOpaque so, OffsetNumber attnum,
key->strategy = strategy;
key->searchMode = searchMode;
key->attnum = attnum;
+ key->opclassOptions = ginstate->opclassOptions[attnum - 1];
ItemPointerSetMin(&key->curItem);
key->curItemMatches = false;
@@ -310,7 +311,7 @@ ginNewScanKey(IndexScanDesc scan)
/* OK to call the extractQueryFn */
queryValues = (Datum *)
-
DatumGetPointer(FunctionCall7Coll(&so->ginstate.extractQueryFn[skey->sk_attno -
1],
+
DatumGetPointer(FunctionCall8Coll(&so->ginstate.extractQueryFn[skey->sk_attno -
1],
so->ginstate.supportCollation[skey->sk_attno - 1],
skey->sk_argument,
PointerGetDatum(&nQueryValues),
@@ -318,7 +319,8 @@ ginNewScanKey(IndexScanDesc scan)
PointerGetDatum(&partial_matches),
PointerGetDatum(&extra_data),
PointerGetDatum(&nullFlags),
-
PointerGetDatum(&searchMode)));
+
PointerGetDatum(&searchMode),
+
PointerGetDatum(so->ginstate.opclassOptions[skey->sk_attno - 1])));
/*
* If bogus searchMode is returned, treat as
GIN_SEARCH_MODE_ALL; note
diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c
index cf9699ad18..03874909b0 100644
--- a/src/backend/access/gin/ginutil.c
+++ b/src/backend/access/gin/ginutil.c
@@ -63,6 +63,7 @@ ginhandler(PG_FUNCTION_ARGS)
amroutine->amcanreturn = NULL;
amroutine->amcostestimate = gincostestimate;
amroutine->amoptions = ginoptions;
+ amroutine->amopclassoptions = ginopclassoptions;
amroutine->amproperty = NULL;
amroutine->ambuildphasename = NULL;
amroutine->amvalidate = ginvalidate;
@@ -96,6 +97,7 @@ initGinState(GinState *state, Relation index)
state->index = index;
state->oneCol = (origTupdesc->natts == 1) ? true : false;
state->origTupdesc = origTupdesc;
+ state->opclassOptions = RelationGetParsedOpclassOptions(index);
for (i = 0; i < origTupdesc->natts; i++)
{
@@ -399,9 +401,10 @@ ginCompareEntries(GinState *ginstate, OffsetNumber attnum,
return 0;
/* both not null, so safe to call the compareFn */
- return DatumGetInt32(FunctionCall2Coll(&ginstate->compareFn[attnum - 1],
+ return DatumGetInt32(FunctionCall3Coll(&ginstate->compareFn[attnum - 1],
ginstate->supportCollation[attnum - 1],
-
a, b));
+
a, b,
+
PointerGetDatum(ginstate->opclassOptions[attnum - 1])));
}
/*
@@ -437,6 +440,7 @@ typedef struct
{
FmgrInfo *cmpDatumFunc;
Oid collation;
+ Datum options;
bool haveDups;
} cmpEntriesArg;
@@ -458,9 +462,10 @@ cmpEntries(const void *a, const void *b, void *arg)
else if (bb->isnull)
res = -1; /* not-NULL "<" NULL */
else
- res = DatumGetInt32(FunctionCall2Coll(data->cmpDatumFunc,
+ res = DatumGetInt32(FunctionCall3Coll(data->cmpDatumFunc,
data->collation,
-
aa->datum, bb->datum));
+
aa->datum, bb->datum,
+
data->options));
/*
* Detect if we have any duplicates. If there are equal keys, qsort
must
@@ -506,11 +511,12 @@ ginExtractEntries(GinState *ginstate, OffsetNumber attnum,
/* OK, call the opclass's extractValueFn */
nullFlags = NULL; /* in case extractValue doesn't
set it */
entries = (Datum *)
-
DatumGetPointer(FunctionCall3Coll(&ginstate->extractValueFn[attnum - 1],
+
DatumGetPointer(FunctionCall4Coll(&ginstate->extractValueFn[attnum - 1],
ginstate->supportCollation[attnum - 1],
value,
PointerGetDatum(nentries),
-
PointerGetDatum(&nullFlags)));
+
PointerGetDatum(&nullFlags),
+
PointerGetDatum(ginstate->opclassOptions[attnum - 1])));
/*
* Generate a placeholder if the item contained no keys.
@@ -553,6 +559,7 @@ ginExtractEntries(GinState *ginstate, OffsetNumber attnum,
arg.cmpDatumFunc = &ginstate->compareFn[attnum - 1];
arg.collation = ginstate->supportCollation[attnum - 1];
+ arg.options = PointerGetDatum(ginstate->opclassOptions[attnum -
1]);
arg.haveDups = false;
qsort_arg(keydata, *nentries, sizeof(keyEntryData),
cmpEntries, (void *) &arg);
@@ -628,6 +635,14 @@ ginoptions(Datum reloptions, bool validate)
return (bytea *) rdopts;
}
+bytea *
+ginopclassoptions(Relation index, AttrNumber colno, Datum attoptions,
+ bool validate)
+{
+ return index_opclass_options_generic(index, colno,
GIN_OPCLASSOPTIONS_PROC,
+
attoptions, validate);
+}
+
/*
* Fetch index's statistical data into *stats
*
diff --git a/src/backend/access/gin/ginvalidate.c
b/src/backend/access/gin/ginvalidate.c
index 63bd7f2adc..a000052f09 100644
--- a/src/backend/access/gin/ginvalidate.c
+++ b/src/backend/access/gin/ginvalidate.c
@@ -108,40 +108,47 @@ ginvalidate(Oid opclassoid)
{
case GIN_COMPARE_PROC:
ok = check_amproc_signature(procform->amproc,
INT4OID, false,
-
2, 2, opckeytype, opckeytype);
+
2, 3, opckeytype, opckeytype,
+
INTERNALOID);
break;
case GIN_EXTRACTVALUE_PROC:
/* Some opclasses omit nullFlags */
ok = check_amproc_signature(procform->amproc,
INTERNALOID, false,
-
2, 3, opcintype, INTERNALOID,
-
INTERNALOID);
+
2, 4, opcintype, INTERNALOID,
+
INTERNALOID, INTERNALOID);
break;
case GIN_EXTRACTQUERY_PROC:
/* Some opclasses omit nullFlags and searchMode
*/
ok = check_amproc_signature(procform->amproc,
INTERNALOID, false,
-
5, 7, opcintype, INTERNALOID,
+
5, 8, opcintype, INTERNALOID,
INT2OID, INTERNALOID, INTERNALOID,
-
INTERNALOID, INTERNALOID);
+
INTERNALOID, INTERNALOID,
+
INTERNALOID);
break;
case GIN_CONSISTENT_PROC:
/* Some opclasses omit queryKeys and nullFlags
*/
ok = check_amproc_signature(procform->amproc,
BOOLOID, false,
-
6, 8, INTERNALOID, INT2OID,
+
6, 9, INTERNALOID, INT2OID,
opcintype, INT4OID,
INTERNALOID, INTERNALOID,
-
INTERNALOID, INTERNALOID);
+
INTERNALOID, INTERNALOID,
+
INTERNALOID);
break;
case GIN_COMPARE_PARTIAL_PROC:
ok = check_amproc_signature(procform->amproc,
INT4OID, false,
-
4, 4, opckeytype, opckeytype,
-
INT2OID, INTERNALOID);
+
4, 5, opckeytype, opckeytype,
+
INT2OID, INTERNALOID, INTERNALOID);
break;
case GIN_TRICONSISTENT_PROC:
ok = check_amproc_signature(procform->amproc,
CHAROID, false,
-
7, 7, INTERNALOID, INT2OID,
+
7, 8, INTERNALOID, INT2OID,
opcintype, INT4OID,
INTERNALOID, INTERNALOID,
-
INTERNALOID);
+
INTERNALOID, INTERNALOID);
+ break;
+ case GIN_OPCLASSOPTIONS_PROC:
+ ok = check_amproc_signature(procform->amproc,
INTERNALOID,
+
false, 2, 2, INTERNALOID, BOOLOID);
break;
default:
ereport(INFO,
@@ -238,7 +245,8 @@ ginvalidate(Oid opclassoid)
if (opclassgroup &&
(opclassgroup->functionset & (((uint64) 1) << i)) != 0)
continue; /* got it */
- if (i == GIN_COMPARE_PROC || i == GIN_COMPARE_PARTIAL_PROC)
+ if (i == GIN_COMPARE_PROC || i == GIN_COMPARE_PARTIAL_PROC ||
+ i == GIN_OPCLASSOPTIONS_PROC)
continue; /* optional method */
if (i == GIN_CONSISTENT_PROC || i == GIN_TRICONSISTENT_PROC)
continue; /* don't need both, see
check below loop */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index d7e3f09f1a..d491bdd7ae 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -135,6 +135,7 @@
#include "utils/lsyscache.h"
#include "utils/pg_locale.h"
#include "utils/rel.h"
+#include "utils/relcache.h"
#include "utils/selfuncs.h"
#include "utils/snapmgr.h"
#include "utils/spccache.h"
@@ -6242,7 +6243,7 @@ gincost_pattern(IndexOptInfo *index, int indexcol,
else
collation = DEFAULT_COLLATION_OID;
- OidFunctionCall7Coll(extractProcOid,
+ OidFunctionCall8Coll(extractProcOid,
collation,
query,
PointerGetDatum(&nentries),
@@ -6250,7 +6251,8 @@ gincost_pattern(IndexOptInfo *index, int indexcol,
PointerGetDatum(&partial_matches),
PointerGetDatum(&extra_data),
PointerGetDatum(&nullFlags),
- PointerGetDatum(&searchMode));
+ PointerGetDatum(&searchMode),
+
PointerGetDatum(index->opclassoptions[indexcol]));
if (nentries <= 0 && searchMode == GIN_SEARCH_MODE_DEFAULT)
{
diff --git a/src/include/access/gin.h b/src/include/access/gin.h
index a8eef5a379..aabfbcd5ce 100644
--- a/src/include/access/gin.h
+++ b/src/include/access/gin.h
@@ -25,7 +25,8 @@
#define GIN_CONSISTENT_PROC 4
#define GIN_COMPARE_PARTIAL_PROC 5
#define GIN_TRICONSISTENT_PROC 6
-#define GINNProcs 6
+#define GIN_OPCLASSOPTIONS_PROC 7
+#define GINNProcs 7
/*
* searchMode settings for extractQueryFn.
diff --git a/src/include/access/gin_private.h b/src/include/access/gin_private.h
index afb3e15721..0f626a7dbb 100644
--- a/src/include/access/gin_private.h
+++ b/src/include/access/gin_private.h
@@ -67,6 +67,8 @@ typedef struct GinState
TupleDesc origTupdesc;
TupleDesc tupdesc[INDEX_MAX_KEYS];
+ bytea **opclassOptions; /* per-index-column opclass options */
+
/*
* Per-index-column opclass support functions
*/
@@ -85,6 +87,8 @@ typedef struct GinState
/* ginutil.c */
extern bytea *ginoptions(Datum reloptions, bool validate);
+extern bytea *ginopclassoptions(Relation index, AttrNumber colno,
+ Datum attoptions, bool validate);
extern void initGinState(GinState *state, Relation index);
extern Buffer GinNewBuffer(Relation index);
extern void GinInitBuffer(Buffer b, uint32 f);
@@ -297,6 +301,7 @@ typedef struct GinScanKeyData
StrategyNumber strategy;
int32 searchMode;
OffsetNumber attnum;
+ bytea *opclassOptions;
/*
* Match status data. curItem is the TID most recently tested (could
be a
--
2.20.1
>From ae97174555cbaf059d2df45df3c80d7e67007fdd Mon Sep 17 00:00:00 2001
From: Tomas Vondra <to...@2ndquadrant.com>
Date: Sun, 9 Jun 2019 21:38:42 +0200
Subject: [PATCH 04/10] Add opclass parameters to GiST tsvector_ops
---
doc/src/sgml/textsearch.sgml | 9 +-
src/backend/utils/adt/tsgistidx.c | 269 ++++++++++++++------------
src/include/catalog/pg_amproc.dat | 5 +-
src/include/catalog/pg_proc.dat | 19 +-
src/test/regress/expected/tsearch.out | 176 +++++++++++++++++
src/test/regress/sql/tsearch.sql | 45 +++++
6 files changed, 392 insertions(+), 131 deletions(-)
diff --git a/doc/src/sgml/textsearch.sgml b/doc/src/sgml/textsearch.sgml
index 40888a4d20..54b796ecf1 100644
--- a/doc/src/sgml/textsearch.sgml
+++ b/doc/src/sgml/textsearch.sgml
@@ -3637,7 +3637,7 @@ SELECT plainto_tsquery('supernovae stars');
<tertiary>text search</tertiary>
</indexterm>
- <literal>CREATE INDEX <replaceable>name</replaceable> ON
<replaceable>table</replaceable> USING GIST
(<replaceable>column</replaceable>);</literal>
+ <literal>CREATE INDEX <replaceable>name</replaceable> ON
<replaceable>table</replaceable> USING GIST (<replaceable>column</replaceable>
[ { DEFAULT | tsvector_ops } (siglen = <replaceable>number</replaceable>) ]
);</literal>
</term>
<listitem>
@@ -3645,6 +3645,8 @@ SELECT plainto_tsquery('supernovae stars');
Creates a GiST (Generalized Search Tree)-based index.
The <replaceable>column</replaceable> can be of <type>tsvector</type> or
<type>tsquery</type> type.
+ Optional integer parameter <literal>siglen</literal> determines
+ signature length in bytes (see below for details).
</para>
</listitem>
</varlistentry>
@@ -3668,7 +3670,10 @@ SELECT plainto_tsquery('supernovae stars');
to check the actual table row to eliminate such false matches.
(<productname>PostgreSQL</productname> does this automatically when needed.)
GiST indexes are lossy because each document is represented in the
- index by a fixed-length signature. The signature is generated by hashing
+ index by a fixed-length signature. Signature length in bytes is determined
+ by the value of the optional integer parameter <literal>siglen</literal>.
+ Default signature length (when <literal>siglen</literal> is not specied) is
+ 124 bytes, maximal length is 484 bytes. The signature is generated by
hashing
each word into a single bit in an n-bit string, with all these bits OR-ed
together to produce an n-bit document signature. When two words hash to
the same bit position there will be a false match. If all words in
diff --git a/src/backend/utils/adt/tsgistidx.c
b/src/backend/utils/adt/tsgistidx.c
index 4f256260fd..91661cf8b8 100644
--- a/src/backend/utils/adt/tsgistidx.c
+++ b/src/backend/utils/adt/tsgistidx.c
@@ -15,6 +15,7 @@
#include "postgres.h"
#include "access/gist.h"
+#include "access/reloptions.h"
#include "access/tuptoaster.h"
#include "port/pg_bitutils.h"
#include "tsearch/ts_utils.h"
@@ -22,17 +23,22 @@
#include "utils/pg_crc.h"
-#define SIGLENINT 31 /* >121 => key will toast, so it will
not work
- * !!! */
+#define SIGLEN_DEFAULT (31 * 4)
+#define SIGLEN_MAX (121 * 4) /* key will toast, so it will
not work !!! */
-#define SIGLEN ( sizeof(int32) * SIGLENINT )
-#define SIGLENBIT (SIGLEN * BITS_PER_BYTE)
+#define SIGLENBIT(siglen) ((siglen) * BITS_PER_BYTE)
+
+/* tsvector_ops opclass options */
+typedef struct GistTsVectorOptions
+{
+ int32 vl_len_; /* varlena header (do not touch
directly!) */
+ int siglen; /* signature length */
+} GistTsVectorOptions;
-typedef char BITVEC[SIGLEN];
typedef char *BITVECP;
-#define LOOPBYTE \
- for(i=0;i<SIGLEN;i++)
+#define LOOPBYTE(siglen) \
+ for (i = 0; i < siglen; i++)
#define GETBYTE(x,i) ( *( (BITVECP)(x) + (int)( (i) / BITS_PER_BYTE ) ) )
#define GETBITBYTE(x,i) ( ((char)(x)) >> (i) & 0x01 )
@@ -40,8 +46,8 @@ typedef char *BITVECP;
#define SETBIT(x,i) GETBYTE(x,i) |= ( 0x01 << ( (i) % BITS_PER_BYTE ) )
#define GETBIT(x,i) ( (GETBYTE(x,i) >> ( (i) % BITS_PER_BYTE )) & 0x01 )
-#define HASHVAL(val) (((unsigned int)(val)) % SIGLENBIT)
-#define HASH(sign, val) SETBIT((sign), HASHVAL(val))
+#define HASHVAL(val, siglen) (((unsigned int)(val)) % SIGLENBIT(siglen))
+#define HASH(sign, val, siglen) SETBIT((sign), HASHVAL(val, siglen))
#define GETENTRY(vec,pos) ((SignTSVector *)
DatumGetPointer((vec)->vector[(pos)].key))
@@ -65,13 +71,14 @@ typedef struct
#define ISALLTRUE(x) ( ((SignTSVector*)(x))->flag & ALLISTRUE )
#define GTHDRSIZE ( VARHDRSZ + sizeof(int32) )
-#define CALCGTSIZE(flag, len) ( GTHDRSIZE + ( ( (flag) & ARRKEY ) ?
((len)*sizeof(int32)) : (((flag) & ALLISTRUE) ? 0 : SIGLEN) ) )
+#define CALCGTSIZE(flag, len) ( GTHDRSIZE + ( ( (flag) & ARRKEY ) ?
((len)*sizeof(int32)) : (((flag) & ALLISTRUE) ? 0 : (len)) ) )
#define GETSIGN(x) ( (BITVECP)( (char*)(x)+GTHDRSIZE ) )
+#define GETSIGLEN(x)( VARSIZE(x) - GTHDRSIZE )
#define GETARR(x) ( (int32*)( (char*)(x)+GTHDRSIZE ) )
#define ARRNELEM(x) ( ( VARSIZE(x) - GTHDRSIZE )/sizeof(int32) )
-static int32 sizebitvec(BITVECP sign);
+static int32 sizebitvec(BITVECP sign, int siglen);
Datum
gtsvectorin(PG_FUNCTION_ARGS)
@@ -102,9 +109,10 @@ gtsvectorout(PG_FUNCTION_ARGS)
sprintf(outbuf, ARROUTSTR, (int) ARRNELEM(key));
else
{
- int cnttrue = (ISALLTRUE(key)) ? SIGLENBIT
: sizebitvec(GETSIGN(key));
+ int siglen = GETSIGLEN(key);
+ int cnttrue = (ISALLTRUE(key)) ?
SIGLENBIT(siglen) : sizebitvec(GETSIGN(key), siglen);
- sprintf(outbuf, SINGOUTSTR, cnttrue, (int) SIGLENBIT - cnttrue);
+ sprintf(outbuf, SINGOUTSTR, cnttrue, (int) SIGLENBIT(siglen) -
cnttrue);
}
PG_FREE_IF_COPY(key, 0);
@@ -148,36 +156,49 @@ uniqueint(int32 *a, int32 l)
}
static void
-makesign(BITVECP sign, SignTSVector *a)
+makesign(BITVECP sign, SignTSVector *a, int siglen)
{
int32 k,
len = ARRNELEM(a);
int32 *ptr = GETARR(a);
- MemSet((void *) sign, 0, sizeof(BITVEC));
+ MemSet((void *) sign, 0, siglen);
for (k = 0; k < len; k++)
- HASH(sign, ptr[k]);
+ HASH(sign, ptr[k], siglen);
+}
+
+static SignTSVector *
+gtsvector_alloc(int flag, int len, BITVECP sign)
+{
+ int size = CALCGTSIZE(flag, len);
+ SignTSVector *res = palloc(size);
+
+ SET_VARSIZE(res, size);
+ res->flag = flag;
+
+ if ((flag & (SIGNKEY | ALLISTRUE)) == SIGNKEY && sign)
+ memcpy(GETSIGN(res), sign, len);
+
+ return res;
}
+
Datum
gtsvector_compress(PG_FUNCTION_ARGS)
{
GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0);
+ int siglen = ((GistTsVectorOptions *)
PG_GETARG_POINTER(1))->siglen;
GISTENTRY *retval = entry;
if (entry->leafkey)
{ /* tsvector */
- SignTSVector *res;
TSVector val = DatumGetTSVector(entry->key);
+ SignTSVector *res = gtsvector_alloc(ARRKEY, val->size, NULL);
int32 len;
int32 *arr;
WordEntry *ptr = ARRPTR(val);
char *words = STRPTR(val);
- len = CALCGTSIZE(ARRKEY, val->size);
- res = (SignTSVector *) palloc(len);
- SET_VARSIZE(res, len);
- res->flag = ARRKEY;
arr = GETARR(res);
len = val->size;
while (len--)
@@ -208,13 +229,9 @@ gtsvector_compress(PG_FUNCTION_ARGS)
/* make signature, if array is too long */
if (VARSIZE(res) > TOAST_INDEX_TARGET)
{
- SignTSVector *ressign;
+ SignTSVector *ressign = gtsvector_alloc(SIGNKEY,
siglen, NULL);
- len = CALCGTSIZE(SIGNKEY, 0);
- ressign = (SignTSVector *) palloc(len);
- SET_VARSIZE(ressign, len);
- ressign->flag = SIGNKEY;
- makesign(GETSIGN(ressign), res);
+ makesign(GETSIGN(ressign), res, siglen);
res = ressign;
}
@@ -226,22 +243,17 @@ gtsvector_compress(PG_FUNCTION_ARGS)
else if (ISSIGNKEY(DatumGetPointer(entry->key)) &&
!ISALLTRUE(DatumGetPointer(entry->key)))
{
- int32 i,
- len;
+ int32 i;
SignTSVector *res;
BITVECP sign = GETSIGN(DatumGetPointer(entry->key));
- LOOPBYTE
+ LOOPBYTE(siglen)
{
if ((sign[i] & 0xff) != 0xff)
PG_RETURN_POINTER(retval);
}
- len = CALCGTSIZE(SIGNKEY | ALLISTRUE, 0);
- res = (SignTSVector *) palloc(len);
- SET_VARSIZE(res, len);
- res->flag = SIGNKEY | ALLISTRUE;
-
+ res = gtsvector_alloc(SIGNKEY | ALLISTRUE, siglen, sign);
retval = (GISTENTRY *) palloc(sizeof(GISTENTRY));
gistentryinit(*retval, PointerGetDatum(res),
entry->rel, entry->page,
@@ -315,12 +327,14 @@ checkcondition_arr(void *checkval, QueryOperand *val,
ExecPhraseData *data)
static bool
checkcondition_bit(void *checkval, QueryOperand *val, ExecPhraseData *data)
{
+ void *key = (SignTSVector *) checkval;
+
/*
* we are not able to find a prefix in signature tree
*/
if (val->prefix)
return true;
- return GETBIT(checkval, HASHVAL(val->valcrc));
+ return GETBIT(GETSIGN(key), HASHVAL(val->valcrc, GETSIGLEN(key)));
}
Datum
@@ -347,7 +361,7 @@ gtsvector_consistent(PG_FUNCTION_ARGS)
/* since signature is lossy, cannot specify CALC_NOT here */
PG_RETURN_BOOL(TS_execute(GETQUERY(query),
- (void *)
GETSIGN(key),
+ key,
TS_EXEC_PHRASE_NO_POS,
checkcondition_bit));
}
@@ -365,7 +379,7 @@ gtsvector_consistent(PG_FUNCTION_ARGS)
}
static int32
-unionkey(BITVECP sbase, SignTSVector *add)
+unionkey(BITVECP sbase, SignTSVector *add, int siglen)
{
int32 i;
@@ -376,7 +390,9 @@ unionkey(BITVECP sbase, SignTSVector *add)
if (ISALLTRUE(add))
return 1;
- LOOPBYTE
+ Assert(GETSIGLEN(add) == siglen);
+
+ LOOPBYTE(siglen)
sbase[i] |= sadd[i];
}
else
@@ -384,7 +400,7 @@ unionkey(BITVECP sbase, SignTSVector *add)
int32 *ptr = GETARR(add);
for (i = 0; i < ARRNELEM(add); i++)
- HASH(sbase, ptr[i]);
+ HASH(sbase, ptr[i], siglen);
}
return 0;
}
@@ -395,30 +411,24 @@ gtsvector_union(PG_FUNCTION_ARGS)
{
GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0);
int *size = (int *) PG_GETARG_POINTER(1);
- BITVEC base;
- int32 i,
- len;
- int32 flag = 0;
- SignTSVector *result;
+ int siglen = ((GistTsVectorOptions *)
PG_GETARG_POINTER(2))->siglen;
+ SignTSVector *result = gtsvector_alloc(SIGNKEY, siglen, NULL);
+ BITVECP base = GETSIGN(result);
+ int32 i;
+
+ memset(base, 0, siglen);
- MemSet((void *) base, 0, sizeof(BITVEC));
for (i = 0; i < entryvec->n; i++)
{
- if (unionkey(base, GETENTRY(entryvec, i)))
+ if (unionkey(base, GETENTRY(entryvec, i), siglen))
{
- flag = ALLISTRUE;
+ result->flag |= ALLISTRUE;
+ SET_VARSIZE(result, CALCGTSIZE(result->flag, siglen));
break;
}
}
- flag |= SIGNKEY;
- len = CALCGTSIZE(flag, 0);
- result = (SignTSVector *) palloc(len);
- *size = len;
- SET_VARSIZE(result, len);
- result->flag = flag;
- if (!ISALLTRUE(result))
- memcpy((void *) GETSIGN(result), (void *) base, sizeof(BITVEC));
+ *size = VARSIZE(result);
PG_RETURN_POINTER(result);
}
@@ -429,6 +439,7 @@ gtsvector_same(PG_FUNCTION_ARGS)
SignTSVector *a = (SignTSVector *) PG_GETARG_POINTER(0);
SignTSVector *b = (SignTSVector *) PG_GETARG_POINTER(1);
bool *result = (bool *) PG_GETARG_POINTER(2);
+ int siglen = ((GistTsVectorOptions *)
PG_GETARG_POINTER(3))->siglen;
if (ISSIGNKEY(a))
{ /* then b also
ISSIGNKEY */
@@ -444,8 +455,10 @@ gtsvector_same(PG_FUNCTION_ARGS)
BITVECP sa = GETSIGN(a),
sb = GETSIGN(b);
+ Assert(GETSIGLEN(a) == siglen && GETSIGLEN(b) ==
siglen);
+
*result = true;
- LOOPBYTE
+ LOOPBYTE(siglen)
{
if (sa[i] != sb[i])
{
@@ -482,19 +495,19 @@ gtsvector_same(PG_FUNCTION_ARGS)
}
static int32
-sizebitvec(BITVECP sign)
+sizebitvec(BITVECP sign, int siglen)
{
- return pg_popcount(sign, SIGLEN);
+ return pg_popcount(sign, siglen);
}
static int
-hemdistsign(BITVECP a, BITVECP b)
+hemdistsign(BITVECP a, BITVECP b, int siglen)
{
int i,
diff,
dist = 0;
- LOOPBYTE
+ LOOPBYTE(siglen)
{
diff = (unsigned char) (a[i] ^ b[i]);
/* Using the popcount functions here isn't likely to win */
@@ -506,17 +519,22 @@ hemdistsign(BITVECP a, BITVECP b)
static int
hemdist(SignTSVector *a, SignTSVector *b)
{
+ int siglena = GETSIGLEN(a);
+ int siglenb = GETSIGLEN(b);
+
if (ISALLTRUE(a))
{
if (ISALLTRUE(b))
return 0;
else
- return SIGLENBIT - sizebitvec(GETSIGN(b));
+ return SIGLENBIT(siglenb) - sizebitvec(GETSIGN(b),
siglenb);
}
else if (ISALLTRUE(b))
- return SIGLENBIT - sizebitvec(GETSIGN(a));
+ return SIGLENBIT(siglena) - sizebitvec(GETSIGN(a), siglena);
- return hemdistsign(GETSIGN(a), GETSIGN(b));
+ Assert(siglena == siglenb);
+
+ return hemdistsign(GETSIGN(a), GETSIGN(b), siglena);
}
Datum
@@ -525,6 +543,7 @@ gtsvector_penalty(PG_FUNCTION_ARGS)
GISTENTRY *origentry = (GISTENTRY *) PG_GETARG_POINTER(0); /* always
ISSIGNKEY */
GISTENTRY *newentry = (GISTENTRY *) PG_GETARG_POINTER(1);
float *penalty = (float *) PG_GETARG_POINTER(2);
+ int siglen = ((GistTsVectorOptions *)
PG_GETARG_POINTER(3))->siglen;
SignTSVector *origval = (SignTSVector *)
DatumGetPointer(origentry->key);
SignTSVector *newval = (SignTSVector *) DatumGetPointer(newentry->key);
BITVECP orig = GETSIGN(origval);
@@ -533,14 +552,22 @@ gtsvector_penalty(PG_FUNCTION_ARGS)
if (ISARRKEY(newval))
{
- BITVEC sign;
+ BITVECP sign = palloc(siglen);
- makesign(sign, newval);
+ makesign(sign, newval, siglen);
if (ISALLTRUE(origval))
- *penalty = ((float) (SIGLENBIT - sizebitvec(sign))) /
(float) (SIGLENBIT + 1);
+ {
+ int siglenbit = SIGLENBIT(siglen);
+
+ *penalty =
+ (float) (siglenbit - sizebitvec(sign, siglen)) /
+ (float) (siglenbit + 1);
+ }
else
- *penalty = hemdistsign(sign, orig);
+ *penalty = hemdistsign(sign, orig, siglen);
+
+ pfree(sign);
}
else
*penalty = hemdist(origval, newval);
@@ -550,19 +577,19 @@ gtsvector_penalty(PG_FUNCTION_ARGS)
typedef struct
{
bool allistrue;
- BITVEC sign;
+ BITVECP sign;
} CACHESIGN;
static void
-fillcache(CACHESIGN *item, SignTSVector *key)
+fillcache(CACHESIGN *item, SignTSVector *key, int siglen)
{
item->allistrue = false;
if (ISARRKEY(key))
- makesign(item->sign, key);
+ makesign(item->sign, key, siglen);
else if (ISALLTRUE(key))
item->allistrue = true;
else
- memcpy((void *) item->sign, (void *) GETSIGN(key),
sizeof(BITVEC));
+ memcpy((void *) item->sign, (void *) GETSIGN(key), siglen);
}
#define WISH_F(a,b,c) (double)( -(double)(((a)-(b))*((a)-(b))*((a)-(b)))*(c) )
@@ -586,19 +613,19 @@ comparecost(const void *va, const void *vb)
static int
-hemdistcache(CACHESIGN *a, CACHESIGN *b)
+hemdistcache(CACHESIGN *a, CACHESIGN *b, int siglen)
{
if (a->allistrue)
{
if (b->allistrue)
return 0;
else
- return SIGLENBIT - sizebitvec(b->sign);
+ return SIGLENBIT(siglen) - sizebitvec(b->sign, siglen);
}
else if (b->allistrue)
- return SIGLENBIT - sizebitvec(a->sign);
+ return SIGLENBIT(siglen) - sizebitvec(a->sign, siglen);
- return hemdistsign(a->sign, b->sign);
+ return hemdistsign(a->sign, b->sign, siglen);
}
Datum
@@ -606,6 +633,7 @@ gtsvector_picksplit(PG_FUNCTION_ARGS)
{
GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0);
GIST_SPLITVEC *v = (GIST_SPLITVEC *) PG_GETARG_POINTER(1);
+ int siglen = ((GistTsVectorOptions *)
PG_GETARG_POINTER(2))->siglen;
OffsetNumber k,
j;
SignTSVector *datum_l,
@@ -625,6 +653,7 @@ gtsvector_picksplit(PG_FUNCTION_ARGS)
BITVECP ptr;
int i;
CACHESIGN *cache;
+ char *cache_sign;
SPLITCOST *costvector;
maxoff = entryvec->n - 2;
@@ -633,16 +662,22 @@ gtsvector_picksplit(PG_FUNCTION_ARGS)
v->spl_right = (OffsetNumber *) palloc(nbytes);
cache = (CACHESIGN *) palloc(sizeof(CACHESIGN) * (maxoff + 2));
- fillcache(&cache[FirstOffsetNumber], GETENTRY(entryvec,
FirstOffsetNumber));
+ cache_sign = palloc(siglen * (maxoff + 2));
+
+ for (j = 0; j < maxoff + 2; j++)
+ cache[j].sign = &cache_sign[siglen * j];
+
+ fillcache(&cache[FirstOffsetNumber], GETENTRY(entryvec,
FirstOffsetNumber),
+ siglen);
for (k = FirstOffsetNumber; k < maxoff; k = OffsetNumberNext(k))
{
for (j = OffsetNumberNext(k); j <= maxoff; j =
OffsetNumberNext(j))
{
if (k == FirstOffsetNumber)
- fillcache(&cache[j], GETENTRY(entryvec, j));
+ fillcache(&cache[j], GETENTRY(entryvec, j),
siglen);
- size_waste = hemdistcache(&(cache[j]), &(cache[k]));
+ size_waste = hemdistcache(&(cache[j]), &(cache[k]),
siglen);
if (size_waste > waste)
{
waste = size_waste;
@@ -664,44 +699,21 @@ gtsvector_picksplit(PG_FUNCTION_ARGS)
}
/* form initial .. */
- if (cache[seed_1].allistrue)
- {
- datum_l = (SignTSVector *) palloc(CALCGTSIZE(SIGNKEY |
ALLISTRUE, 0));
- SET_VARSIZE(datum_l, CALCGTSIZE(SIGNKEY | ALLISTRUE, 0));
- datum_l->flag = SIGNKEY | ALLISTRUE;
- }
- else
- {
- datum_l = (SignTSVector *) palloc(CALCGTSIZE(SIGNKEY, 0));
- SET_VARSIZE(datum_l, CALCGTSIZE(SIGNKEY, 0));
- datum_l->flag = SIGNKEY;
- memcpy((void *) GETSIGN(datum_l), (void *) cache[seed_1].sign,
sizeof(BITVEC));
- }
- if (cache[seed_2].allistrue)
- {
- datum_r = (SignTSVector *) palloc(CALCGTSIZE(SIGNKEY |
ALLISTRUE, 0));
- SET_VARSIZE(datum_r, CALCGTSIZE(SIGNKEY | ALLISTRUE, 0));
- datum_r->flag = SIGNKEY | ALLISTRUE;
- }
- else
- {
- datum_r = (SignTSVector *) palloc(CALCGTSIZE(SIGNKEY, 0));
- SET_VARSIZE(datum_r, CALCGTSIZE(SIGNKEY, 0));
- datum_r->flag = SIGNKEY;
- memcpy((void *) GETSIGN(datum_r), (void *) cache[seed_2].sign,
sizeof(BITVEC));
- }
-
+ datum_l = gtsvector_alloc(SIGNKEY | (cache[seed_1].allistrue ?
ALLISTRUE : 0),
+ siglen,
cache[seed_1].sign);
+ datum_r = gtsvector_alloc(SIGNKEY | (cache[seed_2].allistrue ?
ALLISTRUE : 0),
+ siglen,
cache[seed_2].sign);
union_l = GETSIGN(datum_l);
union_r = GETSIGN(datum_r);
maxoff = OffsetNumberNext(maxoff);
- fillcache(&cache[maxoff], GETENTRY(entryvec, maxoff));
+ fillcache(&cache[maxoff], GETENTRY(entryvec, maxoff), siglen);
/* sort before ... */
costvector = (SPLITCOST *) palloc(sizeof(SPLITCOST) * maxoff);
for (j = FirstOffsetNumber; j <= maxoff; j = OffsetNumberNext(j))
{
costvector[j - 1].pos = j;
- size_alpha = hemdistcache(&(cache[seed_1]), &(cache[j]));
- size_beta = hemdistcache(&(cache[seed_2]), &(cache[j]));
+ size_alpha = hemdistcache(&(cache[seed_1]), &(cache[j]),
siglen);
+ size_beta = hemdistcache(&(cache[seed_2]), &(cache[j]), siglen);
costvector[j - 1].cost = Abs(size_alpha - size_beta);
}
qsort((void *) costvector, maxoff, sizeof(SPLITCOST), comparecost);
@@ -727,36 +739,34 @@ gtsvector_picksplit(PG_FUNCTION_ARGS)
if (ISALLTRUE(datum_l) && cache[j].allistrue)
size_alpha = 0;
else
- size_alpha = SIGLENBIT - sizebitvec(
-
(cache[j].allistrue) ? GETSIGN(datum_l) :
GETSIGN(cache[j].sign)
- );
+ size_alpha = SIGLENBIT(siglen) -
+ sizebitvec((cache[j].allistrue) ?
GETSIGN(datum_l) : GETSIGN(cache[j].sign), siglen);
}
else
- size_alpha = hemdistsign(cache[j].sign,
GETSIGN(datum_l));
+ size_alpha = hemdistsign(cache[j].sign,
GETSIGN(datum_l), siglen);
if (ISALLTRUE(datum_r) || cache[j].allistrue)
{
if (ISALLTRUE(datum_r) && cache[j].allistrue)
size_beta = 0;
else
- size_beta = SIGLENBIT - sizebitvec(
-
(cache[j].allistrue) ? GETSIGN(datum_r) :
GETSIGN(cache[j].sign)
- );
+ size_beta = SIGLENBIT(siglen) -
+ sizebitvec((cache[j].allistrue) ?
GETSIGN(datum_r) : GETSIGN(cache[j].sign), siglen);
}
else
- size_beta = hemdistsign(cache[j].sign,
GETSIGN(datum_r));
+ size_beta = hemdistsign(cache[j].sign,
GETSIGN(datum_r), siglen);
if (size_alpha < size_beta + WISH_F(v->spl_nleft,
v->spl_nright, 0.1))
{
if (ISALLTRUE(datum_l) || cache[j].allistrue)
{
if (!ISALLTRUE(datum_l))
- MemSet((void *) GETSIGN(datum_l), 0xff,
sizeof(BITVEC));
+ MemSet((void *) GETSIGN(datum_l), 0xff,
siglen);
}
else
{
ptr = cache[j].sign;
- LOOPBYTE
+ LOOPBYTE(siglen)
union_l[i] |= ptr[i];
}
*left++ = j;
@@ -767,12 +777,12 @@ gtsvector_picksplit(PG_FUNCTION_ARGS)
if (ISALLTRUE(datum_r) || cache[j].allistrue)
{
if (!ISALLTRUE(datum_r))
- MemSet((void *) GETSIGN(datum_r), 0xff,
sizeof(BITVEC));
+ MemSet((void *) GETSIGN(datum_r), 0xff,
siglen);
}
else
{
ptr = cache[j].sign;
- LOOPBYTE
+ LOOPBYTE(siglen)
union_r[i] |= ptr[i];
}
*right++ = j;
@@ -799,3 +809,20 @@ gtsvector_consistent_oldsig(PG_FUNCTION_ARGS)
{
return gtsvector_consistent(fcinfo);
}
+
+Datum
+gtsvector_options(PG_FUNCTION_ARGS)
+{
+ Datum raw_options = PG_GETARG_DATUM(0);
+ bool validate = PG_GETARG_BOOL(1);
+ relopt_int siglen =
+ { {"siglen", "signature length", 0, 0, 6, RELOPT_TYPE_INT },
+ SIGLEN_DEFAULT, 1, SIGLEN_MAX };
+ relopt_gen *optgen[] = { &siglen.gen };
+ int offsets[] = { offsetof(GistTsVectorOptions,
siglen) };
+ GistTsVectorOptions *options =
+ parseAndFillLocalRelOptions(raw_options, optgen, offsets, 1,
+
sizeof(GistTsVectorOptions), validate);
+
+ PG_RETURN_POINTER(options);
+}
diff --git a/src/include/catalog/pg_amproc.dat
b/src/include/catalog/pg_amproc.dat
index 020b7413cc..5ceee11ab1 100644
--- a/src/include/catalog/pg_amproc.dat
+++ b/src/include/catalog/pg_amproc.dat
@@ -458,7 +458,7 @@
amproc => 'gist_circle_distance' },
{ amprocfamily => 'gist/tsvector_ops', amproclefttype => 'tsvector',
amprocrighttype => 'tsvector', amprocnum => '1',
- amproc => 'gtsvector_consistent(internal,tsvector,int2,oid,internal)' },
+ amproc =>
'gtsvector_consistent(internal,tsvector,int2,oid,internal,internal)' },
{ amprocfamily => 'gist/tsvector_ops', amproclefttype => 'tsvector',
amprocrighttype => 'tsvector', amprocnum => '2',
amproc => 'gtsvector_union' },
@@ -476,6 +476,9 @@
amproc => 'gtsvector_picksplit' },
{ amprocfamily => 'gist/tsvector_ops', amproclefttype => 'tsvector',
amprocrighttype => 'tsvector', amprocnum => '7', amproc => 'gtsvector_same'
},
+{ amprocfamily => 'gist/tsvector_ops', amproclefttype => 'tsvector',
+ amprocrighttype => 'tsvector', amprocnum => '10',
+ amproc => 'gtsvector_options' },
{ amprocfamily => 'gist/tsquery_ops', amproclefttype => 'tsquery',
amprocrighttype => 'tsquery', amprocnum => '1',
amproc => 'gtsquery_consistent(internal,tsquery,int2,oid,internal)' },
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 87335248a0..82b51fc1bb 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8569,30 +8569,35 @@
{ oid => '3648', descr => 'GiST tsvector support',
proname => 'gtsvector_compress', prorettype => 'internal',
- proargtypes => 'internal', prosrc => 'gtsvector_compress' },
+ proargtypes => 'internal internal', prosrc => 'gtsvector_compress' },
{ oid => '3649', descr => 'GiST tsvector support',
proname => 'gtsvector_decompress', prorettype => 'internal',
- proargtypes => 'internal', prosrc => 'gtsvector_decompress' },
+ proargtypes => 'internal internal', prosrc => 'gtsvector_decompress' },
{ oid => '3650', descr => 'GiST tsvector support',
proname => 'gtsvector_picksplit', prorettype => 'internal',
- proargtypes => 'internal internal', prosrc => 'gtsvector_picksplit' },
+ proargtypes => 'internal internal internal', prosrc => 'gtsvector_picksplit'
},
{ oid => '3651', descr => 'GiST tsvector support',
proname => 'gtsvector_union', prorettype => 'gtsvector',
- proargtypes => 'internal internal', prosrc => 'gtsvector_union' },
+ proargtypes => 'internal internal internal', prosrc => 'gtsvector_union' },
{ oid => '3652', descr => 'GiST tsvector support',
proname => 'gtsvector_same', prorettype => 'internal',
- proargtypes => 'gtsvector gtsvector internal', prosrc => 'gtsvector_same' },
+ proargtypes => 'gtsvector gtsvector internal internal',
+ prosrc => 'gtsvector_same' },
{ oid => '3653', descr => 'GiST tsvector support',
proname => 'gtsvector_penalty', prorettype => 'internal',
- proargtypes => 'internal internal internal', prosrc => 'gtsvector_penalty' },
+ proargtypes => 'internal internal internal internal',
+ prosrc => 'gtsvector_penalty' },
{ oid => '3654', descr => 'GiST tsvector support',
proname => 'gtsvector_consistent', prorettype => 'bool',
- proargtypes => 'internal tsvector int2 oid internal',
+ proargtypes => 'internal tsvector int2 oid internal internal',
prosrc => 'gtsvector_consistent' },
{ oid => '3790', descr => 'GiST tsvector support (obsolete)',
proname => 'gtsvector_consistent', prorettype => 'bool',
proargtypes => 'internal gtsvector int4 oid internal',
prosrc => 'gtsvector_consistent_oldsig' },
+{ oid => '3998', descr => 'GiST tsvector support',
+ proname => 'gtsvector_options', prorettype => 'internal',
+ proargtypes => 'internal bool', prosrc => 'gtsvector_options' },
{ oid => '3656', descr => 'GIN tsvector support',
proname => 'gin_extract_tsvector', prorettype => 'internal',
diff --git a/src/test/regress/expected/tsearch.out
b/src/test/regress/expected/tsearch.out
index 6f61acc1ed..a1873dc722 100644
--- a/src/test/regress/expected/tsearch.out
+++ b/src/test/regress/expected/tsearch.out
@@ -260,6 +260,182 @@ SELECT count(*) FROM test_tsvector WHERE a @@
'!no_such_lexeme';
508
(1 row)
+-- Test siglen parameter of GiST tsvector_ops
+CREATE INDEX wowidx1 ON test_tsvector USING gist (a tsvector_ops(foo=1));
+ERROR: unrecognized parameter "foo"
+CREATE INDEX wowidx1 ON test_tsvector USING gist (a tsvector_ops(siglen=0));
+ERROR: value 0 out of bounds for option "siglen"
+DETAIL: Valid values are between "1" and "484".
+CREATE INDEX wowidx1 ON test_tsvector USING gist (a tsvector_ops(siglen=485));
+ERROR: value 485 out of bounds for option "siglen"
+DETAIL: Valid values are between "1" and "484".
+CREATE INDEX wowidx1 ON test_tsvector USING gist (a
tsvector_ops(siglen=100,foo='bar'));
+ERROR: unrecognized parameter "foo"
+CREATE INDEX wowidx1 ON test_tsvector USING gist (a tsvector_ops(siglen=100,
siglen = 200));
+ERROR: parameter "siglen" specified more than once
+CREATE INDEX wowidx2 ON test_tsvector USING gist (a tsvector_ops(siglen=1));
+\d test_tsvector
+ Table "public.test_tsvector"
+ Column | Type | Collation | Nullable | Default
+--------+----------+-----------+----------+---------
+ t | text | | |
+ a | tsvector | | |
+Indexes:
+ "wowidx" gist (a)
+ "wowidx2" gist (a tsvector_ops (siglen='1'))
+
+DROP INDEX wowidx;
+EXPLAIN (costs off) SELECT count(*) FROM test_tsvector WHERE a @@ 'wr|qh';
+ QUERY PLAN
+-------------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on test_tsvector
+ Recheck Cond: (a @@ '''wr'' | ''qh'''::tsquery)
+ -> Bitmap Index Scan on wowidx2
+ Index Cond: (a @@ '''wr'' | ''qh'''::tsquery)
+(5 rows)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ 'wr|qh';
+ count
+-------
+ 158
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ 'wr&qh';
+ count
+-------
+ 17
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ 'eq&yt';
+ count
+-------
+ 6
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ 'eq|yt';
+ count
+-------
+ 98
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ '(eq&yt)|(wr&qh)';
+ count
+-------
+ 23
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ '(eq|yt)&(wr|qh)';
+ count
+-------
+ 39
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ 'w:*|q:*';
+ count
+-------
+ 494
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ any ('{wr,qh}');
+ count
+-------
+ 158
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ 'no_such_lexeme';
+ count
+-------
+ 0
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ '!no_such_lexeme';
+ count
+-------
+ 508
+(1 row)
+
+DROP INDEX wowidx2;
+CREATE INDEX wowidx ON test_tsvector USING gist (a DEFAULT(siglen=484));
+\d test_tsvector
+ Table "public.test_tsvector"
+ Column | Type | Collation | Nullable | Default
+--------+----------+-----------+----------+---------
+ t | text | | |
+ a | tsvector | | |
+Indexes:
+ "wowidx" gist (a tsvector_ops (siglen='484'))
+
+EXPLAIN (costs off) SELECT count(*) FROM test_tsvector WHERE a @@ 'wr|qh';
+ QUERY PLAN
+-------------------------------------------------------------
+ Aggregate
+ -> Bitmap Heap Scan on test_tsvector
+ Recheck Cond: (a @@ '''wr'' | ''qh'''::tsquery)
+ -> Bitmap Index Scan on wowidx
+ Index Cond: (a @@ '''wr'' | ''qh'''::tsquery)
+(5 rows)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ 'wr|qh';
+ count
+-------
+ 158
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ 'wr&qh';
+ count
+-------
+ 17
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ 'eq&yt';
+ count
+-------
+ 6
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ 'eq|yt';
+ count
+-------
+ 98
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ '(eq&yt)|(wr&qh)';
+ count
+-------
+ 23
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ '(eq|yt)&(wr|qh)';
+ count
+-------
+ 39
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ 'w:*|q:*';
+ count
+-------
+ 494
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ any ('{wr,qh}');
+ count
+-------
+ 158
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ 'no_such_lexeme';
+ count
+-------
+ 0
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ '!no_such_lexeme';
+ count
+-------
+ 508
+(1 row)
+
RESET enable_seqscan;
RESET enable_indexscan;
RESET enable_bitmapscan;
diff --git a/src/test/regress/sql/tsearch.sql b/src/test/regress/sql/tsearch.sql
index 637bfb3012..9aed780d0c 100644
--- a/src/test/regress/sql/tsearch.sql
+++ b/src/test/regress/sql/tsearch.sql
@@ -87,6 +87,51 @@ SELECT count(*) FROM test_tsvector WHERE a @@ any
('{wr,qh}');
SELECT count(*) FROM test_tsvector WHERE a @@ 'no_such_lexeme';
SELECT count(*) FROM test_tsvector WHERE a @@ '!no_such_lexeme';
+-- Test siglen parameter of GiST tsvector_ops
+CREATE INDEX wowidx1 ON test_tsvector USING gist (a tsvector_ops(foo=1));
+CREATE INDEX wowidx1 ON test_tsvector USING gist (a tsvector_ops(siglen=0));
+CREATE INDEX wowidx1 ON test_tsvector USING gist (a tsvector_ops(siglen=485));
+CREATE INDEX wowidx1 ON test_tsvector USING gist (a
tsvector_ops(siglen=100,foo='bar'));
+CREATE INDEX wowidx1 ON test_tsvector USING gist (a tsvector_ops(siglen=100,
siglen = 200));
+
+CREATE INDEX wowidx2 ON test_tsvector USING gist (a tsvector_ops(siglen=1));
+
+\d test_tsvector
+
+DROP INDEX wowidx;
+
+EXPLAIN (costs off) SELECT count(*) FROM test_tsvector WHERE a @@ 'wr|qh';
+
+SELECT count(*) FROM test_tsvector WHERE a @@ 'wr|qh';
+SELECT count(*) FROM test_tsvector WHERE a @@ 'wr&qh';
+SELECT count(*) FROM test_tsvector WHERE a @@ 'eq&yt';
+SELECT count(*) FROM test_tsvector WHERE a @@ 'eq|yt';
+SELECT count(*) FROM test_tsvector WHERE a @@ '(eq&yt)|(wr&qh)';
+SELECT count(*) FROM test_tsvector WHERE a @@ '(eq|yt)&(wr|qh)';
+SELECT count(*) FROM test_tsvector WHERE a @@ 'w:*|q:*';
+SELECT count(*) FROM test_tsvector WHERE a @@ any ('{wr,qh}');
+SELECT count(*) FROM test_tsvector WHERE a @@ 'no_such_lexeme';
+SELECT count(*) FROM test_tsvector WHERE a @@ '!no_such_lexeme';
+
+DROP INDEX wowidx2;
+
+CREATE INDEX wowidx ON test_tsvector USING gist (a DEFAULT(siglen=484));
+
+\d test_tsvector
+
+EXPLAIN (costs off) SELECT count(*) FROM test_tsvector WHERE a @@ 'wr|qh';
+
+SELECT count(*) FROM test_tsvector WHERE a @@ 'wr|qh';
+SELECT count(*) FROM test_tsvector WHERE a @@ 'wr&qh';
+SELECT count(*) FROM test_tsvector WHERE a @@ 'eq&yt';
+SELECT count(*) FROM test_tsvector WHERE a @@ 'eq|yt';
+SELECT count(*) FROM test_tsvector WHERE a @@ '(eq&yt)|(wr&qh)';
+SELECT count(*) FROM test_tsvector WHERE a @@ '(eq|yt)&(wr|qh)';
+SELECT count(*) FROM test_tsvector WHERE a @@ 'w:*|q:*';
+SELECT count(*) FROM test_tsvector WHERE a @@ any ('{wr,qh}');
+SELECT count(*) FROM test_tsvector WHERE a @@ 'no_such_lexeme';
+SELECT count(*) FROM test_tsvector WHERE a @@ '!no_such_lexeme';
+
RESET enable_seqscan;
RESET enable_indexscan;
RESET enable_bitmapscan;
--
2.20.1