Attached 3rd version of the patches.
On 20.11.2018 14:15, Nikolay Shaplov wrote:
В письме от 15 ноября 2018 18:26:43 пользователь Nikita Glukhov написал:
Attached 2nd version of the patches. Nothing has changed since March,
this is just a rebased version.
CREATE INDEX syntax and parameters storage method still need discussion.
I've played around a bit with you patch and come to some conclusions, I'd like
to share. They are almost same as those before, but now there are more
details.
Again some issues about storing opclass options in pg_inedx:
1. Having both indoption and indoptions column in pg_index will make someone's
brain explode for sure. If not, it will bring troubles when people start
confusing them.
2. Now I found out how do you store option values for each opclass: text[] of
indoptions in pg_index is not the same as text[] in
reloptions in pg_catalog (and it brings more confusion). In reloption each
member of the array is a single option.
reloptions | {fillfactor=90,autovacuum_enabled=false}
In indoptions, is a whole string of options for one of the indexed attributes,
each array item has all options for one indexed attribute. And this string
needs furthermore parsing, that differs from reloption parsing.
indoptions | {"{numranges=150}","{numranges=160}"}
This brings us to the following issues:
2a. pg_index stores properties of index in general. Properties of each indexed
attributes is stored in pg_attribute table. If we follow this philosophy
it is wrong to add any kind of per-attribute array values into pg_index. These
values should be added to pg_attribute one per each pg_attribute entry.
2b. Since you've chosen method of storage that differs from one that is used
in reloptions, that will lead to two verstions of code that processes the
attributes. And from now on, if we accept this, we should support both of them
and keep them in sync. (I see that you tried to reuse as much code as
possible, but still you added some more that duplicate current reloptions
functionality.)
On 21.11.2018 18:04, Robert Haas wrote:
It seems sensible to have both per-column options and per-index
options. For example, we've got the fastupdate option for GIN, which
is a property of the index as a whole, not any individual column. But
you could also want to specify some column-specific options, which
seems to be what this patch is about, since an opclass is associated
with an individual column. And since an index can have more than one
column, I agree that it seems more appropriate to store this
information in pg_attribute than pg_index.
Ok, I have switched from pg_index.indoptions to pg_attribute.attoptions.
I agree that we should distinguish per-index and per-column options, but they
can also be AM-specific and opclass-specific.
'fastupdate' option for GIN is an example of AM-specific per-index option.
ASC/DESC and NULLS LAST/FIRST are examples of AM-class-specific per-column
options having special SQL syntax. "AM-class-specific" here means "specific
only for the class of AMs that support ordering". Now they are stored as flags
in pg_index.indoption[] but later can be moved to pg_attribute.attoptions.
Don't know should per-column AM-specific and opclass-specific options be stored
in the single column attoptions or have separate columns (like attamoptions and
attopclassoptions). If we will store them together, we can run into name
collisions, but this collisions can be easily resolved using autogenerated
prefixes in option names ("am.foo=bar", "opclass.foo=baz").
And another problem is the options with default values. They may be not
explicitly specified by the user, and in this case in current implementation
nothing will be stored in the catalog because default values can be obtained
from the code. But this will not allow changing of default values without
compatibility issues. So I think it would be better to store both default and
explicitly specified values of all opclass options, but this will require a
major refactoring of current API.
Also I have idea to define list of opclass parameters declaratively when opclass
is created using syntax like the following:
CREATE OPERATOR CLASS name [ (param_name param_type [= default_value] [,...]) ]
FOR TYPE ... AS (
{ OPTIONS function_name ( arg_type [,...] ) /* reloptions => binary */
| OPERATOR ...
} [,...]
)
But if we remember about the min/max values etc. the syntax will definitely
become more complicated, and it will require much more changes in the catalog
(up to creation of new table pg_opclassparams).
In any case, I think it would be nice to give somehow the user the ability to
obtain a list of opclass parameters not only from the documentation.
On 20.11.2018 14:15, Nikolay Shaplov wrote:
I know that relotions code is not really suitable for reusing. This was the
reason why I started solving oplcass option task with rewriting reloptions
code,to make it 100% reusable for any kind of options. So I would offer you
again to join me as a reviewer of that code. This will make opclass code more
simple and more sensible, if my option code is used...
I absolutely agree that reloptions code needs such refactoring, and I am
planning to continue reviewing your patches.
3. Speaking of sensible code
Datum
g_int_options(PG_FUNCTION_ARGS)
{
Datum raw_options = PG_GETARG_DATUM(0);
bool validate = PG_GETARG_BOOL(1);
relopt_int siglen =
{ {"numranges", "number of ranges for compression", 0, 0, 9,
RELOPT_TYPE_INT },
G_INT_NUMRANGES_DEFAULT, 1, G_INT_NUMRANGES_MAX };
relopt_gen *optgen[] = { &siglen.gen };
int offsets[] = { offsetof(IntArrayOptions, num_ranges) };
IntArrayOptions *options =
parseAndFillLocalRelOptions(raw_options, optgen, offsets, 1,
sizeof(IntArrayOptions), validate);
PG_RETURN_POINTER(options);
}
It seems to be not a very nice hack.
What would you do if you would like to have both int, real and boolean options
for one opclass? I do not know how to implement it using this code.
We have only int opclass options for now, but more will come and we should be
ready for it.
Don't understand where is hack.
It's possible to parse options of different types, see the following example:
typedef struct FooOptions
{
int int_opt;
int str_opt_offset;
} FooOptions;
relopt_int int_option = { {"int_opt", "desc", 0, 0, 7, RELOPT_TYPE_INT },
INT_OPT_DEFAULT, INT_OPT_MIN, INT_OPT_MAX };
relopt_string str_option = { {"str_opt", "desc", 0, 0, 7, RELOPT_TYPE_STRING },
0, true, validate_str_option, NULL };
relopt_gen *gen_options[] = { &int_option.gen, &str_option.gen };
int offsets[] = { offsetof(FooOptions, int_opt),
offsetof(FooOptions, str_opt_offset) };
FooOptions *opts =
parseAndFillLocalRelOptions(raw_options, gen_options, offsets, 2,
sizeof(FooOptions), validate);
int int_option_value = opts->int_opt;
char *str_option_value = (char *) opts + opts->str_opt_offset;
Also I can propose to combine this two arrays into the one:
relopt_gen_offset options[] = {
&int_option.gen, offsetof(FooOptions, int_opt),
&str_option.gen, offsetof(FooOptions, str_opt_offset)
};
4. Now getting back to not adding opclass options wherever we can, just
because we can:
4a. For inrarray there were no opclass options tests added. I am sure there
should be one, at least just to make sure it still does not segfault when you
try to set one. And in some cases more tests can be needed. To add and review
them one should be familiar with this opclass internals. So it is good when
different people do it for different opclasses
4b. When you add opclass options instead of hardcoded values, it comes to
setting minimum and maximum value. Why do you choose 1000 as maximum
for num_ranges in gist_int_ops in intarray? Why 1 as minimum? All these
decisions needs careful considerations and can't be done for bunch of
opclasses just in one review.
4c. Patch usually take a long path from prototype to final commit. Do you
really want to update all these opclasses code each time when some changes
in the main opclass option code is made? ;-)
So I would suggest to work only with intarray and add other opclasses later.
I decided to leave only GiST tsvector_ops as an example, because it is an
in-core opclass, not an extension like intarray.
5. You've been asking about SQL grammar
CREATE INDEX idx ON tab USING am (
{expr {opclass | DEFAULT} ({name=value} [,...])} [,...]
);
As for me I do not really care about it. For me all the solutions is
acceptable. But looking at is i came to one notion:
I've never seen before DEFAULT keyword to be used in this way. There is logic
in such usage, but I did not remember any practical usage case.
If there are such usages (I can easily missed it) or if it is somehow
recommended in SQL standard -- let it be. But if none above, I would suggest
to use WITH keyword instead. As it is already used for reloptions. As far as I
remember in my prototype I used "WITH OPTIONS" but did if just because did not
find my way through yac with single "WITH". So ideal way for me would be
create index ON test USING GIST (i WITH (sig_len_int=22));
But as I said it is not thing of importance for me. Just an observation.
"[opclass] WITH OPTIONS (options)" looks too verbose, of course.
"[opclass] WITH (options)" looks acceptable, but it seems to conflict with
exclusion constraints syntax ("index_elem WITH operator").
"opclass (options)" looks the most natural to me. But we still need some
keyword before the parentheses when the opclass is not specified since we
can't distinguish "func_name (func_params)" and "col_name (opclass_options)"
in grammar.
--
Nikita Glukhov
Postgres Professional:http://www.postgrespro.com
The Russian Postgres Company
>From e28cea53ec3d088a3d87093bedc137aa599bd01d Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.glu...@postgrespro.ru>
Date: Wed, 10 Jan 2018 00:46:28 +0300
Subject: [PATCH 1/9] 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 | 126 +++++++++++++++-----------
src/backend/utils/cache/relcache.c | 99 +++++++++++++++++++++
src/include/access/amapi.h | 7 ++
src/include/access/genam.h | 6 ++
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/relation.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, 502 insertions(+), 132 deletions(-)
diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml
index 46f427b..82a3e41 100644
--- a/doc/src/sgml/indices.sgml
+++ b/doc/src/sgml/indices.sgml
@@ -1239,7 +1239,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 ad619cd..1946d3b 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> ]
@@ -279,6 +279,15 @@ CREATE [ UNIQUE ] INDEX [ CONCURRENTLY ] [ [ IF NOT EXISTS ] <replaceable class=
</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>
<para>
@@ -607,8 +616,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 eece89a..87c8e5c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -1040,6 +1040,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.
*
@@ -1094,57 +1148,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 eade540..2b4a7d5 100644
--- a/src/backend/access/index/indexam.c
+++ b/src/backend/access/index/indexam.c
@@ -75,11 +75,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"
#include "utils/tqual.h"
@@ -967,3 +970,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_amroutine->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 11debaa..dc42338 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -647,6 +647,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];
@@ -677,10 +678,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;
@@ -734,7 +736,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;
@@ -772,7 +774,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 8709e8c..f676796 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -25,6 +25,7 @@
#include "access/amapi.h"
#include "access/multixact.h"
+#include "access/reloptions.h"
#include "access/relscan.h"
#include "access/reloptions.h"
#include "access/sysattr.h"
@@ -113,7 +114,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,
@@ -517,7 +519,7 @@ InitializeAttributeOids(Relation indexRelation,
* ----------------------------------------------------------------
*/
static void
-AppendAttributeTuples(Relation indexRelation, int numatts)
+AppendAttributeTuples(Relation indexRelation, int numatts, Datum *attopts)
{
Relation pg_attribute;
CatalogIndexState indstate;
@@ -539,10 +541,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);
@@ -622,6 +625,7 @@ UpdateIndexRelation(Oid indexoid,
else
predDatum = (Datum) 0;
+
/*
* open the system catalog index relation
*/
@@ -957,7 +961,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
@@ -1162,6 +1167,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
@@ -1790,6 +1801,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 = IndexIsReady(indexStruct);
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 462969a..19d52da 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -311,6 +311,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 6c06167..1770081 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -642,6 +642,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;
@@ -1403,7 +1404,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
@@ -1704,6 +1705,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 843ed48..63df4f6 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -5464,7 +5464,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
ReleaseSysCache(typeTuple);
- InsertPgAttributeTuple(attrdesc, &attribute, NULL);
+ InsertPgAttributeTuple(attrdesc, &attribute, (Datum) 0, NULL);
heap_close(attrdesc, RowExclusiveLock);
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index db49968..ad3ff3c 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2856,6 +2856,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 3a084b4..e3dfe6e 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2538,6 +2538,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 f0c3965..a23c508 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2931,6 +2931,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 a570ac0..0dad458 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -361,6 +361,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 2c2208f..e5d3a74 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -485,7 +485,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
@@ -7411,43 +7411,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 4857cae..0ba0bf5 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -452,8 +452,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(ArrayRef *aref, deparse_context *context);
static char *get_relation_name(Oid relid);
@@ -468,6 +466,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 ")
@@ -1197,6 +1196,7 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
oidvector *indcollation;
oidvector *indclass;
int2vector *indoption;
+ Datum *opcoptions = NULL;
StringInfoData buf;
char *str;
char *sep;
@@ -1232,6 +1232,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
*/
@@ -1370,6 +1373,8 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
(!colno || colno == keyno + 1))
{
Oid indcoll;
+ bool has_options =
+ opcoptions && opcoptions[keyno] != (Datum) 0;
/* Add collation, if not default for column */
indcoll = indcollation->values[keyno];
@@ -1378,7 +1383,15 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
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)
@@ -10400,7 +10413,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)
{
@@ -11111,6 +11124,62 @@ string_to_text(char *str)
}
/*
+ * 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.
*/
static char *
@@ -11130,56 +11199,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 c3071db..fa87ff5 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -5192,6 +5192,105 @@ GetRelationPublicationActions(Relation relation)
}
/*
+ * 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
*
* These could have been put into elog.c, but it seems like a module layering
diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h
index 14526a6..de55802 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,
@@ -212,6 +218,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 */
amvalidate_function amvalidate;
ambeginscan_function ambeginscan;
diff --git a/src/include/access/genam.h b/src/include/access/genam.h
index 534fac7..ba2fa92 100644
--- a/src/include/access/genam.h
+++ b/src/include/access/genam.h
@@ -177,6 +177,12 @@ 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 50690b9..7eadb0e 100644
--- a/src/include/access/reloptions.h
+++ b/src/include/access/reloptions.h
@@ -264,12 +264,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 56a341a..a92517c 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -87,6 +87,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 5ed0f40..eddacd4 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -137,6 +137,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?
@@ -165,6 +166,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 e5bdc1c..4a304c0 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -699,6 +699,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/relation.h b/src/include/nodes/relation.h
index 6fd2420..fba493e 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -783,6 +783,7 @@ typedef 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 2217081..0f649c1 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -162,6 +162,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 a99d6b6..79719e2 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 9f9b029..86e6953 100644
--- a/src/include/utils/ruleutils.h
+++ b/src/include/utils/ruleutils.h
@@ -34,5 +34,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 0bd48dc..e420f10 100644
--- a/src/test/regress/expected/btree_index.out
+++ b/src/test/regress/expected/btree_index.out
@@ -179,3 +179,8 @@ 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
diff --git a/src/test/regress/sql/btree_index.sql b/src/test/regress/sql/btree_index.sql
index 21171f7..a3f7d9b 100644
--- a/src/test/regress/sql/btree_index.sql
+++ b/src/test/regress/sql/btree_index.sql
@@ -111,3 +111,7 @@ create index btree_idx_err on btree_test(a) with (vacuum_cleanup_index_scale_fac
-- Simple ALTER INDEX
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));
--
2.7.4
>From 4f8072a2bdb9ca83db9da45df597404a1544faa1 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.glu...@postgrespro.ru>
Date: Mon, 15 Jan 2018 18:58:39 +0300
Subject: [PATCH 2/9] 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 9446f8b..8c5b528 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 8a42eff..905cbbf 100644
--- a/src/backend/access/gist/gist.c
+++ b/src/backend/access/gist/gist.c
@@ -97,6 +97,7 @@ gisthandler(PG_FUNCTION_ARGS)
amroutine->amestimateparallelscan = NULL;
amroutine->aminitparallelscan = NULL;
amroutine->amparallelrescan = NULL;
+ amroutine->amopclassoptions = gistopclassoptions;
PG_RETURN_POINTER(amroutine);
}
@@ -1457,6 +1458,7 @@ initGISTstate(Relation index)
giststate->scanCxt = scanCxt;
giststate->tempCxt = scanCxt; /* caller must change this if needed */
giststate->tupdesc = index->rd_att;
+ giststate->opclassoptions = RelationGetParsedOpclassOptions(index);
for (i = 0; i < index->rd_att->natts; i++)
{
diff --git a/src/backend/access/gist/gistget.c b/src/backend/access/gist/gistget.c
index e4a3786..9302e68 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 a7038cc..96a9290 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 70627e5..567cdf1 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -199,10 +199,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;
}
@@ -268,10 +269,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]));
}
}
@@ -280,10 +282,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;
}
@@ -556,9 +559,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,
@@ -593,9 +597,10 @@ gistFormTuple(GISTSTATE *giststate, Relation r,
/* there may not be a compress function in opclass */
if (OidIsValid(giststate->compressFn[i].fn_oid))
cep = (GISTENTRY *)
- DatumGetPointer(FunctionCall1Coll(&giststate->compressFn[i],
+ DatumGetPointer(FunctionCall2Coll(&giststate->compressFn[i],
giststate->supportCollation[i],
- PointerGetDatum(¢ry)));
+ PointerGetDatum(¢ry),
+ PointerGetDatum(giststate->opclassoptions[i])));
else
cep = ¢ry;
compatt[i] = cep->key;
@@ -624,9 +629,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;
@@ -694,11 +700,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;
@@ -860,6 +867,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 c300e52..c5e1b23 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 827566d..08e7ada 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 36ed724..eedd896 100644
--- a/src/include/access/gist_private.h
+++ b/src/include/access/gist_private.h
@@ -94,6 +94,8 @@ typedef struct GISTSTATE
/* Collations to pass to the support functions */
Oid supportCollation[INDEX_MAX_KEYS];
+
+ bytea **opclassoptions; /* parsed opclass-specific options */
} GISTSTATE;
@@ -436,6 +438,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.7.4
>From 332930913ea6d90e6c61cf7093168ebd4d75cd05 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.glu...@postgrespro.ru>
Date: Tue, 16 Jan 2018 01:50:09 +0300
Subject: [PATCH 3/9] 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 8c5b528..658ec9b 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 8466d94..5eb47e0 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 2c42d1a..5ec3931 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 8ade431..6594a29 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 d7696a1..d4f5b4c 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->amvalidate = ginvalidate;
amroutine->ambeginscan = ginbeginscan;
@@ -95,6 +96,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++)
{
@@ -403,9 +405,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])));
}
/*
@@ -441,6 +444,7 @@ typedef struct
{
FmgrInfo *cmpDatumFunc;
Oid collation;
+ Datum options;
bool haveDups;
} cmpEntriesArg;
@@ -462,9 +466,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
@@ -510,11 +515,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.
@@ -557,6 +563,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);
@@ -632,6 +639,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 1922260..4f87d12 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 ffca0fe..67431c7 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -139,6 +139,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"
@@ -7474,7 +7475,7 @@ gincost_pattern(IndexOptInfo *index, int indexcol,
else
collation = DEFAULT_COLLATION_OID;
- OidFunctionCall7Coll(extractProcOid,
+ OidFunctionCall8Coll(extractProcOid,
collation,
query,
PointerGetDatum(&nentries),
@@ -7482,7 +7483,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 3d8a130..20ce792 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 81bf873..6fecfe0 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);
@@ -296,6 +300,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.7.4
>From 3ecdef58eb4e3413f1a700179d6d38b3d7637d54 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.glu...@postgrespro.ru>
Date: Tue, 16 Jan 2018 18:31:23 +0300
Subject: [PATCH 4/9] 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 ecebade..92abf6e 100644
--- a/doc/src/sgml/textsearch.sgml
+++ b/doc/src/sgml/textsearch.sgml
@@ -3635,7 +3635,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>
@@ -3643,6 +3643,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>
@@ -3666,7 +3668,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 2d9ecc4..462791a 100644
--- a/src/backend/utils/adt/tsgistidx.c
+++ b/src/backend/utils/adt/tsgistidx.c
@@ -15,23 +15,29 @@
#include "postgres.h"
#include "access/gist.h"
+#include "access/reloptions.h"
#include "access/tuptoaster.h"
#include "tsearch/ts_utils.h"
#include "utils/builtins.h"
#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 )
@@ -39,8 +45,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))
@@ -64,9 +70,10 @@ 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) )
@@ -90,7 +97,7 @@ static const uint8 number_of_ones[256] = {
4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8
};
-static int32 sizebitvec(BITVECP sign);
+static int32 sizebitvec(BITVECP sign, int siglen);
Datum
gtsvectorin(PG_FUNCTION_ARGS)
@@ -121,9 +128,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);
@@ -167,36 +175,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--)
@@ -227,13 +248,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;
}
@@ -245,22 +262,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,
@@ -334,12 +346,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
@@ -366,7 +380,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));
}
@@ -384,7 +398,7 @@ gtsvector_consistent(PG_FUNCTION_ARGS)
}
static int32
-unionkey(BITVECP sbase, SignTSVector *add)
+unionkey(BITVECP sbase, SignTSVector *add, int siglen)
{
int32 i;
@@ -395,7 +409,9 @@ unionkey(BITVECP sbase, SignTSVector *add)
if (ISALLTRUE(add))
return 1;
- LOOPBYTE
+ Assert(GETSIGLEN(add) == siglen);
+
+ LOOPBYTE(siglen)
sbase[i] |= sadd[i];
}
else
@@ -403,7 +419,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;
}
@@ -414,30 +430,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);
}
@@ -448,6 +458,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 */
@@ -463,8 +474,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])
{
@@ -501,24 +514,24 @@ gtsvector_same(PG_FUNCTION_ARGS)
}
static int32
-sizebitvec(BITVECP sign)
+sizebitvec(BITVECP sign, int siglen)
{
int32 size = 0,
i;
- LOOPBYTE
+ LOOPBYTE(siglen)
size += number_of_ones[(unsigned char) sign[i]];
return size;
}
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]);
dist += number_of_ones[diff];
@@ -529,17 +542,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
@@ -548,6 +566,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);
@@ -556,14 +575,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);
@@ -573,19 +600,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) )
@@ -609,19 +636,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
@@ -629,6 +656,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,
@@ -648,6 +676,7 @@ gtsvector_picksplit(PG_FUNCTION_ARGS)
BITVECP ptr;
int i;
CACHESIGN *cache;
+ char *cache_sign;
SPLITCOST *costvector;
maxoff = entryvec->n - 2;
@@ -656,16 +685,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;
@@ -687,44 +722,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);
@@ -750,36 +762,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;
@@ -790,12 +800,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;
@@ -822,3 +832,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 0ef2c08..c1ee3db 100644
--- a/src/include/catalog/pg_amproc.dat
+++ b/src/include/catalog/pg_amproc.dat
@@ -450,7 +450,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' },
@@ -468,6 +468,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 034a41e..110d4a5 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8435,30 +8435,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 => '3996', 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 b088ff0..bcadcf7 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 637bfb3..9aed780 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.7.4