Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.89) (envelope-from ) id 1hal1z-0003o9-Kz for pgsql-hackers@arkaria.postgresql.org; Tue, 11 Jun 2019 17:58:00 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.89) (envelope-from ) id 1hal1y-0000qc-Ge for pgsql-hackers@arkaria.postgresql.org; Tue, 11 Jun 2019 17:57:58 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.89) (envelope-from ) id 1hal1y-0000qV-3D for pgsql-hackers@lists.postgresql.org; Tue, 11 Jun 2019 17:57:58 +0000 Received: from mail-wr1-x441.google.com ([2a00:1450:4864:20::441]) by magus.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.89) (envelope-from ) id 1hal1t-00088O-8N for pgsql-hackers@postgresql.org; Tue, 11 Jun 2019 17:57:57 +0000 Received: by mail-wr1-x441.google.com with SMTP id n4so13994572wrw.13 for ; Tue, 11 Jun 2019 10:57:53 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=2ndquadrant-com.20150623.gappssmtp.com; s=20150623; h=date:from:to:cc:subject:message-id:references:mime-version :content-disposition:in-reply-to:user-agent; bh=tgUxus+Sjf4p9MDllXPLxkCFdIn7eCw5hHqzOfq/lsQ=; b=wQ0NKzRZbyytyD2f9SAR7mkFVsLC+5uBKbBDqjumen/VZn+ugp6aQzyDFTQh23fhpH EW61v8otViXv8q7j3lYVTGqnfgyBLROSFfKsdAtSHjrmeImt90cxnumhm/rYUNdQGdMr XRKSJ/u5ZE3188jJps/ud0dUOHdDdkVmMbM0ZHpp0qAVm5Yxqvb4JlGzZU884DPc5vXl Jt/nD6G2Mlrc/WExR3lBBRUVTP5N52Q8oOl8DVF1hrIsUTlVZzRDPWCzix4/KuB14YIT 9xrD/T5r28spc/Yc/QTuMpD/wh4SwD8q3jzEjYM5pfSSMAnpQgm7DWZHIsDCKe44gxg/ O3Yw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:date:from:to:cc:subject:message-id:references :mime-version:content-disposition:in-reply-to:user-agent; bh=tgUxus+Sjf4p9MDllXPLxkCFdIn7eCw5hHqzOfq/lsQ=; b=KqbHBOdHlJxDgLqz9qKFxs0ryTnrKBDQhmv7Jv13QelWh5kOus6iCfG4OqEuRR9iAN luE509DW/nIF9JGzd3AQ1alqySebIXtvmm16j6LdXk1Ry/y1JGpR5rOYE5HVtCJMPon7 oIpRo0HgjnLMOrrGBu5CSKETuIYdO1pFriZQAnRjocjlKR6l+xzs0hvuL0fmcl2Fo4jM zrKTgaRJWBQs6Uv9MNy9NDBL0TQ0w5FoMujlLptDnum4QMEsyAewwibRp9d4bJmZyuxh gnE9/qjt1/Ar8ykayqC0np+lCMl7nPoETIpZp1FoBD8e4gU6yF5rI7vttszcQrYannSu XzDw== X-Gm-Message-State: APjAAAWpx/e78HKqbe1Nge+SeB2v8qpp56ACeeUd5BY0/o9VJe4jRBtN IUmWsg6SCWbDPzny2sDrPS8GYw== X-Google-Smtp-Source: APXvYqytZ+uERh5I7ElhUrdRtlLlf3hyifLsRUwtaKeJ/NVPbT5bCkFhPxgKkaY99TFlLGq1KXyMEw== X-Received: by 2002:adf:db46:: with SMTP id f6mr35285075wrj.330.1560275871363; Tue, 11 Jun 2019 10:57:51 -0700 (PDT) Received: from localhost (static-84-42-175-93.net.upcbroadband.cz. [84.42.175.93]) by smtp.gmail.com with ESMTPSA id q20sm39276240wra.36.2019.06.11.10.57.49 (version=TLS1_3 cipher=AEAD-AES256-GCM-SHA384 bits=256/256); Tue, 11 Jun 2019 10:57:50 -0700 (PDT) Date: Tue, 11 Jun 2019 19:57:45 +0200 From: Tomas Vondra To: Robert Haas Cc: Tom Lane , Nikita Glukhov , Nikolay Shaplov , PostgreSQL Hackers , "pgsql-hackers@postgresql.org" , Oleg Bartunov Subject: Re: [PATCH] Opclass parameters Message-ID: <20190611175745.wwb6jywbch2pz23f@development> References: <6382cb29-83d8-90f2-1dbb-b4ac606cbf1f@postgrespro.ru> <71c6164e-59ad-e0c4-76c5-b1dfdd68eee3@postgrespro.ru> <12473406.MrkCg5crFA@x200m> <6105d858-a060-dec7-48a1-016ac24884cf@postgrespro.ru> <21380.1544115339@sss.pgh.pa.us> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="kw6iywuasi5tjnlh" Content-Disposition: inline In-Reply-To: User-Agent: NeoMutt/20180716-1444-295967-dirty List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Precedence: bulk --kw6iywuasi5tjnlh Content-Type: text/plain; charset=us-ascii; format=flowed Content-Disposition: inline Hi, while rebasing the patch series [1] adding bloom/multi-minmax BRIN opclasses, I've decided to also rebase it on top of this patch, because it needs the opclass parameters. So I had to rebase this too - it went mostly fine, with reasonably limited bitrot. The rebased patch series is attached. Using this patch series in [1] was mostly smooth, I only have two minor comments at this point: 1) We need a better infrastructure to parse opclass parameters. For example the gtsvector_options does this: Datum gtsvector_options(PG_FUNCTION_ARGS) { Datum raw_options = PG_GETARG_DATUM(0); bool validate = PG_GETARG_BOOL(1); relopt_int siglen = { {"siglen", "signature length", 0, 0, 6, RELOPT_TYPE_INT }, SIGLEN_DEFAULT, 1, SIGLEN_MAX }; relopt_gen *optgen[] = { &siglen.gen }; int offsets[] = { offsetof(GistTsVectorOptions, siglen) }; GistTsVectorOptions *options = parseAndFillLocalRelOptions(raw_options, optgen, offsets, 1, sizeof(GistTsVectorOptions), validate); PG_RETURN_POINTER(options); } So in other words, it builds all the various pieces (relopts, optgen, offsets, lengths etc.) manually, which is really error-prone and difficult to maintain. We need to make it simpler - ideally as simple as defining a custom GUC, or just an array of relopt_* structs. 2) The 0001 part does this in index_opclass_options_generic: get_opclass_name(opclass, InvalidOid, &str); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("operator class \"%s\" has no options", opclassname.data))); But that's a bit broken, because get_opclass_name() appends the opclass name to 'str', but with a space at the beginning. So this produces messages like ERROR: operator class " int4_bloom_ops" has no options which is not right. I haven't checked if a better function already exists, or whether we need to implement it. regards https://www.postgresql.org/message-id/flat/c1138ead-7668-f0e1-0638-c3be3237e812%402ndquadrant.com -- Tomas Vondra http://www.2ndQuadrant.com PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services --kw6iywuasi5tjnlh Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename="0001-Add-opclass-parameters-20190611.patch" From 436eca7a1dd45b78b50c3868fd4be518b814cecd Mon Sep 17 00:00:00 2001 From: Tomas Vondra Date: Sun, 9 Jun 2019 20:59:01 +0200 Subject: [PATCH 01/10] Add opclass parameters --- doc/src/sgml/indices.sgml | 2 +- doc/src/sgml/ref/create_index.sgml | 16 ++- src/backend/access/common/reloptions.c | 142 +++++++++++++++------- src/backend/access/index/indexam.c | 81 ++++++++++++ src/backend/catalog/heap.c | 8 +- src/backend/catalog/index.c | 23 +++- src/backend/catalog/toasting.c | 1 + src/backend/commands/indexcmds.c | 17 ++- src/backend/commands/tablecmds.c | 2 +- src/backend/nodes/copyfuncs.c | 1 + src/backend/nodes/equalfuncs.c | 1 + src/backend/nodes/outfuncs.c | 1 + src/backend/optimizer/util/plancat.c | 4 + src/backend/parser/gram.y | 72 +++++++---- src/backend/utils/adt/ruleutils.c | 128 +++++++++++-------- src/backend/utils/cache/relcache.c | 99 +++++++++++++++ src/include/access/amapi.h | 7 ++ src/include/access/genam.h | 5 + src/include/access/reloptions.h | 5 + src/include/catalog/heap.h | 1 + src/include/nodes/execnodes.h | 2 + src/include/nodes/parsenodes.h | 1 + src/include/nodes/pathnodes.h | 1 + src/include/utils/rel.h | 1 + src/include/utils/relcache.h | 3 + src/include/utils/ruleutils.h | 2 + src/test/regress/expected/btree_index.out | 5 + src/test/regress/sql/btree_index.sql | 4 + 28 files changed, 503 insertions(+), 132 deletions(-) diff --git a/doc/src/sgml/indices.sgml b/doc/src/sgml/indices.sgml index 95c0a1926c..ea3acea88e 100644 --- a/doc/src/sgml/indices.sgml +++ b/doc/src/sgml/indices.sgml @@ -1253,7 +1253,7 @@ SELECT target FROM tests WHERE subject = 'some-subject' AND success; An index definition can specify an operator class for each column of an index. -CREATE INDEX name ON table (column opclass sort options , ...); +CREATE INDEX name ON table (column opclass [ ( opclass_options ) ] sort options , ...); The operator class identifies the operators to be used by the index for that column. For example, a B-tree index on the type int4 diff --git a/doc/src/sgml/ref/create_index.sgml b/doc/src/sgml/ref/create_index.sgml index 629a31ef79..61401f3645 100644 --- a/doc/src/sgml/ref/create_index.sgml +++ b/doc/src/sgml/ref/create_index.sgml @@ -22,7 +22,7 @@ PostgreSQL documentation CREATE [ UNIQUE ] INDEX [ CONCURRENTLY ] [ [ IF NOT EXISTS ] name ] ON [ ONLY ] table_name [ USING method ] - ( { column_name | ( expression ) } [ COLLATE collation ] [ opclass ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ] [, ...] ) + ( { column_name | ( expression ) } [ COLLATE collation ] { opclass | DEFAULT } [ ( opclass_parameter = value [, ... ] ) ] [ ASC | DESC ] [ NULLS { FIRST | LAST } ] [, ...] ) [ INCLUDE ( column_name [, ...] ) ] [ WITH ( storage_parameter = value [, ... ] ) ] [ TABLESPACE tablespace_name ] @@ -278,6 +278,15 @@ CREATE [ UNIQUE ] INDEX [ CONCURRENTLY ] [ [ IF NOT EXISTS ] + + opclass_parameter + + + The name of an operator class parameter. See below for details. + + + + ASC @@ -646,8 +655,9 @@ Indexes: - An operator class can be specified for each - column of an index. The operator class identifies the operators to be + An operator class with its optional parameters + can be specified for each column of an index. + The operator class identifies the operators to be used by the index for that column. For example, a B-tree index on four-byte integers would use the int4_ops class; this operator class includes comparison functions for four-byte diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index de06c92574..abc0082ce7 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -1051,6 +1051,60 @@ extractRelOptions(HeapTuple tuple, TupleDesc tupdesc, return options; } +static void +parseRelOptionsInternal(Datum options, bool validate, + relopt_value *reloptions, int numoptions) +{ + ArrayType *array = DatumGetArrayTypeP(options); + Datum *optiondatums; + int noptions; + int i; + + deconstruct_array(array, TEXTOID, -1, false, 'i', + &optiondatums, NULL, &noptions); + + for (i = 0; i < noptions; i++) + { + char *text_str = VARDATA(optiondatums[i]); + int text_len = VARSIZE(optiondatums[i]) - VARHDRSZ; + int j; + + /* Search for a match in reloptions */ + for (j = 0; j < numoptions; j++) + { + int kw_len = reloptions[j].gen->namelen; + + if (text_len > kw_len && text_str[kw_len] == '=' && + strncmp(text_str, reloptions[j].gen->name, kw_len) == 0) + { + parse_one_reloption(&reloptions[j], text_str, text_len, + validate); + break; + } + } + + if (j >= numoptions && validate) + { + char *s; + char *p; + + s = TextDatumGetCString(optiondatums[i]); + p = strchr(s, '='); + if (p) + *p = '\0'; + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unrecognized parameter \"%s\"", s))); + } + } + + /* It's worth avoiding memory leaks in this function */ + pfree(optiondatums); + + if (((void *) array) != DatumGetPointer(options)) + pfree(array); +} + /* * Interpret reloptions that are given in text-array format. * @@ -1105,57 +1159,61 @@ parseRelOptions(Datum options, bool validate, relopt_kind kind, /* Done if no options */ if (PointerIsValid(DatumGetPointer(options))) - { - ArrayType *array = DatumGetArrayTypeP(options); - Datum *optiondatums; - int noptions; + parseRelOptionsInternal(options, validate, reloptions, numoptions); - deconstruct_array(array, TEXTOID, -1, false, 'i', - &optiondatums, NULL, &noptions); + *numrelopts = numoptions; + return reloptions; +} - for (i = 0; i < noptions; i++) - { - char *text_str = VARDATA(optiondatums[i]); - int text_len = VARSIZE(optiondatums[i]) - VARHDRSZ; - int j; +/* Parse local unregistered options. */ +relopt_value * +parseLocalRelOptions(Datum options, bool validate, + relopt_gen *optgen[], int nopts) +{ + relopt_value *values = palloc(sizeof(*values) * nopts); + int i; - /* Search for a match in reloptions */ - for (j = 0; j < numoptions; j++) - { - int kw_len = reloptions[j].gen->namelen; + for (i = 0; i < nopts; i++) + { + values[i].gen = optgen[i]; + values[i].isset = false; + } - if (text_len > kw_len && text_str[kw_len] == '=' && - strncmp(text_str, reloptions[j].gen->name, kw_len) == 0) - { - parse_one_reloption(&reloptions[j], text_str, text_len, - validate); - break; - } - } + if (options != (Datum) 0) + parseRelOptionsInternal(options, validate, values, nopts); - if (j >= numoptions && validate) - { - char *s; - char *p; + return values; +} - s = TextDatumGetCString(optiondatums[i]); - p = strchr(s, '='); - if (p) - *p = '\0'; - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("unrecognized parameter \"%s\"", s))); - } - } +/* + * Parse local options, allocate a bytea struct that's of the specified + * 'base_size' plus any extra space that's needed for string variables, + * fill its option's fields located at the given offsets and return it. + */ +void * +parseAndFillLocalRelOptions(Datum options, relopt_gen *optgen[], int offsets[], + int noptions, size_t base_size, bool validate) +{ + relopt_parse_elt *elems = palloc(sizeof(*elems) * noptions); + relopt_value *vals; + void *opts; + int i; - /* It's worth avoiding memory leaks in this function */ - pfree(optiondatums); - if (((void *) array) != DatumGetPointer(options)) - pfree(array); + for (i = 0; i < noptions; i++) + { + elems[i].optname = optgen[i]->name; + elems[i].opttype = optgen[i]->type; + elems[i].offset = offsets[i]; } - *numrelopts = numoptions; - return reloptions; + vals = parseLocalRelOptions(options, validate, optgen, noptions); + opts = allocateReloptStruct(base_size, vals, noptions); + fillRelOptions(opts, base_size, vals, noptions, validate, elems, noptions); + + if (elems) + pfree(elems); + + return opts; } /* diff --git a/src/backend/access/index/indexam.c b/src/backend/access/index/indexam.c index aefdd2916d..6cc76914de 100644 --- a/src/backend/access/index/indexam.c +++ b/src/backend/access/index/indexam.c @@ -51,11 +51,14 @@ #include "access/xlog.h" #include "catalog/index.h" #include "catalog/pg_type.h" +#include "commands/defrem.h" #include "pgstat.h" #include "storage/bufmgr.h" #include "storage/lmgr.h" #include "storage/predicate.h" +#include "utils/ruleutils.h" #include "utils/snapmgr.h" +#include "utils/syscache.h" /* ---------------------------------------------------------------- @@ -905,3 +908,81 @@ index_store_float8_orderby_distances(IndexScanDesc scan, Oid *orderByTypes, } } } + +/* ---------------- + * index_opclass_options + * + * Parse opclass-specific options for index column. + * ---------------- + */ +bytea * +index_opclass_options(Relation relation, AttrNumber attnum, Datum attoptions, + bool validate) +{ + amopclassoptions_function amopclassoptions = + relation->rd_indam->amopclassoptions; + + if (!amopclassoptions) + { + if (validate && PointerIsValid(DatumGetPointer(attoptions))) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("access method \"%s\" does not support opclass options ", + get_am_name(relation->rd_rel->relam)))); + + return NULL; + } + + return amopclassoptions(relation, attnum, attoptions, validate); +} + +/* ---------------- + * index_opclass_options_generic + * + * Parse opclass options for index column using the specified support + * function 'procnum' of column's opclass. + * ---------------- + */ +bytea * +index_opclass_options_generic(Relation indrel, AttrNumber attnum, + uint16 procnum, Datum attoptions, bool validate) +{ + Oid procid = index_getprocid(indrel, attnum, procnum); + FmgrInfo *procinfo; + + if (!OidIsValid(procid)) + { + StringInfoData opclassname; + Oid opclass; + Datum indclassDatum; + oidvector *indclass; + bool isnull; + + if (!DatumGetPointer(attoptions)) + return NULL; /* ok, no options, no procedure */ + + /* + * Report an error if the opclass's options-parsing procedure does not + * exist but the opclass options are specified. + */ + indclassDatum = SysCacheGetAttr(INDEXRELID, indrel->rd_indextuple, + Anum_pg_index_indclass, &isnull); + Assert(!isnull); + indclass = (oidvector *) DatumGetPointer(indclassDatum); + opclass = indclass->values[attnum - 1]; + + initStringInfo(&opclassname); + get_opclass_name(opclass, InvalidOid, &opclassname); + + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("operator class \"%s\" has no options", + opclassname.data))); + } + + procinfo = index_getprocinfo(indrel, attnum, procnum); + + return (bytea *) DatumGetPointer(FunctionCall2(procinfo, + attoptions, + BoolGetDatum(validate))); +} diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index 86820eecfc..4a42cb1523 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -694,6 +694,7 @@ CheckAttributeType(const char *attname, void InsertPgAttributeTuple(Relation pg_attribute_rel, Form_pg_attribute new_attribute, + Datum attoptions, CatalogIndexState indstate) { Datum values[Natts_pg_attribute]; @@ -725,10 +726,11 @@ InsertPgAttributeTuple(Relation pg_attribute_rel, values[Anum_pg_attribute_attislocal - 1] = BoolGetDatum(new_attribute->attislocal); values[Anum_pg_attribute_attinhcount - 1] = Int32GetDatum(new_attribute->attinhcount); values[Anum_pg_attribute_attcollation - 1] = ObjectIdGetDatum(new_attribute->attcollation); + values[Anum_pg_attribute_attoptions - 1] = attoptions; /* start out with empty permissions and empty options */ nulls[Anum_pg_attribute_attacl - 1] = true; - nulls[Anum_pg_attribute_attoptions - 1] = true; + nulls[Anum_pg_attribute_attoptions - 1] = attoptions == (Datum) 0; nulls[Anum_pg_attribute_attfdwoptions - 1] = true; nulls[Anum_pg_attribute_attmissingval - 1] = true; @@ -782,7 +784,7 @@ AddNewAttributeTuples(Oid new_rel_oid, /* Make sure this is OK, too */ attr->attstattarget = -1; - InsertPgAttributeTuple(rel, attr, indstate); + InsertPgAttributeTuple(rel, attr, (Datum) 0, indstate); /* Add dependency info */ myself.classId = RelationRelationId; @@ -820,7 +822,7 @@ AddNewAttributeTuples(Oid new_rel_oid, /* Fill in the correct relation OID in the copied tuple */ attStruct.attrelid = new_rel_oid; - InsertPgAttributeTuple(rel, &attStruct, indstate); + InsertPgAttributeTuple(rel, &attStruct, (Datum) 0, indstate); } } diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index d2e4f53a80..125174852b 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -26,6 +26,7 @@ #include "access/amapi.h" #include "access/heapam.h" #include "access/multixact.h" +#include "access/reloptions.h" #include "access/relscan.h" #include "access/sysattr.h" #include "access/tableam.h" @@ -106,7 +107,8 @@ static TupleDesc ConstructTupleDescriptor(Relation heapRelation, Oid *classObjectId); static void InitializeAttributeOids(Relation indexRelation, int numatts, Oid indexoid); -static void AppendAttributeTuples(Relation indexRelation, int numatts); +static void AppendAttributeTuples(Relation indexRelation, int numatts, + Datum *attopts); static void UpdateIndexRelation(Oid indexoid, Oid heapoid, Oid parentIndexId, IndexInfo *indexInfo, @@ -486,7 +488,7 @@ InitializeAttributeOids(Relation indexRelation, * ---------------------------------------------------------------- */ static void -AppendAttributeTuples(Relation indexRelation, int numatts) +AppendAttributeTuples(Relation indexRelation, int numatts, Datum *attopts) { Relation pg_attribute; CatalogIndexState indstate; @@ -508,10 +510,11 @@ AppendAttributeTuples(Relation indexRelation, int numatts) for (i = 0; i < numatts; i++) { Form_pg_attribute attr = TupleDescAttr(indexTupDesc, i); + Datum attoptions = attopts ? attopts[i] : (Datum) 0; Assert(attr->attnum == i + 1); - InsertPgAttributeTuple(pg_attribute, attr, indstate); + InsertPgAttributeTuple(pg_attribute, attr, attoptions, indstate); } CatalogCloseIndexes(indstate); @@ -591,6 +594,7 @@ UpdateIndexRelation(Oid indexoid, else predDatum = (Datum) 0; + /* * open the system catalog index relation */ @@ -933,7 +937,8 @@ index_create(Relation heapRelation, /* * append ATTRIBUTE tuples for the index */ - AppendAttributeTuples(indexRelation, indexInfo->ii_NumIndexAttrs); + AppendAttributeTuples(indexRelation, indexInfo->ii_NumIndexAttrs, + indexInfo->ii_OpclassOptions); /* ---------------- * update pg_index @@ -1146,6 +1151,12 @@ index_create(Relation heapRelation, indexRelation->rd_index->indnkeyatts = indexInfo->ii_NumIndexKeyAttrs; + /* Validate opclass-specific options */ + if (indexInfo->ii_OpclassOptions) + for (i = 0; i < indexInfo->ii_NumIndexKeyAttrs; i++) + (void) index_opclass_options(indexRelation, i + 1, + indexInfo->ii_OpclassOptions[i], true); + /* * If this is bootstrap (initdb) time, then we don't actually fill in the * index yet. We'll be creating more indexes and classes later, so we @@ -2208,6 +2219,10 @@ BuildIndexInfo(Relation index) ii->ii_ExclusionStrats = NULL; } + ii->ii_OpclassOptions = + RelationGetRawOpclassOptions(RelationGetRelid(index), + RelationGetNumberOfAttributes(index)); + /* other info */ ii->ii_Unique = indexStruct->indisunique; ii->ii_ReadyForInserts = indexStruct->indisready; diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c index de6282a667..7290731b3d 100644 --- a/src/backend/catalog/toasting.c +++ b/src/backend/catalog/toasting.c @@ -304,6 +304,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid, indexInfo->ii_ExclusionOps = NULL; indexInfo->ii_ExclusionProcs = NULL; indexInfo->ii_ExclusionStrats = NULL; + indexInfo->ii_OpclassOptions = NULL; indexInfo->ii_Unique = true; indexInfo->ii_ReadyForInserts = true; indexInfo->ii_Concurrent = false; diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c index d05d2fd3d5..23a57be749 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -792,6 +792,7 @@ DefineIndex(Oid relationId, indexInfo->ii_ExclusionOps = NULL; indexInfo->ii_ExclusionProcs = NULL; indexInfo->ii_ExclusionStrats = NULL; + indexInfo->ii_OpclassOptions = NULL; /* for now */ indexInfo->ii_Unique = stmt->unique; /* In a concurrent build, mark it not-ready-for-inserts */ indexInfo->ii_ReadyForInserts = !stmt->concurrent; @@ -1513,7 +1514,7 @@ CheckPredicate(Expr *predicate) /* * Compute per-index-column information, including indexed column numbers - * or index expressions, opclasses, and indoptions. Note, all output vectors + * or index expressions, opclasses and their options. Note, all output vectors * should be allocated for all columns, including "including" ones. */ static void @@ -1814,6 +1815,20 @@ ComputeIndexAttrs(IndexInfo *indexInfo, accessMethodName))); } + /* Set up the per-column opclass options (attoptions field). */ + if (attribute->opclassopts) + { + Assert(attn < nkeycols); + + if (!indexInfo->ii_OpclassOptions) + indexInfo->ii_OpclassOptions = + palloc0(sizeof(Datum) * indexInfo->ii_NumIndexAttrs); + + indexInfo->ii_OpclassOptions[attn] = + transformRelOptions((Datum) 0, attribute->opclassopts, + NULL, NULL, false, false); + } + attn++; } } diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 98519ef836..a7c8fa38a0 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -5688,7 +5688,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, ReleaseSysCache(typeTuple); - InsertPgAttributeTuple(attrdesc, &attribute, NULL); + InsertPgAttributeTuple(attrdesc, &attribute, (Datum) 0, NULL); table_close(attrdesc, RowExclusiveLock); diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 78deade89b..f5fad43eba 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -2864,6 +2864,7 @@ _copyIndexElem(const IndexElem *from) COPY_STRING_FIELD(indexcolname); COPY_NODE_FIELD(collation); COPY_NODE_FIELD(opclass); + COPY_NODE_FIELD(opclassopts); COPY_SCALAR_FIELD(ordering); COPY_SCALAR_FIELD(nulls_ordering); diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 4f2ebe5118..6b14095c82 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -2546,6 +2546,7 @@ _equalIndexElem(const IndexElem *a, const IndexElem *b) COMPARE_STRING_FIELD(indexcolname); COMPARE_NODE_FIELD(collation); COMPARE_NODE_FIELD(opclass); + COMPARE_NODE_FIELD(opclassopts); COMPARE_SCALAR_FIELD(ordering); COMPARE_SCALAR_FIELD(nulls_ordering); diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 237598e110..35806c0681 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2844,6 +2844,7 @@ _outIndexElem(StringInfo str, const IndexElem *node) WRITE_STRING_FIELD(indexcolname); WRITE_NODE_FIELD(collation); WRITE_NODE_FIELD(opclass); + WRITE_NODE_FIELD(opclassopts); WRITE_ENUM_FIELD(ordering, SortByDir); WRITE_ENUM_FIELD(nulls_ordering, SortByNulls); } diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 2405acbf6f..5c6be745c0 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -362,6 +362,10 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, info->nulls_first = NULL; } + /* Fetch index opclass options */ + info->opclassoptions = + RelationGetParsedOpclassOptions(indexRelation); + /* * Fetch the index expressions and predicate, if any. We must * modify the copies we obtain from the relcache to have the diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 8311b1dd46..2feade5327 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -491,7 +491,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type alias_clause opt_alias_clause %type func_alias_clause %type sortby -%type index_elem +%type index_elem index_elem_options %type table_ref %type joined_table %type relation_expr @@ -7420,43 +7420,65 @@ index_params: index_elem { $$ = list_make1($1); } | index_params ',' index_elem { $$ = lappend($1, $3); } ; + +index_elem_options: + opt_collate opt_class opt_asc_desc opt_nulls_order + { + $$ = makeNode(IndexElem); + $$->name = NULL; + $$->expr = NULL; + $$->indexcolname = NULL; + $$->collation = $1; + $$->opclass = $2; + $$->opclassopts = NIL; + $$->ordering = $3; + $$->nulls_ordering = $4; + } + | opt_collate any_name reloptions opt_asc_desc opt_nulls_order + { + $$ = makeNode(IndexElem); + $$->name = NULL; + $$->expr = NULL; + $$->indexcolname = NULL; + $$->collation = $1; + $$->opclass = $2; + $$->opclassopts = $3; + $$->ordering = $4; + $$->nulls_ordering = $5; + } + | opt_collate DEFAULT reloptions opt_asc_desc opt_nulls_order + { + $$ = makeNode(IndexElem); + $$->name = NULL; + $$->expr = NULL; + $$->indexcolname = NULL; + $$->collation = $1; + $$->opclass = NIL; + $$->opclassopts = $3; + $$->ordering = $4; + $$->nulls_ordering = $5; + } + ; + /* * Index attributes can be either simple column references, or arbitrary * expressions in parens. For backwards-compatibility reasons, we allow * an expression that's just a function call to be written without parens. */ -index_elem: ColId opt_collate opt_class opt_asc_desc opt_nulls_order +index_elem: ColId index_elem_options { - $$ = makeNode(IndexElem); + $$ = $2; $$->name = $1; - $$->expr = NULL; - $$->indexcolname = NULL; - $$->collation = $2; - $$->opclass = $3; - $$->ordering = $4; - $$->nulls_ordering = $5; } - | func_expr_windowless opt_collate opt_class opt_asc_desc opt_nulls_order + | func_expr_windowless index_elem_options { - $$ = makeNode(IndexElem); - $$->name = NULL; + $$ = $2; $$->expr = $1; - $$->indexcolname = NULL; - $$->collation = $2; - $$->opclass = $3; - $$->ordering = $4; - $$->nulls_ordering = $5; } - | '(' a_expr ')' opt_collate opt_class opt_asc_desc opt_nulls_order + | '(' a_expr ')' index_elem_options { - $$ = makeNode(IndexElem); - $$->name = NULL; + $$ = $4; $$->expr = $2; - $$->indexcolname = NULL; - $$->collation = $4; - $$->opclass = $5; - $$->ordering = $6; - $$->nulls_ordering = $7; } ; diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 9dda4820af..ea49bef807 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -453,8 +453,6 @@ static void get_from_clause_coldeflist(RangeTblFunction *rtfunc, deparse_context *context); static void get_tablesample_def(TableSampleClause *tablesample, deparse_context *context); -static void get_opclass_name(Oid opclass, Oid actual_datatype, - StringInfo buf); static Node *processIndirection(Node *node, deparse_context *context); static void printSubscripts(SubscriptingRef *sbsref, deparse_context *context); static char *get_relation_name(Oid relid); @@ -469,6 +467,7 @@ static void add_cast_to(StringInfo buf, Oid typid); static char *generate_qualified_type_name(Oid typid); static text *string_to_text(char *str); static char *flatten_reloptions(Oid relid); +static void get_reloptions(StringInfo buf, Datum reloptions); #define only_marker(rte) ((rte)->inh ? "" : "ONLY ") @@ -1198,6 +1197,7 @@ pg_get_indexdef_worker(Oid indexrelid, int colno, oidvector *indcollation; oidvector *indclass; int2vector *indoption; + Datum *opcoptions = NULL; StringInfoData buf; char *str; char *sep; @@ -1233,6 +1233,9 @@ pg_get_indexdef_worker(Oid indexrelid, int colno, Assert(!isnull); indoption = (int2vector *) DatumGetPointer(indoptionDatum); + if (!attrsOnly) + opcoptions = RelationGetRawOpclassOptions(indexrelid, idxrec->indnatts); + /* * Fetch the pg_class tuple of the index relation */ @@ -1369,16 +1372,28 @@ pg_get_indexdef_worker(Oid indexrelid, int colno, if (!attrsOnly && keyno < idxrec->indnkeyatts && (!colno || colno == keyno + 1)) { + bool has_options = false; int16 opt = indoption->values[keyno]; Oid indcoll = indcollation->values[keyno]; + if (opcoptions) + has_options = (opcoptions[keyno] != (Datum) 0); + /* Add collation, if not default for column */ if (OidIsValid(indcoll) && indcoll != keycolcollation) appendStringInfo(&buf, " COLLATE %s", generate_collation_name((indcoll))); /* Add the operator class name, if not default */ - get_opclass_name(indclass->values[keyno], keycoltype, &buf); + get_opclass_name(indclass->values[keyno], + has_options ? InvalidOid : keycoltype, &buf); + + if (has_options) + { + appendStringInfoString(&buf, " ("); + get_reloptions(&buf, opcoptions[keyno]); + appendStringInfoChar(&buf, ')'); + } /* Add options if relevant */ if (amroutine->amcanorder) @@ -10459,7 +10474,7 @@ get_tablesample_def(TableSampleClause *tablesample, deparse_context *context) * actual_datatype. (If you don't want this behavior, just pass * InvalidOid for actual_datatype.) */ -static void +void get_opclass_name(Oid opclass, Oid actual_datatype, StringInfo buf) { @@ -11168,6 +11183,62 @@ string_to_text(char *str) return result; } +/* + * Generate a C string representing a relation options from text[] datum. + */ +static void +get_reloptions(StringInfo buf, Datum reloptions) +{ + Datum *options; + int noptions; + int i; + + deconstruct_array(DatumGetArrayTypeP(reloptions), + TEXTOID, -1, false, 'i', + &options, NULL, &noptions); + + for (i = 0; i < noptions; i++) + { + char *option = TextDatumGetCString(options[i]); + char *name; + char *separator; + char *value; + + /* + * Each array element should have the form name=value. If the "=" + * is missing for some reason, treat it like an empty value. + */ + name = option; + separator = strchr(option, '='); + if (separator) + { + *separator = '\0'; + value = separator + 1; + } + else + value = ""; + + if (i > 0) + appendStringInfoString(buf, ", "); + appendStringInfo(buf, "%s=", quote_identifier(name)); + + /* + * In general we need to quote the value; but to avoid unnecessary + * clutter, do not quote if it is an identifier that would not + * need quoting. (We could also allow numbers, but that is a bit + * trickier than it looks --- for example, are leading zeroes + * significant? We don't want to assume very much here about what + * custom reloptions might mean.) + */ + if (quote_identifier(value) == value) + appendStringInfoString(buf, value); + else + simple_quote_literal(buf, value); + + pfree(option); + } +} + /* * Generate a C string representing a relation's reloptions, or NULL if none. */ @@ -11188,56 +11259,9 @@ flatten_reloptions(Oid relid) if (!isnull) { StringInfoData buf; - Datum *options; - int noptions; - int i; initStringInfo(&buf); - - deconstruct_array(DatumGetArrayTypeP(reloptions), - TEXTOID, -1, false, 'i', - &options, NULL, &noptions); - - for (i = 0; i < noptions; i++) - { - char *option = TextDatumGetCString(options[i]); - char *name; - char *separator; - char *value; - - /* - * Each array element should have the form name=value. If the "=" - * is missing for some reason, treat it like an empty value. - */ - name = option; - separator = strchr(option, '='); - if (separator) - { - *separator = '\0'; - value = separator + 1; - } - else - value = ""; - - if (i > 0) - appendStringInfoString(&buf, ", "); - appendStringInfo(&buf, "%s=", quote_identifier(name)); - - /* - * In general we need to quote the value; but to avoid unnecessary - * clutter, do not quote if it is an identifier that would not - * need quoting. (We could also allow numbers, but that is a bit - * trickier than it looks --- for example, are leading zeroes - * significant? We don't want to assume very much here about what - * custom reloptions might mean.) - */ - if (quote_identifier(value) == value) - appendStringInfoString(&buf, value); - else - simple_quote_literal(&buf, value); - - pfree(option); - } + get_reloptions(&buf, reloptions); result = buf.data; } diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 2b992d7832..62caa4cfaf 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -5173,6 +5173,105 @@ GetRelationPublicationActions(Relation relation) return pubactions; } +/* + * RelationGetIndexOpclassOptions -- get opclass-specific options for the index + */ +Datum * +RelationGetRawOpclassOptions(Oid indexrelid, int16 natts) +{ + Datum *options = NULL; + int16 attnum; + + for (attnum = 1; attnum <= natts; attnum++) + { + HeapTuple tuple; + Datum attopts; + bool isnull; + + tuple = SearchSysCache2(ATTNUM, ObjectIdGetDatum(indexrelid), + Int16GetDatum(attnum)); + + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for attribute %d of relation %u", + attnum, indexrelid); + + attopts = SysCacheGetAttr(ATTNAME, tuple, Anum_pg_attribute_attoptions, + &isnull); + + if (!isnull) + { + if (!options) + options = palloc0(sizeof(Datum) * natts); + + options[attnum - 1] = datumCopy(attopts, false, -1); /* text */ + } + + ReleaseSysCache(tuple); + } + + return options; +} + +/* + * RelationGetOpclassOptions -- get parsed opclass-specific options for an index + */ +bytea ** +RelationGetParsedOpclassOptions(Relation relation) +{ + MemoryContext oldcxt; + bytea **opts; + Datum *rawopts; + int natts = RelationGetNumberOfAttributes(relation); + int i; + + /* Try to copy cached options. */ + if (relation->rd_opcoptions) + { + opts = palloc(sizeof(*opts) * natts); + + for (i = 0; i < natts; i++) + { + bytea *opt = relation->rd_opcoptions[i]; + + opts[i] = !opt ? NULL : (bytea *) + DatumGetPointer(datumCopy(PointerGetDatum(opt), false, -1)); + } + + return opts; + } + + /* Get and parse opclass options. */ + opts = palloc0(sizeof(*opts) * natts); + + rawopts = RelationGetRawOpclassOptions(RelationGetRelid(relation), natts); + + for (i = 0; i < natts; i++) + { + Datum options = rawopts ? rawopts[i] : (Datum) 0; + + opts[i] = index_opclass_options(relation, i + 1, options, false); + + if (options != (Datum) 0) + pfree(DatumGetPointer(options)); + } + + if (rawopts) + pfree(rawopts); + + /* Copy parsed options to the cache. */ + oldcxt = MemoryContextSwitchTo(CacheMemoryContext); + + relation->rd_opcoptions = palloc(sizeof(*opts) * natts); + + for (i = 0; i < natts; i++) + relation->rd_opcoptions[i] = !opts[i] ? NULL : (bytea *) + DatumGetPointer(datumCopy(PointerGetDatum(opts[i]), false, -1)); + + MemoryContextSwitchTo(oldcxt); + + return opts; +} + /* * Routines to support ereport() reports of relation-related errors * diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h index 6e3db06eed..9c06d1a094 100644 --- a/src/include/access/amapi.h +++ b/src/include/access/amapi.h @@ -103,6 +103,12 @@ typedef void (*amcostestimate_function) (struct PlannerInfo *root, typedef bytea *(*amoptions_function) (Datum reloptions, bool validate); +/* parse column opclass-specific options */ +typedef bytea *(*amopclassoptions_function) (Relation index, + AttrNumber colno, + Datum attoptions, + bool validate); + /* report AM, index, or index column property */ typedef bool (*amproperty_function) (Oid index_oid, int attno, IndexAMProperty prop, const char *propname, @@ -215,6 +221,7 @@ typedef struct IndexAmRoutine amcanreturn_function amcanreturn; /* can be NULL */ amcostestimate_function amcostestimate; amoptions_function amoptions; + amopclassoptions_function amopclassoptions; amproperty_function amproperty; /* can be NULL */ ambuildphasename_function ambuildphasename; /* can be NULL */ amvalidate_function amvalidate; diff --git a/src/include/access/genam.h b/src/include/access/genam.h index 8c053be2ca..aa170f6de6 100644 --- a/src/include/access/genam.h +++ b/src/include/access/genam.h @@ -180,6 +180,11 @@ extern FmgrInfo *index_getprocinfo(Relation irel, AttrNumber attnum, extern void index_store_float8_orderby_distances(IndexScanDesc scan, Oid *orderByTypes, double *distances, bool recheckOrderBy); +extern bytea *index_opclass_options(Relation relation, AttrNumber attnum, + Datum attoptions, bool validate); +extern bytea *index_opclass_options_generic(Relation relation, + AttrNumber attnum, uint16 procnum, + Datum attoptions, bool validate); /* * index access method support routines (in genam.c) diff --git a/src/include/access/reloptions.h b/src/include/access/reloptions.h index a1912f41e6..8dea2d8e69 100644 --- a/src/include/access/reloptions.h +++ b/src/include/access/reloptions.h @@ -263,12 +263,17 @@ extern bytea *extractRelOptions(HeapTuple tuple, TupleDesc tupdesc, amoptions_function amoptions); extern relopt_value *parseRelOptions(Datum options, bool validate, relopt_kind kind, int *numrelopts); +extern relopt_value *parseLocalRelOptions(Datum options, bool validate, + relopt_gen **gen, int nelems); extern void *allocateReloptStruct(Size base, relopt_value *options, int numoptions); extern void fillRelOptions(void *rdopts, Size basesize, relopt_value *options, int numoptions, bool validate, const relopt_parse_elt *elems, int nelems); +extern void *parseAndFillLocalRelOptions(Datum options, relopt_gen *optgen[], + int offsets[], int noptions, size_t base_size, + bool validate); extern bytea *default_reloptions(Datum reloptions, bool validate, relopt_kind kind); diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h index eec71c29d5..866e94d04f 100644 --- a/src/include/catalog/heap.h +++ b/src/include/catalog/heap.h @@ -94,6 +94,7 @@ extern List *heap_truncate_find_FKs(List *relationIds); extern void InsertPgAttributeTuple(Relation pg_attribute_rel, Form_pg_attribute new_attribute, + Datum attoptions, CatalogIndexState indstate); extern void InsertPgClassTuple(Relation pg_class_desc, diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 99b9fa414f..e0d6c6c375 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -138,6 +138,7 @@ typedef struct ExprState * UniqueProcs * UniqueStrats * Unique is it a unique index? + * OpclassOptions opclass-specific options, or NULL if none * ReadyForInserts is it valid for inserts? * Concurrent are we doing a concurrent index build? * BrokenHotChain did we detect any broken HOT chains? @@ -166,6 +167,7 @@ typedef struct IndexInfo Oid *ii_UniqueOps; /* array with one entry per column */ Oid *ii_UniqueProcs; /* array with one entry per column */ uint16 *ii_UniqueStrats; /* array with one entry per column */ + Datum *ii_OpclassOptions; /* array with one entry per column */ bool ii_Unique; bool ii_ReadyForInserts; bool ii_Concurrent; diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 2a8edf934f..15699971a7 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -701,6 +701,7 @@ typedef struct IndexElem char *indexcolname; /* name for index column; NULL = default */ List *collation; /* name of collation; NIL = default */ List *opclass; /* name of desired opclass; NIL = default */ + List *opclassopts; /* opclass-specific options, or NIL */ SortByDir ordering; /* ASC/DESC/default */ SortByNulls nulls_ordering; /* FIRST/LAST/default */ } IndexElem; diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index 4b7703d478..f8a935f501 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -802,6 +802,7 @@ struct IndexOptInfo Oid *sortopfamily; /* OIDs of btree opfamilies, if orderable */ bool *reverse_sort; /* is sort order descending? */ bool *nulls_first; /* do NULLs come first in the sort order? */ + bytea **opclassoptions; /* opclass-specific options for columns */ bool *canreturn; /* which index cols can be returned in an * index-only scan? */ Oid relam; /* OID of the access method (in pg_am) */ diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index d7f33abce3..8440e225ee 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -175,6 +175,7 @@ typedef struct RelationData uint16 *rd_exclstrats; /* exclusion ops' strategy numbers, if any */ void *rd_amcache; /* available for use by index AM */ Oid *rd_indcollation; /* OIDs of index collations */ + bytea **rd_opcoptions; /* parsed opclass-specific options */ /* * foreign-table support diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h index d9c10ffcba..562929c058 100644 --- a/src/include/utils/relcache.h +++ b/src/include/utils/relcache.h @@ -14,6 +14,7 @@ #ifndef RELCACHE_H #define RELCACHE_H +#include "postgres.h" #include "access/tupdesc.h" #include "nodes/bitmapset.h" @@ -49,6 +50,8 @@ extern Oid RelationGetPrimaryKeyIndex(Relation relation); extern Oid RelationGetReplicaIndex(Relation relation); extern List *RelationGetIndexExpressions(Relation relation); extern List *RelationGetIndexPredicate(Relation relation); +extern bytea **RelationGetParsedOpclassOptions(Relation relation); +extern Datum *RelationGetRawOpclassOptions(Oid indexrelid, int16 natts); typedef enum IndexAttrBitmapKind { diff --git a/src/include/utils/ruleutils.h b/src/include/utils/ruleutils.h index d34cad2f4b..68d7ca892e 100644 --- a/src/include/utils/ruleutils.h +++ b/src/include/utils/ruleutils.h @@ -35,5 +35,7 @@ extern List *select_rtable_names_for_explain(List *rtable, Bitmapset *rels_used); extern char *generate_collation_name(Oid collid); extern char *get_range_partbound_string(List *bound_datums); +extern void get_opclass_name(Oid opclass, Oid actual_datatype, StringInfo buf); + #endif /* RULEUTILS_H */ diff --git a/src/test/regress/expected/btree_index.out b/src/test/regress/expected/btree_index.out index acab8e0b11..33f2bf0e3f 100644 --- a/src/test/regress/expected/btree_index.out +++ b/src/test/regress/expected/btree_index.out @@ -244,6 +244,11 @@ select reloptions from pg_class WHERE oid = 'btree_idx1'::regclass; {vacuum_cleanup_index_scale_factor=70.0} (1 row) +-- Test unsupported btree opclass parameters +create index on btree_tall_tbl (id int4_ops(foo=1)); +ERROR: access method "btree" does not support opclass options +create index on btree_tall_tbl (id default(foo=1)); +ERROR: access method "btree" does not support opclass options -- -- Test for multilevel page deletion -- diff --git a/src/test/regress/sql/btree_index.sql b/src/test/regress/sql/btree_index.sql index 48eaf4fe42..aecd690a01 100644 --- a/src/test/regress/sql/btree_index.sql +++ b/src/test/regress/sql/btree_index.sql @@ -121,6 +121,10 @@ create index btree_idx_err on btree_test(a) with (vacuum_cleanup_index_scale_fac alter index btree_idx1 set (vacuum_cleanup_index_scale_factor = 70.0); select reloptions from pg_class WHERE oid = 'btree_idx1'::regclass; +-- Test unsupported btree opclass parameters +create index on btree_tall_tbl (id int4_ops(foo=1)); +create index on btree_tall_tbl (id default(foo=1)); + -- -- Test for multilevel page deletion -- -- 2.20.1 --kw6iywuasi5tjnlh Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename="0002-Add-opclass-parameters-to-GiST-20190611.patch" From ddd99a8fb48774cfaf9ca151ebb8a5238bc1d2b3 Mon Sep 17 00:00:00 2001 From: Tomas Vondra Date: Sun, 9 Jun 2019 21:17:42 +0200 Subject: [PATCH 02/10] Add opclass parameters to GiST --- doc/src/sgml/xindex.sgml | 5 +++ src/backend/access/gist/gist.c | 2 ++ src/backend/access/gist/gistget.c | 12 ++++--- src/backend/access/gist/gistsplit.c | 15 +++++---- src/backend/access/gist/gistutil.c | 44 ++++++++++++++++++-------- src/backend/access/gist/gistvalidate.c | 34 +++++++++++++------- src/include/access/gist.h | 3 +- src/include/access/gist_private.h | 4 +++ 8 files changed, 82 insertions(+), 37 deletions(-) diff --git a/doc/src/sgml/xindex.sgml b/doc/src/sgml/xindex.sgml index 9446f8b836..8c5b5289d7 100644 --- a/doc/src/sgml/xindex.sgml +++ b/doc/src/sgml/xindex.sgml @@ -546,6 +546,11 @@ index-only scans (optional) 9 + + options + parse opclass-specific options (optional) + 10 + diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c index 470b121e7d..8e3460f456 100644 --- a/src/backend/access/gist/gist.c +++ b/src/backend/access/gist/gist.c @@ -98,6 +98,7 @@ gisthandler(PG_FUNCTION_ARGS) amroutine->amestimateparallelscan = NULL; amroutine->aminitparallelscan = NULL; amroutine->amparallelrescan = NULL; + amroutine->amopclassoptions = gistopclassoptions; PG_RETURN_POINTER(amroutine); } @@ -1528,6 +1529,7 @@ initGISTstate(Relation index) giststate->scanCxt = scanCxt; giststate->tempCxt = scanCxt; /* caller must change this if needed */ giststate->leafTupdesc = index->rd_att; + giststate->opclassoptions = RelationGetParsedOpclassOptions(index); /* * The truncated tupdesc for non-leaf index tuples, which doesn't contain diff --git a/src/backend/access/gist/gistget.c b/src/backend/access/gist/gistget.c index 8108fbb7d8..abfe659ca5 100644 --- a/src/backend/access/gist/gistget.c +++ b/src/backend/access/gist/gistget.c @@ -196,6 +196,7 @@ gistindex_keytest(IndexScanDesc scan, Datum test; bool recheck; GISTENTRY de; + bytea *options = giststate->opclassoptions[key->sk_attno - 1]; gistdentryinit(giststate, key->sk_attno - 1, &de, datum, r, page, offset, @@ -216,13 +217,14 @@ gistindex_keytest(IndexScanDesc scan, */ recheck = true; - test = FunctionCall5Coll(&key->sk_func, + test = FunctionCall6Coll(&key->sk_func, key->sk_collation, PointerGetDatum(&de), key->sk_argument, Int16GetDatum(key->sk_strategy), ObjectIdGetDatum(key->sk_subtype), - PointerGetDatum(&recheck)); + PointerGetDatum(&recheck), + PointerGetDatum(options)); if (!DatumGetBool(test)) return false; @@ -257,6 +259,7 @@ gistindex_keytest(IndexScanDesc scan, Datum dist; bool recheck; GISTENTRY de; + bytea *options = giststate->opclassoptions[key->sk_attno - 1]; gistdentryinit(giststate, key->sk_attno - 1, &de, datum, r, page, offset, @@ -279,13 +282,14 @@ gistindex_keytest(IndexScanDesc scan, * about the flag, but are expected to never be lossy. */ recheck = false; - dist = FunctionCall5Coll(&key->sk_func, + dist = FunctionCall6Coll(&key->sk_func, key->sk_collation, PointerGetDatum(&de), key->sk_argument, Int16GetDatum(key->sk_strategy), ObjectIdGetDatum(key->sk_subtype), - PointerGetDatum(&recheck)); + PointerGetDatum(&recheck), + PointerGetDatum(options)); *recheck_distances_p |= recheck; *distance_p = DatumGetFloat8(dist); } diff --git a/src/backend/access/gist/gistsplit.c b/src/backend/access/gist/gistsplit.c index 6a9c54d86c..2b3cb967e1 100644 --- a/src/backend/access/gist/gistsplit.c +++ b/src/backend/access/gist/gistsplit.c @@ -378,18 +378,20 @@ genericPickSplit(GISTSTATE *giststate, GistEntryVector *entryvec, GIST_SPLITVEC evec->n = v->spl_nleft; memcpy(evec->vector, entryvec->vector + FirstOffsetNumber, sizeof(GISTENTRY) * evec->n); - v->spl_ldatum = FunctionCall2Coll(&giststate->unionFn[attno], + v->spl_ldatum = FunctionCall3Coll(&giststate->unionFn[attno], giststate->supportCollation[attno], PointerGetDatum(evec), - PointerGetDatum(&nbytes)); + PointerGetDatum(&nbytes), + PointerGetDatum(giststate->opclassoptions[attno])); evec->n = v->spl_nright; memcpy(evec->vector, entryvec->vector + FirstOffsetNumber + v->spl_nleft, sizeof(GISTENTRY) * evec->n); - v->spl_rdatum = FunctionCall2Coll(&giststate->unionFn[attno], + v->spl_rdatum = FunctionCall3Coll(&giststate->unionFn[attno], giststate->supportCollation[attno], PointerGetDatum(evec), - PointerGetDatum(&nbytes)); + PointerGetDatum(&nbytes), + PointerGetDatum(giststate->opclassoptions[attno])); } /* @@ -430,10 +432,11 @@ gistUserPicksplit(Relation r, GistEntryVector *entryvec, int attno, GistSplitVec * Let the opclass-specific PickSplit method do its thing. Note that at * this point we know there are no null keys in the entryvec. */ - FunctionCall2Coll(&giststate->picksplitFn[attno], + FunctionCall3Coll(&giststate->picksplitFn[attno], giststate->supportCollation[attno], PointerGetDatum(entryvec), - PointerGetDatum(sv)); + PointerGetDatum(sv), + PointerGetDatum(giststate->opclassoptions[attno])); if (sv->spl_nleft == 0 || sv->spl_nright == 0) { diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c index 49df05653b..000f10103c 100644 --- a/src/backend/access/gist/gistutil.c +++ b/src/backend/access/gist/gistutil.c @@ -201,10 +201,11 @@ gistMakeUnionItVec(GISTSTATE *giststate, IndexTuple *itvec, int len, } /* Make union and store in attr array */ - attr[i] = FunctionCall2Coll(&giststate->unionFn[i], + attr[i] = FunctionCall3Coll(&giststate->unionFn[i], giststate->supportCollation[i], PointerGetDatum(evec), - PointerGetDatum(&attrsize)); + PointerGetDatum(&attrsize), + PointerGetDatum(giststate->opclassoptions[i])); isnull[i] = false; } @@ -270,10 +271,11 @@ gistMakeUnionKey(GISTSTATE *giststate, int attno, } *dstisnull = false; - *dst = FunctionCall2Coll(&giststate->unionFn[attno], + *dst = FunctionCall3Coll(&giststate->unionFn[attno], giststate->supportCollation[attno], PointerGetDatum(evec), - PointerGetDatum(&dstsize)); + PointerGetDatum(&dstsize), + PointerGetDatum(giststate->opclassoptions[attno])); } } @@ -282,10 +284,11 @@ gistKeyIsEQ(GISTSTATE *giststate, int attno, Datum a, Datum b) { bool result; - FunctionCall3Coll(&giststate->equalFn[attno], + FunctionCall4Coll(&giststate->equalFn[attno], giststate->supportCollation[attno], a, b, - PointerGetDatum(&result)); + PointerGetDatum(&result), + PointerGetDatum(giststate->opclassoptions[attno])); return result; } @@ -559,9 +562,10 @@ gistdentryinit(GISTSTATE *giststate, int nkey, GISTENTRY *e, return; dep = (GISTENTRY *) - DatumGetPointer(FunctionCall1Coll(&giststate->decompressFn[nkey], + DatumGetPointer(FunctionCall2Coll(&giststate->decompressFn[nkey], giststate->supportCollation[nkey], - PointerGetDatum(e))); + PointerGetDatum(e), + PointerGetDatum(giststate->opclassoptions[nkey]))); /* decompressFn may just return the given pointer */ if (dep != e) gistentryinit(*e, dep->key, dep->rel, dep->page, dep->offset, @@ -596,9 +600,10 @@ gistFormTuple(GISTSTATE *giststate, Relation r, /* there may not be a compress function in opclass */ if (OidIsValid(giststate->compressFn[i].fn_oid)) cep = (GISTENTRY *) - DatumGetPointer(FunctionCall1Coll(&giststate->compressFn[i], + DatumGetPointer(FunctionCall2Coll(&giststate->compressFn[i], giststate->supportCollation[i], - PointerGetDatum(¢ry))); + PointerGetDatum(¢ry), + PointerGetDatum(giststate->opclassoptions[i]))); else cep = ¢ry; compatt[i] = cep->key; @@ -643,9 +648,10 @@ gistFetchAtt(GISTSTATE *giststate, int nkey, Datum k, Relation r) gistentryinit(fentry, k, r, NULL, (OffsetNumber) 0, false); fep = (GISTENTRY *) - DatumGetPointer(FunctionCall1Coll(&giststate->fetchFn[nkey], + DatumGetPointer(FunctionCall2Coll(&giststate->fetchFn[nkey], giststate->supportCollation[nkey], - PointerGetDatum(&fentry))); + PointerGetDatum(&fentry), + PointerGetDatum(giststate->opclassoptions[nkey]))); /* fetchFn set 'key', return it to the caller */ return fep->key; @@ -722,11 +728,12 @@ gistpenalty(GISTSTATE *giststate, int attno, if (giststate->penaltyFn[attno].fn_strict == false || (isNullOrig == false && isNullAdd == false)) { - FunctionCall3Coll(&giststate->penaltyFn[attno], + FunctionCall4Coll(&giststate->penaltyFn[attno], giststate->supportCollation[attno], PointerGetDatum(orig), PointerGetDatum(add), - PointerGetDatum(&penalty)); + PointerGetDatum(&penalty), + PointerGetDatum(giststate->opclassoptions[attno])); /* disallow negative or NaN penalty */ if (isnan(penalty) || penalty < 0.0) penalty = 0.0; @@ -915,6 +922,15 @@ gistoptions(Datum reloptions, bool validate) return (bytea *) rdopts; } +bytea * +gistopclassoptions(Relation index, AttrNumber attnum, Datum attoptions, + bool validate) +{ + return index_opclass_options_generic(index, attnum, GIST_OPCLASSOPT_PROC, + attoptions, validate); +} + + /* * gistproperty() -- Check boolean properties of indexes. * diff --git a/src/backend/access/gist/gistvalidate.c b/src/backend/access/gist/gistvalidate.c index dfc1a87a75..7ae820070b 100644 --- a/src/backend/access/gist/gistvalidate.c +++ b/src/backend/access/gist/gistvalidate.c @@ -108,37 +108,46 @@ gistvalidate(Oid opclassoid) { case GIST_CONSISTENT_PROC: ok = check_amproc_signature(procform->amproc, BOOLOID, false, - 5, 5, INTERNALOID, opcintype, - INT2OID, OIDOID, INTERNALOID); + 5, 6, INTERNALOID, opcintype, + INT2OID, OIDOID, INTERNALOID, + INTERNALOID); break; case GIST_UNION_PROC: ok = check_amproc_signature(procform->amproc, opckeytype, false, - 2, 2, INTERNALOID, INTERNALOID); + 2, 3, INTERNALOID, INTERNALOID, + INTERNALOID); break; case GIST_COMPRESS_PROC: case GIST_DECOMPRESS_PROC: case GIST_FETCH_PROC: ok = check_amproc_signature(procform->amproc, INTERNALOID, true, - 1, 1, INTERNALOID); + 1, 2, INTERNALOID, INTERNALOID); break; case GIST_PENALTY_PROC: ok = check_amproc_signature(procform->amproc, INTERNALOID, true, - 3, 3, INTERNALOID, - INTERNALOID, INTERNALOID); + 3, 4, INTERNALOID, + INTERNALOID, INTERNALOID, + INTERNALOID); break; case GIST_PICKSPLIT_PROC: ok = check_amproc_signature(procform->amproc, INTERNALOID, true, - 2, 2, INTERNALOID, INTERNALOID); + 2, 3, INTERNALOID, INTERNALOID, + INTERNALOID); break; case GIST_EQUAL_PROC: ok = check_amproc_signature(procform->amproc, INTERNALOID, false, - 3, 3, opckeytype, opckeytype, - INTERNALOID); + 3, 4, opckeytype, opckeytype, + INTERNALOID, INTERNALOID); break; case GIST_DISTANCE_PROC: ok = check_amproc_signature(procform->amproc, FLOAT8OID, false, - 5, 5, INTERNALOID, opcintype, - INT2OID, OIDOID, INTERNALOID); + 5, 6, INTERNALOID, opcintype, + INT2OID, OIDOID, INTERNALOID, + INTERNALOID); + break; + case GIST_OPCLASSOPT_PROC: + ok = check_amproc_signature(procform->amproc, INTERNALOID, false, + 2, 2, INTERNALOID, BOOLOID); break; default: ereport(INFO, @@ -259,7 +268,8 @@ gistvalidate(Oid opclassoid) (opclassgroup->functionset & (((uint64) 1) << i)) != 0) continue; /* got it */ if (i == GIST_DISTANCE_PROC || i == GIST_FETCH_PROC || - i == GIST_COMPRESS_PROC || i == GIST_DECOMPRESS_PROC) + i == GIST_COMPRESS_PROC || i == GIST_DECOMPRESS_PROC || + i == GIST_OPCLASSOPT_PROC) continue; /* optional methods */ ereport(INFO, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), diff --git a/src/include/access/gist.h b/src/include/access/gist.h index 6902f4115b..0c57a64369 100644 --- a/src/include/access/gist.h +++ b/src/include/access/gist.h @@ -34,7 +34,8 @@ #define GIST_EQUAL_PROC 7 #define GIST_DISTANCE_PROC 8 #define GIST_FETCH_PROC 9 -#define GISTNProcs 9 +#define GIST_OPCLASSOPT_PROC 10 +#define GISTNProcs 10 /* * Page opaque data in a GiST index page. diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h index f80694bf9a..d46b6b89f9 100644 --- a/src/include/access/gist_private.h +++ b/src/include/access/gist_private.h @@ -96,6 +96,8 @@ typedef struct GISTSTATE /* Collations to pass to the support functions */ Oid supportCollation[INDEX_MAX_KEYS]; + + bytea **opclassoptions; /* parsed opclass-specific options */ } GISTSTATE; @@ -462,6 +464,8 @@ extern bool gistvalidate(Oid opclassoid); #define GIST_DEFAULT_FILLFACTOR 90 extern bytea *gistoptions(Datum reloptions, bool validate); +extern bytea *gistopclassoptions(Relation index, AttrNumber colno, + Datum options, bool validate); extern bool gistproperty(Oid index_oid, int attno, IndexAMProperty prop, const char *propname, bool *res, bool *isnull); -- 2.20.1 --kw6iywuasi5tjnlh Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename="0003-Add-opclass-parameters-to-GIN-20190611.patch" From f056cb2deddde2ff0cbef894be2699710720507b Mon Sep 17 00:00:00 2001 From: Tomas Vondra Date: Sun, 9 Jun 2019 21:18:18 +0200 Subject: [PATCH 03/10] Add opclass parameters to GIN --- doc/src/sgml/xindex.sgml | 7 ++++++ src/backend/access/gin/ginget.c | 11 +++++----- src/backend/access/gin/ginlogic.c | 15 +++++++------ src/backend/access/gin/ginscan.c | 6 ++++-- src/backend/access/gin/ginutil.c | 27 +++++++++++++++++------ src/backend/access/gin/ginvalidate.c | 32 +++++++++++++++++----------- src/backend/utils/adt/selfuncs.c | 6 ++++-- src/include/access/gin.h | 3 ++- src/include/access/gin_private.h | 5 +++++ 9 files changed, 78 insertions(+), 34 deletions(-) diff --git a/doc/src/sgml/xindex.sgml b/doc/src/sgml/xindex.sgml index 8c5b5289d7..658ec9bc5a 100644 --- a/doc/src/sgml/xindex.sgml +++ b/doc/src/sgml/xindex.sgml @@ -665,6 +665,13 @@ 6 + + options + + parse opclass-specific options (optional) + + 7 + diff --git a/src/backend/access/gin/ginget.c b/src/backend/access/gin/ginget.c index b18ae2b3ed..547b7b4762 100644 --- a/src/backend/access/gin/ginget.c +++ b/src/backend/access/gin/ginget.c @@ -188,13 +188,13 @@ collectMatchBitmap(GinBtreeData *btree, GinBtreeStack *stack, * case cmp < 0 => not match and continue scan *---------- */ - cmp = DatumGetInt32(FunctionCall4Coll(&btree->ginstate->comparePartialFn[attnum - 1], + cmp = DatumGetInt32(FunctionCall5Coll(&btree->ginstate->comparePartialFn[attnum - 1], btree->ginstate->supportCollation[attnum - 1], scanEntry->queryKey, idatum, UInt16GetDatum(scanEntry->strategy), - PointerGetDatum(scanEntry->extra_data))); - + PointerGetDatum(scanEntry->extra_data), + PointerGetDatum(btree->ginstate->opclassOptions[attnum - 1]))); if (cmp > 0) return true; else if (cmp < 0) @@ -1508,12 +1508,13 @@ matchPartialInPendingList(GinState *ginstate, Page page, * case cmp < 0 => not match and continue scan *---------- */ - cmp = DatumGetInt32(FunctionCall4Coll(&ginstate->comparePartialFn[entry->attnum - 1], + cmp = DatumGetInt32(FunctionCall5Coll(&ginstate->comparePartialFn[entry->attnum - 1], ginstate->supportCollation[entry->attnum - 1], entry->queryKey, datum[off - 1], UInt16GetDatum(entry->strategy), - PointerGetDatum(entry->extra_data))); + PointerGetDatum(entry->extra_data), + PointerGetDatum(ginstate->opclassOptions[entry->attnum - 1]))); if (cmp == 0) return true; else if (cmp > 0) diff --git a/src/backend/access/gin/ginlogic.c b/src/backend/access/gin/ginlogic.c index 8f85978972..028b29a8c0 100644 --- a/src/backend/access/gin/ginlogic.c +++ b/src/backend/access/gin/ginlogic.c @@ -76,7 +76,7 @@ directBoolConsistentFn(GinScanKey key) */ key->recheckCurItem = true; - return DatumGetBool(FunctionCall8Coll(key->consistentFmgrInfo, + return DatumGetBool(FunctionCall9Coll(key->consistentFmgrInfo, key->collation, PointerGetDatum(key->entryRes), UInt16GetDatum(key->strategy), @@ -85,7 +85,8 @@ directBoolConsistentFn(GinScanKey key) PointerGetDatum(key->extra_data), PointerGetDatum(&key->recheckCurItem), PointerGetDatum(key->queryValues), - PointerGetDatum(key->queryCategories))); + PointerGetDatum(key->queryCategories), + PointerGetDatum(key->opclassOptions))); } /* @@ -94,7 +95,7 @@ directBoolConsistentFn(GinScanKey key) static GinTernaryValue directTriConsistentFn(GinScanKey key) { - return DatumGetGinTernaryValue(FunctionCall7Coll( + return DatumGetGinTernaryValue(FunctionCall8Coll( key->triConsistentFmgrInfo, key->collation, PointerGetDatum(key->entryRes), @@ -103,7 +104,8 @@ directTriConsistentFn(GinScanKey key) UInt32GetDatum(key->nuserentries), PointerGetDatum(key->extra_data), PointerGetDatum(key->queryValues), - PointerGetDatum(key->queryCategories))); + PointerGetDatum(key->queryCategories), + PointerGetDatum(key->opclassOptions))); } /* @@ -116,7 +118,7 @@ shimBoolConsistentFn(GinScanKey key) { GinTernaryValue result; - result = DatumGetGinTernaryValue(FunctionCall7Coll( + result = DatumGetGinTernaryValue(FunctionCall8Coll( key->triConsistentFmgrInfo, key->collation, PointerGetDatum(key->entryRes), @@ -125,7 +127,8 @@ shimBoolConsistentFn(GinScanKey key) UInt32GetDatum(key->nuserentries), PointerGetDatum(key->extra_data), PointerGetDatum(key->queryValues), - PointerGetDatum(key->queryCategories))); + PointerGetDatum(key->queryCategories), + PointerGetDatum(key->opclassOptions))); if (result == GIN_MAYBE) { key->recheckCurItem = true; diff --git a/src/backend/access/gin/ginscan.c b/src/backend/access/gin/ginscan.c index 74d9821ac1..d1384a33d4 100644 --- a/src/backend/access/gin/ginscan.c +++ b/src/backend/access/gin/ginscan.c @@ -156,6 +156,7 @@ ginFillScanKey(GinScanOpaque so, OffsetNumber attnum, key->strategy = strategy; key->searchMode = searchMode; key->attnum = attnum; + key->opclassOptions = ginstate->opclassOptions[attnum - 1]; ItemPointerSetMin(&key->curItem); key->curItemMatches = false; @@ -310,7 +311,7 @@ ginNewScanKey(IndexScanDesc scan) /* OK to call the extractQueryFn */ queryValues = (Datum *) - DatumGetPointer(FunctionCall7Coll(&so->ginstate.extractQueryFn[skey->sk_attno - 1], + DatumGetPointer(FunctionCall8Coll(&so->ginstate.extractQueryFn[skey->sk_attno - 1], so->ginstate.supportCollation[skey->sk_attno - 1], skey->sk_argument, PointerGetDatum(&nQueryValues), @@ -318,7 +319,8 @@ ginNewScanKey(IndexScanDesc scan) PointerGetDatum(&partial_matches), PointerGetDatum(&extra_data), PointerGetDatum(&nullFlags), - PointerGetDatum(&searchMode))); + PointerGetDatum(&searchMode), + PointerGetDatum(so->ginstate.opclassOptions[skey->sk_attno - 1]))); /* * If bogus searchMode is returned, treat as GIN_SEARCH_MODE_ALL; note diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c index cf9699ad18..03874909b0 100644 --- a/src/backend/access/gin/ginutil.c +++ b/src/backend/access/gin/ginutil.c @@ -63,6 +63,7 @@ ginhandler(PG_FUNCTION_ARGS) amroutine->amcanreturn = NULL; amroutine->amcostestimate = gincostestimate; amroutine->amoptions = ginoptions; + amroutine->amopclassoptions = ginopclassoptions; amroutine->amproperty = NULL; amroutine->ambuildphasename = NULL; amroutine->amvalidate = ginvalidate; @@ -96,6 +97,7 @@ initGinState(GinState *state, Relation index) state->index = index; state->oneCol = (origTupdesc->natts == 1) ? true : false; state->origTupdesc = origTupdesc; + state->opclassOptions = RelationGetParsedOpclassOptions(index); for (i = 0; i < origTupdesc->natts; i++) { @@ -399,9 +401,10 @@ ginCompareEntries(GinState *ginstate, OffsetNumber attnum, return 0; /* both not null, so safe to call the compareFn */ - return DatumGetInt32(FunctionCall2Coll(&ginstate->compareFn[attnum - 1], + return DatumGetInt32(FunctionCall3Coll(&ginstate->compareFn[attnum - 1], ginstate->supportCollation[attnum - 1], - a, b)); + a, b, + PointerGetDatum(ginstate->opclassOptions[attnum - 1]))); } /* @@ -437,6 +440,7 @@ typedef struct { FmgrInfo *cmpDatumFunc; Oid collation; + Datum options; bool haveDups; } cmpEntriesArg; @@ -458,9 +462,10 @@ cmpEntries(const void *a, const void *b, void *arg) else if (bb->isnull) res = -1; /* not-NULL "<" NULL */ else - res = DatumGetInt32(FunctionCall2Coll(data->cmpDatumFunc, + res = DatumGetInt32(FunctionCall3Coll(data->cmpDatumFunc, data->collation, - aa->datum, bb->datum)); + aa->datum, bb->datum, + data->options)); /* * Detect if we have any duplicates. If there are equal keys, qsort must @@ -506,11 +511,12 @@ ginExtractEntries(GinState *ginstate, OffsetNumber attnum, /* OK, call the opclass's extractValueFn */ nullFlags = NULL; /* in case extractValue doesn't set it */ entries = (Datum *) - DatumGetPointer(FunctionCall3Coll(&ginstate->extractValueFn[attnum - 1], + DatumGetPointer(FunctionCall4Coll(&ginstate->extractValueFn[attnum - 1], ginstate->supportCollation[attnum - 1], value, PointerGetDatum(nentries), - PointerGetDatum(&nullFlags))); + PointerGetDatum(&nullFlags), + PointerGetDatum(ginstate->opclassOptions[attnum - 1]))); /* * Generate a placeholder if the item contained no keys. @@ -553,6 +559,7 @@ ginExtractEntries(GinState *ginstate, OffsetNumber attnum, arg.cmpDatumFunc = &ginstate->compareFn[attnum - 1]; arg.collation = ginstate->supportCollation[attnum - 1]; + arg.options = PointerGetDatum(ginstate->opclassOptions[attnum - 1]); arg.haveDups = false; qsort_arg(keydata, *nentries, sizeof(keyEntryData), cmpEntries, (void *) &arg); @@ -628,6 +635,14 @@ ginoptions(Datum reloptions, bool validate) return (bytea *) rdopts; } +bytea * +ginopclassoptions(Relation index, AttrNumber colno, Datum attoptions, + bool validate) +{ + return index_opclass_options_generic(index, colno, GIN_OPCLASSOPTIONS_PROC, + attoptions, validate); +} + /* * Fetch index's statistical data into *stats * diff --git a/src/backend/access/gin/ginvalidate.c b/src/backend/access/gin/ginvalidate.c index 63bd7f2adc..a000052f09 100644 --- a/src/backend/access/gin/ginvalidate.c +++ b/src/backend/access/gin/ginvalidate.c @@ -108,40 +108,47 @@ ginvalidate(Oid opclassoid) { case GIN_COMPARE_PROC: ok = check_amproc_signature(procform->amproc, INT4OID, false, - 2, 2, opckeytype, opckeytype); + 2, 3, opckeytype, opckeytype, + INTERNALOID); break; case GIN_EXTRACTVALUE_PROC: /* Some opclasses omit nullFlags */ ok = check_amproc_signature(procform->amproc, INTERNALOID, false, - 2, 3, opcintype, INTERNALOID, - INTERNALOID); + 2, 4, opcintype, INTERNALOID, + INTERNALOID, INTERNALOID); break; case GIN_EXTRACTQUERY_PROC: /* Some opclasses omit nullFlags and searchMode */ ok = check_amproc_signature(procform->amproc, INTERNALOID, false, - 5, 7, opcintype, INTERNALOID, + 5, 8, opcintype, INTERNALOID, INT2OID, INTERNALOID, INTERNALOID, - INTERNALOID, INTERNALOID); + INTERNALOID, INTERNALOID, + INTERNALOID); break; case GIN_CONSISTENT_PROC: /* Some opclasses omit queryKeys and nullFlags */ ok = check_amproc_signature(procform->amproc, BOOLOID, false, - 6, 8, INTERNALOID, INT2OID, + 6, 9, INTERNALOID, INT2OID, opcintype, INT4OID, INTERNALOID, INTERNALOID, - INTERNALOID, INTERNALOID); + INTERNALOID, INTERNALOID, + INTERNALOID); break; case GIN_COMPARE_PARTIAL_PROC: ok = check_amproc_signature(procform->amproc, INT4OID, false, - 4, 4, opckeytype, opckeytype, - INT2OID, INTERNALOID); + 4, 5, opckeytype, opckeytype, + INT2OID, INTERNALOID, INTERNALOID); break; case GIN_TRICONSISTENT_PROC: ok = check_amproc_signature(procform->amproc, CHAROID, false, - 7, 7, INTERNALOID, INT2OID, + 7, 8, INTERNALOID, INT2OID, opcintype, INT4OID, INTERNALOID, INTERNALOID, - INTERNALOID); + INTERNALOID, INTERNALOID); + break; + case GIN_OPCLASSOPTIONS_PROC: + ok = check_amproc_signature(procform->amproc, INTERNALOID, + false, 2, 2, INTERNALOID, BOOLOID); break; default: ereport(INFO, @@ -238,7 +245,8 @@ ginvalidate(Oid opclassoid) if (opclassgroup && (opclassgroup->functionset & (((uint64) 1) << i)) != 0) continue; /* got it */ - if (i == GIN_COMPARE_PROC || i == GIN_COMPARE_PARTIAL_PROC) + if (i == GIN_COMPARE_PROC || i == GIN_COMPARE_PARTIAL_PROC || + i == GIN_OPCLASSOPTIONS_PROC) continue; /* optional method */ if (i == GIN_CONSISTENT_PROC || i == GIN_TRICONSISTENT_PROC) continue; /* don't need both, see check below loop */ diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c index d7e3f09f1a..d491bdd7ae 100644 --- a/src/backend/utils/adt/selfuncs.c +++ b/src/backend/utils/adt/selfuncs.c @@ -135,6 +135,7 @@ #include "utils/lsyscache.h" #include "utils/pg_locale.h" #include "utils/rel.h" +#include "utils/relcache.h" #include "utils/selfuncs.h" #include "utils/snapmgr.h" #include "utils/spccache.h" @@ -6242,7 +6243,7 @@ gincost_pattern(IndexOptInfo *index, int indexcol, else collation = DEFAULT_COLLATION_OID; - OidFunctionCall7Coll(extractProcOid, + OidFunctionCall8Coll(extractProcOid, collation, query, PointerGetDatum(&nentries), @@ -6250,7 +6251,8 @@ gincost_pattern(IndexOptInfo *index, int indexcol, PointerGetDatum(&partial_matches), PointerGetDatum(&extra_data), PointerGetDatum(&nullFlags), - PointerGetDatum(&searchMode)); + PointerGetDatum(&searchMode), + PointerGetDatum(index->opclassoptions[indexcol])); if (nentries <= 0 && searchMode == GIN_SEARCH_MODE_DEFAULT) { diff --git a/src/include/access/gin.h b/src/include/access/gin.h index a8eef5a379..aabfbcd5ce 100644 --- a/src/include/access/gin.h +++ b/src/include/access/gin.h @@ -25,7 +25,8 @@ #define GIN_CONSISTENT_PROC 4 #define GIN_COMPARE_PARTIAL_PROC 5 #define GIN_TRICONSISTENT_PROC 6 -#define GINNProcs 6 +#define GIN_OPCLASSOPTIONS_PROC 7 +#define GINNProcs 7 /* * searchMode settings for extractQueryFn. diff --git a/src/include/access/gin_private.h b/src/include/access/gin_private.h index afb3e15721..0f626a7dbb 100644 --- a/src/include/access/gin_private.h +++ b/src/include/access/gin_private.h @@ -67,6 +67,8 @@ typedef struct GinState TupleDesc origTupdesc; TupleDesc tupdesc[INDEX_MAX_KEYS]; + bytea **opclassOptions; /* per-index-column opclass options */ + /* * Per-index-column opclass support functions */ @@ -85,6 +87,8 @@ typedef struct GinState /* ginutil.c */ extern bytea *ginoptions(Datum reloptions, bool validate); +extern bytea *ginopclassoptions(Relation index, AttrNumber colno, + Datum attoptions, bool validate); extern void initGinState(GinState *state, Relation index); extern Buffer GinNewBuffer(Relation index); extern void GinInitBuffer(Buffer b, uint32 f); @@ -297,6 +301,7 @@ typedef struct GinScanKeyData StrategyNumber strategy; int32 searchMode; OffsetNumber attnum; + bytea *opclassOptions; /* * Match status data. curItem is the TID most recently tested (could be a -- 2.20.1 --kw6iywuasi5tjnlh Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename="0004-Add-opclass-parameters-to-GiST-tsvector_ops-20190611.patch" From ae97174555cbaf059d2df45df3c80d7e67007fdd Mon Sep 17 00:00:00 2001 From: Tomas Vondra Date: Sun, 9 Jun 2019 21:38:42 +0200 Subject: [PATCH 04/10] Add opclass parameters to GiST tsvector_ops --- doc/src/sgml/textsearch.sgml | 9 +- src/backend/utils/adt/tsgistidx.c | 269 ++++++++++++++------------ src/include/catalog/pg_amproc.dat | 5 +- src/include/catalog/pg_proc.dat | 19 +- src/test/regress/expected/tsearch.out | 176 +++++++++++++++++ src/test/regress/sql/tsearch.sql | 45 +++++ 6 files changed, 392 insertions(+), 131 deletions(-) diff --git a/doc/src/sgml/textsearch.sgml b/doc/src/sgml/textsearch.sgml index 40888a4d20..54b796ecf1 100644 --- a/doc/src/sgml/textsearch.sgml +++ b/doc/src/sgml/textsearch.sgml @@ -3637,7 +3637,7 @@ SELECT plainto_tsquery('supernovae stars'); text search - CREATE INDEX name ON table USING GIST (column); + CREATE INDEX name ON table USING GIST (column [ { DEFAULT | tsvector_ops } (siglen = number) ] ); @@ -3645,6 +3645,8 @@ SELECT plainto_tsquery('supernovae stars'); Creates a GiST (Generalized Search Tree)-based index. The column can be of tsvector or tsquery type. + Optional integer parameter siglen determines + signature length in bytes (see below for details). @@ -3668,7 +3670,10 @@ SELECT plainto_tsquery('supernovae stars'); to check the actual table row to eliminate such false matches. (PostgreSQL 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 siglen. + Default signature length (when siglen is not specied) is + 124 bytes, maximal length is 484 bytes. The signature is generated by hashing each word into a single bit in an n-bit string, with all these bits OR-ed together to produce an n-bit document signature. When two words hash to the same bit position there will be a false match. If all words in diff --git a/src/backend/utils/adt/tsgistidx.c b/src/backend/utils/adt/tsgistidx.c index 4f256260fd..91661cf8b8 100644 --- a/src/backend/utils/adt/tsgistidx.c +++ b/src/backend/utils/adt/tsgistidx.c @@ -15,6 +15,7 @@ #include "postgres.h" #include "access/gist.h" +#include "access/reloptions.h" #include "access/tuptoaster.h" #include "port/pg_bitutils.h" #include "tsearch/ts_utils.h" @@ -22,17 +23,22 @@ #include "utils/pg_crc.h" -#define SIGLENINT 31 /* >121 => key will toast, so it will not work - * !!! */ +#define SIGLEN_DEFAULT (31 * 4) +#define SIGLEN_MAX (121 * 4) /* key will toast, so it will not work !!! */ -#define SIGLEN ( sizeof(int32) * SIGLENINT ) -#define SIGLENBIT (SIGLEN * BITS_PER_BYTE) +#define SIGLENBIT(siglen) ((siglen) * BITS_PER_BYTE) + +/* tsvector_ops opclass options */ +typedef struct GistTsVectorOptions +{ + int32 vl_len_; /* varlena header (do not touch directly!) */ + int siglen; /* signature length */ +} GistTsVectorOptions; -typedef char BITVEC[SIGLEN]; typedef char *BITVECP; -#define LOOPBYTE \ - for(i=0;i> (i) & 0x01 ) @@ -40,8 +46,8 @@ typedef char *BITVECP; #define SETBIT(x,i) GETBYTE(x,i) |= ( 0x01 << ( (i) % BITS_PER_BYTE ) ) #define GETBIT(x,i) ( (GETBYTE(x,i) >> ( (i) % BITS_PER_BYTE )) & 0x01 ) -#define HASHVAL(val) (((unsigned int)(val)) % SIGLENBIT) -#define HASH(sign, val) SETBIT((sign), HASHVAL(val)) +#define HASHVAL(val, siglen) (((unsigned int)(val)) % SIGLENBIT(siglen)) +#define HASH(sign, val, siglen) SETBIT((sign), HASHVAL(val, siglen)) #define GETENTRY(vec,pos) ((SignTSVector *) DatumGetPointer((vec)->vector[(pos)].key)) @@ -65,13 +71,14 @@ typedef struct #define ISALLTRUE(x) ( ((SignTSVector*)(x))->flag & ALLISTRUE ) #define GTHDRSIZE ( VARHDRSZ + sizeof(int32) ) -#define CALCGTSIZE(flag, len) ( GTHDRSIZE + ( ( (flag) & ARRKEY ) ? ((len)*sizeof(int32)) : (((flag) & ALLISTRUE) ? 0 : SIGLEN) ) ) +#define CALCGTSIZE(flag, len) ( GTHDRSIZE + ( ( (flag) & ARRKEY ) ? ((len)*sizeof(int32)) : (((flag) & ALLISTRUE) ? 0 : (len)) ) ) #define GETSIGN(x) ( (BITVECP)( (char*)(x)+GTHDRSIZE ) ) +#define GETSIGLEN(x)( VARSIZE(x) - GTHDRSIZE ) #define GETARR(x) ( (int32*)( (char*)(x)+GTHDRSIZE ) ) #define ARRNELEM(x) ( ( VARSIZE(x) - GTHDRSIZE )/sizeof(int32) ) -static int32 sizebitvec(BITVECP sign); +static int32 sizebitvec(BITVECP sign, int siglen); Datum gtsvectorin(PG_FUNCTION_ARGS) @@ -102,9 +109,10 @@ gtsvectorout(PG_FUNCTION_ARGS) sprintf(outbuf, ARROUTSTR, (int) ARRNELEM(key)); else { - int cnttrue = (ISALLTRUE(key)) ? SIGLENBIT : sizebitvec(GETSIGN(key)); + int siglen = GETSIGLEN(key); + int cnttrue = (ISALLTRUE(key)) ? SIGLENBIT(siglen) : sizebitvec(GETSIGN(key), siglen); - sprintf(outbuf, SINGOUTSTR, cnttrue, (int) SIGLENBIT - cnttrue); + sprintf(outbuf, SINGOUTSTR, cnttrue, (int) SIGLENBIT(siglen) - cnttrue); } PG_FREE_IF_COPY(key, 0); @@ -148,36 +156,49 @@ uniqueint(int32 *a, int32 l) } static void -makesign(BITVECP sign, SignTSVector *a) +makesign(BITVECP sign, SignTSVector *a, int siglen) { int32 k, len = ARRNELEM(a); int32 *ptr = GETARR(a); - MemSet((void *) sign, 0, sizeof(BITVEC)); + MemSet((void *) sign, 0, siglen); for (k = 0; k < len; k++) - HASH(sign, ptr[k]); + HASH(sign, ptr[k], siglen); +} + +static SignTSVector * +gtsvector_alloc(int flag, int len, BITVECP sign) +{ + int size = CALCGTSIZE(flag, len); + SignTSVector *res = palloc(size); + + SET_VARSIZE(res, size); + res->flag = flag; + + if ((flag & (SIGNKEY | ALLISTRUE)) == SIGNKEY && sign) + memcpy(GETSIGN(res), sign, len); + + return res; } + Datum gtsvector_compress(PG_FUNCTION_ARGS) { GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); + int siglen = ((GistTsVectorOptions *) PG_GETARG_POINTER(1))->siglen; GISTENTRY *retval = entry; if (entry->leafkey) { /* tsvector */ - SignTSVector *res; TSVector val = DatumGetTSVector(entry->key); + SignTSVector *res = gtsvector_alloc(ARRKEY, val->size, NULL); int32 len; int32 *arr; WordEntry *ptr = ARRPTR(val); char *words = STRPTR(val); - len = CALCGTSIZE(ARRKEY, val->size); - res = (SignTSVector *) palloc(len); - SET_VARSIZE(res, len); - res->flag = ARRKEY; arr = GETARR(res); len = val->size; while (len--) @@ -208,13 +229,9 @@ gtsvector_compress(PG_FUNCTION_ARGS) /* make signature, if array is too long */ if (VARSIZE(res) > TOAST_INDEX_TARGET) { - SignTSVector *ressign; + SignTSVector *ressign = gtsvector_alloc(SIGNKEY, siglen, NULL); - len = CALCGTSIZE(SIGNKEY, 0); - ressign = (SignTSVector *) palloc(len); - SET_VARSIZE(ressign, len); - ressign->flag = SIGNKEY; - makesign(GETSIGN(ressign), res); + makesign(GETSIGN(ressign), res, siglen); res = ressign; } @@ -226,22 +243,17 @@ gtsvector_compress(PG_FUNCTION_ARGS) else if (ISSIGNKEY(DatumGetPointer(entry->key)) && !ISALLTRUE(DatumGetPointer(entry->key))) { - int32 i, - len; + int32 i; SignTSVector *res; BITVECP sign = GETSIGN(DatumGetPointer(entry->key)); - LOOPBYTE + LOOPBYTE(siglen) { if ((sign[i] & 0xff) != 0xff) PG_RETURN_POINTER(retval); } - len = CALCGTSIZE(SIGNKEY | ALLISTRUE, 0); - res = (SignTSVector *) palloc(len); - SET_VARSIZE(res, len); - res->flag = SIGNKEY | ALLISTRUE; - + res = gtsvector_alloc(SIGNKEY | ALLISTRUE, siglen, sign); retval = (GISTENTRY *) palloc(sizeof(GISTENTRY)); gistentryinit(*retval, PointerGetDatum(res), entry->rel, entry->page, @@ -315,12 +327,14 @@ checkcondition_arr(void *checkval, QueryOperand *val, ExecPhraseData *data) static bool checkcondition_bit(void *checkval, QueryOperand *val, ExecPhraseData *data) { + void *key = (SignTSVector *) checkval; + /* * we are not able to find a prefix in signature tree */ if (val->prefix) return true; - return GETBIT(checkval, HASHVAL(val->valcrc)); + return GETBIT(GETSIGN(key), HASHVAL(val->valcrc, GETSIGLEN(key))); } Datum @@ -347,7 +361,7 @@ gtsvector_consistent(PG_FUNCTION_ARGS) /* since signature is lossy, cannot specify CALC_NOT here */ PG_RETURN_BOOL(TS_execute(GETQUERY(query), - (void *) GETSIGN(key), + key, TS_EXEC_PHRASE_NO_POS, checkcondition_bit)); } @@ -365,7 +379,7 @@ gtsvector_consistent(PG_FUNCTION_ARGS) } static int32 -unionkey(BITVECP sbase, SignTSVector *add) +unionkey(BITVECP sbase, SignTSVector *add, int siglen) { int32 i; @@ -376,7 +390,9 @@ unionkey(BITVECP sbase, SignTSVector *add) if (ISALLTRUE(add)) return 1; - LOOPBYTE + Assert(GETSIGLEN(add) == siglen); + + LOOPBYTE(siglen) sbase[i] |= sadd[i]; } else @@ -384,7 +400,7 @@ unionkey(BITVECP sbase, SignTSVector *add) int32 *ptr = GETARR(add); for (i = 0; i < ARRNELEM(add); i++) - HASH(sbase, ptr[i]); + HASH(sbase, ptr[i], siglen); } return 0; } @@ -395,30 +411,24 @@ gtsvector_union(PG_FUNCTION_ARGS) { GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); int *size = (int *) PG_GETARG_POINTER(1); - BITVEC base; - int32 i, - len; - int32 flag = 0; - SignTSVector *result; + int siglen = ((GistTsVectorOptions *) PG_GETARG_POINTER(2))->siglen; + SignTSVector *result = gtsvector_alloc(SIGNKEY, siglen, NULL); + BITVECP base = GETSIGN(result); + int32 i; + + memset(base, 0, siglen); - MemSet((void *) base, 0, sizeof(BITVEC)); for (i = 0; i < entryvec->n; i++) { - if (unionkey(base, GETENTRY(entryvec, i))) + if (unionkey(base, GETENTRY(entryvec, i), siglen)) { - flag = ALLISTRUE; + result->flag |= ALLISTRUE; + SET_VARSIZE(result, CALCGTSIZE(result->flag, siglen)); break; } } - flag |= SIGNKEY; - len = CALCGTSIZE(flag, 0); - result = (SignTSVector *) palloc(len); - *size = len; - SET_VARSIZE(result, len); - result->flag = flag; - if (!ISALLTRUE(result)) - memcpy((void *) GETSIGN(result), (void *) base, sizeof(BITVEC)); + *size = VARSIZE(result); PG_RETURN_POINTER(result); } @@ -429,6 +439,7 @@ gtsvector_same(PG_FUNCTION_ARGS) SignTSVector *a = (SignTSVector *) PG_GETARG_POINTER(0); SignTSVector *b = (SignTSVector *) PG_GETARG_POINTER(1); bool *result = (bool *) PG_GETARG_POINTER(2); + int siglen = ((GistTsVectorOptions *) PG_GETARG_POINTER(3))->siglen; if (ISSIGNKEY(a)) { /* then b also ISSIGNKEY */ @@ -444,8 +455,10 @@ gtsvector_same(PG_FUNCTION_ARGS) BITVECP sa = GETSIGN(a), sb = GETSIGN(b); + Assert(GETSIGLEN(a) == siglen && GETSIGLEN(b) == siglen); + *result = true; - LOOPBYTE + LOOPBYTE(siglen) { if (sa[i] != sb[i]) { @@ -482,19 +495,19 @@ gtsvector_same(PG_FUNCTION_ARGS) } static int32 -sizebitvec(BITVECP sign) +sizebitvec(BITVECP sign, int siglen) { - return pg_popcount(sign, SIGLEN); + return pg_popcount(sign, siglen); } static int -hemdistsign(BITVECP a, BITVECP b) +hemdistsign(BITVECP a, BITVECP b, int siglen) { int i, diff, dist = 0; - LOOPBYTE + LOOPBYTE(siglen) { diff = (unsigned char) (a[i] ^ b[i]); /* Using the popcount functions here isn't likely to win */ @@ -506,17 +519,22 @@ hemdistsign(BITVECP a, BITVECP b) static int hemdist(SignTSVector *a, SignTSVector *b) { + int siglena = GETSIGLEN(a); + int siglenb = GETSIGLEN(b); + if (ISALLTRUE(a)) { if (ISALLTRUE(b)) return 0; else - return SIGLENBIT - sizebitvec(GETSIGN(b)); + return SIGLENBIT(siglenb) - sizebitvec(GETSIGN(b), siglenb); } else if (ISALLTRUE(b)) - return SIGLENBIT - sizebitvec(GETSIGN(a)); + return SIGLENBIT(siglena) - sizebitvec(GETSIGN(a), siglena); - return hemdistsign(GETSIGN(a), GETSIGN(b)); + Assert(siglena == siglenb); + + return hemdistsign(GETSIGN(a), GETSIGN(b), siglena); } Datum @@ -525,6 +543,7 @@ gtsvector_penalty(PG_FUNCTION_ARGS) GISTENTRY *origentry = (GISTENTRY *) PG_GETARG_POINTER(0); /* always ISSIGNKEY */ GISTENTRY *newentry = (GISTENTRY *) PG_GETARG_POINTER(1); float *penalty = (float *) PG_GETARG_POINTER(2); + int siglen = ((GistTsVectorOptions *) PG_GETARG_POINTER(3))->siglen; SignTSVector *origval = (SignTSVector *) DatumGetPointer(origentry->key); SignTSVector *newval = (SignTSVector *) DatumGetPointer(newentry->key); BITVECP orig = GETSIGN(origval); @@ -533,14 +552,22 @@ gtsvector_penalty(PG_FUNCTION_ARGS) if (ISARRKEY(newval)) { - BITVEC sign; + BITVECP sign = palloc(siglen); - makesign(sign, newval); + makesign(sign, newval, siglen); if (ISALLTRUE(origval)) - *penalty = ((float) (SIGLENBIT - sizebitvec(sign))) / (float) (SIGLENBIT + 1); + { + int siglenbit = SIGLENBIT(siglen); + + *penalty = + (float) (siglenbit - sizebitvec(sign, siglen)) / + (float) (siglenbit + 1); + } else - *penalty = hemdistsign(sign, orig); + *penalty = hemdistsign(sign, orig, siglen); + + pfree(sign); } else *penalty = hemdist(origval, newval); @@ -550,19 +577,19 @@ gtsvector_penalty(PG_FUNCTION_ARGS) typedef struct { bool allistrue; - BITVEC sign; + BITVECP sign; } CACHESIGN; static void -fillcache(CACHESIGN *item, SignTSVector *key) +fillcache(CACHESIGN *item, SignTSVector *key, int siglen) { item->allistrue = false; if (ISARRKEY(key)) - makesign(item->sign, key); + makesign(item->sign, key, siglen); else if (ISALLTRUE(key)) item->allistrue = true; else - memcpy((void *) item->sign, (void *) GETSIGN(key), sizeof(BITVEC)); + memcpy((void *) item->sign, (void *) GETSIGN(key), siglen); } #define WISH_F(a,b,c) (double)( -(double)(((a)-(b))*((a)-(b))*((a)-(b)))*(c) ) @@ -586,19 +613,19 @@ comparecost(const void *va, const void *vb) static int -hemdistcache(CACHESIGN *a, CACHESIGN *b) +hemdistcache(CACHESIGN *a, CACHESIGN *b, int siglen) { if (a->allistrue) { if (b->allistrue) return 0; else - return SIGLENBIT - sizebitvec(b->sign); + return SIGLENBIT(siglen) - sizebitvec(b->sign, siglen); } else if (b->allistrue) - return SIGLENBIT - sizebitvec(a->sign); + return SIGLENBIT(siglen) - sizebitvec(a->sign, siglen); - return hemdistsign(a->sign, b->sign); + return hemdistsign(a->sign, b->sign, siglen); } Datum @@ -606,6 +633,7 @@ gtsvector_picksplit(PG_FUNCTION_ARGS) { GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0); GIST_SPLITVEC *v = (GIST_SPLITVEC *) PG_GETARG_POINTER(1); + int siglen = ((GistTsVectorOptions *) PG_GETARG_POINTER(2))->siglen; OffsetNumber k, j; SignTSVector *datum_l, @@ -625,6 +653,7 @@ gtsvector_picksplit(PG_FUNCTION_ARGS) BITVECP ptr; int i; CACHESIGN *cache; + char *cache_sign; SPLITCOST *costvector; maxoff = entryvec->n - 2; @@ -633,16 +662,22 @@ gtsvector_picksplit(PG_FUNCTION_ARGS) v->spl_right = (OffsetNumber *) palloc(nbytes); cache = (CACHESIGN *) palloc(sizeof(CACHESIGN) * (maxoff + 2)); - fillcache(&cache[FirstOffsetNumber], GETENTRY(entryvec, FirstOffsetNumber)); + cache_sign = palloc(siglen * (maxoff + 2)); + + for (j = 0; j < maxoff + 2; j++) + cache[j].sign = &cache_sign[siglen * j]; + + fillcache(&cache[FirstOffsetNumber], GETENTRY(entryvec, FirstOffsetNumber), + siglen); for (k = FirstOffsetNumber; k < maxoff; k = OffsetNumberNext(k)) { for (j = OffsetNumberNext(k); j <= maxoff; j = OffsetNumberNext(j)) { if (k == FirstOffsetNumber) - fillcache(&cache[j], GETENTRY(entryvec, j)); + fillcache(&cache[j], GETENTRY(entryvec, j), siglen); - size_waste = hemdistcache(&(cache[j]), &(cache[k])); + size_waste = hemdistcache(&(cache[j]), &(cache[k]), siglen); if (size_waste > waste) { waste = size_waste; @@ -664,44 +699,21 @@ gtsvector_picksplit(PG_FUNCTION_ARGS) } /* form initial .. */ - if (cache[seed_1].allistrue) - { - datum_l = (SignTSVector *) palloc(CALCGTSIZE(SIGNKEY | ALLISTRUE, 0)); - SET_VARSIZE(datum_l, CALCGTSIZE(SIGNKEY | ALLISTRUE, 0)); - datum_l->flag = SIGNKEY | ALLISTRUE; - } - else - { - datum_l = (SignTSVector *) palloc(CALCGTSIZE(SIGNKEY, 0)); - SET_VARSIZE(datum_l, CALCGTSIZE(SIGNKEY, 0)); - datum_l->flag = SIGNKEY; - memcpy((void *) GETSIGN(datum_l), (void *) cache[seed_1].sign, sizeof(BITVEC)); - } - if (cache[seed_2].allistrue) - { - datum_r = (SignTSVector *) palloc(CALCGTSIZE(SIGNKEY | ALLISTRUE, 0)); - SET_VARSIZE(datum_r, CALCGTSIZE(SIGNKEY | ALLISTRUE, 0)); - datum_r->flag = SIGNKEY | ALLISTRUE; - } - else - { - datum_r = (SignTSVector *) palloc(CALCGTSIZE(SIGNKEY, 0)); - SET_VARSIZE(datum_r, CALCGTSIZE(SIGNKEY, 0)); - datum_r->flag = SIGNKEY; - memcpy((void *) GETSIGN(datum_r), (void *) cache[seed_2].sign, sizeof(BITVEC)); - } - + datum_l = gtsvector_alloc(SIGNKEY | (cache[seed_1].allistrue ? ALLISTRUE : 0), + siglen, cache[seed_1].sign); + datum_r = gtsvector_alloc(SIGNKEY | (cache[seed_2].allistrue ? ALLISTRUE : 0), + siglen, cache[seed_2].sign); union_l = GETSIGN(datum_l); union_r = GETSIGN(datum_r); maxoff = OffsetNumberNext(maxoff); - fillcache(&cache[maxoff], GETENTRY(entryvec, maxoff)); + fillcache(&cache[maxoff], GETENTRY(entryvec, maxoff), siglen); /* sort before ... */ costvector = (SPLITCOST *) palloc(sizeof(SPLITCOST) * maxoff); for (j = FirstOffsetNumber; j <= maxoff; j = OffsetNumberNext(j)) { costvector[j - 1].pos = j; - size_alpha = hemdistcache(&(cache[seed_1]), &(cache[j])); - size_beta = hemdistcache(&(cache[seed_2]), &(cache[j])); + size_alpha = hemdistcache(&(cache[seed_1]), &(cache[j]), siglen); + size_beta = hemdistcache(&(cache[seed_2]), &(cache[j]), siglen); costvector[j - 1].cost = Abs(size_alpha - size_beta); } qsort((void *) costvector, maxoff, sizeof(SPLITCOST), comparecost); @@ -727,36 +739,34 @@ gtsvector_picksplit(PG_FUNCTION_ARGS) if (ISALLTRUE(datum_l) && cache[j].allistrue) size_alpha = 0; else - size_alpha = SIGLENBIT - sizebitvec( - (cache[j].allistrue) ? GETSIGN(datum_l) : GETSIGN(cache[j].sign) - ); + size_alpha = SIGLENBIT(siglen) - + sizebitvec((cache[j].allistrue) ? GETSIGN(datum_l) : GETSIGN(cache[j].sign), siglen); } else - size_alpha = hemdistsign(cache[j].sign, GETSIGN(datum_l)); + size_alpha = hemdistsign(cache[j].sign, GETSIGN(datum_l), siglen); if (ISALLTRUE(datum_r) || cache[j].allistrue) { if (ISALLTRUE(datum_r) && cache[j].allistrue) size_beta = 0; else - size_beta = SIGLENBIT - sizebitvec( - (cache[j].allistrue) ? GETSIGN(datum_r) : GETSIGN(cache[j].sign) - ); + size_beta = SIGLENBIT(siglen) - + sizebitvec((cache[j].allistrue) ? GETSIGN(datum_r) : GETSIGN(cache[j].sign), siglen); } else - size_beta = hemdistsign(cache[j].sign, GETSIGN(datum_r)); + size_beta = hemdistsign(cache[j].sign, GETSIGN(datum_r), siglen); if (size_alpha < size_beta + WISH_F(v->spl_nleft, v->spl_nright, 0.1)) { if (ISALLTRUE(datum_l) || cache[j].allistrue) { if (!ISALLTRUE(datum_l)) - MemSet((void *) GETSIGN(datum_l), 0xff, sizeof(BITVEC)); + MemSet((void *) GETSIGN(datum_l), 0xff, siglen); } else { ptr = cache[j].sign; - LOOPBYTE + LOOPBYTE(siglen) union_l[i] |= ptr[i]; } *left++ = j; @@ -767,12 +777,12 @@ gtsvector_picksplit(PG_FUNCTION_ARGS) if (ISALLTRUE(datum_r) || cache[j].allistrue) { if (!ISALLTRUE(datum_r)) - MemSet((void *) GETSIGN(datum_r), 0xff, sizeof(BITVEC)); + MemSet((void *) GETSIGN(datum_r), 0xff, siglen); } else { ptr = cache[j].sign; - LOOPBYTE + LOOPBYTE(siglen) union_r[i] |= ptr[i]; } *right++ = j; @@ -799,3 +809,20 @@ gtsvector_consistent_oldsig(PG_FUNCTION_ARGS) { return gtsvector_consistent(fcinfo); } + +Datum +gtsvector_options(PG_FUNCTION_ARGS) +{ + Datum raw_options = PG_GETARG_DATUM(0); + bool validate = PG_GETARG_BOOL(1); + relopt_int siglen = + { {"siglen", "signature length", 0, 0, 6, RELOPT_TYPE_INT }, + SIGLEN_DEFAULT, 1, SIGLEN_MAX }; + relopt_gen *optgen[] = { &siglen.gen }; + int offsets[] = { offsetof(GistTsVectorOptions, siglen) }; + GistTsVectorOptions *options = + parseAndFillLocalRelOptions(raw_options, optgen, offsets, 1, + sizeof(GistTsVectorOptions), validate); + + PG_RETURN_POINTER(options); +} diff --git a/src/include/catalog/pg_amproc.dat b/src/include/catalog/pg_amproc.dat index 020b7413cc..5ceee11ab1 100644 --- a/src/include/catalog/pg_amproc.dat +++ b/src/include/catalog/pg_amproc.dat @@ -458,7 +458,7 @@ amproc => 'gist_circle_distance' }, { amprocfamily => 'gist/tsvector_ops', amproclefttype => 'tsvector', amprocrighttype => 'tsvector', amprocnum => '1', - amproc => 'gtsvector_consistent(internal,tsvector,int2,oid,internal)' }, + amproc => 'gtsvector_consistent(internal,tsvector,int2,oid,internal,internal)' }, { amprocfamily => 'gist/tsvector_ops', amproclefttype => 'tsvector', amprocrighttype => 'tsvector', amprocnum => '2', amproc => 'gtsvector_union' }, @@ -476,6 +476,9 @@ amproc => 'gtsvector_picksplit' }, { amprocfamily => 'gist/tsvector_ops', amproclefttype => 'tsvector', amprocrighttype => 'tsvector', amprocnum => '7', amproc => 'gtsvector_same' }, +{ amprocfamily => 'gist/tsvector_ops', amproclefttype => 'tsvector', + amprocrighttype => 'tsvector', amprocnum => '10', + amproc => 'gtsvector_options' }, { amprocfamily => 'gist/tsquery_ops', amproclefttype => 'tsquery', amprocrighttype => 'tsquery', amprocnum => '1', amproc => 'gtsquery_consistent(internal,tsquery,int2,oid,internal)' }, diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 87335248a0..82b51fc1bb 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8569,30 +8569,35 @@ { oid => '3648', descr => 'GiST tsvector support', proname => 'gtsvector_compress', prorettype => 'internal', - proargtypes => 'internal', prosrc => 'gtsvector_compress' }, + proargtypes => 'internal internal', prosrc => 'gtsvector_compress' }, { oid => '3649', descr => 'GiST tsvector support', proname => 'gtsvector_decompress', prorettype => 'internal', - proargtypes => 'internal', prosrc => 'gtsvector_decompress' }, + proargtypes => 'internal internal', prosrc => 'gtsvector_decompress' }, { oid => '3650', descr => 'GiST tsvector support', proname => 'gtsvector_picksplit', prorettype => 'internal', - proargtypes => 'internal internal', prosrc => 'gtsvector_picksplit' }, + proargtypes => 'internal internal internal', prosrc => 'gtsvector_picksplit' }, { oid => '3651', descr => 'GiST tsvector support', proname => 'gtsvector_union', prorettype => 'gtsvector', - proargtypes => 'internal internal', prosrc => 'gtsvector_union' }, + proargtypes => 'internal internal internal', prosrc => 'gtsvector_union' }, { oid => '3652', descr => 'GiST tsvector support', proname => 'gtsvector_same', prorettype => 'internal', - proargtypes => 'gtsvector gtsvector internal', prosrc => 'gtsvector_same' }, + proargtypes => 'gtsvector gtsvector internal internal', + prosrc => 'gtsvector_same' }, { oid => '3653', descr => 'GiST tsvector support', proname => 'gtsvector_penalty', prorettype => 'internal', - proargtypes => 'internal internal internal', prosrc => 'gtsvector_penalty' }, + proargtypes => 'internal internal internal internal', + prosrc => 'gtsvector_penalty' }, { oid => '3654', descr => 'GiST tsvector support', proname => 'gtsvector_consistent', prorettype => 'bool', - proargtypes => 'internal tsvector int2 oid internal', + proargtypes => 'internal tsvector int2 oid internal internal', prosrc => 'gtsvector_consistent' }, { oid => '3790', descr => 'GiST tsvector support (obsolete)', proname => 'gtsvector_consistent', prorettype => 'bool', proargtypes => 'internal gtsvector int4 oid internal', prosrc => 'gtsvector_consistent_oldsig' }, +{ oid => '3998', descr => 'GiST tsvector support', + proname => 'gtsvector_options', prorettype => 'internal', + proargtypes => 'internal bool', prosrc => 'gtsvector_options' }, { oid => '3656', descr => 'GIN tsvector support', proname => 'gin_extract_tsvector', prorettype => 'internal', diff --git a/src/test/regress/expected/tsearch.out b/src/test/regress/expected/tsearch.out index 6f61acc1ed..a1873dc722 100644 --- a/src/test/regress/expected/tsearch.out +++ b/src/test/regress/expected/tsearch.out @@ -260,6 +260,182 @@ SELECT count(*) FROM test_tsvector WHERE a @@ '!no_such_lexeme'; 508 (1 row) +-- Test siglen parameter of GiST tsvector_ops +CREATE INDEX wowidx1 ON test_tsvector USING gist (a tsvector_ops(foo=1)); +ERROR: unrecognized parameter "foo" +CREATE INDEX wowidx1 ON test_tsvector USING gist (a tsvector_ops(siglen=0)); +ERROR: value 0 out of bounds for option "siglen" +DETAIL: Valid values are between "1" and "484". +CREATE INDEX wowidx1 ON test_tsvector USING gist (a tsvector_ops(siglen=485)); +ERROR: value 485 out of bounds for option "siglen" +DETAIL: Valid values are between "1" and "484". +CREATE INDEX wowidx1 ON test_tsvector USING gist (a tsvector_ops(siglen=100,foo='bar')); +ERROR: unrecognized parameter "foo" +CREATE INDEX wowidx1 ON test_tsvector USING gist (a tsvector_ops(siglen=100, siglen = 200)); +ERROR: parameter "siglen" specified more than once +CREATE INDEX wowidx2 ON test_tsvector USING gist (a tsvector_ops(siglen=1)); +\d test_tsvector + Table "public.test_tsvector" + Column | Type | Collation | Nullable | Default +--------+----------+-----------+----------+--------- + t | text | | | + a | tsvector | | | +Indexes: + "wowidx" gist (a) + "wowidx2" gist (a tsvector_ops (siglen='1')) + +DROP INDEX wowidx; +EXPLAIN (costs off) SELECT count(*) FROM test_tsvector WHERE a @@ 'wr|qh'; + QUERY PLAN +------------------------------------------------------------- + Aggregate + -> Bitmap Heap Scan on test_tsvector + Recheck Cond: (a @@ '''wr'' | ''qh'''::tsquery) + -> Bitmap Index Scan on wowidx2 + Index Cond: (a @@ '''wr'' | ''qh'''::tsquery) +(5 rows) + +SELECT count(*) FROM test_tsvector WHERE a @@ 'wr|qh'; + count +------- + 158 +(1 row) + +SELECT count(*) FROM test_tsvector WHERE a @@ 'wr&qh'; + count +------- + 17 +(1 row) + +SELECT count(*) FROM test_tsvector WHERE a @@ 'eq&yt'; + count +------- + 6 +(1 row) + +SELECT count(*) FROM test_tsvector WHERE a @@ 'eq|yt'; + count +------- + 98 +(1 row) + +SELECT count(*) FROM test_tsvector WHERE a @@ '(eq&yt)|(wr&qh)'; + count +------- + 23 +(1 row) + +SELECT count(*) FROM test_tsvector WHERE a @@ '(eq|yt)&(wr|qh)'; + count +------- + 39 +(1 row) + +SELECT count(*) FROM test_tsvector WHERE a @@ 'w:*|q:*'; + count +------- + 494 +(1 row) + +SELECT count(*) FROM test_tsvector WHERE a @@ any ('{wr,qh}'); + count +------- + 158 +(1 row) + +SELECT count(*) FROM test_tsvector WHERE a @@ 'no_such_lexeme'; + count +------- + 0 +(1 row) + +SELECT count(*) FROM test_tsvector WHERE a @@ '!no_such_lexeme'; + count +------- + 508 +(1 row) + +DROP INDEX wowidx2; +CREATE INDEX wowidx ON test_tsvector USING gist (a DEFAULT(siglen=484)); +\d test_tsvector + Table "public.test_tsvector" + Column | Type | Collation | Nullable | Default +--------+----------+-----------+----------+--------- + t | text | | | + a | tsvector | | | +Indexes: + "wowidx" gist (a tsvector_ops (siglen='484')) + +EXPLAIN (costs off) SELECT count(*) FROM test_tsvector WHERE a @@ 'wr|qh'; + QUERY PLAN +------------------------------------------------------------- + Aggregate + -> Bitmap Heap Scan on test_tsvector + Recheck Cond: (a @@ '''wr'' | ''qh'''::tsquery) + -> Bitmap Index Scan on wowidx + Index Cond: (a @@ '''wr'' | ''qh'''::tsquery) +(5 rows) + +SELECT count(*) FROM test_tsvector WHERE a @@ 'wr|qh'; + count +------- + 158 +(1 row) + +SELECT count(*) FROM test_tsvector WHERE a @@ 'wr&qh'; + count +------- + 17 +(1 row) + +SELECT count(*) FROM test_tsvector WHERE a @@ 'eq&yt'; + count +------- + 6 +(1 row) + +SELECT count(*) FROM test_tsvector WHERE a @@ 'eq|yt'; + count +------- + 98 +(1 row) + +SELECT count(*) FROM test_tsvector WHERE a @@ '(eq&yt)|(wr&qh)'; + count +------- + 23 +(1 row) + +SELECT count(*) FROM test_tsvector WHERE a @@ '(eq|yt)&(wr|qh)'; + count +------- + 39 +(1 row) + +SELECT count(*) FROM test_tsvector WHERE a @@ 'w:*|q:*'; + count +------- + 494 +(1 row) + +SELECT count(*) FROM test_tsvector WHERE a @@ any ('{wr,qh}'); + count +------- + 158 +(1 row) + +SELECT count(*) FROM test_tsvector WHERE a @@ 'no_such_lexeme'; + count +------- + 0 +(1 row) + +SELECT count(*) FROM test_tsvector WHERE a @@ '!no_such_lexeme'; + count +------- + 508 +(1 row) + RESET enable_seqscan; RESET enable_indexscan; RESET enable_bitmapscan; diff --git a/src/test/regress/sql/tsearch.sql b/src/test/regress/sql/tsearch.sql index 637bfb3012..9aed780d0c 100644 --- a/src/test/regress/sql/tsearch.sql +++ b/src/test/regress/sql/tsearch.sql @@ -87,6 +87,51 @@ SELECT count(*) FROM test_tsvector WHERE a @@ any ('{wr,qh}'); SELECT count(*) FROM test_tsvector WHERE a @@ 'no_such_lexeme'; SELECT count(*) FROM test_tsvector WHERE a @@ '!no_such_lexeme'; +-- Test siglen parameter of GiST tsvector_ops +CREATE INDEX wowidx1 ON test_tsvector USING gist (a tsvector_ops(foo=1)); +CREATE INDEX wowidx1 ON test_tsvector USING gist (a tsvector_ops(siglen=0)); +CREATE INDEX wowidx1 ON test_tsvector USING gist (a tsvector_ops(siglen=485)); +CREATE INDEX wowidx1 ON test_tsvector USING gist (a tsvector_ops(siglen=100,foo='bar')); +CREATE INDEX wowidx1 ON test_tsvector USING gist (a tsvector_ops(siglen=100, siglen = 200)); + +CREATE INDEX wowidx2 ON test_tsvector USING gist (a tsvector_ops(siglen=1)); + +\d test_tsvector + +DROP INDEX wowidx; + +EXPLAIN (costs off) SELECT count(*) FROM test_tsvector WHERE a @@ 'wr|qh'; + +SELECT count(*) FROM test_tsvector WHERE a @@ 'wr|qh'; +SELECT count(*) FROM test_tsvector WHERE a @@ 'wr&qh'; +SELECT count(*) FROM test_tsvector WHERE a @@ 'eq&yt'; +SELECT count(*) FROM test_tsvector WHERE a @@ 'eq|yt'; +SELECT count(*) FROM test_tsvector WHERE a @@ '(eq&yt)|(wr&qh)'; +SELECT count(*) FROM test_tsvector WHERE a @@ '(eq|yt)&(wr|qh)'; +SELECT count(*) FROM test_tsvector WHERE a @@ 'w:*|q:*'; +SELECT count(*) FROM test_tsvector WHERE a @@ any ('{wr,qh}'); +SELECT count(*) FROM test_tsvector WHERE a @@ 'no_such_lexeme'; +SELECT count(*) FROM test_tsvector WHERE a @@ '!no_such_lexeme'; + +DROP INDEX wowidx2; + +CREATE INDEX wowidx ON test_tsvector USING gist (a DEFAULT(siglen=484)); + +\d test_tsvector + +EXPLAIN (costs off) SELECT count(*) FROM test_tsvector WHERE a @@ 'wr|qh'; + +SELECT count(*) FROM test_tsvector WHERE a @@ 'wr|qh'; +SELECT count(*) FROM test_tsvector WHERE a @@ 'wr&qh'; +SELECT count(*) FROM test_tsvector WHERE a @@ 'eq&yt'; +SELECT count(*) FROM test_tsvector WHERE a @@ 'eq|yt'; +SELECT count(*) FROM test_tsvector WHERE a @@ '(eq&yt)|(wr&qh)'; +SELECT count(*) FROM test_tsvector WHERE a @@ '(eq|yt)&(wr|qh)'; +SELECT count(*) FROM test_tsvector WHERE a @@ 'w:*|q:*'; +SELECT count(*) FROM test_tsvector WHERE a @@ any ('{wr,qh}'); +SELECT count(*) FROM test_tsvector WHERE a @@ 'no_such_lexeme'; +SELECT count(*) FROM test_tsvector WHERE a @@ '!no_such_lexeme'; + RESET enable_seqscan; RESET enable_indexscan; RESET enable_bitmapscan; -- 2.20.1 --kw6iywuasi5tjnlh--