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(&centry)));
+                                                                               
                          PointerGetDatum(&centry),
+                                                                               
                          PointerGetDatum(giststate->opclassoptions[i])));
                        else
                                cep = &centry;
                        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

Reply via email to