Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA384:256) (Exim 4.89) (envelope-from ) id 1eilxI-0007NV-7D for pgsql-hackers@arkaria.postgresql.org; Mon, 05 Feb 2018 18:57:28 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.84_2) (envelope-from ) id 1eilxF-0006CF-JR for pgsql-hackers@arkaria.postgresql.org; Mon, 05 Feb 2018 18:57:25 +0000 Received: from makus.postgresql.org ([2001:4800:1501:1::229]) by malur.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA384:256) (Exim 4.84_2) (envelope-from ) id 1eilxE-0006C4-Hu for pgsql-hackers@lists.postgresql.org; Mon, 05 Feb 2018 18:57:25 +0000 Received: from mail-wm0-x241.google.com ([2a00:1450:400c:c09::241]) by makus.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.89) (envelope-from ) id 1eilx3-0003KR-9V for pgsql-hackers@postgresql.org; Mon, 05 Feb 2018 18:57:22 +0000 Received: by mail-wm0-x241.google.com with SMTP id g1so28112514wmg.2 for ; Mon, 05 Feb 2018 10:57:12 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=2ndquadrant-com.20150623.gappssmtp.com; s=20150623; h=subject:from:to:cc:references:message-id:date:user-agent :mime-version:in-reply-to:content-language; bh=6Rw+n10aO76/oejFRtCGgflNEzIPdmvWohhEIOswk4g=; b=q82Q4LYQxRQTcnrhSGffBHDEp7wP85unLKVbAKIqYZnsSJZOywuD0Lh9LgrqWBBqhK 1cy3d48OEkQj9priNrxVI3bBBCnTSossn5mDoGRgtX6aOLtjbm4olcKZ4kXWFMVx8eWn 9QIui3c5jCpezx9YMbleOrceFtai6r0M4+q3Z0yR6mLjBlcg8xIDioWE7H4/JydBeviJ iXwIY71e+OfiqOuSdcmH3xhy/otBYdpjyQZgSCox7HbMIYWarQCluaSP4dTeliSNdafE q0H1fmc02nkHY95qxtC2u23DsRiWwtVkakBMCkre8SE0mEgFevxwELl0HBrOqRjie35Y 1qEA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:subject:from:to:cc:references:message-id:date :user-agent:mime-version:in-reply-to:content-language; bh=6Rw+n10aO76/oejFRtCGgflNEzIPdmvWohhEIOswk4g=; b=XahB2J+P/LtsUzEFhA1bMo8Va1kA60HvRX05t9YO1kJY4N2IQ/M8QGzERjAABasY7n 2mvKgcEVZl1K/453+Sl4H4jY2UikCt9XIphAPXbNb/hE+5LXTSNpom/wcbFSoCXKlJyb 11NzSi2NGccyuwKhrbrgRye2YGK3RdXfVpBIjCwPI4ISpxiV9RPtW1FIFTQQ18qc9e48 1alEAtEkoFhyUijNqr/BD50AOzvTcuSPgApVQUQuFSkyCzyzsqXqz1tlpWQOfUheJ0hD ljdOsGQ5Zcynr/yT0Vc+bvEnsoKTjEs1CVAXeiBn1Tw8XwC9l1p9ghacyEruk5aeJkrl Bxlg== X-Gm-Message-State: APf1xPAWORrCNaMznpQTxNbJDju+6gxd6SrCeFYB12pvC2+ANgtJ8Y/6 abhrqIUBHidQT9MGDBi1vz6Mhle478m9qDSYOCvtWXP5QM1YeodHqnHU8IwRqbvckqFQZqsb6Ku cz5vHVQXPgl0eWSc8JT5Bz9XlL7NuiGDrsuoiLXpI2OvLj+CYiF9G8Q0aqHeab/tMerGrQ/O8wK fm12A89vq8l8I= X-Google-Smtp-Source: AH8x226P7+EVoO0+b8ByxJMHLTf/bf3JkOxo6xfmZQHxUpB1r0+YC983hl547FZg1upTPi92bCceDg== X-Received: by 10.28.166.195 with SMTP id p186mr223226wme.81.1517857030493; Mon, 05 Feb 2018 10:57:10 -0800 (PST) Received: from [10.137.2.17] (ip-78-102-97-226.net.upcbroadband.cz. [78.102.97.226]) by smtp.gmail.com with ESMTPSA id x26sm3879083wmc.27.2018.02.05.10.57.08 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 05 Feb 2018 10:57:09 -0800 (PST) Subject: Re: WIP: BRIN multi-range indexes From: Tomas Vondra To: Alvaro Herrera Cc: Mark Dilger , pgsql-hackers@postgreSQL.org References: <20180123200537.gfx5hxcp5k5hktpe@alvherre.pgsql> <821c4c1d-592e-8916-e323-04607b11777e@2ndquadrant.com> Message-ID: Date: Mon, 5 Feb 2018 19:57:06 +0100 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Thunderbird/52.4.0 MIME-Version: 1.0 In-Reply-To: <821c4c1d-592e-8916-e323-04607b11777e@2ndquadrant.com> Content-Type: multipart/mixed; boundary="------------53C4B41F409B92A47ECAD3E5" Content-Language: en-US List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Precedence: bulk This is a multi-part message in MIME format. --------------53C4B41F409B92A47ECAD3E5 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 7bit On 01/23/2018 10:07 PM, Tomas Vondra wrote: > > > On 01/23/2018 09:05 PM, Alvaro Herrera wrote: >> This stuff sounds pretty nice. However, have a look at this report: >> >> https://codecov.io/gh/postgresql-cfbot/postgresql/commit/2aa632dae3066900e15d2d42a4aad811dec11f08 >> >> it seems to me that the new code is not tested at all. Shouldn't you >> add a few more tests? >> > > I have a hard time reading the report, but you're right I haven't added > any tests for the new opclasses (bloom and minmax_multi). I agree that's > something that needs to be addressed. > >> I think 0004 should apply to unpatched master (except for the parts >> that concern files not in master); sounds like a good candidate for >> first apply. Then 0001, which seems mostly just refactoring. 0002 and >> 0003 are the really interesting ones (minus the code removed by >> 0004). >> > > That sounds like a reasonable plan. I'll reorder the patch series along > those lines in the next few days. > And here we go. Attached is a reworked patch series that moves the IS NULL tweak to the beginning of the series, and also adds proper regression tests both for the bloom and multi-minmax opclasses. I've simply copied the brin.sql tests and tweaked it for the new opclasses. I've also added a bunch of missing multi-minmax opclasses. At this point all data types that have minmax opclass should also have multi-minmax one, except for these types: * bytea * char * name * text * bpchar * bit * varbit The reason is that I'm not quite sure how to define the 'distance' function, which is needed when picking ranges to merge when building/updating the index. BTW while working on the regression tests, I've noticed that brin.sql fails to test a couple of minmax opclasses (e.g. abstime/reltime). Is that intentional or is that something we should fix eventually? regards -- Tomas Vondra http://www.2ndQuadrant.com PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services --------------53C4B41F409B92A47ECAD3E5 Content-Type: text/x-patch; name="0001-Pass-all-keys-to-BRIN-consistent-function-at-once.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0001-Pass-all-keys-to-BRIN-consistent-function-at-once.patch" From bbaaf2dd5ac6fcfff37b226afa1197b02c48ce8b Mon Sep 17 00:00:00 2001 From: Tomas Vondra Date: Tue, 9 Jan 2018 00:59:09 +0100 Subject: [PATCH 1/4] Pass all keys to BRIN consistent function at once When building the bitmap, bringetbitmap was passing the scan keys to consistent support procedure one by one. That works fine for the simple default opclasses (minmax and inclusion), but not for opclasses with more complicated summaries. For example the multi-minmax opclass needs to see all the keys to efficiently eliminate the partial ranges, otherwise it's mostly just a more expensive minmax opclass. So instead split the scan keys into per-attribute groups, and pass the whole group at once. --- src/backend/access/brin/brin.c | 97 +++++-- src/backend/access/brin/brin_inclusion.c | 484 ++++++++++++++++++------------- src/backend/access/brin/brin_minmax.c | 168 +++++++---- 3 files changed, 451 insertions(+), 298 deletions(-) diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c index 68b3371..b568786 100644 --- a/src/backend/access/brin/brin.c +++ b/src/backend/access/brin/brin.c @@ -367,6 +367,9 @@ bringetbitmap(IndexScanDesc scan, TIDBitmap *tbm) BrinMemTuple *dtup; BrinTuple *btup = NULL; Size btupsz = 0; + ScanKey **keys; + int *nkeys; + int keyno; opaque = (BrinOpaque *) scan->opaque; bdesc = opaque->bo_bdesc; @@ -388,6 +391,53 @@ bringetbitmap(IndexScanDesc scan, TIDBitmap *tbm) */ consistentFn = palloc0(sizeof(FmgrInfo) * bdesc->bd_tupdesc->natts); + /* + * Make room for per-attribute lists of scan keys that we'll pass to the + * consistent support procedure. + */ + keys = palloc0(sizeof(ScanKey *) * bdesc->bd_tupdesc->natts); + nkeys = palloc0(sizeof(int) * bdesc->bd_tupdesc->natts); + + /* + * Preprocess the scan keys - split them into per-attribute arrays. + */ + for (keyno = 0; keyno < scan->numberOfKeys; keyno++) + { + ScanKey key = &scan->keyData[keyno]; + AttrNumber keyattno = key->sk_attno; + + /* + * The collation of the scan key must match the collation + * used in the index column (but only if the search is not + * IS NULL/ IS NOT NULL). Otherwise we shouldn't be using + * this index ... + */ + Assert((key->sk_flags & SK_ISNULL) || + (key->sk_collation == + TupleDescAttr(bdesc->bd_tupdesc, + keyattno - 1)->attcollation)); + + /* First time we see this index attribute, so init as needed. */ + if (!keys[keyattno-1]) + { + FmgrInfo *tmp; + + keys[keyattno-1] = palloc0(sizeof(ScanKey) * scan->numberOfKeys); + + /* First time this column, so look up consistent function */ + Assert(consistentFn[keyattno - 1].fn_oid == InvalidOid); + + tmp = index_getprocinfo(idxRel, keyattno, + BRIN_PROCNUM_CONSISTENT); + fmgr_info_copy(&consistentFn[keyattno - 1], tmp, + CurrentMemoryContext); + } + + /* Add key to the per-attribute array. */ + keys[keyattno - 1][nkeys[keyattno - 1]] = key; + nkeys[keyattno - 1]++; + } + /* allocate an initial in-memory tuple, out of the per-range memcxt */ dtup = brin_new_memtuple(bdesc); @@ -448,7 +498,7 @@ bringetbitmap(IndexScanDesc scan, TIDBitmap *tbm) } else { - int keyno; + int attno; /* * Compare scan keys with summary values stored for the range. @@ -458,34 +508,26 @@ bringetbitmap(IndexScanDesc scan, TIDBitmap *tbm) * no keys. */ addrange = true; - for (keyno = 0; keyno < scan->numberOfKeys; keyno++) + for (attno = 1; attno <= bdesc->bd_tupdesc->natts; attno++) { - ScanKey key = &scan->keyData[keyno]; - AttrNumber keyattno = key->sk_attno; - BrinValues *bval = &dtup->bt_columns[keyattno - 1]; + BrinValues *bval; Datum add; + Oid collation; + + /* skip attributes without any san keys */ + if (!nkeys[attno - 1]) + continue; + + bval = &dtup->bt_columns[attno - 1]; + + Assert((nkeys[attno - 1] > 0) && + (nkeys[attno - 1] <= scan->numberOfKeys)); /* - * The collation of the scan key must match the collation - * used in the index column (but only if the search is not - * IS NULL/ IS NOT NULL). Otherwise we shouldn't be using - * this index ... + * Collation from the first key (has to be the same for + * all keys for the same attribue). */ - Assert((key->sk_flags & SK_ISNULL) || - (key->sk_collation == - TupleDescAttr(bdesc->bd_tupdesc, - keyattno - 1)->attcollation)); - - /* First time this column? look up consistent function */ - if (consistentFn[keyattno - 1].fn_oid == InvalidOid) - { - FmgrInfo *tmp; - - tmp = index_getprocinfo(idxRel, keyattno, - BRIN_PROCNUM_CONSISTENT); - fmgr_info_copy(&consistentFn[keyattno - 1], tmp, - CurrentMemoryContext); - } + collation = keys[attno - 1][0]->sk_collation; /* * Check whether the scan key is consistent with the page @@ -497,11 +539,12 @@ bringetbitmap(IndexScanDesc scan, TIDBitmap *tbm) * the range as a whole, so break out of the loop as soon * as a false return value is obtained. */ - add = FunctionCall3Coll(&consistentFn[keyattno - 1], - key->sk_collation, + add = FunctionCall4Coll(&consistentFn[attno - 1], + collation, PointerGetDatum(bdesc), PointerGetDatum(bval), - PointerGetDatum(key)); + PointerGetDatum(keys[attno - 1]), + Int32GetDatum(nkeys[attno - 1])); addrange = DatumGetBool(add); if (!addrange) break; diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 2542877..fb7f2b0 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -248,7 +248,8 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) { BrinDesc *bdesc = (BrinDesc *) PG_GETARG_POINTER(0); BrinValues *column = (BrinValues *) PG_GETARG_POINTER(1); - ScanKey key = (ScanKey) PG_GETARG_POINTER(2); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); Oid colloid = PG_GET_COLLATION(), subtype; Datum unionval; @@ -256,235 +257,300 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) Datum query; FmgrInfo *finfo; Datum result; + int keyno; + bool matches; + bool regular_keys = false; - Assert(key->sk_attno == column->bv_attno); - - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* + * First check if there are any IS NULL scan keys, and if we're + * violating them. In that case we can terminate early, without + * inspecting the ranges. + */ + for (keyno = 0; keyno < nkeys; keyno++) { - if (key->sk_flags & SK_SEARCHNULL) - { - if (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); - PG_RETURN_BOOL(false); - } + ScanKey key = keys[keyno]; - /* - * For IS NOT NULL, we can only skip ranges that are known to have - * only nulls. - */ - if (key->sk_flags & SK_SEARCHNOTNULL) - PG_RETURN_BOOL(!column->bv_allnulls); - - /* - * Neither IS NULL nor IS NOT NULL was used; assume all indexable - * operators are strict and return false. - */ - PG_RETURN_BOOL(false); - } + Assert(key->sk_attno == column->bv_attno); - /* If it is all nulls, it cannot possibly be consistent. */ - if (column->bv_allnulls) - PG_RETURN_BOOL(false); + /* handle IS NULL/IS NOT NULL tests */ + if (key->sk_flags & SK_ISNULL) + { + if (key->sk_flags & SK_SEARCHNULL) + { + if (column->bv_allnulls || column->bv_hasnulls) + continue; /* this key is fine, continue */ - /* It has to be checked, if it contains elements that are not mergeable. */ - if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) - PG_RETURN_BOOL(true); + PG_RETURN_BOOL(false); + } - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; - switch (key->sk_strategy) - { /* - * Placement strategies - * - * These are implemented by logically negating the result of the - * converse placement operator; for this to work, the converse - * operator must be part of the opclass. An error will be thrown - * by inclusion_get_strategy_procinfo() if the required strategy - * is not part of the opclass. - * - * These all return false if either argument is empty, so there is - * no need to check for empty elements. + * For IS NOT NULL, we can only skip ranges that are known to have + * only nulls. */ + if (key->sk_flags & SK_SEARCHNOTNULL) + { + if (column->bv_allnulls) + PG_RETURN_BOOL(false); - case RTLeftStrategyNumber: - finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, - RTOverRightStrategyNumber); - result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); - - case RTOverLeftStrategyNumber: - finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, - RTRightStrategyNumber); - result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); - - case RTOverRightStrategyNumber: - finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, - RTLeftStrategyNumber); - result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); - - case RTRightStrategyNumber: - finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, - RTOverLeftStrategyNumber); - result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); - - case RTBelowStrategyNumber: - finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, - RTOverAboveStrategyNumber); - result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); - - case RTOverBelowStrategyNumber: - finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, - RTAboveStrategyNumber); - result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); - - case RTOverAboveStrategyNumber: - finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, - RTBelowStrategyNumber); - result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); - - case RTAboveStrategyNumber: - finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, - RTOverBelowStrategyNumber); - result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + continue; + } /* - * Overlap and contains strategies - * - * These strategies are simple enough that we can simply call the - * operator and return its result. Empty elements don't change - * the result. + * Neither IS NULL nor IS NOT NULL was used; assume all indexable + * operators are strict and return false. */ + PG_RETURN_BOOL(false); + } + else + /* note we have regular (non-NULL) scan keys */ + regular_keys = true; + } - case RTOverlapStrategyNumber: - case RTContainsStrategyNumber: - case RTOldContainsStrategyNumber: - case RTContainsElemStrategyNumber: - case RTSubStrategyNumber: - case RTSubEqualStrategyNumber: - finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, - key->sk_strategy); - result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); - - /* - * Contained by strategies - * - * We cannot just call the original operator for the contained by - * strategies because some elements can be contained even though - * the union is not; instead we use the overlap operator. - * - * We check for empty elements separately as they are not merged - * to the union but contained by everything. - */ + /* + * If the page range is all nulls, it cannot possibly be consistent if + * there are some regular scan keys. + */ + if (column->bv_allnulls && regular_keys) + PG_RETURN_BOOL(false); - case RTContainedByStrategyNumber: - case RTOldContainedByStrategyNumber: - case RTSuperStrategyNumber: - case RTSuperEqualStrategyNumber: - finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, - RTOverlapStrategyNumber); - result = FunctionCall2Coll(finfo, colloid, unionval, query); - if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + /* If there are no regular keys, the page range is considered consistent. */ + if (!regular_keys) + PG_RETURN_BOOL(true); - PG_RETURN_DATUM(column->bv_values[INCLUSION_CONTAINS_EMPTY]); + /* It has to be checked, if it contains elements that are not mergeable. */ + if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) + PG_RETURN_BOOL(true); - /* - * Adjacent strategy - * - * We test for overlap first but to be safe we need to call the - * actual adjacent operator also. - * - * An empty element cannot be adjacent to any other, so there is - * no need to check for it. - */ + matches = true; - case RTAdjacentStrategyNumber: - finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, - RTOverlapStrategyNumber); - result = FunctionCall2Coll(finfo, colloid, unionval, query); - if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + for (keyno = 0; keyno < nkeys; keyno++) + { + ScanKey key = keys[keyno]; - finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, - RTAdjacentStrategyNumber); - result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + /* ignore IS NULL/IS NOT NULL tests handled above */ + if (key->sk_flags & SK_ISNULL) + continue; - /* - * Basic comparison strategies - * - * It is straightforward to support the equality strategies with - * the contains operator. Generally, inequality strategies do not - * make much sense for the types which will be used with the - * inclusion BRIN family of opclasses, but is possible to - * implement them with logical negation of the left-of and - * right-of operators. - * - * NB: These strategies cannot be used with geometric datatypes - * that use comparison of areas! The only exception is the "same" - * strategy. - * - * Empty elements are considered to be less than the others. We - * cannot use the empty support function to check the query is an - * empty element, because the query can be another data type than - * the empty support function argument. So we will return true, - * if there is a possibility that empty elements will change the - * result. - */ + attno = key->sk_attno; + subtype = key->sk_subtype; + query = key->sk_argument; + unionval = column->bv_values[INCLUSION_UNION]; + switch (key->sk_strategy) + { + /* + * Placement strategies + * + * These are implemented by logically negating the result of the + * converse placement operator; for this to work, the converse + * operator must be part of the opclass. An error will be thrown + * by inclusion_get_strategy_procinfo() if the required strategy + * is not part of the opclass. + * + * These all return false if either argument is empty, so there is + * no need to check for empty elements. + */ + + case RTLeftStrategyNumber: + finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, + RTOverRightStrategyNumber); + result = FunctionCall2Coll(finfo, colloid, unionval, query); + matches = (!DatumGetBool(result)); + break; + + case RTOverLeftStrategyNumber: + finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, + RTRightStrategyNumber); + result = FunctionCall2Coll(finfo, colloid, unionval, query); + matches = (!DatumGetBool(result)); + break; + + case RTOverRightStrategyNumber: + finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, + RTLeftStrategyNumber); + result = FunctionCall2Coll(finfo, colloid, unionval, query); + matches = (!DatumGetBool(result)); + break; + + case RTRightStrategyNumber: + finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, + RTOverLeftStrategyNumber); + result = FunctionCall2Coll(finfo, colloid, unionval, query); + matches = (!DatumGetBool(result)); + break; + + case RTBelowStrategyNumber: + finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, + RTOverAboveStrategyNumber); + result = FunctionCall2Coll(finfo, colloid, unionval, query); + matches = (!DatumGetBool(result)); + break; + + case RTOverBelowStrategyNumber: + finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, + RTAboveStrategyNumber); + result = FunctionCall2Coll(finfo, colloid, unionval, query); + matches = (!DatumGetBool(result)); + break; + + case RTOverAboveStrategyNumber: + finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, + RTBelowStrategyNumber); + result = FunctionCall2Coll(finfo, colloid, unionval, query); + matches = (!DatumGetBool(result)); + break; + + case RTAboveStrategyNumber: + finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, + RTOverBelowStrategyNumber); + result = FunctionCall2Coll(finfo, colloid, unionval, query); + matches = (!DatumGetBool(result)); + break; + + /* + * Overlap and contains strategies + * + * These strategies are simple enough that we can simply call the + * operator and return its result. Empty elements don't change + * the result. + */ + + case RTOverlapStrategyNumber: + case RTContainsStrategyNumber: + case RTOldContainsStrategyNumber: + case RTContainsElemStrategyNumber: + case RTSubStrategyNumber: + case RTSubEqualStrategyNumber: + finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, + key->sk_strategy); + result = FunctionCall2Coll(finfo, colloid, unionval, query); + matches = DatumGetBool(result); + break; + + /* + * Contained by strategies + * + * We cannot just call the original operator for the contained by + * strategies because some elements can be contained even though + * the union is not; instead we use the overlap operator. + * + * We check for empty elements separately as they are not merged + * to the union but contained by everything. + */ + + case RTContainedByStrategyNumber: + case RTOldContainedByStrategyNumber: + case RTSuperStrategyNumber: + case RTSuperEqualStrategyNumber: + finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, + RTOverlapStrategyNumber); + result = FunctionCall2Coll(finfo, colloid, unionval, query); + if (DatumGetBool(result)) + matches = true; + else + matches = DatumGetBool(column->bv_values[INCLUSION_CONTAINS_EMPTY]); + + break; + + /* + * Adjacent strategy + * + * We test for overlap first but to be safe we need to call the + * actual adjacent operator also. + * + * An empty element cannot be adjacent to any other, so there is + * no need to check for it. + */ + + case RTAdjacentStrategyNumber: + finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, + RTOverlapStrategyNumber); + result = FunctionCall2Coll(finfo, colloid, unionval, query); + if (DatumGetBool(result)) + matches = (true); + else + { + + finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, + RTAdjacentStrategyNumber); + result = FunctionCall2Coll(finfo, colloid, unionval, query); + matches = DatumGetBool(result); + } + + break; + + /* + * Basic comparison strategies + * + * It is straightforward to support the equality strategies with + * the contains operator. Generally, inequality strategies do not + * make much sense for the types which will be used with the + * inclusion BRIN family of opclasses, but is possible to + * implement them with logical negation of the left-of and + * right-of operators. + * + * NB: These strategies cannot be used with geometric datatypes + * that use comparison of areas! The only exception is the "same" + * strategy. + * + * Empty elements are considered to be less than the others. We + * cannot use the empty support function to check the query is an + * empty element, because the query can be another data type than + * the empty support function argument. So we will return true, + * if there is a possibility that empty elements will change the + * result. + */ + + case RTLessStrategyNumber: + case RTLessEqualStrategyNumber: + finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, + RTRightStrategyNumber); + result = FunctionCall2Coll(finfo, colloid, unionval, query); + if (!DatumGetBool(result)) + matches = (true); + else + matches = DatumGetBool(column->bv_values[INCLUSION_CONTAINS_EMPTY]); + break; + + case RTSameStrategyNumber: + case RTEqualStrategyNumber: + finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, + RTContainsStrategyNumber); + result = FunctionCall2Coll(finfo, colloid, unionval, query); + if (DatumGetBool(result)) + matches = (true); + else + matches = DatumGetBool(column->bv_values[INCLUSION_CONTAINS_EMPTY]); + break; + + case RTGreaterEqualStrategyNumber: + finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, + RTLeftStrategyNumber); + result = FunctionCall2Coll(finfo, colloid, unionval, query); + if (!DatumGetBool(result)) + matches = (true); + else + matches = DatumGetBool(column->bv_values[INCLUSION_CONTAINS_EMPTY]); + break; + + case RTGreaterStrategyNumber: + /* no need to check for empty elements */ + finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, + RTLeftStrategyNumber); + result = FunctionCall2Coll(finfo, colloid, unionval, query); + matches = (!DatumGetBool(result)); + break; + + default: + /* shouldn't happen */ + elog(ERROR, "invalid strategy number %d", key->sk_strategy); + matches = (false); + } - case RTLessStrategyNumber: - case RTLessEqualStrategyNumber: - finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, - RTRightStrategyNumber); - result = FunctionCall2Coll(finfo, colloid, unionval, query); - if (!DatumGetBool(result)) - PG_RETURN_BOOL(true); - - PG_RETURN_DATUM(column->bv_values[INCLUSION_CONTAINS_EMPTY]); - - case RTSameStrategyNumber: - case RTEqualStrategyNumber: - finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, - RTContainsStrategyNumber); - result = FunctionCall2Coll(finfo, colloid, unionval, query); - if (DatumGetBool(result)) - PG_RETURN_BOOL(true); - - PG_RETURN_DATUM(column->bv_values[INCLUSION_CONTAINS_EMPTY]); - - case RTGreaterEqualStrategyNumber: - finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, - RTLeftStrategyNumber); - result = FunctionCall2Coll(finfo, colloid, unionval, query); - if (!DatumGetBool(result)) - PG_RETURN_BOOL(true); - - PG_RETURN_DATUM(column->bv_values[INCLUSION_CONTAINS_EMPTY]); - - case RTGreaterStrategyNumber: - /* no need to check for empty elements */ - finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, - RTLeftStrategyNumber); - result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); - - default: - /* shouldn't happen */ - elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + if (!matches) + break; } + + PG_RETURN_BOOL(matches); } /* diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 0f6aa33..e58fd16 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -147,86 +147,130 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) { BrinDesc *bdesc = (BrinDesc *) PG_GETARG_POINTER(0); BrinValues *column = (BrinValues *) PG_GETARG_POINTER(1); - ScanKey key = (ScanKey) PG_GETARG_POINTER(2); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); Oid colloid = PG_GET_COLLATION(), subtype; AttrNumber attno; Datum value; Datum matches; FmgrInfo *finfo; + int keyno; + bool regular_keys = false; - Assert(key->sk_attno == column->bv_attno); - - /* handle IS NULL/IS NOT NULL tests */ - if (key->sk_flags & SK_ISNULL) + /* + * First check if there are any IS NULL scan keys, and if we're + * violating them. In that case we can terminate early, without + * inspecting the ranges. + */ + for (keyno = 0; keyno < nkeys; keyno++) { - if (key->sk_flags & SK_SEARCHNULL) + ScanKey key = keys[keyno]; + + Assert(key->sk_attno == column->bv_attno); + + /* handle IS NULL/IS NOT NULL tests */ + if (key->sk_flags & SK_ISNULL) { - if (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + if (key->sk_flags & SK_SEARCHNULL) + { + if (column->bv_allnulls || column->bv_hasnulls) + continue; /* this key is fine, continue */ + + PG_RETURN_BOOL(false); + } + + /* + * For IS NOT NULL, we can only skip ranges that are known to have + * only nulls. + */ + if (key->sk_flags & SK_SEARCHNOTNULL) + { + if (column->bv_allnulls) + PG_RETURN_BOOL(false); + + continue; + } + + /* + * Neither IS NULL nor IS NOT NULL was used; assume all indexable + * operators are strict and return false. + */ PG_RETURN_BOOL(false); } - - /* - * For IS NOT NULL, we can only skip ranges that are known to have - * only nulls. - */ - if (key->sk_flags & SK_SEARCHNOTNULL) - PG_RETURN_BOOL(!column->bv_allnulls); - - /* - * Neither IS NULL nor IS NOT NULL was used; assume all indexable - * operators are strict and return false. - */ - PG_RETURN_BOOL(false); + else + /* note we have regular (non-NULL) scan keys */ + regular_keys = true; } - /* if the range is all empty, it cannot possibly be consistent */ - if (column->bv_allnulls) + /* + * If the page range is all nulls, it cannot possibly be consistent if + * there are some regular scan keys. + */ + if (column->bv_allnulls && regular_keys) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; - switch (key->sk_strategy) + /* If there are no regular keys, the page range is considered consistent. */ + if (!regular_keys) + PG_RETURN_BOOL(true); + + matches = true; + + for (keyno = 0; keyno < nkeys; keyno++) { - case BTLessStrategyNumber: - case BTLessEqualStrategyNumber: - finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype, - key->sk_strategy); - matches = FunctionCall2Coll(finfo, colloid, column->bv_values[0], - value); - break; - case BTEqualStrategyNumber: + ScanKey key = keys[keyno]; - /* - * In the equality case (WHERE col = someval), we want to return - * the current page range if the minimum value in the range <= - * scan key, and the maximum value >= scan key. - */ - finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype, - BTLessEqualStrategyNumber); - matches = FunctionCall2Coll(finfo, colloid, column->bv_values[0], - value); - if (!DatumGetBool(matches)) + /* ignore IS NULL/IS NOT NULL tests handled above */ + if (key->sk_flags & SK_ISNULL) + continue; + + attno = key->sk_attno; + subtype = key->sk_subtype; + value = key->sk_argument; + switch (key->sk_strategy) + { + case BTLessStrategyNumber: + case BTLessEqualStrategyNumber: + finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype, + key->sk_strategy); + matches = FunctionCall2Coll(finfo, colloid, column->bv_values[0], + value); break; - /* max() >= scankey */ - finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype, - BTGreaterEqualStrategyNumber); - matches = FunctionCall2Coll(finfo, colloid, column->bv_values[1], - value); - break; - case BTGreaterEqualStrategyNumber: - case BTGreaterStrategyNumber: - finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype, - key->sk_strategy); - matches = FunctionCall2Coll(finfo, colloid, column->bv_values[1], - value); - break; - default: - /* shouldn't happen */ - elog(ERROR, "invalid strategy number %d", key->sk_strategy); - matches = 0; + case BTEqualStrategyNumber: + + /* + * In the equality case (WHERE col = someval), we want to return + * the current page range if the minimum value in the range <= + * scan key, and the maximum value >= scan key. + */ + finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype, + BTLessEqualStrategyNumber); + matches = FunctionCall2Coll(finfo, colloid, column->bv_values[0], + value); + if (!DatumGetBool(matches)) + break; + /* max() >= scankey */ + finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype, + BTGreaterEqualStrategyNumber); + matches = FunctionCall2Coll(finfo, colloid, column->bv_values[1], + value); + break; + case BTGreaterEqualStrategyNumber: + case BTGreaterStrategyNumber: + finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype, + key->sk_strategy); + matches = FunctionCall2Coll(finfo, colloid, column->bv_values[1], + value); + break; + default: + /* shouldn't happen */ + elog(ERROR, "invalid strategy number %d", key->sk_strategy); + matches = 0; + break; + } + + /* found non-matching key */ + if (!matches) break; } -- 2.9.5 --------------53C4B41F409B92A47ECAD3E5 Content-Type: text/x-patch; name="0002-Move-IS-NOT-NULL-checks-to-bringetbitmap.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="0002-Move-IS-NOT-NULL-checks-to-bringetbitmap.patch" From ed8b14643a8c313767f8285b40b44f96a9b8ece3 Mon Sep 17 00:00:00 2001 From: Tomas Vondra Date: Sun, 4 Feb 2018 14:27:42 +0100 Subject: [PATCH 2/4] Move IS [NOT] NULL checks to bringetbitmap These checks are exactly the same in all BRIN opclasses, so it makes sense to move them to bringetbimap() and evaluate them there. It also allows us to handle some ranges without invoking the consistent support procedure (e.g. when the key says IS NULL) but the range is marked as not containing any NULL values. Note: It may also be a good idea to first process NULL keys for all attributes on a given page range, and then only proceed with the remaining keys if it's still needed (when the page range is not eliminated by NULL keys on any attribute). --- src/backend/access/brin/brin.c | 116 ++++++++++++++++++++++++++++--- src/backend/access/brin/brin_inclusion.c | 62 +---------------- src/backend/access/brin/brin_minmax.c | 62 +---------------- 3 files changed, 109 insertions(+), 131 deletions(-) diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c index b568786..7786f58 100644 --- a/src/backend/access/brin/brin.c +++ b/src/backend/access/brin/brin.c @@ -367,8 +367,10 @@ bringetbitmap(IndexScanDesc scan, TIDBitmap *tbm) BrinMemTuple *dtup; BrinTuple *btup = NULL; Size btupsz = 0; - ScanKey **keys; - int *nkeys; + ScanKey **keys, + **nullkeys; + int *nkeys, + *nnullkeys; int keyno; opaque = (BrinOpaque *) scan->opaque; @@ -393,10 +395,13 @@ bringetbitmap(IndexScanDesc scan, TIDBitmap *tbm) /* * Make room for per-attribute lists of scan keys that we'll pass to the - * consistent support procedure. + * consistent support procedure. We keep null and regular keys separate, + * so that we can easily pass regular keys to the consistent function. */ keys = palloc0(sizeof(ScanKey *) * bdesc->bd_tupdesc->natts); + nullkeys = palloc0(sizeof(ScanKey *) * bdesc->bd_tupdesc->natts); nkeys = palloc0(sizeof(int) * bdesc->bd_tupdesc->natts); + nnullkeys = palloc0(sizeof(int) * bdesc->bd_tupdesc->natts); /* * Preprocess the scan keys - split them into per-attribute arrays. @@ -418,14 +423,12 @@ bringetbitmap(IndexScanDesc scan, TIDBitmap *tbm) keyattno - 1)->attcollation)); /* First time we see this index attribute, so init as needed. */ - if (!keys[keyattno-1]) + if (consistentFn[keyattno - 1].fn_oid == InvalidOid) { FmgrInfo *tmp; - keys[keyattno-1] = palloc0(sizeof(ScanKey) * scan->numberOfKeys); - - /* First time this column, so look up consistent function */ - Assert(consistentFn[keyattno - 1].fn_oid == InvalidOid); + Assert((keys[keyattno - 1] == NULL) && (nkeys[keyattno - 1] == 0)); + Assert((nullkeys[keyattno - 1] == NULL) && (nnullkeys[keyattno - 1] == 0)); tmp = index_getprocinfo(idxRel, keyattno, BRIN_PROCNUM_CONSISTENT); @@ -433,9 +436,23 @@ bringetbitmap(IndexScanDesc scan, TIDBitmap *tbm) CurrentMemoryContext); } - /* Add key to the per-attribute array. */ - keys[keyattno - 1][nkeys[keyattno - 1]] = key; - nkeys[keyattno - 1]++; + /* Add key to the proper per-attribute array. */ + if (key->sk_flags & SK_ISNULL) + { + if (!nullkeys[keyattno - 1]) + nullkeys[keyattno - 1] = palloc0(sizeof(ScanKey) * scan->numberOfKeys); + + nullkeys[keyattno - 1][nnullkeys[keyattno - 1]] = key; + nnullkeys[keyattno - 1]++; + } + else + { + if (!keys[keyattno - 1]) + keys[keyattno - 1] = palloc0(sizeof(ScanKey) * scan->numberOfKeys); + + keys[keyattno - 1][nkeys[keyattno - 1]] = key; + nkeys[keyattno - 1]++; + } } /* allocate an initial in-memory tuple, out of the per-range memcxt */ @@ -530,6 +547,83 @@ bringetbitmap(IndexScanDesc scan, TIDBitmap *tbm) collation = keys[attno - 1][0]->sk_collation; /* + * First check if there are any IS [NOT] NULL scan keys, and + * if we're violating them. In that case we can terminate + * early, without invoking the support function. + * + * As there may be more keys, we can only detemine mismatch + * within this loop. + */ + for (keyno = 0; (keyno < nnullkeys[attno - 1]); keyno++) + { + ScanKey key = nullkeys[attno - 1][keyno]; + + Assert(key->sk_attno == bval->bv_attno); + + /* interrupt the loop as soon as we find a mismatch */ + if (!addrange) + break; + + /* handle IS NULL/IS NOT NULL tests */ + if (key->sk_flags & SK_ISNULL) + { + /* IS NULL scan key, but range has no NULLs */ + if (key->sk_flags & SK_SEARCHNULL) + { + if (!bval->bv_allnulls && !bval->bv_hasnulls) + addrange = false; + + continue; + } + + /* + * For IS NOT NULL, we can only skip ranges that are + * known to have only nulls. + */ + if (key->sk_flags & SK_SEARCHNOTNULL) + { + if (bval->bv_allnulls) + addrange = false; + + continue; + } + + /* + * Neither IS NULL nor IS NOT NULL was used; assume all + * indexable operators are strict and thus return false + * with NULL value in the scan key. + */ + addrange = false; + } + } + + /* + * If any of the IS [NOT] NULL keys failed, the page range as + * a whole can't pass. So terminate the loop. + */ + if (!addrange) + break; + + /* + * So either there are no IS [NOT] NULL keys, or all passed. If + * there are no regular scan keys, we're done - the page range + * matches. If there are regular keys, but the page range is + * marked as 'all nulls' it can't possibly pass (we're assuming + * the operators are strict). + */ + + /* No regular scan keys - page range as a whole passes. */ + if (!nkeys[attno - 1]) + continue; + + /* If it is all nulls, it cannot possibly be consistent. */ + if (bval->bv_allnulls) + { + addrange = false; + break; + } + + /* * Check whether the scan key is consistent with the page * range values; if so, have the pages in the range added * to the output bitmap. diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index fb7f2b0..6d51c71 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -259,63 +259,6 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) Datum result; int keyno; bool matches; - bool regular_keys = false; - - /* - * First check if there are any IS NULL scan keys, and if we're - * violating them. In that case we can terminate early, without - * inspecting the ranges. - */ - for (keyno = 0; keyno < nkeys; keyno++) - { - ScanKey key = keys[keyno]; - - Assert(key->sk_attno == column->bv_attno); - - /* handle IS NULL/IS NOT NULL tests */ - if (key->sk_flags & SK_ISNULL) - { - if (key->sk_flags & SK_SEARCHNULL) - { - if (column->bv_allnulls || column->bv_hasnulls) - continue; /* this key is fine, continue */ - - PG_RETURN_BOOL(false); - } - - /* - * For IS NOT NULL, we can only skip ranges that are known to have - * only nulls. - */ - if (key->sk_flags & SK_SEARCHNOTNULL) - { - if (column->bv_allnulls) - PG_RETURN_BOOL(false); - - continue; - } - - /* - * Neither IS NULL nor IS NOT NULL was used; assume all indexable - * operators are strict and return false. - */ - PG_RETURN_BOOL(false); - } - else - /* note we have regular (non-NULL) scan keys */ - regular_keys = true; - } - - /* - * If the page range is all nulls, it cannot possibly be consistent if - * there are some regular scan keys. - */ - if (column->bv_allnulls && regular_keys) - PG_RETURN_BOOL(false); - - /* If there are no regular keys, the page range is considered consistent. */ - if (!regular_keys) - PG_RETURN_BOOL(true); /* It has to be checked, if it contains elements that are not mergeable. */ if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) @@ -327,9 +270,8 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) { ScanKey key = keys[keyno]; - /* ignore IS NULL/IS NOT NULL tests handled above */ - if (key->sk_flags & SK_ISNULL) - continue; + /* NULL keys are handled and filtered-out in bringetbitmap */ + Assert(!(key->sk_flags & SK_ISNULL)); attno = key->sk_attno; subtype = key->sk_subtype; diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index e58fd16..4621bea 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -156,63 +156,6 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) Datum matches; FmgrInfo *finfo; int keyno; - bool regular_keys = false; - - /* - * First check if there are any IS NULL scan keys, and if we're - * violating them. In that case we can terminate early, without - * inspecting the ranges. - */ - for (keyno = 0; keyno < nkeys; keyno++) - { - ScanKey key = keys[keyno]; - - Assert(key->sk_attno == column->bv_attno); - - /* handle IS NULL/IS NOT NULL tests */ - if (key->sk_flags & SK_ISNULL) - { - if (key->sk_flags & SK_SEARCHNULL) - { - if (column->bv_allnulls || column->bv_hasnulls) - continue; /* this key is fine, continue */ - - PG_RETURN_BOOL(false); - } - - /* - * For IS NOT NULL, we can only skip ranges that are known to have - * only nulls. - */ - if (key->sk_flags & SK_SEARCHNOTNULL) - { - if (column->bv_allnulls) - PG_RETURN_BOOL(false); - - continue; - } - - /* - * Neither IS NULL nor IS NOT NULL was used; assume all indexable - * operators are strict and return false. - */ - PG_RETURN_BOOL(false); - } - else - /* note we have regular (non-NULL) scan keys */ - regular_keys = true; - } - - /* - * If the page range is all nulls, it cannot possibly be consistent if - * there are some regular scan keys. - */ - if (column->bv_allnulls && regular_keys) - PG_RETURN_BOOL(false); - - /* If there are no regular keys, the page range is considered consistent. */ - if (!regular_keys) - PG_RETURN_BOOL(true); matches = true; @@ -220,9 +163,8 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) { ScanKey key = keys[keyno]; - /* ignore IS NULL/IS NOT NULL tests handled above */ - if (key->sk_flags & SK_ISNULL) - continue; + /* NULL keys are handled and filtered-out in bringetbitmap */ + Assert(!(key->sk_flags & SK_ISNULL)); attno = key->sk_attno; subtype = key->sk_subtype; -- 2.9.5 --------------53C4B41F409B92A47ECAD3E5 Content-Type: text/x-patch; name="0003-BRIN-bloom-indexes.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="0003-BRIN-bloom-indexes.patch" From a67c77d7754de938f90fbd60044c5c0fcccc6395 Mon Sep 17 00:00:00 2001 From: Tomas Vondra Date: Tue, 9 Jan 2018 01:00:39 +0100 Subject: [PATCH 3/4] BRIN bloom indexes An opclass implementing BRIN indexes with bloom filter summary. Naturally, this opclass only supports equality searches, but that is fine for many use cases (e.g. it's uncommon to do range queries on identifiers or labels). It's not quite clear how to size the bloom filter, or what to do if it gets too full (more than 50% of bits set). The patch picks some reasonable defaults, but perhaps we can improve this based on more information (ndistinct from pg_stats, pages_per_range, ...). Or make it configurable when creating the index. Another option would be to implement scalable bloom filters, i.e. instead of creating one filter and stop using it when it gets too full, create another (larger) one and use it for new additions. But considering we can only see very limited number of distinct values per range, that seems like an overkill, and it might make certain operations (particularly "union" support procedure) more complicated. The patch also implements an optimization that we initially keep the values "raw", and only switch to the actual bloom filter once we use the same space. This somewhat reduces the space usage when the column is low-cardinality (at least within a page range), so that the bloom filter defaults would be too high. --- doc/src/sgml/brin.sgml | 229 ++++++++++ src/backend/access/brin/Makefile | 2 +- src/backend/access/brin/brin_bloom.c | 755 +++++++++++++++++++++++++++++++ src/include/catalog/pg_amop.h | 59 +++ src/include/catalog/pg_amproc.h | 153 +++++++ src/include/catalog/pg_opclass.h | 25 + src/include/catalog/pg_opfamily.h | 20 + src/include/catalog/pg_proc.h | 10 + src/test/regress/expected/brin_bloom.out | 438 ++++++++++++++++++ src/test/regress/expected/opr_sanity.out | 3 +- src/test/regress/parallel_schedule | 2 +- src/test/regress/serial_schedule | 1 + src/test/regress/sql/brin_bloom.sql | 391 ++++++++++++++++ 13 files changed, 2085 insertions(+), 3 deletions(-) create mode 100644 src/backend/access/brin/brin_bloom.c create mode 100644 src/test/regress/expected/brin_bloom.out create mode 100644 src/test/regress/sql/brin_bloom.sql diff --git a/doc/src/sgml/brin.sgml b/doc/src/sgml/brin.sgml index 23c0e05..434538e 100644 --- a/doc/src/sgml/brin.sgml +++ b/doc/src/sgml/brin.sgml @@ -118,6 +118,13 @@ + abstime_bloom_ops + abstime + + = + + + abstime_minmax_ops abstime @@ -129,6 +136,13 @@ + int8_bloom_ops + bigint + + = + + + int8_minmax_ops bigint @@ -180,6 +194,13 @@ + bytea_bloom_ops + bytea + + = + + + bytea_minmax_ops bytea @@ -191,6 +212,13 @@ + bpchar_bloom_ops + character + + = + + + bpchar_minmax_ops character @@ -202,6 +230,13 @@ + char_bloom_ops + "char" + + = + + + char_minmax_ops "char" @@ -213,6 +248,13 @@ + date_bloom_ops + date + + = + + + date_minmax_ops date @@ -224,6 +266,13 @@ + float8_bloom_ops + double precision + + = + + + float8_minmax_ops double precision @@ -235,6 +284,13 @@ + inet_bloom_ops + inet + + = + + + inet_minmax_ops inet @@ -258,6 +314,13 @@ + int4_bloom_ops + integer + + = + + + int4_minmax_ops integer @@ -269,6 +332,13 @@ + interval_bloom_ops + interval + + = + + + interval_minmax_ops interval @@ -280,6 +350,13 @@ + macaddr_bloom_ops + macaddr + + = + + + macaddr_minmax_ops macaddr @@ -291,6 +368,13 @@ + macaddr8_bloom_ops + macaddr8 + + = + + + macaddr8_minmax_ops macaddr8 @@ -302,6 +386,13 @@ + name_bloom_ops + name + + = + + + name_minmax_ops name @@ -313,6 +404,13 @@ + numeric_bloom_ops + numeric + + = + + + numeric_minmax_ops numeric @@ -324,6 +422,13 @@ + pg_lsn_bloom_ops + pg_lsn + + = + + + pg_lsn_minmax_ops pg_lsn @@ -335,6 +440,13 @@ + oid_bloom_ops + oid + + = + + + oid_minmax_ops oid @@ -366,6 +478,13 @@ + float4_bloom_ops + real + + = + + + float4_minmax_ops real @@ -377,6 +496,13 @@ + reltime_bloom_ops + reltime + + = + + + reltime_minmax_ops reltime @@ -388,6 +514,13 @@ + int2_bloom_ops + smallint + + = + + + int2_minmax_ops smallint @@ -399,6 +532,13 @@ + text_bloom_ops + text + + = + + + text_minmax_ops text @@ -421,6 +561,13 @@ + timestamp_bloom_ops + timestamp without time zone + + = + + + timestamp_minmax_ops timestamp without time zone @@ -432,6 +579,13 @@ + timestamptz_bloom_ops + timestamp with time zone + + = + + + timestamptz_minmax_ops timestamp with time zone @@ -443,6 +597,13 @@ + time_bloom_ops + time without time zone + + = + + + time_minmax_ops time without time zone @@ -454,6 +615,13 @@ + timetz_bloom_ops + time with time zone + + = + + + timetz_minmax_ops time with time zone @@ -465,6 +633,13 @@ + uuid_bloom_ops + uuid + + = + + + uuid_minmax_ops uuid @@ -814,6 +989,60 @@ typedef struct BrinOpcInfo + To write an operator class for a data type that implements only an equality + operator and supports hashing, it is possible to use the bloom support procedures + alongside the corresponding operators, as shown in + . + All operator class members (procedures and operators) are mandatory. + + + + Procedure and Support Numbers for Bloom Operator Classes + + + + Operator class member + Object + + + + + Support Procedure 1 + internal function brin_bloom_opcinfo() + + + Support Procedure 2 + internal function brin_bloom_add_value() + + + Support Procedure 3 + internal function brin_bloom_consistent() + + + Support Procedure 4 + internal function brin_bloom_union() + + + Support Procedure 11 + function to compute hash of an element + + + Operator Strategy 1 + operator equal-to + + + +
+ + + Support procedure numbers 1-10 are reserved for the BRIN internal + functions, so the SQL level functions start with number 11. Support + function number 11 is the main function required to build the index. + It should accept one argument with the same data type as the operator class, + and return a hash of the value. + + + Both minmax and inclusion operator classes support cross-data-type operators, though with these the dependencies become more complicated. The minmax operator class requires a full set of operators to be diff --git a/src/backend/access/brin/Makefile b/src/backend/access/brin/Makefile index 5aef925..a76d927 100644 --- a/src/backend/access/brin/Makefile +++ b/src/backend/access/brin/Makefile @@ -13,6 +13,6 @@ top_builddir = ../../../.. include $(top_builddir)/src/Makefile.global OBJS = brin.o brin_pageops.o brin_revmap.o brin_tuple.o brin_xlog.o \ - brin_minmax.o brin_inclusion.o brin_validate.o + brin_minmax.o brin_inclusion.o brin_validate.o brin_bloom.o include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/access/brin/brin_bloom.c b/src/backend/access/brin/brin_bloom.c new file mode 100644 index 0000000..461bfce --- /dev/null +++ b/src/backend/access/brin/brin_bloom.c @@ -0,0 +1,755 @@ +/* + * brin_bloom.c + * Implementation of Bloom opclass for BRIN + * + * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/access/brin/brin_bloom.c + */ +#include "postgres.h" + +#include "access/genam.h" +#include "access/brin_internal.h" +#include "access/brin_tuple.h" +#include "access/hash.h" +#include "access/stratnum.h" +#include "catalog/pg_type.h" +#include "catalog/pg_amop.h" +#include "utils/builtins.h" +#include "utils/datum.h" +#include "utils/lsyscache.h" +#include "utils/rel.h" +#include "utils/syscache.h" + +#include + +#define BloomEqualStrategyNumber 1 + +/* + * Additional SQL level support functions + * + * Procedure numbers must not use values reserved for BRIN itself; see + * brin_internal.h. + */ +#define BLOOM_MAX_PROCNUMS 4 /* maximum support procs we need */ +#define PROCNUM_HASH 11 /* required */ + +/* + * Subtract this from procnum to obtain index in BloomOpaque arrays + * (Must be equal to minimum of private procnums). + */ +#define PROCNUM_BASE 11 + + +#define BLOOM_PHASE_SORTED 1 +#define BLOOM_PHASE_HASH 2 + +/* how many hashes to accumulate before hashing */ +#define BLOOM_MAX_UNSORTED 32 +#define BLOOM_GROW_BYTES 32 +#define BLOOM_NDISTINCT 1000 /* number of distinct values */ +#define BLOOM_ERROR_RATE 0.05 /* 2% false positive rate */ + +/* + * Bloom Filter + * + * Represents a bloom filter, built on hashes of the indexed values. That is, + * we compute a uint32 hash of the value, and then store this hash into the + * bloom filter (and compute additional hashes on it). + * + * We use an optimisation that initially we store the uint32 values directly, + * without the extra hashing step. And only later filling the bitmap space, + * we switch to the regular bloom filter mode. + * + * PHASE_SORTED + * + * Initially we copy the uint32 hash into the bitmap, regularly sorting the + * hash values for fast lookup (we keep at most BLOOM_MAX_UNSORTED unsorted + * values). + * + * The idea is that if we only see very few distinct values, we can store + * them in less space compared to the (sparse) bloom filter bitmap. It also + * stores them exactly, although that's not a big advantage as almost-empty + * bloom filter has false positive rate close to zero anyway. + * + * PHASE_HASH + * + * Once we fill the bitmap space in the sorted phase, we switch to the hash + * phase, where we actually use the bloom filter. We treat the uint32 hashes + * as input values, and hash them again with different seeds (to get the k + * hash functions needed for bloom filter). + * + * + * XXX Maybe instead of explicitly limiting the number of unsorted values + * by BLOOM_MAX_UNSORTED, we should cap them by (filter size / 4B), i.e. + * allow up to the whole filter size. + * + * XXX Perhaps we could save a few bytes by using different data types, but + * considering the size of the bitmap, the difference is negligible. + * + * XXX We could also implement "sparse" bloom filters, keeping only the + * bytes that are not entirely 0. That might make the "sorted" phase + * mostly unnecessary. + * + * XXX We can also watch the number of bits set in the bloom filter, and + * then stop using it (and not store the bitmap, to save space) when the + * false positive rate gets too high. + */ +typedef struct BloomFilter +{ + /* varlena header (do not touch directly!) */ + int32 vl_len_; + + /* global bloom filter parameters */ + uint32 phase; /* phase (initially SORTED, then HASH) */ + uint32 nhashes; /* number of hash functions */ + uint32 nbits; /* number of bits in the bitmap (optimal) */ + uint32 nbits_set; /* number of bits set to 1 */ + + /* fields used only in the EXACT phase */ + uint32 nvalues; /* number of hashes stored (sorted + extra) */ + uint32 nsorted; /* number of uint32 hashes in sorted part */ + + /* bitmap of the bloom filter */ + char bitmap[FLEXIBLE_ARRAY_MEMBER]; + +} BloomFilter; + +/* + * bloom_init + * Initialize the Bloom Filter, allocate all the memory. + * + * The filter is initialized with optimal size for ndistinct expected + * values, and requested false positive rate. The filter is stored as + * varlena. + */ +static BloomFilter * +bloom_init(int ndistinct, double false_positive_rate) +{ + Size len; + BloomFilter *filter; + + /* https://en.wikipedia.org/wiki/Bloom_filter */ + int m; /* number of bits */ + int k; /* number of hash functions */ + + Assert((ndistinct > 0) && (ndistinct < 10000)); /* 10k is mostly arbitrary limit */ + Assert((false_positive_rate > 0) && (false_positive_rate < 1.0)); + + m = ceil((ndistinct * log(false_positive_rate)) / log(1.0 / (pow(2.0, log(2.0))))); + + /* round m to whole bytes */ + m = ((m + 7) / 8) * 8; + + k = round(log(2.0) * m / ndistinct); + + /* + * Allocate the bloom filter with a minimum size 64B (about 40B in the + * bitmap part). We require space at least for the header. + */ + len = Max(offsetof(BloomFilter, bitmap), 64); + + filter = (BloomFilter *) palloc0(len); + + filter->phase = BLOOM_PHASE_SORTED; + filter->nhashes = k; + filter->nbits = m; + + SET_VARSIZE(filter, len); + + return filter; +} + +/* simple uint32 comparator, for pg_qsort and bsearch */ +static int +cmp_uint32(const void *a, const void *b) +{ + uint32 *ia = (uint32 *) a; + uint32 *ib = (uint32 *) b; + + if (*ia == *ib) + return 0; + else if (*ia < *ib) + return -1; + else + return 1; +} + +/* + * bloom_compact + * Compact the filter during the 'sorted' phase. + * + * We sort the uint32 hashes and remove duplicates, for two main reasons. + * Firstly, to keep most of the data sorted for bsearch lookups. Secondly, + * we try to save space by removing the duplicates, allowing us to stay + * in the sorted phase a bit longer. + * + * We currently don't repalloc the bitmap, i.e. we don't free the memory + * here - in the worst case we waste space for up to 32 unsorted hashes + * (if all of them are already in the sorted part), so about 128B. We can + * either reduce the number of unsorted items (e.g. to 8 hashes, which + * would mean 32B), or start doing the repalloc. + * + * XXX Actually, we don't need to do repalloc - we just need to set the + * varlena header length! + */ +static void +bloom_compact(BloomFilter *filter) +{ + int i, + nvalues; + uint32 *values; + + /* never call compact on filters in HASH phase */ + Assert(filter->phase == BLOOM_PHASE_SORTED); + + /* no chance to compact anything */ + if (filter->nvalues == filter->nsorted) + return; + + values = (uint32 *) palloc(filter->nvalues * sizeof(uint32)); + + /* copy the data, then reset the bitmap */ + memcpy(values, filter->bitmap, filter->nvalues * sizeof(uint32)); + memset(filter->bitmap, 0, filter->nvalues * sizeof(uint32)); + + /* FIXME optimization: sort only the unsorted part, then merge */ + pg_qsort(values, filter->nvalues, sizeof(uint32), cmp_uint32); + + nvalues = 1; + for (i = 1; i < filter->nvalues; i++) + { + /* if a new value, keep it */ + if (values[i] != values[i-1]) + { + values[nvalues] = values[i]; + nvalues++; + } + } + + filter->nvalues = nvalues; + filter->nsorted = nvalues; + + memcpy(filter->bitmap, values, nvalues * sizeof(uint32)); + + pfree(values); +} + +static BloomFilter * +bloom_switch_to_hashing(BloomFilter *filter); + +/* + * bloom_add_value + * Add value to the bloom filter. + */ +static BloomFilter * +bloom_add_value(BloomFilter *filter, uint32 value, bool *updated) +{ + int i; + uint32 big_h, h, d; + + /* assume 'not updated' by default */ + Assert(filter); + + /* if we're in the sorted phase, we store the hashes directly */ + if (filter->phase == BLOOM_PHASE_SORTED) + { + /* how many uint32 hashes can we fit into the bitmap */ + int maxvalues = filter->nbits / (8 * sizeof(uint32)); + + /* do not overflow the bitmap space or number of unsorted items */ + Assert(filter->nvalues <= maxvalues); + Assert(filter->nvalues - filter->nsorted <= BLOOM_MAX_UNSORTED); + + /* + * In this branch we always update the filter - we either add the + * hash to the unsorted part, or switch the filter to hashing. + */ + if (updated) + *updated = true; + + /* + * If the array is full, or if we reached the limit on unsorted + * items, try to compact the filter first, before attempting to + * add the new value. + */ + if ((filter->nvalues == maxvalues) || + (filter->nvalues - filter->nsorted == BLOOM_MAX_UNSORTED)) + bloom_compact(filter); + + /* + * Can we squeeze one more uint32 hash into the bitmap? Also make + * sure there's enough space in the bytea value first. + */ + if (filter->nvalues < maxvalues) + { + Size len = VARSIZE_ANY(filter); + Size need = offsetof(BloomFilter,bitmap) + (filter->nvalues+1) * sizeof(uint32); + + if (len < need) + { + /* + * We don't double the size here, as in the first place we care about + * reducing storage requirements, and the doubling happens automatically + * in memory contexts anyway. + * + * XXX Zero the newly allocated part. Maybe not really needed? + */ + filter = (BloomFilter *) repalloc(filter, len + BLOOM_GROW_BYTES); + memset((char *)filter + len, 0, BLOOM_GROW_BYTES); + SET_VARSIZE(filter, len + BLOOM_GROW_BYTES); + } + + /* copy the data into the bitmap */ + memcpy(&filter->bitmap[filter->nvalues * sizeof(uint32)], + &value, sizeof(uint32)); + + filter->nvalues++; + + /* we're done */ + return filter; + } + + /* can't add any more exact hashes, so switch to hashing */ + filter = bloom_switch_to_hashing(filter); + } + + /* we better be in the hashing phase */ + Assert(filter->phase == BLOOM_PHASE_HASH); + + /* compute the hashes, used for the bloom filter */ + big_h = ((uint32)DatumGetInt64(hash_uint32(value))); + + h = big_h % filter->nbits; + d = big_h % (filter->nbits - 1); + + /* compute the requested number of hashes */ + for (i = 0; i < filter->nhashes; i++) + { + int byte = (h / 8); + int bit = (h % 8); + + /* if the bit is not set, set it and remember we did that */ + if (! (filter->bitmap[byte] & (0x01 << bit))) + { + filter->bitmap[byte] |= (0x01 << bit); + filter->nbits_set++; + if (updated) + *updated = true; + } + + /* next bit */ + h += d++; + if (h >= filter->nbits) h -= filter->nbits; + if (d == filter->nbits) d = 0; + } + + return filter; +} + +/* + * bloom_switch_to_hashing + * Switch the bloom filter from sorted to hashing mode. + */ +static BloomFilter * +bloom_switch_to_hashing(BloomFilter *filter) +{ + int i; + uint32 *values; + Size len; + BloomFilter *newfilter; + + /* + * The new filter is allocated with all the memory, directly into + * the HASH phase. + */ + len = offsetof(BloomFilter, bitmap) + (filter->nbits / 8); + + newfilter = (BloomFilter *) palloc0(len); + + newfilter->nhashes = filter->nhashes; + newfilter->nbits = filter->nbits; + newfilter->phase = BLOOM_PHASE_HASH; + + SET_VARSIZE(newfilter, len); + + values = (uint32 *) filter->bitmap; + + for (i = 0; i < filter->nvalues; i++) + /* ignore the return value here, re don't repalloc in hashing mode */ + bloom_add_value(newfilter, values[i], NULL); + + /* free the original filter, return the newly allocated one */ + pfree(filter); + + return newfilter; +} + +/* + * bloom_contains_value + * Check if the bloom filter contains a particular value. + */ +static bool +bloom_contains_value(BloomFilter *filter, uint32 value) +{ + int i; + uint32 big_h, h, d; + + Assert(filter); + + /* in sorted mode we simply search the two arrays (sorted, unsorted) */ + if (filter->phase == BLOOM_PHASE_SORTED) + { + int i; + uint32 *values = (uint32 *)filter->bitmap; + + /* first search through the sorted part */ + if ((filter->nsorted > 0) && + (bsearch(&value, values, filter->nsorted, sizeof(uint32), cmp_uint32) != NULL)) + return true; + + /* now search through the unsorted part - linear search */ + for (i = filter->nsorted; i < filter->nvalues; i++) + if (value == values[i]) + return true; + + /* nothing found */ + return false; + } + + /* now the regular hashing mode */ + Assert(filter->phase == BLOOM_PHASE_HASH); + + big_h = ((uint32)DatumGetInt64(hash_uint32(value))); + + h = big_h % filter->nbits; + d = big_h % (filter->nbits - 1); + + /* compute the requested number of hashes */ + for (i = 0; i < filter->nhashes; i++) + { + int byte = (h / 8); + int bit = (h % 8); + + /* if the bit is not set, the value is not there */ + if (! (filter->bitmap[byte] & (0x01 << bit))) + return false; + + /* next bit */ + h += d++; + if (h >= filter->nbits) h -= filter->nbits; + if (d == filter->nbits) d = 0; + } + + /* all hashes found in bloom filter */ + return true; +} + +typedef struct BloomOpaque +{ + /* + * XXX At this point we only need a single proc (to compute the hash), + * but let's keep the array just like inclusion and minman opclasses, + * for consistency. We may need additional procs in the future. + */ + FmgrInfo extra_procinfos[BLOOM_MAX_PROCNUMS]; + bool extra_proc_missing[BLOOM_MAX_PROCNUMS]; +} BloomOpaque; + +static FmgrInfo *bloom_get_procinfo(BrinDesc *bdesc, uint16 attno, + uint16 procnum); + + +Datum +brin_bloom_opcinfo(PG_FUNCTION_ARGS) +{ + BrinOpcInfo *result; + + /* + * opaque->strategy_procinfos is initialized lazily; here it is set to + * all-uninitialized by palloc0 which sets fn_oid to InvalidOid. + * + * bloom indexes only store a the filter as a single BYTEA column + */ + + result = palloc0(MAXALIGN(SizeofBrinOpcInfo(1)) + + sizeof(BloomOpaque)); + result->oi_nstored = 1; + result->oi_opaque = (BloomOpaque *) + MAXALIGN((char *) result + SizeofBrinOpcInfo(1)); + result->oi_typcache[0] = lookup_type_cache(BYTEAOID, 0); + + PG_RETURN_POINTER(result); +} + +/* + * Examine the given index tuple (which contains partial status of a certain + * page range) by comparing it to the given value that comes from another heap + * tuple. If the new value is outside the bloom filter specified by the + * existing tuple values, update the index tuple and return true. Otherwise, + * return false and do not modify in this case. + */ +Datum +brin_bloom_add_value(PG_FUNCTION_ARGS) +{ + BrinDesc *bdesc = (BrinDesc *) PG_GETARG_POINTER(0); + BrinValues *column = (BrinValues *) PG_GETARG_POINTER(1); + Datum newval = PG_GETARG_DATUM(2); + bool isnull = PG_GETARG_DATUM(3); + Oid colloid = PG_GET_COLLATION(); + FmgrInfo *hashFn; + uint32 hashValue; + bool updated = false; + AttrNumber attno; + BloomFilter *filter; + + /* + * If the new value is null, we record that we saw it if it's the first + * one; otherwise, there's nothing to do. + */ + if (isnull) + { + if (column->bv_hasnulls) + PG_RETURN_BOOL(false); + + column->bv_hasnulls = true; + PG_RETURN_BOOL(true); + } + + attno = column->bv_attno; + + /* + * If this is the first non-null value, we need to initialize the bloom + * filter. Otherwise just extract the existing bloom filter from BrinValues. + */ + if (column->bv_allnulls) + { + filter = bloom_init(BLOOM_NDISTINCT, BLOOM_ERROR_RATE); + column->bv_values[0] = PointerGetDatum(filter); + column->bv_allnulls = false; + updated = true; + } + else + filter = (BloomFilter *) PG_DETOAST_DATUM(column->bv_values[0]); + + /* + * Compute the hash of the new value, using the supplied hash function, + * and then add the hash value to the bloom filter. + */ + hashFn = bloom_get_procinfo(bdesc, attno, PROCNUM_HASH); + + hashValue = DatumGetUInt32(FunctionCall1Coll(hashFn, colloid, newval)); + + filter = bloom_add_value(filter, hashValue, &updated); + + column->bv_values[0] = PointerGetDatum(filter); + + PG_RETURN_BOOL(updated); +} + +/* + * Given an index tuple corresponding to a certain page range and a scan key, + * return whether the scan key is consistent with the index tuple's bloom + * filter. Return true if so, false otherwise. + */ +Datum +brin_bloom_consistent(PG_FUNCTION_ARGS) +{ + BrinDesc *bdesc = (BrinDesc *) PG_GETARG_POINTER(0); + BrinValues *column = (BrinValues *) PG_GETARG_POINTER(1); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + AttrNumber attno; + Datum value; + Datum matches; + FmgrInfo *finfo; + uint32 hashValue; + BloomFilter *filter; + int keyno; + + filter = (BloomFilter *) PG_DETOAST_DATUM(column->bv_values[0]); + + Assert(filter); + + matches = true; + + for (keyno = 0; keyno < nkeys; keyno++) + { + ScanKey key = keys[keyno]; + + /* NULL keys are handled and filtered-out in bringetbitmap */ + Assert(!(key->sk_flags & SK_ISNULL)); + + attno = key->sk_attno; + value = key->sk_argument; + + switch (key->sk_strategy) + { + case BloomEqualStrategyNumber: + + /* + * In the equality case (WHERE col = someval), we want to return + * the current page range if the minimum value in the range <= + * scan key, and the maximum value >= scan key. + */ + finfo = bloom_get_procinfo(bdesc, attno, PROCNUM_HASH); + + hashValue = DatumGetUInt32(FunctionCall1Coll(finfo, colloid, value)); + matches &= bloom_contains_value(filter, hashValue); + + break; + default: + /* shouldn't happen */ + elog(ERROR, "invalid strategy number %d", key->sk_strategy); + matches = 0; + break; + } + + if (!matches) + break; + } + + PG_RETURN_DATUM(matches); +} + +/* + * Given two BrinValues, update the first of them as a union of the summary + * values contained in both. The second one is untouched. + * + * XXX We assume the bloom filters have the same parameters fow now. In the + * future we should have 'can union' function, to decide if we can combine + * two particular bloom filters. + */ +Datum +brin_bloom_union(PG_FUNCTION_ARGS) +{ + BrinValues *col_a = (BrinValues *) PG_GETARG_POINTER(1); + BrinValues *col_b = (BrinValues *) PG_GETARG_POINTER(2); + BloomFilter *filter_a; + BloomFilter *filter_b; + + Assert(col_a->bv_attno == col_b->bv_attno); + + /* Adjust "hasnulls" */ + if (!col_a->bv_hasnulls && col_b->bv_hasnulls) + col_a->bv_hasnulls = true; + + /* If there are no values in B, there's nothing left to do */ + if (col_b->bv_allnulls) + PG_RETURN_VOID(); + + /* + * Adjust "allnulls". If A doesn't have values, just copy the values from + * B into A, and we're done. We cannot run the operators in this case, + * because values in A might contain garbage. Note we already established + * that B contains values. + */ + if (col_a->bv_allnulls) + { + col_a->bv_allnulls = false; + col_a->bv_values[0] = datumCopy(col_b->bv_values[0], false, -1); + PG_RETURN_VOID(); + } + + filter_a = (BloomFilter *) PG_DETOAST_DATUM(col_a->bv_values[0]); + filter_b = (BloomFilter *) PG_DETOAST_DATUM(col_b->bv_values[0]); + + /* make sure neither of the bloom filters is NULL */ + Assert(filter_a && filter_b); + Assert(filter_a->nbits == filter_b->nbits); + + /* + * Merging of the filters depends on the phase of both filters. + */ + if (filter_b->phase == BLOOM_PHASE_SORTED) + { + /* + * Simply read all items from 'b' and add them to 'a' (the phase of + * 'a' does not really matter). + */ + int i; + uint32 *values = (uint32 *) filter_b->bitmap; + + for (i = 0; i < filter_b->nvalues; i++) + filter_a = bloom_add_value(filter_a, values[i], NULL); + + col_a->bv_values[0] = PointerGetDatum(filter_a); + } + else if (filter_a->phase == BLOOM_PHASE_SORTED) + { + /* + * 'b' hashed, 'a' sorted - copy 'b' into 'a' and then add all values + * from 'a' into the new copy. + */ + int i; + BloomFilter *filter_c; + uint32 *values = (uint32 *) filter_a->bitmap; + + filter_c = (BloomFilter *) PG_DETOAST_DATUM(datumCopy(PointerGetDatum(filter_b), false, -1)); + + for (i = 0; i < filter_a->nvalues; i++) + filter_c = bloom_add_value(filter_c, values[i], NULL); + + col_a->bv_values[0] = PointerGetDatum(filter_c); + } + else if (filter_a->phase == BLOOM_PHASE_HASH) + { + /* + * 'b' hashed, 'a' hashed - merge the bitmaps by OR + */ + int i; + int nbytes = (filter_a->nbits + 7) / 8; + + for (i = 0; i < nbytes; i++) + filter_a->bitmap[i] |= filter_b->bitmap[i]; + } + + PG_RETURN_VOID(); +} + +/* + * Cache and return inclusion opclass support procedure + * + * Return the procedure corresponding to the given function support number + * or null if it is not exists. + */ +static FmgrInfo * +bloom_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum) +{ + BloomOpaque *opaque; + uint16 basenum = procnum - PROCNUM_BASE; + + /* + * We cache these in the opaque struct, to avoid repetitive syscache + * lookups. + */ + opaque = (BloomOpaque *) bdesc->bd_info[attno - 1]->oi_opaque; + + /* + * If we already searched for this proc and didn't find it, don't bother + * searching again. + */ + if (opaque->extra_proc_missing[basenum]) + return NULL; + + if (opaque->extra_procinfos[basenum].fn_oid == InvalidOid) + { + if (RegProcedureIsValid(index_getprocid(bdesc->bd_index, attno, + procnum))) + { + fmgr_info_copy(&opaque->extra_procinfos[basenum], + index_getprocinfo(bdesc->bd_index, attno, procnum), + bdesc->bd_context); + } + else + { + opaque->extra_proc_missing[basenum] = true; + return NULL; + } + } + + return &opaque->extra_procinfos[basenum]; +} diff --git a/src/include/catalog/pg_amop.h b/src/include/catalog/pg_amop.h index 03af581..84dc6e3 100644 --- a/src/include/catalog/pg_amop.h +++ b/src/include/catalog/pg_amop.h @@ -910,18 +910,24 @@ DATA(insert ( 4064 17 17 2 s 1958 3580 0 )); DATA(insert ( 4064 17 17 3 s 1955 3580 0 )); DATA(insert ( 4064 17 17 4 s 1960 3580 0 )); DATA(insert ( 4064 17 17 5 s 1959 3580 0 )); +/* bloom bytea */ +DATA(insert ( 5021 17 17 1 s 1955 3580 0 )); /* minmax "char" */ DATA(insert ( 4062 18 18 1 s 631 3580 0 )); DATA(insert ( 4062 18 18 2 s 632 3580 0 )); DATA(insert ( 4062 18 18 3 s 92 3580 0 )); DATA(insert ( 4062 18 18 4 s 634 3580 0 )); DATA(insert ( 4062 18 18 5 s 633 3580 0 )); +/* bloom "char" */ +DATA(insert ( 5022 18 18 1 s 92 3580 0 )); /* minmax name */ DATA(insert ( 4065 19 19 1 s 660 3580 0 )); DATA(insert ( 4065 19 19 2 s 661 3580 0 )); DATA(insert ( 4065 19 19 3 s 93 3580 0 )); DATA(insert ( 4065 19 19 4 s 663 3580 0 )); DATA(insert ( 4065 19 19 5 s 662 3580 0 )); +/* bloom name */ +DATA(insert ( 5023 19 19 1 s 93 3580 0 )); /* minmax integer */ DATA(insert ( 4054 20 20 1 s 412 3580 0 )); DATA(insert ( 4054 20 20 2 s 414 3580 0 )); @@ -968,6 +974,16 @@ DATA(insert ( 4054 23 20 2 s 80 3580 0 )); DATA(insert ( 4054 23 20 3 s 15 3580 0 )); DATA(insert ( 4054 23 20 4 s 82 3580 0 )); DATA(insert ( 4054 23 20 5 s 76 3580 0 )); +/* bloom integer */ +DATA(insert ( 5024 20 20 1 s 410 3580 0 )); +DATA(insert ( 5024 20 21 1 s 1868 3580 0 )); +DATA(insert ( 5024 20 23 1 s 416 3580 0 )); +DATA(insert ( 5024 21 21 1 s 94 3580 0 )); +DATA(insert ( 5024 21 20 1 s 1862 3580 0 )); +DATA(insert ( 5024 21 23 1 s 532 3580 0 )); +DATA(insert ( 5024 23 23 1 s 96 3580 0 )); +DATA(insert ( 5024 23 21 1 s 533 3580 0 )); +DATA(insert ( 5024 23 20 1 s 15 3580 0 )); /* minmax text */ DATA(insert ( 4056 25 25 1 s 664 3580 0 )); @@ -975,12 +991,16 @@ DATA(insert ( 4056 25 25 2 s 665 3580 0 )); DATA(insert ( 4056 25 25 3 s 98 3580 0 )); DATA(insert ( 4056 25 25 4 s 667 3580 0 )); DATA(insert ( 4056 25 25 5 s 666 3580 0 )); +/* bloom text */ +DATA(insert ( 5027 25 25 1 s 98 3580 0 )); /* minmax oid */ DATA(insert ( 4068 26 26 1 s 609 3580 0 )); DATA(insert ( 4068 26 26 2 s 611 3580 0 )); DATA(insert ( 4068 26 26 3 s 607 3580 0 )); DATA(insert ( 4068 26 26 4 s 612 3580 0 )); DATA(insert ( 4068 26 26 5 s 610 3580 0 )); +/* bloom oid */ +DATA(insert ( 5029 26 26 1 s 607 3580 0 )); /* minmax tid */ DATA(insert ( 4069 27 27 1 s 2799 3580 0 )); DATA(insert ( 4069 27 27 2 s 2801 3580 0 )); @@ -1008,6 +1028,11 @@ DATA(insert ( 4070 701 701 2 s 673 3580 0 )); DATA(insert ( 4070 701 701 3 s 670 3580 0 )); DATA(insert ( 4070 701 701 4 s 675 3580 0 )); DATA(insert ( 4070 701 701 5 s 674 3580 0 )); +/* bloom float (float4, float8) */ +DATA(insert ( 5030 700 700 1 s 620 3580 0 )); +DATA(insert ( 5030 700 701 1 s 1120 3580 0 )); +DATA(insert ( 5030 701 700 1 s 1130 3580 0 )); +DATA(insert ( 5030 701 701 1 s 670 3580 0 )); /* minmax abstime */ DATA(insert ( 4072 702 702 1 s 562 3580 0 )); @@ -1015,24 +1040,32 @@ DATA(insert ( 4072 702 702 2 s 564 3580 0 )); DATA(insert ( 4072 702 702 3 s 560 3580 0 )); DATA(insert ( 4072 702 702 4 s 565 3580 0 )); DATA(insert ( 4072 702 702 5 s 563 3580 0 )); +/* bloom abstime */ +DATA(insert ( 5031 702 702 1 s 560 3580 0 )); /* minmax reltime */ DATA(insert ( 4073 703 703 1 s 568 3580 0 )); DATA(insert ( 4073 703 703 2 s 570 3580 0 )); DATA(insert ( 4073 703 703 3 s 566 3580 0 )); DATA(insert ( 4073 703 703 4 s 571 3580 0 )); DATA(insert ( 4073 703 703 5 s 569 3580 0 )); +/* bloom reltime */ +DATA(insert ( 5032 703 703 1 s 566 3580 0 )); /* minmax macaddr */ DATA(insert ( 4074 829 829 1 s 1222 3580 0 )); DATA(insert ( 4074 829 829 2 s 1223 3580 0 )); DATA(insert ( 4074 829 829 3 s 1220 3580 0 )); DATA(insert ( 4074 829 829 4 s 1225 3580 0 )); DATA(insert ( 4074 829 829 5 s 1224 3580 0 )); +/* bloom macaddr */ +DATA(insert ( 5033 829 829 1 s 1220 3580 0 )); /* minmax macaddr8 */ DATA(insert ( 4109 774 774 1 s 3364 3580 0 )); DATA(insert ( 4109 774 774 2 s 3365 3580 0 )); DATA(insert ( 4109 774 774 3 s 3362 3580 0 )); DATA(insert ( 4109 774 774 4 s 3367 3580 0 )); DATA(insert ( 4109 774 774 5 s 3366 3580 0 )); +/* bloom macaddr8 */ +DATA(insert ( 5034 774 774 1 s 3362 3580 0 )); /* minmax inet */ DATA(insert ( 4075 869 869 1 s 1203 3580 0 )); DATA(insert ( 4075 869 869 2 s 1204 3580 0 )); @@ -1046,18 +1079,24 @@ DATA(insert ( 4102 869 869 8 s 932 3580 0 )); DATA(insert ( 4102 869 869 18 s 1201 3580 0 )); DATA(insert ( 4102 869 869 24 s 933 3580 0 )); DATA(insert ( 4102 869 869 26 s 931 3580 0 )); +/* bloom inet */ +DATA(insert ( 5035 869 869 1 s 1201 3580 0 )); /* minmax character */ DATA(insert ( 4076 1042 1042 1 s 1058 3580 0 )); DATA(insert ( 4076 1042 1042 2 s 1059 3580 0 )); DATA(insert ( 4076 1042 1042 3 s 1054 3580 0 )); DATA(insert ( 4076 1042 1042 4 s 1061 3580 0 )); DATA(insert ( 4076 1042 1042 5 s 1060 3580 0 )); +/* bloom character */ +DATA(insert ( 5036 1042 1042 1 s 1054 3580 0 )); /* minmax time without time zone */ DATA(insert ( 4077 1083 1083 1 s 1110 3580 0 )); DATA(insert ( 4077 1083 1083 2 s 1111 3580 0 )); DATA(insert ( 4077 1083 1083 3 s 1108 3580 0 )); DATA(insert ( 4077 1083 1083 4 s 1113 3580 0 )); DATA(insert ( 4077 1083 1083 5 s 1112 3580 0 )); +/* bloom time without time zone */ +DATA(insert ( 5037 1083 1083 1 s 1108 3580 0 )); /* minmax datetime (date, timestamp, timestamptz) */ DATA(insert ( 4059 1114 1114 1 s 2062 3580 0 )); DATA(insert ( 4059 1114 1114 2 s 2063 3580 0 )); @@ -1104,6 +1143,16 @@ DATA(insert ( 4059 1184 1184 2 s 1323 3580 0 )); DATA(insert ( 4059 1184 1184 3 s 1320 3580 0 )); DATA(insert ( 4059 1184 1184 4 s 1325 3580 0 )); DATA(insert ( 4059 1184 1184 5 s 1324 3580 0 )); +/* bloom datetime (date, timestamp, timestamptz) */ +DATA(insert ( 5038 1114 1114 1 s 2060 3580 0 )); +DATA(insert ( 5038 1114 1082 1 s 2373 3580 0 )); +DATA(insert ( 5038 1114 1184 1 s 2536 3580 0 )); +DATA(insert ( 5038 1082 1082 1 s 1093 3580 0 )); +DATA(insert ( 5038 1082 1114 1 s 2347 3580 0 )); +DATA(insert ( 5038 1082 1184 1 s 2360 3580 0 )); +DATA(insert ( 5038 1184 1082 1 s 2386 3580 0 )); +DATA(insert ( 5038 1184 1114 1 s 2542 3580 0 )); +DATA(insert ( 5038 1184 1184 1 s 1320 3580 0 )); /* minmax interval */ DATA(insert ( 4078 1186 1186 1 s 1332 3580 0 )); @@ -1111,12 +1160,16 @@ DATA(insert ( 4078 1186 1186 2 s 1333 3580 0 )); DATA(insert ( 4078 1186 1186 3 s 1330 3580 0 )); DATA(insert ( 4078 1186 1186 4 s 1335 3580 0 )); DATA(insert ( 4078 1186 1186 5 s 1334 3580 0 )); +/* bloom interval */ +DATA(insert ( 5041 1186 1186 1 s 1330 3580 0 )); /* minmax time with time zone */ DATA(insert ( 4058 1266 1266 1 s 1552 3580 0 )); DATA(insert ( 4058 1266 1266 2 s 1553 3580 0 )); DATA(insert ( 4058 1266 1266 3 s 1550 3580 0 )); DATA(insert ( 4058 1266 1266 4 s 1555 3580 0 )); DATA(insert ( 4058 1266 1266 5 s 1554 3580 0 )); +/* bloom time with time zone */ +DATA(insert ( 5042 1266 1266 1 s 1550 3580 0 )); /* minmax bit */ DATA(insert ( 4079 1560 1560 1 s 1786 3580 0 )); DATA(insert ( 4079 1560 1560 2 s 1788 3580 0 )); @@ -1135,12 +1188,16 @@ DATA(insert ( 4055 1700 1700 2 s 1755 3580 0 )); DATA(insert ( 4055 1700 1700 3 s 1752 3580 0 )); DATA(insert ( 4055 1700 1700 4 s 1757 3580 0 )); DATA(insert ( 4055 1700 1700 5 s 1756 3580 0 )); +/* bloom numeric */ +DATA(insert ( 5045 1700 1700 1 s 1752 3580 0 )); /* minmax uuid */ DATA(insert ( 4081 2950 2950 1 s 2974 3580 0 )); DATA(insert ( 4081 2950 2950 2 s 2976 3580 0 )); DATA(insert ( 4081 2950 2950 3 s 2972 3580 0 )); DATA(insert ( 4081 2950 2950 4 s 2977 3580 0 )); DATA(insert ( 4081 2950 2950 5 s 2975 3580 0 )); +/* bloom uuid */ +DATA(insert ( 5046 2950 2950 1 s 2972 3580 0 )); /* inclusion range types */ DATA(insert ( 4103 3831 3831 1 s 3893 3580 0 )); DATA(insert ( 4103 3831 3831 2 s 3895 3580 0 )); @@ -1162,6 +1219,8 @@ DATA(insert ( 4082 3220 3220 2 s 3226 3580 0 )); DATA(insert ( 4082 3220 3220 3 s 3222 3580 0 )); DATA(insert ( 4082 3220 3220 4 s 3227 3580 0 )); DATA(insert ( 4082 3220 3220 5 s 3225 3580 0 )); +/* bloom pg_lsn */ +DATA(insert ( 5047 3220 3220 1 s 3222 3580 0 )); /* inclusion box */ DATA(insert ( 4104 603 603 1 s 493 3580 0 )); DATA(insert ( 4104 603 603 2 s 494 3580 0 )); diff --git a/src/include/catalog/pg_amproc.h b/src/include/catalog/pg_amproc.h index f545a058..4871836 100644 --- a/src/include/catalog/pg_amproc.h +++ b/src/include/catalog/pg_amproc.h @@ -347,16 +347,34 @@ DATA(insert ( 4064 17 17 1 3383 )); DATA(insert ( 4064 17 17 2 3384 )); DATA(insert ( 4064 17 17 3 3385 )); DATA(insert ( 4064 17 17 4 3386 )); +/* bloom bytea */ +DATA(insert ( 5021 17 17 1 5017 )); +DATA(insert ( 5021 17 17 2 5018 )); +DATA(insert ( 5021 17 17 3 5019 )); +DATA(insert ( 5021 17 17 4 5020 )); +DATA(insert ( 5021 17 17 11 456 )); /* minmax "char" */ DATA(insert ( 4062 18 18 1 3383 )); DATA(insert ( 4062 18 18 2 3384 )); DATA(insert ( 4062 18 18 3 3385 )); DATA(insert ( 4062 18 18 4 3386 )); +/* bloom "char" */ +DATA(insert ( 5022 18 18 1 5017 )); +DATA(insert ( 5022 18 18 2 5018 )); +DATA(insert ( 5022 18 18 3 5019 )); +DATA(insert ( 5022 18 18 4 5020 )); +DATA(insert ( 5022 18 18 11 454 )); /* minmax name */ DATA(insert ( 4065 19 19 1 3383 )); DATA(insert ( 4065 19 19 2 3384 )); DATA(insert ( 4065 19 19 3 3385 )); DATA(insert ( 4065 19 19 4 3386 )); +/* bloom name */ +DATA(insert ( 5023 19 19 1 5017 )); +DATA(insert ( 5023 19 19 2 5018 )); +DATA(insert ( 5023 19 19 3 5019 )); +DATA(insert ( 5023 19 19 4 5020 )); +DATA(insert ( 5023 19 19 11 455 )); /* minmax integer: int2, int4, int8 */ DATA(insert ( 4054 20 20 1 3383 )); DATA(insert ( 4054 20 20 2 3384 )); @@ -397,16 +415,47 @@ DATA(insert ( 4054 23 21 2 3384 )); DATA(insert ( 4054 23 21 3 3385 )); DATA(insert ( 4054 23 21 4 3386 )); +/* bloom integer: int2, int4, int8 */ +DATA(insert ( 5024 20 20 1 5017 )); +DATA(insert ( 5024 20 20 2 5018 )); +DATA(insert ( 5024 20 20 3 5019 )); +DATA(insert ( 5024 20 20 4 5020 )); +DATA(insert ( 5024 20 20 11 949 )); + +DATA(insert ( 5024 21 21 1 5017 )); +DATA(insert ( 5024 21 21 2 5018 )); +DATA(insert ( 5024 21 21 3 5019 )); +DATA(insert ( 5024 21 21 4 5020 )); +DATA(insert ( 5024 21 21 11 449 )); + +DATA(insert ( 5024 23 23 1 5017 )); +DATA(insert ( 5024 23 23 2 5018 )); +DATA(insert ( 5024 23 23 3 5019 )); +DATA(insert ( 5024 23 23 4 5020 )); +DATA(insert ( 5024 23 23 11 450 )); + /* minmax text */ DATA(insert ( 4056 25 25 1 3383 )); DATA(insert ( 4056 25 25 2 3384 )); DATA(insert ( 4056 25 25 3 3385 )); DATA(insert ( 4056 25 25 4 3386 )); +/* bloom text */ +DATA(insert ( 5027 25 25 1 5017 )); +DATA(insert ( 5027 25 25 2 5018 )); +DATA(insert ( 5027 25 25 3 5019 )); +DATA(insert ( 5027 25 25 4 5020 )); +DATA(insert ( 5027 25 25 11 400 )); /* minmax oid */ DATA(insert ( 4068 26 26 1 3383 )); DATA(insert ( 4068 26 26 2 3384 )); DATA(insert ( 4068 26 26 3 3385 )); DATA(insert ( 4068 26 26 4 3386 )); +/* bloom oid */ +DATA(insert ( 5029 26 26 1 5017 )); +DATA(insert ( 5029 26 26 2 5018 )); +DATA(insert ( 5029 26 26 3 5019 )); +DATA(insert ( 5029 26 26 4 5020 )); +DATA(insert ( 5029 26 26 11 453 )); /* minmax tid */ DATA(insert ( 4069 27 27 1 3383 )); DATA(insert ( 4069 27 27 2 3384 )); @@ -433,26 +482,63 @@ DATA(insert ( 4070 701 700 2 3384 )); DATA(insert ( 4070 701 700 3 3385 )); DATA(insert ( 4070 701 700 4 3386 )); +/* bloom float */ +DATA(insert ( 5030 700 700 1 5017 )); +DATA(insert ( 5030 700 700 2 5018 )); +DATA(insert ( 5030 700 700 3 5019 )); +DATA(insert ( 5030 700 700 4 5020 )); +DATA(insert ( 5030 700 700 11 451 )); + +DATA(insert ( 5030 701 701 1 5017 )); +DATA(insert ( 5030 701 701 2 5018 )); +DATA(insert ( 5030 701 701 3 5019 )); +DATA(insert ( 5030 701 701 4 5020 )); +DATA(insert ( 5030 701 701 11 452 )); + /* minmax abstime */ DATA(insert ( 4072 702 702 1 3383 )); DATA(insert ( 4072 702 702 2 3384 )); DATA(insert ( 4072 702 702 3 3385 )); DATA(insert ( 4072 702 702 4 3386 )); +/* bloom abstime */ +DATA(insert ( 5031 702 702 1 5017 )); +DATA(insert ( 5031 702 702 2 5018 )); +DATA(insert ( 5031 702 702 3 5019 )); +DATA(insert ( 5031 702 702 4 5020 )); +DATA(insert ( 5031 702 702 11 450 )); /* minmax reltime */ DATA(insert ( 4073 703 703 1 3383 )); DATA(insert ( 4073 703 703 2 3384 )); DATA(insert ( 4073 703 703 3 3385 )); DATA(insert ( 4073 703 703 4 3386 )); +/* bloom reltime */ +DATA(insert ( 5032 703 703 1 5017 )); +DATA(insert ( 5032 703 703 2 5018 )); +DATA(insert ( 5032 703 703 3 5019 )); +DATA(insert ( 5032 703 703 4 5020 )); +DATA(insert ( 5032 703 703 11 450 )); /* minmax macaddr */ DATA(insert ( 4074 829 829 1 3383 )); DATA(insert ( 4074 829 829 2 3384 )); DATA(insert ( 4074 829 829 3 3385 )); DATA(insert ( 4074 829 829 4 3386 )); +/* bloom macaddr */ +DATA(insert ( 5033 829 829 1 5017 )); +DATA(insert ( 5033 829 829 2 5018 )); +DATA(insert ( 5033 829 829 3 5019 )); +DATA(insert ( 5033 829 829 4 5020 )); +DATA(insert ( 5033 829 829 11 399 )); /* minmax macaddr8 */ DATA(insert ( 4109 774 774 1 3383 )); DATA(insert ( 4109 774 774 2 3384 )); DATA(insert ( 4109 774 774 3 3385 )); DATA(insert ( 4109 774 774 4 3386 )); +/* bloom macaddr8 */ +DATA(insert ( 5034 774 774 1 5017 )); +DATA(insert ( 5034 774 774 2 5018 )); +DATA(insert ( 5034 774 774 3 5019 )); +DATA(insert ( 5034 774 774 4 5020 )); +DATA(insert ( 5034 774 774 11 328 )); /* minmax inet */ DATA(insert ( 4075 869 869 1 3383 )); DATA(insert ( 4075 869 869 2 3384 )); @@ -466,16 +552,34 @@ DATA(insert ( 4102 869 869 4 4108 )); DATA(insert ( 4102 869 869 11 4063 )); DATA(insert ( 4102 869 869 12 4071 )); DATA(insert ( 4102 869 869 13 930 )); +/* bloom inet */ +DATA(insert ( 5035 869 869 1 5017 )); +DATA(insert ( 5035 869 869 2 5018 )); +DATA(insert ( 5035 869 869 3 5019 )); +DATA(insert ( 5035 869 869 4 5020 )); +DATA(insert ( 5035 869 869 11 422 )); /* minmax character */ DATA(insert ( 4076 1042 1042 1 3383 )); DATA(insert ( 4076 1042 1042 2 3384 )); DATA(insert ( 4076 1042 1042 3 3385 )); DATA(insert ( 4076 1042 1042 4 3386 )); +/* bloom character */ +DATA(insert ( 5036 1042 1042 1 5017 )); +DATA(insert ( 5036 1042 1042 2 5018 )); +DATA(insert ( 5036 1042 1042 3 5019 )); +DATA(insert ( 5036 1042 1042 4 5020 )); +DATA(insert ( 5036 1042 1042 11 454 )); /* minmax time without time zone */ DATA(insert ( 4077 1083 1083 1 3383 )); DATA(insert ( 4077 1083 1083 2 3384 )); DATA(insert ( 4077 1083 1083 3 3385 )); DATA(insert ( 4077 1083 1083 4 3386 )); +/* bloom time without time zone */ +DATA(insert ( 5037 1083 1083 1 5017 )); +DATA(insert ( 5037 1083 1083 2 5018 )); +DATA(insert ( 5037 1083 1083 3 5019 )); +DATA(insert ( 5037 1083 1083 4 5020 )); +DATA(insert ( 5037 1083 1083 11 1688 )); /* minmax datetime (date, timestamp, timestamptz) */ DATA(insert ( 4059 1114 1114 1 3383 )); DATA(insert ( 4059 1114 1114 2 3384 )); @@ -516,16 +620,47 @@ DATA(insert ( 4059 1082 1184 2 3384 )); DATA(insert ( 4059 1082 1184 3 3385 )); DATA(insert ( 4059 1082 1184 4 3386 )); +/* bloom datetime (date, timestamp, timestamptz) */ +DATA(insert ( 5038 1114 1114 1 5017 )); +DATA(insert ( 5038 1114 1114 2 5018 )); +DATA(insert ( 5038 1114 1114 3 5019 )); +DATA(insert ( 5038 1114 1114 4 5020 )); +DATA(insert ( 5038 1114 1114 11 2039 )); + +DATA(insert ( 5038 1184 1184 1 5017 )); +DATA(insert ( 5038 1184 1184 2 5018 )); +DATA(insert ( 5038 1184 1184 3 5019 )); +DATA(insert ( 5038 1184 1184 4 5020 )); +DATA(insert ( 5038 1184 1184 11 2039 )); + +DATA(insert ( 5038 1082 1082 1 5017 )); +DATA(insert ( 5038 1082 1082 2 5018 )); +DATA(insert ( 5038 1082 1082 3 5019 )); +DATA(insert ( 5038 1082 1082 4 5020 )); +DATA(insert ( 5038 1082 1082 11 450 )); + /* minmax interval */ DATA(insert ( 4078 1186 1186 1 3383 )); DATA(insert ( 4078 1186 1186 2 3384 )); DATA(insert ( 4078 1186 1186 3 3385 )); DATA(insert ( 4078 1186 1186 4 3386 )); +/* bloom interval */ +DATA(insert ( 5041 1186 1186 1 5017 )); +DATA(insert ( 5041 1186 1186 2 5018 )); +DATA(insert ( 5041 1186 1186 3 5019 )); +DATA(insert ( 5041 1186 1186 4 5020 )); +DATA(insert ( 5041 1186 1186 11 1697 )); /* minmax time with time zone */ DATA(insert ( 4058 1266 1266 1 3383 )); DATA(insert ( 4058 1266 1266 2 3384 )); DATA(insert ( 4058 1266 1266 3 3385 )); DATA(insert ( 4058 1266 1266 4 3386 )); +/* bloom time with time zone */ +DATA(insert ( 5042 1266 1266 1 5017 )); +DATA(insert ( 5042 1266 1266 2 5018 )); +DATA(insert ( 5042 1266 1266 3 5019 )); +DATA(insert ( 5042 1266 1266 4 5020 )); +DATA(insert ( 5042 1266 1266 11 1696 )); /* minmax bit */ DATA(insert ( 4079 1560 1560 1 3383 )); DATA(insert ( 4079 1560 1560 2 3384 )); @@ -541,11 +676,23 @@ DATA(insert ( 4055 1700 1700 1 3383 )); DATA(insert ( 4055 1700 1700 2 3384 )); DATA(insert ( 4055 1700 1700 3 3385 )); DATA(insert ( 4055 1700 1700 4 3386 )); +/* bloom numeric */ +DATA(insert ( 5045 1700 1700 1 5017 )); +DATA(insert ( 5045 1700 1700 2 5018 )); +DATA(insert ( 5045 1700 1700 3 5019 )); +DATA(insert ( 5045 1700 1700 4 5020 )); +DATA(insert ( 5045 1700 1700 11 432 )); /* minmax uuid */ DATA(insert ( 4081 2950 2950 1 3383 )); DATA(insert ( 4081 2950 2950 2 3384 )); DATA(insert ( 4081 2950 2950 3 3385 )); DATA(insert ( 4081 2950 2950 4 3386 )); +/* bloom uuid */ +DATA(insert ( 5046 2950 2950 1 5017 )); +DATA(insert ( 5046 2950 2950 2 5018 )); +DATA(insert ( 5046 2950 2950 3 5019 )); +DATA(insert ( 5046 2950 2950 4 5020 )); +DATA(insert ( 5046 2950 2950 11 2963 )); /* inclusion range types */ DATA(insert ( 4103 3831 3831 1 4105 )); DATA(insert ( 4103 3831 3831 2 4106 )); @@ -559,6 +706,12 @@ DATA(insert ( 4082 3220 3220 1 3383 )); DATA(insert ( 4082 3220 3220 2 3384 )); DATA(insert ( 4082 3220 3220 3 3385 )); DATA(insert ( 4082 3220 3220 4 3386 )); +/* bloom pg_lsn */ +DATA(insert ( 5047 3220 3220 1 5017 )); +DATA(insert ( 5047 3220 3220 2 5018 )); +DATA(insert ( 5047 3220 3220 3 5019 )); +DATA(insert ( 5047 3220 3220 4 5020 )); +DATA(insert ( 5047 3220 3220 11 3252 )); /* inclusion box */ DATA(insert ( 4104 603 603 1 4105 )); DATA(insert ( 4104 603 603 2 4106 )); diff --git a/src/include/catalog/pg_opclass.h b/src/include/catalog/pg_opclass.h index e11d52e..7dbd4ff 100644 --- a/src/include/catalog/pg_opclass.h +++ b/src/include/catalog/pg_opclass.h @@ -214,36 +214,61 @@ DATA(insert ( 2742 jsonb_path_ops PGNSP PGUID 4037 3802 f 23 )); /* BRIN operator classes */ /* no brin opclass for bool */ DATA(insert ( 3580 bytea_minmax_ops PGNSP PGUID 4064 17 t 17 )); +DATA(insert ( 3580 bytea_bloom_ops PGNSP PGUID 5021 17 f 17 )); DATA(insert ( 3580 char_minmax_ops PGNSP PGUID 4062 18 t 18 )); +DATA(insert ( 3580 char_bloom_ops PGNSP PGUID 5022 18 f 18 )); DATA(insert ( 3580 name_minmax_ops PGNSP PGUID 4065 19 t 19 )); +DATA(insert ( 3580 name_bloom_ops PGNSP PGUID 5023 19 f 19 )); DATA(insert ( 3580 int8_minmax_ops PGNSP PGUID 4054 20 t 20 )); +DATA(insert ( 3580 int8_bloom_ops PGNSP PGUID 5024 20 f 20 )); DATA(insert ( 3580 int2_minmax_ops PGNSP PGUID 4054 21 t 21 )); +DATA(insert ( 3580 int2_bloom_ops PGNSP PGUID 5024 21 f 21 )); DATA(insert ( 3580 int4_minmax_ops PGNSP PGUID 4054 23 t 23 )); +DATA(insert ( 3580 int4_bloom_ops PGNSP PGUID 5024 23 f 23 )); DATA(insert ( 3580 text_minmax_ops PGNSP PGUID 4056 25 t 25 )); +DATA(insert ( 3580 text_bloom_ops PGNSP PGUID 5027 25 f 25 )); DATA(insert ( 3580 oid_minmax_ops PGNSP PGUID 4068 26 t 26 )); +DATA(insert ( 3580 oid_bloom_ops PGNSP PGUID 5029 26 f 26 )); DATA(insert ( 3580 tid_minmax_ops PGNSP PGUID 4069 27 t 27 )); DATA(insert ( 3580 float4_minmax_ops PGNSP PGUID 4070 700 t 700 )); +DATA(insert ( 3580 float4_bloom_ops PGNSP PGUID 5030 700 f 700 )); DATA(insert ( 3580 float8_minmax_ops PGNSP PGUID 4070 701 t 701 )); +DATA(insert ( 3580 float8_bloom_ops PGNSP PGUID 5030 701 f 701 )); DATA(insert ( 3580 abstime_minmax_ops PGNSP PGUID 4072 702 t 702 )); +DATA(insert ( 3580 abstime_bloom_ops PGNSP PGUID 5031 702 f 702 )); DATA(insert ( 3580 reltime_minmax_ops PGNSP PGUID 4073 703 t 703 )); +DATA(insert ( 3580 reltime_bloom_ops PGNSP PGUID 5032 703 f 703 )); DATA(insert ( 3580 macaddr_minmax_ops PGNSP PGUID 4074 829 t 829 )); +DATA(insert ( 3580 macaddr_bloom_ops PGNSP PGUID 5033 829 f 829 )); DATA(insert ( 3580 macaddr8_minmax_ops PGNSP PGUID 4109 774 t 774 )); +DATA(insert ( 3580 macaddr8_bloom_ops PGNSP PGUID 5034 774 f 774 )); DATA(insert ( 3580 inet_minmax_ops PGNSP PGUID 4075 869 f 869 )); DATA(insert ( 3580 inet_inclusion_ops PGNSP PGUID 4102 869 t 869 )); +DATA(insert ( 3580 inet_bloom_ops PGNSP PGUID 5035 869 f 869 )); DATA(insert ( 3580 bpchar_minmax_ops PGNSP PGUID 4076 1042 t 1042 )); +DATA(insert ( 3580 bpchar_bloom_ops PGNSP PGUID 5036 1042 f 1042 )); DATA(insert ( 3580 time_minmax_ops PGNSP PGUID 4077 1083 t 1083 )); +DATA(insert ( 3580 time_bloom_ops PGNSP PGUID 5037 1083 f 1083 )); DATA(insert ( 3580 date_minmax_ops PGNSP PGUID 4059 1082 t 1082 )); +DATA(insert ( 3580 date_bloom_ops PGNSP PGUID 5038 1082 f 1082 )); DATA(insert ( 3580 timestamp_minmax_ops PGNSP PGUID 4059 1114 t 1114 )); +DATA(insert ( 3580 timestamp_bloom_ops PGNSP PGUID 5038 1114 f 1114 )); DATA(insert ( 3580 timestamptz_minmax_ops PGNSP PGUID 4059 1184 t 1184 )); +DATA(insert ( 3580 timestamptz_bloom_ops PGNSP PGUID 5038 1184 f 1184 )); DATA(insert ( 3580 interval_minmax_ops PGNSP PGUID 4078 1186 t 1186 )); +DATA(insert ( 3580 interval_bloom_ops PGNSP PGUID 5041 1186 f 1186 )); DATA(insert ( 3580 timetz_minmax_ops PGNSP PGUID 4058 1266 t 1266 )); +DATA(insert ( 3580 timetz_bloom_ops PGNSP PGUID 5042 1266 f 1266 )); DATA(insert ( 3580 bit_minmax_ops PGNSP PGUID 4079 1560 t 1560 )); DATA(insert ( 3580 varbit_minmax_ops PGNSP PGUID 4080 1562 t 1562 )); DATA(insert ( 3580 numeric_minmax_ops PGNSP PGUID 4055 1700 t 1700 )); +DATA(insert ( 3580 numeric_bloom_ops PGNSP PGUID 5045 1700 f 1700 )); /* no brin opclass for record, anyarray */ DATA(insert ( 3580 uuid_minmax_ops PGNSP PGUID 4081 2950 t 2950 )); +DATA(insert ( 3580 uuid_bloom_ops PGNSP PGUID 5046 2950 f 2950 )); DATA(insert ( 3580 range_inclusion_ops PGNSP PGUID 4103 3831 t 3831 )); DATA(insert ( 3580 pg_lsn_minmax_ops PGNSP PGUID 4082 3220 t 3220 )); +DATA(insert ( 3580 pg_lsn_bloom_ops PGNSP PGUID 5047 3220 f 3220 )); /* no brin opclass for enum, tsvector, tsquery, jsonb */ DATA(insert ( 3580 box_inclusion_ops PGNSP PGUID 4104 603 t 603 )); /* no brin opclass for the geometric types except box */ diff --git a/src/include/catalog/pg_opfamily.h b/src/include/catalog/pg_opfamily.h index b544474..bc22ce3 100644 --- a/src/include/catalog/pg_opfamily.h +++ b/src/include/catalog/pg_opfamily.h @@ -160,30 +160,50 @@ DATA(insert OID = 4036 ( 2742 jsonb_ops PGNSP PGUID )); DATA(insert OID = 4037 ( 2742 jsonb_path_ops PGNSP PGUID )); DATA(insert OID = 4054 ( 3580 integer_minmax_ops PGNSP PGUID )); +DATA(insert OID = 5024 ( 3580 integer_bloom_ops PGNSP PGUID )); DATA(insert OID = 4055 ( 3580 numeric_minmax_ops PGNSP PGUID )); +DATA(insert OID = 5045 ( 3580 numeric_bloom_ops PGNSP PGUID )); DATA(insert OID = 4056 ( 3580 text_minmax_ops PGNSP PGUID )); +DATA(insert OID = 5027 ( 3580 text_bloom_ops PGNSP PGUID )); DATA(insert OID = 4058 ( 3580 timetz_minmax_ops PGNSP PGUID )); +DATA(insert OID = 5042 ( 3580 timetz_bloom_ops PGNSP PGUID )); DATA(insert OID = 4059 ( 3580 datetime_minmax_ops PGNSP PGUID )); +DATA(insert OID = 5038 ( 3580 datetime_bloom_ops PGNSP PGUID )); DATA(insert OID = 4062 ( 3580 char_minmax_ops PGNSP PGUID )); +DATA(insert OID = 5022 ( 3580 char_bloom_ops PGNSP PGUID )); DATA(insert OID = 4064 ( 3580 bytea_minmax_ops PGNSP PGUID )); +DATA(insert OID = 5021 ( 3580 bytea_bloom_ops PGNSP PGUID )); DATA(insert OID = 4065 ( 3580 name_minmax_ops PGNSP PGUID )); +DATA(insert OID = 5023 ( 3580 name_bloom_ops PGNSP PGUID )); DATA(insert OID = 4068 ( 3580 oid_minmax_ops PGNSP PGUID )); +DATA(insert OID = 5029 ( 3580 oid_bloom_ops PGNSP PGUID )); DATA(insert OID = 4069 ( 3580 tid_minmax_ops PGNSP PGUID )); DATA(insert OID = 4070 ( 3580 float_minmax_ops PGNSP PGUID )); +DATA(insert OID = 5030 ( 3580 float_bloom_ops PGNSP PGUID )); DATA(insert OID = 4072 ( 3580 abstime_minmax_ops PGNSP PGUID )); +DATA(insert OID = 5031 ( 3580 abstime_bloom_ops PGNSP PGUID )); DATA(insert OID = 4073 ( 3580 reltime_minmax_ops PGNSP PGUID )); +DATA(insert OID = 5032 ( 3580 reltime_bloom_ops PGNSP PGUID )); DATA(insert OID = 4074 ( 3580 macaddr_minmax_ops PGNSP PGUID )); +DATA(insert OID = 5033 ( 3580 macaddr_bloom_ops PGNSP PGUID )); DATA(insert OID = 4109 ( 3580 macaddr8_minmax_ops PGNSP PGUID )); +DATA(insert OID = 5034 ( 3580 macaddr8_bloom_ops PGNSP PGUID )); DATA(insert OID = 4075 ( 3580 network_minmax_ops PGNSP PGUID )); DATA(insert OID = 4102 ( 3580 network_inclusion_ops PGNSP PGUID )); +DATA(insert OID = 5035 ( 3580 network_bloom_ops PGNSP PGUID )); DATA(insert OID = 4076 ( 3580 bpchar_minmax_ops PGNSP PGUID )); +DATA(insert OID = 5036 ( 3580 bpchar_bloom_ops PGNSP PGUID )); DATA(insert OID = 4077 ( 3580 time_minmax_ops PGNSP PGUID )); +DATA(insert OID = 5037 ( 3580 time_bloom_ops PGNSP PGUID )); DATA(insert OID = 4078 ( 3580 interval_minmax_ops PGNSP PGUID )); +DATA(insert OID = 5041 ( 3580 interval_bloom_ops PGNSP PGUID )); DATA(insert OID = 4079 ( 3580 bit_minmax_ops PGNSP PGUID )); DATA(insert OID = 4080 ( 3580 varbit_minmax_ops PGNSP PGUID )); DATA(insert OID = 4081 ( 3580 uuid_minmax_ops PGNSP PGUID )); +DATA(insert OID = 5046 ( 3580 uuid_bloom_ops PGNSP PGUID )); DATA(insert OID = 4103 ( 3580 range_inclusion_ops PGNSP PGUID )); DATA(insert OID = 4082 ( 3580 pg_lsn_minmax_ops PGNSP PGUID )); +DATA(insert OID = 5047 ( 3580 pg_lsn_bloom_ops PGNSP PGUID )); DATA(insert OID = 4104 ( 3580 box_inclusion_ops PGNSP PGUID )); DATA(insert OID = 5000 ( 4000 box_ops PGNSP PGUID )); DATA(insert OID = 5008 ( 4000 poly_ops PGNSP PGUID )); diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index f01648c..7154e5e 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -4374,6 +4374,16 @@ DESCR("BRIN inclusion support"); DATA(insert OID = 4108 ( brin_inclusion_union PGNSP PGUID 12 1 0 0 0 f f f f t f i s 3 0 16 "2281 2281 2281" _null_ _null_ _null_ _null_ _null_ brin_inclusion_union _null_ _null_ _null_ )); DESCR("BRIN inclusion support"); +/* BRIN bloom */ +DATA(insert OID = 5017 ( brin_bloom_opcinfo PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 2281 "2281" _null_ _null_ _null_ _null_ _null_ brin_bloom_opcinfo _null_ _null_ _null_ )); +DESCR("BRIN bloom support"); +DATA(insert OID = 5018 ( brin_bloom_add_value PGNSP PGUID 12 1 0 0 0 f f f f t f i s 4 0 16 "2281 2281 2281 2281" _null_ _null_ _null_ _null_ _null_ brin_bloom_add_value _null_ _null_ _null_ )); +DESCR("BRIN bloom support"); +DATA(insert OID = 5019 ( brin_bloom_consistent PGNSP PGUID 12 1 0 0 0 f f f f t f i s 3 0 16 "2281 2281 2281" _null_ _null_ _null_ _null_ _null_ brin_bloom_consistent _null_ _null_ _null_ )); +DESCR("BRIN bloom support"); +DATA(insert OID = 5020 ( brin_bloom_union PGNSP PGUID 12 1 0 0 0 f f f f t f i s 3 0 16 "2281 2281 2281" _null_ _null_ _null_ _null_ _null_ brin_bloom_union _null_ _null_ _null_ )); +DESCR("BRIN bloom support"); + /* userlock replacements */ DATA(insert OID = 2880 ( pg_advisory_lock PGNSP PGUID 12 1 0 0 0 f f f f t f v u 1 0 2278 "20" _null_ _null_ _null_ _null_ _null_ pg_advisory_lock_int8 _null_ _null_ _null_ )); DESCR("obtain exclusive advisory lock"); diff --git a/src/test/regress/expected/brin_bloom.out b/src/test/regress/expected/brin_bloom.out new file mode 100644 index 0000000..ec6e27a --- /dev/null +++ b/src/test/regress/expected/brin_bloom.out @@ -0,0 +1,438 @@ +CREATE TABLE brintest_bloom (byteacol bytea, + charcol "char", + namecol name, + int8col bigint, + int2col smallint, + int4col integer, + textcol text, + oidcol oid, + float4col real, + float8col double precision, + macaddrcol macaddr, + inetcol inet, + cidrcol cidr, + bpcharcol character, + datecol date, + timecol time without time zone, + timestampcol timestamp without time zone, + timestamptzcol timestamp with time zone, + intervalcol interval, + timetzcol time with time zone, + numericcol numeric, + uuidcol uuid, + lsncol pg_lsn +) WITH (fillfactor=10); +INSERT INTO brintest_bloom SELECT + repeat(stringu1, 8)::bytea, + substr(stringu1, 1, 1)::"char", + stringu1::name, 142857 * tenthous, + thousand, + twothousand, + repeat(stringu1, 8), + unique1::oid, + (four + 1.0)/(hundred+1), + odd::float8 / (tenthous + 1), + format('%s:00:%s:00:%s:00', to_hex(odd), to_hex(even), to_hex(hundred))::macaddr, + inet '10.2.3.4/24' + tenthous, + cidr '10.2.3/24' + tenthous, + substr(stringu1, 1, 1)::bpchar, + date '1995-08-15' + tenthous, + time '01:20:30' + thousand * interval '18.5 second', + timestamp '1942-07-23 03:05:09' + tenthous * interval '36.38 hours', + timestamptz '1972-10-10 03:00' + thousand * interval '1 hour', + justify_days(justify_hours(tenthous * interval '12 minutes')), + timetz '01:30:20+02' + hundred * interval '15 seconds', + tenthous::numeric(36,30) * fivethous * even / (hundred + 1), + format('%s%s-%s-%s-%s-%s%s%s', to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'))::uuid, + format('%s/%s%s', odd, even, tenthous)::pg_lsn +FROM tenk1 ORDER BY unique2 LIMIT 100; +-- throw in some NULL's and different values +INSERT INTO brintest_bloom (inetcol, cidrcol) SELECT + inet 'fe80::6e40:8ff:fea9:8c46' + tenthous, + cidr 'fe80::6e40:8ff:fea9:8c46' + tenthous +FROM tenk1 ORDER BY thousand, tenthous LIMIT 25; +CREATE INDEX brinidx_bloom ON brintest_bloom USING brin ( + byteacol bytea_bloom_ops, + charcol char_bloom_ops, + namecol name_bloom_ops, + int8col int8_bloom_ops, + int2col int2_bloom_ops, + int4col int4_bloom_ops, + textcol text_bloom_ops, + oidcol oid_bloom_ops, + float4col float4_bloom_ops, + float8col float8_bloom_ops, + macaddrcol macaddr_bloom_ops, + inetcol inet_bloom_ops, + cidrcol inet_bloom_ops, + bpcharcol bpchar_bloom_ops, + datecol date_bloom_ops, + timecol time_bloom_ops, + timestampcol timestamp_bloom_ops, + timestamptzcol timestamptz_bloom_ops, + intervalcol interval_bloom_ops, + timetzcol timetz_bloom_ops, + numericcol numeric_bloom_ops, + uuidcol uuid_bloom_ops, + lsncol pg_lsn_bloom_ops +) with (pages_per_range = 1); +CREATE TABLE brinopers_bloom (colname name, typ text, + op text[], value text[], matches int[], + check (cardinality(op) = cardinality(value)), + check (cardinality(op) = cardinality(matches))); +INSERT INTO brinopers_bloom VALUES + ('byteacol', 'bytea', + '{=}', + '{BNAAAABNAAAABNAAAABNAAAABNAAAABNAAAABNAAAABNAAAA}', + '{1}'), + ('charcol', '"char"', + '{=}', + '{M}', + '{6}'), + ('namecol', 'name', + '{=}', + '{MAAAAA}', + '{2}'), + ('int2col', 'int2', + '{=}', + '{800}', + '{1}'), + ('int2col', 'int4', + '{=}', + '{800}', + '{1}'), + ('int2col', 'int8', + '{=}', + '{800}', + '{1}'), + ('int4col', 'int2', + '{=}', + '{800}', + '{1}'), + ('int4col', 'int4', + '{=}', + '{800}', + '{1}'), + ('int4col', 'int8', + '{=}', + '{800}', + '{1}'), + ('int8col', 'int8', + '{=}', + '{1257141600}', + '{1}'), + ('textcol', 'text', + '{=}', + '{BNAAAABNAAAABNAAAABNAAAABNAAAABNAAAABNAAAABNAAAA}', + '{1}'), + ('oidcol', 'oid', + '{=}', + '{8800}', + '{1}'), + ('float4col', 'float4', + '{=}', + '{1}', + '{4}'), + ('float4col', 'float8', + '{=}', + '{1}', + '{4}'), + ('float8col', 'float4', + '{=}', + '{0}', + '{1}'), + ('float8col', 'float8', + '{=}', + '{0}', + '{1}'), + ('macaddrcol', 'macaddr', + '{=}', + '{2c:00:2d:00:16:00}', + '{2}'), + ('inetcol', 'inet', + '{=}', + '{10.2.14.231/24}', + '{1}'), + ('inetcol', 'cidr', + '{=}', + '{fe80::6e40:8ff:fea9:8c46}', + '{1}'), + ('cidrcol', 'inet', + '{=}', + '{10.2.14/24}', + '{2}'), + ('cidrcol', 'inet', + '{=}', + '{fe80::6e40:8ff:fea9:8c46}', + '{1}'), + ('cidrcol', 'cidr', + '{=}', + '{10.2.14/24}', + '{2}'), + ('cidrcol', 'cidr', + '{=}', + '{fe80::6e40:8ff:fea9:8c46}', + '{1}'), + ('bpcharcol', 'bpchar', + '{=}', + '{W}', + '{6}'), + ('datecol', 'date', + '{=}', + '{2009-12-01}', + '{1}'), + ('timecol', 'time', + '{=}', + '{02:28:57}', + '{1}'), + ('timestampcol', 'timestamp', + '{=}', + '{1964-03-24 19:26:45}', + '{1}'), + ('timestampcol', 'timestamptz', + '{=}', + '{1964-03-24 19:26:45}', + '{1}'), + ('timestamptzcol', 'timestamptz', + '{=}', + '{1972-10-19 09:00:00-07}', + '{1}'), + ('intervalcol', 'interval', + '{=}', + '{1 mons 13 days 12:24}', + '{1}'), + ('timetzcol', 'timetz', + '{=}', + '{01:35:50+02}', + '{2}'), + ('numericcol', 'numeric', + '{=}', + '{2268164.347826086956521739130434782609}', + '{1}'), + ('uuidcol', 'uuid', + '{=}', + '{52225222-5222-5222-5222-522252225222}', + '{1}'), + ('lsncol', 'pg_lsn', + '{=, IS, IS NOT}', + '{44/455222, NULL, NULL}', + '{1, 25, 100}'); +DO $x$ +DECLARE + r record; + r2 record; + cond text; + idx_ctids tid[]; + ss_ctids tid[]; + count int; + plan_ok bool; + plan_line text; +BEGIN + FOR r IN SELECT colname, oper, typ, value[ordinality], matches[ordinality] FROM brinopers_bloom, unnest(op) WITH ORDINALITY AS oper LOOP + + -- prepare the condition + IF r.value IS NULL THEN + cond := format('%I %s %L', r.colname, r.oper, r.value); + ELSE + cond := format('%I %s %L::%s', r.colname, r.oper, r.value, r.typ); + END IF; + + -- run the query using the brin index + SET enable_seqscan = 0; + SET enable_bitmapscan = 1; + + plan_ok := false; + FOR plan_line IN EXECUTE format($y$EXPLAIN SELECT array_agg(ctid) FROM brintest_bloom WHERE %s $y$, cond) LOOP + IF plan_line LIKE '%Bitmap Heap Scan on brintest_bloom%' THEN + plan_ok := true; + END IF; + END LOOP; + IF NOT plan_ok THEN + RAISE WARNING 'did not get bitmap indexscan plan for %', r; + END IF; + + EXECUTE format($y$SELECT array_agg(ctid) FROM brintest_bloom WHERE %s $y$, cond) + INTO idx_ctids; + + -- run the query using a seqscan + SET enable_seqscan = 1; + SET enable_bitmapscan = 0; + + plan_ok := false; + FOR plan_line IN EXECUTE format($y$EXPLAIN SELECT array_agg(ctid) FROM brintest_bloom WHERE %s $y$, cond) LOOP + IF plan_line LIKE '%Seq Scan on brintest_bloom%' THEN + plan_ok := true; + END IF; + END LOOP; + IF NOT plan_ok THEN + RAISE WARNING 'did not get seqscan plan for %', r; + END IF; + + EXECUTE format($y$SELECT array_agg(ctid) FROM brintest_bloom WHERE %s $y$, cond) + INTO ss_ctids; + + -- make sure both return the same results + count := array_length(idx_ctids, 1); + + IF NOT (count = array_length(ss_ctids, 1) AND + idx_ctids @> ss_ctids AND + idx_ctids <@ ss_ctids) THEN + -- report the results of each scan to make the differences obvious + RAISE WARNING 'something not right in %: count %', r, count; + SET enable_seqscan = 1; + SET enable_bitmapscan = 0; + FOR r2 IN EXECUTE 'SELECT ' || r.colname || ' FROM brintest_bloom WHERE ' || cond LOOP + RAISE NOTICE 'seqscan: %', r2; + END LOOP; + + SET enable_seqscan = 0; + SET enable_bitmapscan = 1; + FOR r2 IN EXECUTE 'SELECT ' || r.colname || ' FROM brintest_bloom WHERE ' || cond LOOP + RAISE NOTICE 'bitmapscan: %', r2; + END LOOP; + END IF; + + -- make sure we found expected number of matches + IF count != r.matches THEN RAISE WARNING 'unexpected number of results % for %', count, r; END IF; + END LOOP; +END; +$x$; +RESET enable_seqscan; +RESET enable_bitmapscan; +INSERT INTO brintest_bloom SELECT + repeat(stringu1, 42)::bytea, + substr(stringu1, 1, 1)::"char", + stringu1::name, 142857 * tenthous, + thousand, + twothousand, + repeat(stringu1, 42), + unique1::oid, + (four + 1.0)/(hundred+1), + odd::float8 / (tenthous + 1), + format('%s:00:%s:00:%s:00', to_hex(odd), to_hex(even), to_hex(hundred))::macaddr, + inet '10.2.3.4' + tenthous, + cidr '10.2.3/24' + tenthous, + substr(stringu1, 1, 1)::bpchar, + date '1995-08-15' + tenthous, + time '01:20:30' + thousand * interval '18.5 second', + timestamp '1942-07-23 03:05:09' + tenthous * interval '36.38 hours', + timestamptz '1972-10-10 03:00' + thousand * interval '1 hour', + justify_days(justify_hours(tenthous * interval '12 minutes')), + timetz '01:30:20' + hundred * interval '15 seconds', + tenthous::numeric(36,30) * fivethous * even / (hundred + 1), + format('%s%s-%s-%s-%s-%s%s%s', to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'))::uuid, + format('%s/%s%s', odd, even, tenthous)::pg_lsn +FROM tenk1 ORDER BY unique2 LIMIT 5 OFFSET 5; +SELECT brin_desummarize_range('brinidx_bloom', 0); + brin_desummarize_range +------------------------ + +(1 row) + +VACUUM brintest_bloom; -- force a summarization cycle in brinidx +UPDATE brintest_bloom SET int8col = int8col * int4col; +UPDATE brintest_bloom SET textcol = '' WHERE textcol IS NOT NULL; +-- Tests for brin_summarize_new_values +SELECT brin_summarize_new_values('brintest_bloom'); -- error, not an index +ERROR: "brintest_bloom" is not an index +SELECT brin_summarize_new_values('tenk1_unique1'); -- error, not a BRIN index +ERROR: "tenk1_unique1" is not a BRIN index +SELECT brin_summarize_new_values('brinidx_bloom'); -- ok, no change expected + brin_summarize_new_values +--------------------------- + 0 +(1 row) + +-- Tests for brin_desummarize_range +SELECT brin_desummarize_range('brinidx_bloom', -1); -- error, invalid range +ERROR: block number out of range: -1 +SELECT brin_desummarize_range('brinidx_bloom', 0); + brin_desummarize_range +------------------------ + +(1 row) + +SELECT brin_desummarize_range('brinidx_bloom', 0); + brin_desummarize_range +------------------------ + +(1 row) + +SELECT brin_desummarize_range('brinidx_bloom', 100000000); + brin_desummarize_range +------------------------ + +(1 row) + +-- Test brin_summarize_range +CREATE TABLE brin_summarize_bloom ( + value int +) WITH (fillfactor=10, autovacuum_enabled=false); +CREATE INDEX brin_summarize_bloom_idx ON brin_summarize_bloom USING brin (value) WITH (pages_per_range=2); +-- Fill a few pages +DO $$ +DECLARE curtid tid; +BEGIN + LOOP + INSERT INTO brin_summarize_bloom VALUES (1) RETURNING ctid INTO curtid; + EXIT WHEN curtid > tid '(2, 0)'; + END LOOP; +END; +$$; +-- summarize one range +SELECT brin_summarize_range('brin_summarize_bloom_idx', 0); + brin_summarize_range +---------------------- + 0 +(1 row) + +-- nothing: already summarized +SELECT brin_summarize_range('brin_summarize_bloom_idx', 1); + brin_summarize_range +---------------------- + 0 +(1 row) + +-- summarize one range +SELECT brin_summarize_range('brin_summarize_bloom_idx', 2); + brin_summarize_range +---------------------- + 1 +(1 row) + +-- nothing: page doesn't exist in table +SELECT brin_summarize_range('brin_summarize_bloom_idx', 4294967295); + brin_summarize_range +---------------------- + 0 +(1 row) + +-- invalid block number values +SELECT brin_summarize_range('brin_summarize_bloom_idx', -1); +ERROR: block number out of range: -1 +SELECT brin_summarize_range('brin_summarize_bloom_idx', 4294967296); +ERROR: block number out of range: 4294967296 +-- test brin cost estimates behave sanely based on correlation of values +CREATE TABLE brin_test_bloom (a INT, b INT); +INSERT INTO brin_test_bloom SELECT x/100,x%100 FROM generate_series(1,10000) x(x); +CREATE INDEX brin_test_bloom_a_idx ON brin_test_bloom USING brin (a) WITH (pages_per_range = 2); +CREATE INDEX brin_test_bloom_b_idx ON brin_test_bloom USING brin (b) WITH (pages_per_range = 2); +VACUUM ANALYZE brin_test_bloom; +-- Ensure brin index is used when columns are perfectly correlated +EXPLAIN (COSTS OFF) SELECT * FROM brin_test_bloom WHERE a = 1; + QUERY PLAN +-------------------------------------------------- + Bitmap Heap Scan on brin_test_bloom + Recheck Cond: (a = 1) + -> Bitmap Index Scan on brin_test_bloom_a_idx + Index Cond: (a = 1) +(4 rows) + +-- Ensure brin index is not used when values are not correlated +EXPLAIN (COSTS OFF) SELECT * FROM brin_test_bloom WHERE b = 1; + QUERY PLAN +----------------------------- + Seq Scan on brin_test_bloom + Filter: (b = 1) +(2 rows) + diff --git a/src/test/regress/expected/opr_sanity.out b/src/test/regress/expected/opr_sanity.out index 684f7f2..91b2ecc 100644 --- a/src/test/regress/expected/opr_sanity.out +++ b/src/test/regress/expected/opr_sanity.out @@ -1819,6 +1819,7 @@ ORDER BY 1, 2, 3; 2742 | 11 | ?& 3580 | 1 | < 3580 | 1 | << + 3580 | 1 | = 3580 | 2 | &< 3580 | 2 | <= 3580 | 3 | && @@ -1880,7 +1881,7 @@ ORDER BY 1, 2, 3; 4000 | 25 | <<= 4000 | 26 | >> 4000 | 27 | >>= -(121 rows) +(122 rows) -- Check that all opclass search operators have selectivity estimators. -- This is not absolutely required, but it seems a reasonable thing diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index ad9434f..149999d 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -84,7 +84,7 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi # ---------- # Another group of parallel tests # ---------- -test: brin gin gist spgist privileges init_privs security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets drop_operator password +test: brin brin_bloom gin gist spgist privileges init_privs security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets drop_operator password # ---------- # Another group of parallel tests diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule index 27cd498..06c3488 100644 --- a/src/test/regress/serial_schedule +++ b/src/test/regress/serial_schedule @@ -106,6 +106,7 @@ test: delete test: namespace test: prepared_xacts test: brin +test: brin_bloom test: gin test: gist test: spgist diff --git a/src/test/regress/sql/brin_bloom.sql b/src/test/regress/sql/brin_bloom.sql new file mode 100644 index 0000000..8b2c005 --- /dev/null +++ b/src/test/regress/sql/brin_bloom.sql @@ -0,0 +1,391 @@ +CREATE TABLE brintest_bloom (byteacol bytea, + charcol "char", + namecol name, + int8col bigint, + int2col smallint, + int4col integer, + textcol text, + oidcol oid, + float4col real, + float8col double precision, + macaddrcol macaddr, + inetcol inet, + cidrcol cidr, + bpcharcol character, + datecol date, + timecol time without time zone, + timestampcol timestamp without time zone, + timestamptzcol timestamp with time zone, + intervalcol interval, + timetzcol time with time zone, + numericcol numeric, + uuidcol uuid, + lsncol pg_lsn +) WITH (fillfactor=10); + +INSERT INTO brintest_bloom SELECT + repeat(stringu1, 8)::bytea, + substr(stringu1, 1, 1)::"char", + stringu1::name, 142857 * tenthous, + thousand, + twothousand, + repeat(stringu1, 8), + unique1::oid, + (four + 1.0)/(hundred+1), + odd::float8 / (tenthous + 1), + format('%s:00:%s:00:%s:00', to_hex(odd), to_hex(even), to_hex(hundred))::macaddr, + inet '10.2.3.4/24' + tenthous, + cidr '10.2.3/24' + tenthous, + substr(stringu1, 1, 1)::bpchar, + date '1995-08-15' + tenthous, + time '01:20:30' + thousand * interval '18.5 second', + timestamp '1942-07-23 03:05:09' + tenthous * interval '36.38 hours', + timestamptz '1972-10-10 03:00' + thousand * interval '1 hour', + justify_days(justify_hours(tenthous * interval '12 minutes')), + timetz '01:30:20+02' + hundred * interval '15 seconds', + tenthous::numeric(36,30) * fivethous * even / (hundred + 1), + format('%s%s-%s-%s-%s-%s%s%s', to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'))::uuid, + format('%s/%s%s', odd, even, tenthous)::pg_lsn +FROM tenk1 ORDER BY unique2 LIMIT 100; + +-- throw in some NULL's and different values +INSERT INTO brintest_bloom (inetcol, cidrcol) SELECT + inet 'fe80::6e40:8ff:fea9:8c46' + tenthous, + cidr 'fe80::6e40:8ff:fea9:8c46' + tenthous +FROM tenk1 ORDER BY thousand, tenthous LIMIT 25; + +CREATE INDEX brinidx_bloom ON brintest_bloom USING brin ( + byteacol bytea_bloom_ops, + charcol char_bloom_ops, + namecol name_bloom_ops, + int8col int8_bloom_ops, + int2col int2_bloom_ops, + int4col int4_bloom_ops, + textcol text_bloom_ops, + oidcol oid_bloom_ops, + float4col float4_bloom_ops, + float8col float8_bloom_ops, + macaddrcol macaddr_bloom_ops, + inetcol inet_bloom_ops, + cidrcol inet_bloom_ops, + bpcharcol bpchar_bloom_ops, + datecol date_bloom_ops, + timecol time_bloom_ops, + timestampcol timestamp_bloom_ops, + timestamptzcol timestamptz_bloom_ops, + intervalcol interval_bloom_ops, + timetzcol timetz_bloom_ops, + numericcol numeric_bloom_ops, + uuidcol uuid_bloom_ops, + lsncol pg_lsn_bloom_ops +) with (pages_per_range = 1); + +CREATE TABLE brinopers_bloom (colname name, typ text, + op text[], value text[], matches int[], + check (cardinality(op) = cardinality(value)), + check (cardinality(op) = cardinality(matches))); + +INSERT INTO brinopers_bloom VALUES + ('byteacol', 'bytea', + '{=}', + '{BNAAAABNAAAABNAAAABNAAAABNAAAABNAAAABNAAAABNAAAA}', + '{1}'), + ('charcol', '"char"', + '{=}', + '{M}', + '{6}'), + ('namecol', 'name', + '{=}', + '{MAAAAA}', + '{2}'), + ('int2col', 'int2', + '{=}', + '{800}', + '{1}'), + ('int2col', 'int4', + '{=}', + '{800}', + '{1}'), + ('int2col', 'int8', + '{=}', + '{800}', + '{1}'), + ('int4col', 'int2', + '{=}', + '{800}', + '{1}'), + ('int4col', 'int4', + '{=}', + '{800}', + '{1}'), + ('int4col', 'int8', + '{=}', + '{800}', + '{1}'), + ('int8col', 'int8', + '{=}', + '{1257141600}', + '{1}'), + ('textcol', 'text', + '{=}', + '{BNAAAABNAAAABNAAAABNAAAABNAAAABNAAAABNAAAABNAAAA}', + '{1}'), + ('oidcol', 'oid', + '{=}', + '{8800}', + '{1}'), + ('float4col', 'float4', + '{=}', + '{1}', + '{4}'), + ('float4col', 'float8', + '{=}', + '{1}', + '{4}'), + ('float8col', 'float4', + '{=}', + '{0}', + '{1}'), + ('float8col', 'float8', + '{=}', + '{0}', + '{1}'), + ('macaddrcol', 'macaddr', + '{=}', + '{2c:00:2d:00:16:00}', + '{2}'), + ('inetcol', 'inet', + '{=}', + '{10.2.14.231/24}', + '{1}'), + ('inetcol', 'cidr', + '{=}', + '{fe80::6e40:8ff:fea9:8c46}', + '{1}'), + ('cidrcol', 'inet', + '{=}', + '{10.2.14/24}', + '{2}'), + ('cidrcol', 'inet', + '{=}', + '{fe80::6e40:8ff:fea9:8c46}', + '{1}'), + ('cidrcol', 'cidr', + '{=}', + '{10.2.14/24}', + '{2}'), + ('cidrcol', 'cidr', + '{=}', + '{fe80::6e40:8ff:fea9:8c46}', + '{1}'), + ('bpcharcol', 'bpchar', + '{=}', + '{W}', + '{6}'), + ('datecol', 'date', + '{=}', + '{2009-12-01}', + '{1}'), + ('timecol', 'time', + '{=}', + '{02:28:57}', + '{1}'), + ('timestampcol', 'timestamp', + '{=}', + '{1964-03-24 19:26:45}', + '{1}'), + ('timestampcol', 'timestamptz', + '{=}', + '{1964-03-24 19:26:45}', + '{1}'), + ('timestamptzcol', 'timestamptz', + '{=}', + '{1972-10-19 09:00:00-07}', + '{1}'), + ('intervalcol', 'interval', + '{=}', + '{1 mons 13 days 12:24}', + '{1}'), + ('timetzcol', 'timetz', + '{=}', + '{01:35:50+02}', + '{2}'), + ('numericcol', 'numeric', + '{=}', + '{2268164.347826086956521739130434782609}', + '{1}'), + ('uuidcol', 'uuid', + '{=}', + '{52225222-5222-5222-5222-522252225222}', + '{1}'), + ('lsncol', 'pg_lsn', + '{=, IS, IS NOT}', + '{44/455222, NULL, NULL}', + '{1, 25, 100}'); + +DO $x$ +DECLARE + r record; + r2 record; + cond text; + idx_ctids tid[]; + ss_ctids tid[]; + count int; + plan_ok bool; + plan_line text; +BEGIN + FOR r IN SELECT colname, oper, typ, value[ordinality], matches[ordinality] FROM brinopers_bloom, unnest(op) WITH ORDINALITY AS oper LOOP + + -- prepare the condition + IF r.value IS NULL THEN + cond := format('%I %s %L', r.colname, r.oper, r.value); + ELSE + cond := format('%I %s %L::%s', r.colname, r.oper, r.value, r.typ); + END IF; + + -- run the query using the brin index + SET enable_seqscan = 0; + SET enable_bitmapscan = 1; + + plan_ok := false; + FOR plan_line IN EXECUTE format($y$EXPLAIN SELECT array_agg(ctid) FROM brintest_bloom WHERE %s $y$, cond) LOOP + IF plan_line LIKE '%Bitmap Heap Scan on brintest_bloom%' THEN + plan_ok := true; + END IF; + END LOOP; + IF NOT plan_ok THEN + RAISE WARNING 'did not get bitmap indexscan plan for %', r; + END IF; + + EXECUTE format($y$SELECT array_agg(ctid) FROM brintest_bloom WHERE %s $y$, cond) + INTO idx_ctids; + + -- run the query using a seqscan + SET enable_seqscan = 1; + SET enable_bitmapscan = 0; + + plan_ok := false; + FOR plan_line IN EXECUTE format($y$EXPLAIN SELECT array_agg(ctid) FROM brintest_bloom WHERE %s $y$, cond) LOOP + IF plan_line LIKE '%Seq Scan on brintest_bloom%' THEN + plan_ok := true; + END IF; + END LOOP; + IF NOT plan_ok THEN + RAISE WARNING 'did not get seqscan plan for %', r; + END IF; + + EXECUTE format($y$SELECT array_agg(ctid) FROM brintest_bloom WHERE %s $y$, cond) + INTO ss_ctids; + + -- make sure both return the same results + count := array_length(idx_ctids, 1); + + IF NOT (count = array_length(ss_ctids, 1) AND + idx_ctids @> ss_ctids AND + idx_ctids <@ ss_ctids) THEN + -- report the results of each scan to make the differences obvious + RAISE WARNING 'something not right in %: count %', r, count; + SET enable_seqscan = 1; + SET enable_bitmapscan = 0; + FOR r2 IN EXECUTE 'SELECT ' || r.colname || ' FROM brintest_bloom WHERE ' || cond LOOP + RAISE NOTICE 'seqscan: %', r2; + END LOOP; + + SET enable_seqscan = 0; + SET enable_bitmapscan = 1; + FOR r2 IN EXECUTE 'SELECT ' || r.colname || ' FROM brintest_bloom WHERE ' || cond LOOP + RAISE NOTICE 'bitmapscan: %', r2; + END LOOP; + END IF; + + -- make sure we found expected number of matches + IF count != r.matches THEN RAISE WARNING 'unexpected number of results % for %', count, r; END IF; + END LOOP; +END; +$x$; + +RESET enable_seqscan; +RESET enable_bitmapscan; + +INSERT INTO brintest_bloom SELECT + repeat(stringu1, 42)::bytea, + substr(stringu1, 1, 1)::"char", + stringu1::name, 142857 * tenthous, + thousand, + twothousand, + repeat(stringu1, 42), + unique1::oid, + (four + 1.0)/(hundred+1), + odd::float8 / (tenthous + 1), + format('%s:00:%s:00:%s:00', to_hex(odd), to_hex(even), to_hex(hundred))::macaddr, + inet '10.2.3.4' + tenthous, + cidr '10.2.3/24' + tenthous, + substr(stringu1, 1, 1)::bpchar, + date '1995-08-15' + tenthous, + time '01:20:30' + thousand * interval '18.5 second', + timestamp '1942-07-23 03:05:09' + tenthous * interval '36.38 hours', + timestamptz '1972-10-10 03:00' + thousand * interval '1 hour', + justify_days(justify_hours(tenthous * interval '12 minutes')), + timetz '01:30:20' + hundred * interval '15 seconds', + tenthous::numeric(36,30) * fivethous * even / (hundred + 1), + format('%s%s-%s-%s-%s-%s%s%s', to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'))::uuid, + format('%s/%s%s', odd, even, tenthous)::pg_lsn +FROM tenk1 ORDER BY unique2 LIMIT 5 OFFSET 5; + +SELECT brin_desummarize_range('brinidx_bloom', 0); +VACUUM brintest_bloom; -- force a summarization cycle in brinidx + +UPDATE brintest_bloom SET int8col = int8col * int4col; +UPDATE brintest_bloom SET textcol = '' WHERE textcol IS NOT NULL; + +-- Tests for brin_summarize_new_values +SELECT brin_summarize_new_values('brintest_bloom'); -- error, not an index +SELECT brin_summarize_new_values('tenk1_unique1'); -- error, not a BRIN index +SELECT brin_summarize_new_values('brinidx_bloom'); -- ok, no change expected + +-- Tests for brin_desummarize_range +SELECT brin_desummarize_range('brinidx_bloom', -1); -- error, invalid range +SELECT brin_desummarize_range('brinidx_bloom', 0); +SELECT brin_desummarize_range('brinidx_bloom', 0); +SELECT brin_desummarize_range('brinidx_bloom', 100000000); + +-- Test brin_summarize_range +CREATE TABLE brin_summarize_bloom ( + value int +) WITH (fillfactor=10, autovacuum_enabled=false); +CREATE INDEX brin_summarize_bloom_idx ON brin_summarize_bloom USING brin (value) WITH (pages_per_range=2); +-- Fill a few pages +DO $$ +DECLARE curtid tid; +BEGIN + LOOP + INSERT INTO brin_summarize_bloom VALUES (1) RETURNING ctid INTO curtid; + EXIT WHEN curtid > tid '(2, 0)'; + END LOOP; +END; +$$; + +-- summarize one range +SELECT brin_summarize_range('brin_summarize_bloom_idx', 0); +-- nothing: already summarized +SELECT brin_summarize_range('brin_summarize_bloom_idx', 1); +-- summarize one range +SELECT brin_summarize_range('brin_summarize_bloom_idx', 2); +-- nothing: page doesn't exist in table +SELECT brin_summarize_range('brin_summarize_bloom_idx', 4294967295); +-- invalid block number values +SELECT brin_summarize_range('brin_summarize_bloom_idx', -1); +SELECT brin_summarize_range('brin_summarize_bloom_idx', 4294967296); + + +-- test brin cost estimates behave sanely based on correlation of values +CREATE TABLE brin_test_bloom (a INT, b INT); +INSERT INTO brin_test_bloom SELECT x/100,x%100 FROM generate_series(1,10000) x(x); +CREATE INDEX brin_test_bloom_a_idx ON brin_test_bloom USING brin (a) WITH (pages_per_range = 2); +CREATE INDEX brin_test_bloom_b_idx ON brin_test_bloom USING brin (b) WITH (pages_per_range = 2); +VACUUM ANALYZE brin_test_bloom; + +-- Ensure brin index is used when columns are perfectly correlated +EXPLAIN (COSTS OFF) SELECT * FROM brin_test_bloom WHERE a = 1; +-- Ensure brin index is not used when values are not correlated +EXPLAIN (COSTS OFF) SELECT * FROM brin_test_bloom WHERE b = 1; -- 2.9.5 --------------53C4B41F409B92A47ECAD3E5 Content-Type: text/x-patch; name="0004-BRIN-multi-range-minmax-indexes.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="0004-BRIN-multi-range-minmax-indexes.patch" From 871bab2388b79b5d4c3de2bb008a762ed2246986 Mon Sep 17 00:00:00 2001 From: Tomas Vondra Date: Tue, 9 Jan 2018 01:01:26 +0100 Subject: [PATCH 4/4] BRIN multi-range minmax indexes The built-in minmax opclass works well for column correlated with physical location in the table (e.g. timestamp in append-only tables). But when the data is not perfectly correlated (possibly due to later DELETE/UPDATE/INSERT commands), the minmax ranges get very wide and ineffective to eliminate the page ranges. This opclass deals with that by replacing the one [min,max] range with multiple smaller ones. This allows us to track gaps, and deal with outlier values. Note: Currently, this only works with float8-based data types. Supporting additional data types is not a big issue, but will require extending the opclass with "subtract" operator (used to compute distance between values when merging ranges). --- doc/src/sgml/brin.sgml | 234 ++- src/backend/access/brin/Makefile | 3 +- src/backend/access/brin/brin_minmax_multi.c | 2200 +++++++++++++++++++++++++++ src/include/catalog/pg_amop.h | 191 +++ src/include/catalog/pg_amproc.h | 201 +++ src/include/catalog/pg_opclass.h | 21 + src/include/catalog/pg_opfamily.h | 16 + src/include/catalog/pg_proc.h | 44 + src/test/regress/expected/brin_multi.out | 408 +++++ src/test/regress/parallel_schedule | 2 +- src/test/regress/sql/brin_multi.sql | 361 +++++ 11 files changed, 3672 insertions(+), 9 deletions(-) create mode 100644 src/backend/access/brin/brin_minmax_multi.c create mode 100644 src/test/regress/expected/brin_multi.out create mode 100644 src/test/regress/sql/brin_multi.sql diff --git a/doc/src/sgml/brin.sgml b/doc/src/sgml/brin.sgml index 434538e..184c886 100644 --- a/doc/src/sgml/brin.sgml +++ b/doc/src/sgml/brin.sgml @@ -136,6 +136,17 @@
+ abstime_minmax_multi_ops + abstime + + < + <= + = + >= + > + + + int8_bloom_ops bigint @@ -266,6 +277,17 @@ + date_minmax_multi_ops + date + + < + <= + = + >= + > + + + float8_bloom_ops double precision @@ -284,6 +306,17 @@ + float8_minmax_multi_ops + double precision + + < + <= + = + >= + > + + + inet_bloom_ops inet @@ -302,6 +335,17 @@ + inet_minmax_multi_ops + inet + + < + <= + = + >= + > + + + network_inclusion_ops inet @@ -350,6 +394,17 @@ + interval_minmax_multi_ops + interval + + < + <= + = + >= + > + + + macaddr_bloom_ops macaddr @@ -368,6 +423,17 @@ + macaddr_minmax_multi_ops + macaddr + + < + <= + = + >= + > + + + macaddr8_bloom_ops macaddr8 @@ -386,6 +452,17 @@ + macaddr8_minmax_multi_ops + macaddr8 + + < + <= + = + >= + > + + + name_bloom_ops name @@ -440,6 +517,17 @@ + pg_lsn_minmax_multi_ops + pg_lsn + + < + <= + = + >= + > + + + oid_bloom_ops oid @@ -514,6 +602,17 @@ + reltime_minmax_multi_ops + reltime + + < + <= + = + >= + > + + + int2_bloom_ops smallint @@ -579,6 +678,17 @@ + timestamp_minmax_multi_ops + timestamp without time zone + + < + <= + = + >= + > + + + timestamptz_bloom_ops timestamp with time zone @@ -597,6 +707,17 @@ + timestamptz_minmax_multi_ops + timestamp with time zone + + < + <= + = + >= + > + + + time_bloom_ops time without time zone @@ -615,6 +736,17 @@ + time_minmax_multi_ops + time without time zone + + < + <= + = + >= + > + + + timetz_bloom_ops time with time zone @@ -633,6 +765,17 @@ + timetz_minmax_multi_ops + time with time zone + + < + <= + = + >= + > + + + uuid_bloom_ops uuid @@ -650,6 +793,17 @@ > + + uuid_minmax_multi_ops + uuid + + < + <= + = + >= + > + + @@ -744,13 +898,13 @@ typedef struct BrinOpcInfo - The core distribution includes support for two types of operator classes: - minmax and inclusion. Operator class definitions using them are shipped for - in-core data types as appropriate. Additional operator classes can be - defined by the user for other data types using equivalent definitions, - without having to write any source code; appropriate catalog entries being - declared is enough. Note that assumptions about the semantics of operator - strategies are embedded in the support procedures' source code. + The core distribution includes support for four types of operator classes: + minmax, multi-minmax, inclusion and bloom. Operator class definitions using + them are shipped for in-core data types as appropriate. Additional operator + classes can be defined by the user for other data types using equivalent + definitions, without having to write any source code; appropriate catalog + entries being declared is enough. Note that assumptions about the semantics + of operator strategies are embedded in the support procedures' source code. @@ -820,6 +974,72 @@ typedef struct BrinOpcInfo + The multi-minmax operator class is also intended for data types implementing + a totally ordered sets, and may be seen as a simple extension of the minmax + operator class. While minmax operator class summarizes values from each block + range into a single contiguous interval, multi-minmax allows summarization + into multiple smaller intervals to improve handling of outlier values. + It is possible to use the multi-minmax support procedures alongside the + corresponding operators, as shown in + . + All operator class members (procedures and operators) are mandatory. + + + + Procedure and Support Numbers for Multi-Minmax Operator Classes + + + + Operator class member + Object + + + + + Support Procedure 1 + internal function brin_minmax_multi_opcinfo() + + + Support Procedure 2 + internal function brin_minmax_multi_add_value() + + + Support Procedure 3 + internal function brin_minmax_multi_consistent() + + + Support Procedure 4 + internal function brin_minmax_multi_union() + + + Support Procedure 11 + function to compute distance between two values (length of a range) + + + Operator Strategy 1 + operator less-than + + + Operator Strategy 2 + operator less-than-or-equal-to + + + Operator Strategy 3 + operator equal-to + + + Operator Strategy 4 + operator greater-than-or-equal-to + + + Operator Strategy 5 + operator greater-than + + + +
+ + To write an operator class for a complex data type which has values included within another type, it's possible to use the inclusion support procedures alongside the corresponding operators, as shown diff --git a/src/backend/access/brin/Makefile b/src/backend/access/brin/Makefile index a76d927..c87c796 100644 --- a/src/backend/access/brin/Makefile +++ b/src/backend/access/brin/Makefile @@ -13,6 +13,7 @@ top_builddir = ../../../.. include $(top_builddir)/src/Makefile.global OBJS = brin.o brin_pageops.o brin_revmap.o brin_tuple.o brin_xlog.o \ - brin_minmax.o brin_inclusion.o brin_validate.o brin_bloom.o + brin_minmax.o brin_inclusion.o brin_validate.o brin_bloom.o \ + brin_minmax_multi.o include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/access/brin/brin_minmax_multi.c b/src/backend/access/brin/brin_minmax_multi.c new file mode 100644 index 0000000..cdb2857 --- /dev/null +++ b/src/backend/access/brin/brin_minmax_multi.c @@ -0,0 +1,2200 @@ +/* + * brin_minmax_multi.c + * Implementation of Multi Min/Max opclass for BRIN + * + * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * Implements a variant of minmax opclass, where the summary is composed of + * multiple smaller intervals. This allows us to handle outliers, which + * usually makes the simple minmax opclass inefficient. + * + * Consider for example page range with simple minmax interval [1000,2000], + * and assume a new row gets inserted into the range with value 1000000. + * Due to that the interval gets [1000,1000000]. I.e. the minmax interval + * got 1000x wider and won't be useful to eliminate scan keys between 2001 + * and 1000000. + * + * With multi-minmax opclass, we may have [1000,2000] interval initially, + * but after adding the new row we start tracking it as two interval: + * + * [1000,2000] and [1000000,1000000] + * + * This allow us to still eliminate the page range when the scan keys hit + * the gap between 2000 and 1000000, making it useful in cases when the + * simple minmax opclass gets inefficient. + * + * The number of intervals tracked per page range is somewhat flexible. + * What is restricted is the number of values per page range, and the limit + * is currently 64 (see MINMAX_MAX_VALUES). Collapsed intervals (with equal + * minimum and maximum value) are stored as a single value, while regular + * intervals require two values. + * + * When the number of values gets too high (by adding new values to the + * summary), we merge some of the intervals to free space for more values. + * This is done in a greedy way - we simply pick the two closest intervals, + * merge them, and repeat this until the number of values to store gets + * sufficiently low (below 75% of MINMAX_MAX_VALUES), but that is mostly + * arbitrary threshold and may be changed easily). + * + * To pick the closest intervals we use the "distance" support procedure, + * which measures space between two ranges (i.e. length of an interval). + * The computed value may be an approximation - in the worst case we will + * merge two ranges that are slightly less optimal at that step, but the + * index should still produce correct results. + * + * + * IDENTIFICATION + * src/backend/access/brin/brin_minmax_multi.c + */ +#include "postgres.h" + +#include "access/genam.h" +#include "access/brin_internal.h" +#include "access/brin_tuple.h" +#include "access/stratnum.h" +#include "access/htup_details.h" +#include "catalog/pg_type.h" +#include "catalog/pg_amop.h" +#include "utils/builtins.h" +#include "utils/date.h" +#include "utils/datum.h" +#include "utils/inet.h" +#include "utils/lsyscache.h" +#include "utils/memutils.h" +#include "utils/nabstime.h" +#include "utils/numeric.h" +#include "utils/pg_lsn.h" +#include "utils/rel.h" +#include "utils/syscache.h" +#include "utils/uuid.h" + +/* + * Maximum number of values (individual points or range boundaries) to + * keep in the summary values. + */ +#define MINMAX_MAX_VALUES 64 + +/* + * Additional SQL level support functions + * + * Procedure numbers must not use values reserved for BRIN itself; see + * brin_internal.h. + */ +#define MINMAX_MAX_PROCNUMS 1 /* maximum support procs we need */ +#define PROCNUM_DISTANCE 11 /* required, distance between values*/ + +/* + * Subtract this from procnum to obtain index in MinmaxMultiOpaque arrays + * (Must be equal to minimum of private procnums). + */ +#define PROCNUM_BASE 11 + + +typedef struct MinmaxMultiOpaque +{ + FmgrInfo extra_procinfos[MINMAX_MAX_PROCNUMS]; + bool extra_proc_missing[MINMAX_MAX_PROCNUMS]; + Oid cached_subtype; + FmgrInfo strategy_procinfos[BTMaxStrategyNumber]; +} MinmaxMultiOpaque; + + +/* + * The summary of multi-minmax indexes has two representations - Ranges for + * convenient processing, and SerializedRanges for storage in bytea value. + * + * The Ranges struct stores the boundary values in a single array, but we + * treat regular and single-point ranges differently to save space. For + * regular ranges (with different boundary values) we have to store both + * values, while for "single-point ranges" we only need to save one value. + * + * The 'values' array stores boundary values for regular ranges first (there + * are 2*nranges values to store), and then the nvalues boundary values for + * single-point ranges. That is, we have (2*nranges + nvalues) boundary + * values in the array. + * + * +---------------------------------+-------------------------------+ + * | ranges (sorted pairs of values) | sorted values (single points) | + * +---------------------------------+-------------------------------+ + * + * This allows us to quickly add new values, and store outliers without + * making the other ranges very wide. + * + * We never store more than MINMAX_MAX_VALUES values - if needed we perform + * compaction by merging some of the ranges. + * + * To minimize palloc overhead, we always allocate the full array with + * space for MINMAX_MAX_VALUES elements. This should be fine as long as + * the MINMAX_MAX_VALUES is reasonably small (64 seems fine). + */ +typedef struct Ranges +{ + /* (2*nranges + nvalues) <= MINMAX_MAX_VALUES */ + int nranges; /* number of ranges in the array (stored) */ + int nvalues; /* number of values in the data array (all) */ + + /* values stored for this range - either raw values, or ranges */ + Datum values[FLEXIBLE_ARRAY_MEMBER]; +} Ranges; + +/* + * On-disk the summary is stored as a bytea value, represented by the + * SerializedRanges structure. It has a 4B varlena header, so can treated + * as varlena directly. + * + * See range_serialize/range_deserialize methods for serialization details. + */ +typedef struct SerializedRanges +{ + /* varlena header (do not touch directly!) */ + int32 vl_len_; + + /* (2*nranges + nvalues) <= MINMAX_MAX_VALUES */ + int nranges; /* number of ranges in the array (stored) */ + int nvalues; /* number of values in the data array (all) */ + + /* contains the actual data */ + char data[FLEXIBLE_ARRAY_MEMBER]; +} SerializedRanges; + +static SerializedRanges *range_serialize(Ranges *range, + AttrNumber attno, Form_pg_attribute attr); + +static Ranges *range_deserialize(SerializedRanges *range, + AttrNumber attno, Form_pg_attribute attr); + +/* Cache for support and strategy procesures. */ + +static FmgrInfo *minmax_multi_get_procinfo(BrinDesc *bdesc, uint16 attno, + uint16 procnum); + +static FmgrInfo *minmax_multi_get_strategy_procinfo(BrinDesc *bdesc, + uint16 attno, Oid subtype, uint16 strategynum); + + +/* + * minmax_multi_init + * Initialize the deserialized range list, allocate all the memory. + * + * This is only in-memory representation of the ranges, so we allocate + * everything enough space for the maximum number of values (so as not + * to have to do repallocs as the ranges grow). + */ +static Ranges * +minmax_multi_init(void) +{ + Size len; + + len = offsetof(Ranges, values); /* fixed header */ + len += MINMAX_MAX_VALUES * sizeof(Datum); /* Datum values */ + + return (Ranges *) palloc0(len); +} + +/* + * range_serialize + * Serialize the in-memory representation into a compact varlena value. + * + * Simply copy the header and then also the individual values, as stored + * in the in-memory value array. + */ +static SerializedRanges * +range_serialize(Ranges *range, AttrNumber attno, Form_pg_attribute attr) +{ + Size len; + int nvalues; + SerializedRanges *serialized; + + int i; + char *ptr; + + /* simple sanity checks */ + Assert(range->nranges >= 0); + Assert(range->nvalues >= 0); + + /* see how many Datum values we actually have */ + nvalues = 2*range->nranges + range->nvalues; + + Assert(2*range->nranges + range->nvalues <= MINMAX_MAX_VALUES); + + /* header is always needed */ + len = offsetof(SerializedRanges,data); + + /* + * The space needed depends on data type - for fixed-length data types + * (by-value and some by-reference) it's pretty simple, just multiply + * (attlen * nvalues) and we're done. For variable-length by-reference + * types we need to actually walk all the values and sum the lengths. + */ + if (attr->attlen == -1) /* varlena */ + { + int i; + for (i = 0; i < nvalues; i++) + { + len += VARSIZE_ANY(range->values[i]); + } + } + else if (attr->attlen == -2) /* cstring */ + { + int i; + for (i = 0; i < nvalues; i++) + { + /* don't forget to include the null terminator ;-) */ + len += strlen(DatumGetPointer(range->values[i])) + 1; + } + } + else /* fixed-length types (even by-reference) */ + { + Assert(attr->attlen > 0); + len += nvalues * attr->attlen; + } + + /* + * Allocate the serialized object, copy the basic information. The + * serialized object is a varlena, so update the header. + */ + serialized = (SerializedRanges *) palloc0(len); + SET_VARSIZE(serialized, len); + + serialized->nranges = range->nranges; + serialized->nvalues = range->nvalues; + + /* + * And now copy also the boundary values (like the length calculation + * this depends on the particular data type). + */ + ptr = serialized->data; /* start of the serialized data */ + + for (i = 0; i < nvalues; i++) + { + if (attr->attbyval) /* simple by-value data types */ + { + memcpy(ptr, &range->values[i], attr->attlen); + ptr += attr->attlen; + } + else if (attr->attlen > 0) /* fixed-length by-ref types */ + { + memcpy(ptr, DatumGetPointer(range->values[i]), attr->attlen); + ptr += attr->attlen; + } + else if (attr->attlen == -1) /* varlena */ + { + int tmp = VARSIZE_ANY(DatumGetPointer(range->values[i])); + memcpy(ptr, DatumGetPointer(range->values[i]), tmp); + ptr += tmp; + } + else if (attr->attlen == -2) /* cstring */ + { + int tmp = strlen(DatumGetPointer(range->values[i])) + 1; + memcpy(ptr, DatumGetPointer(range->values[i]), tmp); + ptr += tmp; + } + + /* make sure we haven't overflown the buffer end */ + Assert(ptr <= ((char *)serialized + len)); + } + + /* exact size */ + Assert(ptr == ((char *)serialized + len)); + + return serialized; +} + +/* + * range_deserialize + * Serialize the in-memory representation into a compact varlena value. + * + * Simply copy the header and then also the individual values, as stored + * in the in-memory value array. + */ +static Ranges * +range_deserialize(SerializedRanges *serialized, + AttrNumber attno, Form_pg_attribute attr) +{ + int i, + nvalues; + char *ptr; + + Ranges *range = minmax_multi_init(); + + Assert(serialized->nranges >= 0); + Assert(serialized->nvalues >= 0); + + nvalues = 2*serialized->nranges + serialized->nvalues; + + Assert(nvalues <= MINMAX_MAX_VALUES); + + /* copy the header info */ + range->nranges = serialized->nranges; + range->nvalues = serialized->nvalues; + + /* + * And now deconstruct the values into Datum array. We don't need + * to copy the values and will instead just point the values to the + * serialized varlena value (assuming it will be kept around). + */ + ptr = serialized->data; + + for (i = 0; i < nvalues; i++) + { + if (attr->attbyval) /* simple by-value data types */ + { + memcpy(&range->values[i], ptr, attr->attlen); + ptr += attr->attlen; + } + else if (attr->attlen > 0) /* fixed-length by-ref types */ + { + /* no copy, just set the value to the pointer */ + range->values[i] = PointerGetDatum(ptr); + ptr += attr->attlen; + } + else if (attr->attlen == -1) /* varlena */ + { + range->values[i] = PointerGetDatum(ptr); + ptr += VARSIZE_ANY(DatumGetPointer(range->values[i])); + } + else if (attr->attlen == -2) /* cstring */ + { + range->values[i] = PointerGetDatum(ptr); + ptr += strlen(DatumGetPointer(range->values[i])) + 1; + } + + /* make sure we haven't overflown the buffer end */ + Assert(ptr <= ((char *)serialized + VARSIZE_ANY(serialized))); + } + + /* should have consumed the whole input value exactly */ + Assert(ptr == ((char *)serialized + VARSIZE_ANY(serialized))); + + /* return the deserialized value */ + return range; +} + +typedef struct compare_context +{ + FmgrInfo *cmpFn; + Oid colloid; +} compare_context; + +/* + * Used to represent ranges expanded during merging and combining (to + * reduce number of boundary values to store). + * + * XXX CombineRange name seems a bit weird. Consider renaming, perhaps to + * something ExpandedRange or so. + */ +typedef struct CombineRange +{ + Datum minval; /* lower boundary */ + Datum maxval; /* upper boundary */ + bool collapsed; /* true if minval==maxval */ +} CombineRange; + +/* + * compare_combine_ranges + * Compare the combine ranges - first by minimum, then by maximum. + * + * We do guarantee that ranges in a single Range object do not overlap, + * so it may seem strange that we don't order just by minimum. But when + * merging two Ranges (which happens in the union function), the ranges + * may in fact overlap. So we do compare both. + */ +static int +compare_combine_ranges(const void *a, const void *b, void *arg) +{ + CombineRange *ra = (CombineRange *)a; + CombineRange *rb = (CombineRange *)b; + Datum r; + + compare_context *cxt = (compare_context *)arg; + + /* first compare minvals */ + r = FunctionCall2Coll(cxt->cmpFn, cxt->colloid, ra->minval, rb->minval); + + if (DatumGetBool(r)) + return -1; + + r = FunctionCall2Coll(cxt->cmpFn, cxt->colloid, rb->minval, ra->minval); + + if (DatumGetBool(r)) + return 1; + + /* then compare maxvals */ + r = FunctionCall2Coll(cxt->cmpFn, cxt->colloid, ra->maxval, rb->maxval); + + if (DatumGetBool(r)) + return -1; + + r = FunctionCall2Coll(cxt->cmpFn, cxt->colloid, rb->maxval, ra->maxval); + + if (DatumGetBool(r)) + return 1; + + return 0; +} + +/* + * range_contains_value + * See if the new value is already contained in the range list. + * + * We first inspect the list of intervals. We use a small trick - we check + * the value against min/max of the whole range (min of the first interval, + * max of the last one) first, and only inspect the individual intervals if + * this passes. + * + * If the value matches none of the intervals, we check the exact values. + * We simply loop through them and invoke equality operator on them. + * + * XXX This might benefit from the fact that both the intervals and exact + * values are sorted - we might do bsearch or something. Currently that + * does not make much difference (there are only ~32 intervals), but if + * this gets increased and/or the comparator function is more expensive, + * it might be a huge win. + */ +static bool +range_contains_value(BrinDesc *bdesc, Oid colloid, + AttrNumber attno, Form_pg_attribute attr, + Ranges *ranges, Datum newval) +{ + int i; + FmgrInfo *cmpFn; + Oid typid = attr->atttypid; + + /* + * First inspect the ranges, if there are any. We first check the whole + * range, and only when there's still a chance of getting a match we + * inspect the individual ranges. + */ + if (ranges->nranges > 0) + { + Datum compar; + bool match = true; + + Datum minvalue = ranges->values[0]; + Datum maxvalue = ranges->values[2*ranges->nranges - 1]; + + /* + * Otherwise, need to compare the new value with boundaries of all + * the ranges. First check if it's less than the absolute minimum, + * which is the first value in the array. + */ + cmpFn = minmax_multi_get_strategy_procinfo(bdesc, attno, typid, + BTLessStrategyNumber); + compar = FunctionCall2Coll(cmpFn, colloid, newval, minvalue); + + /* smaller than the smallest value in the range list */ + if (DatumGetBool(compar)) + match = false; + + /* + * And now compare it to the existing maximum (last value in the + * data array). But only if we haven't already ruled out a possible + * match in the minvalue check. + */ + if (match) + { + cmpFn = minmax_multi_get_strategy_procinfo(bdesc, attno, typid, + BTGreaterStrategyNumber); + compar = FunctionCall2Coll(cmpFn, colloid, newval, maxvalue); + + if (DatumGetBool(compar)) + match = false; + } + + /* + * So it's in the general range, but is it actually covered by any + * of the ranges? Repeat the check for each range. + * + * XXX We simply walk the ranges sequentially, but maybe we could + * further leverage the ordering and non-overlap and use bsearch to + * speed this up a bit. + */ + for (i = 0; i < ranges->nranges && match; i++) + { + /* copy the min/max values from the ranges */ + minvalue = ranges->values[2*i]; + maxvalue = ranges->values[2*i+1]; + + /* + * Otherwise, need to compare the new value with boundaries of all + * the ranges. First check if it's less than the absolute minimum, + * which is the first value in the array. + */ + cmpFn = minmax_multi_get_strategy_procinfo(bdesc, attno, typid, + BTLessStrategyNumber); + compar = FunctionCall2Coll(cmpFn, colloid, newval, minvalue); + + /* smaller than the smallest value in this range */ + if (DatumGetBool(compar)) + continue; + + cmpFn = minmax_multi_get_strategy_procinfo(bdesc, attno, typid, + BTGreaterStrategyNumber); + compar = FunctionCall2Coll(cmpFn, colloid, newval, maxvalue); + + /* larger than the largest value in this range */ + if (DatumGetBool(compar)) + continue; + + /* hey, we found a matching row */ + return true; + } + } + + /* + * We're done with the ranges, now let's inspect the exact values. + * + * XXX Again, we do sequentially search the values - consider leveraging + * the ordering of values to improve performance. + */ + for (i = 2*ranges->nranges; i < 2*ranges->nranges + ranges->nvalues; i++) + { + Datum compar; + + cmpFn = minmax_multi_get_strategy_procinfo(bdesc, attno, typid, + BTEqualStrategyNumber); + + compar = FunctionCall2Coll(cmpFn, colloid, newval, ranges->values[i]); + + /* found an exact match */ + if (DatumGetBool(compar)) + return true; + } + + /* the value is not covered by this BRIN tuple */ + return false; +} + +/* + * insert_value + * Adds a new value into the single-point part, while maintaining ordering. + * + * The function inserts the new value to the right place in the single-point + * part of the range. It assumes there's enough free space, and then does + * essentially an insert-sort. + * + * XXX Assumes the 'values' array has space for (nvalues+1) entries, and that + * only the first nvalues are used. + */ +static void +insert_value(FmgrInfo *cmp, Oid colloid, Datum *values, int nvalues, + Datum newvalue) +{ + int i; + Datum lt; + + /* If there are no values yet, store the new one and we're done. */ + if (!nvalues) + { + values[0] = newvalue; + return; + } + + /* + * A common case is that the new value is entirely out of the existing + * range, i.e. it's either smaller or larger than all previous values. + * So we check and handle this case first - first we check the larger + * case, because in that case we can just append the value to the end + * of the array and we're done. + */ + + /* Is it greater than all existing values in the array? */ + lt = FunctionCall2Coll(cmp, colloid, values[nvalues-1], newvalue); + if (DatumGetBool(lt)) + { + /* just copy it in-place and we're done */ + values[nvalues] = newvalue; + return; + } + + /* + * OK, I lied a bit - we won't check the smaller case explicitly, but + * we'll just compare the value to all existing values in the array. + * But we happen to start with the smallest value, so we're actually + * doing the check anyway. + * + * XXX We do walk the values sequentially. Perhaps we could/should be + * smarter and do some sort of bisection, to improve performance? + */ + for (i = 0; i < nvalues; i++) + { + lt = FunctionCall2Coll(cmp, colloid, newvalue, values[i]); + if (DatumGetBool(lt)) + { + /* + * Move values to make space for the new entry, which should go + * to index 'i'. Entries 0 ... (i-1) should stay where they are. + */ + memmove(&values[i+1], &values[i], (nvalues-i) * sizeof(Datum)); + values[i] = newvalue; + return; + } + } + + /* We should never really get here. */ + Assert(false); +} + +/* + * Check that the order of the array values is correct, using the cmp + * function (which should be BTLessStrategyNumber). + */ +static void +AssertArrayOrder(FmgrInfo *cmp, Oid colloid, Datum *values, int nvalues) +{ +#ifdef USE_ASSERT_CHECKING + int i; + Datum lt; + + for (i = 0; i < (nvalues-1); i++) + { + lt = FunctionCall2Coll(cmp, colloid, values[i], values[i+1]); + Assert(DatumGetBool(lt)); + } +#endif +} + +/* + * Check ordering of boundary values in both parts of the ranges, using + * the cmp function (which should be BTLessStrategyNumber). + * + * XXX We might also check that the ranges and points do not overlap. But + * that will break this check sooner or later anyway. + */ +static void +AssertRangeOrdering(FmgrInfo *cmpFn, Oid colloid, Ranges *ranges) +{ +#ifdef USE_ASSERT_CHECKING + /* first the ranges (there are 2*nranges boundary values) */ + AssertArrayOrder(cmpFn, colloid, ranges->values, 2*ranges->nranges); + + /* then the single-point ranges (with nvalues boundar values ) */ + AssertArrayOrder(cmpFn, colloid, &ranges->values[2*ranges->nranges], + ranges->nvalues); +#endif +} + +/* + * Check that the expanded ranges (built when reducing the number of ranges + * by combining some of them) are correctly sorted and do not overlap. + */ +static void +AssertValidCombineRanges(BrinDesc *bdesc, Oid colloid, AttrNumber attno, + Form_pg_attribute attr, CombineRange *ranges, + int nranges) +{ +#ifdef USE_ASSERT_CHECKING + int i; + FmgrInfo *eq; + FmgrInfo *lt; + + eq = minmax_multi_get_strategy_procinfo(bdesc, attno, attr->atttypid, + BTEqualStrategyNumber); + + lt = minmax_multi_get_strategy_procinfo(bdesc, attno, attr->atttypid, + BTLessStrategyNumber); + + /* + * Each range independently should be valid, i.e. that for the boundary + * values (lower <= upper). + */ + for (i = 0; i < nranges; i++) + { + Datum r; + Datum minval = ranges[i].minval; + Datum maxval = ranges[i].maxval; + + if (ranges[i].collapsed) /* collapsed: minval == maxval */ + r = FunctionCall2Coll(eq, colloid, minval, maxval); + else /* non-collapsed: minval < maxval */ + r = FunctionCall2Coll(lt, colloid, minval, maxval); + + Assert(DatumGetBool(r)); + } + + /* + * And the ranges should be ordered and must nor overlap, i.e. + * upper < lower for boundaries of consecutive ranges. + */ + for (i = 0; i < nranges-1; i++) + { + Datum r; + Datum maxval = ranges[i].maxval; + Datum minval = ranges[i+1].minval; + + r = FunctionCall2Coll(lt, colloid, maxval, minval); + + Assert(DatumGetBool(r)); + } +#endif +} + +/* + * Expand ranges from Ranges into CombineRange array. This expects the + * cranges to be pre-allocated and sufficiently large (there needs to be + * at least (nranges + nvalues) slots). + */ +static void +fill_combine_ranges(CombineRange *cranges, int ncranges, Ranges *ranges) +{ + int idx; + int i; + + idx = 0; + for (i = 0; i < ranges->nranges; i++) + { + cranges[idx].minval = ranges->values[2*i]; + cranges[idx].maxval = ranges->values[2*i+1]; + cranges[idx].collapsed = false; + idx++; + + Assert(idx <= ncranges); + } + + for (i = 0; i < ranges->nvalues; i++) + { + cranges[idx].minval = ranges->values[2*ranges->nranges + i]; + cranges[idx].maxval = ranges->values[2*ranges->nranges + i]; + cranges[idx].collapsed = true; + idx++; + + Assert(idx <= ncranges); + } + + Assert(idx == ncranges); + + return; +} + +/* + * Sort combine ranges using qsort (with BTLessStrategyNumber function). + */ +static void +sort_combine_ranges(FmgrInfo *cmp, Oid colloid, + CombineRange *cranges, int ncranges) +{ + compare_context cxt; + + /* sort the values */ + cxt.colloid = colloid; + cxt.cmpFn = cmp; + + qsort_arg(cranges, ncranges, sizeof(CombineRange), + compare_combine_ranges, (void *) &cxt); +} + +/* + * When combining multiple Range values (in union function), some of the + * ranges may overlap. We simply merge the overlapping ranges to fix that. + * + * XXX This assumes the combine ranges were previously sorted (by minval + * and then maxval). We leverage this when detecting overlap. + */ +static int +merge_combine_ranges(FmgrInfo *cmp, Oid colloid, + CombineRange *cranges, int ncranges) +{ + int idx; + + /* TODO: add assert checking the ordering of input ranges */ + + /* try merging ranges (idx) and (idx+1) if they overlap */ + idx = 0; + while (idx < (ncranges-1)) + { + Datum r; + + /* + * comparing [?,maxval] vs. [minval,?] - the ranges overlap + * if (minval < maxval) + */ + r = FunctionCall2Coll(cmp, colloid, + cranges[idx].maxval, + cranges[idx+1].minval); + + /* + * Nope, maxval < minval, so no overlap. And we know the ranges + * are ordered, so there are no more overlaps, because all the + * remaining ranges have greater or equal minval. + */ + if (DatumGetBool(r)) + { + /* proceed to the next range */ + idx += 1; + continue; + } + + /* + * So ranges 'idx' and 'idx+1' do overlap, but we don't know if + * 'idx+1' is contained in 'idx', or if they only overlap only + * partially. So compare the upper bounds and keep the larger one. + */ + r = FunctionCall2Coll(cmp, colloid, + cranges[idx].maxval, + cranges[idx+1].maxval); + + if (DatumGetBool(r)) + cranges[idx].maxval = cranges[idx+1].maxval; + + /* + * The range certainly is no longer collapsed (irrespectedly of + * the previous state). + */ + cranges[idx].collapsed = false; + + /* + * Now get rid of the (idx+1) range entirely by shifting the + * remaining ranges by 1. There are ncranges elements, and we + * need to move elements from (idx+2). That means the number + * of elements to move is [ncranges - (idx+2)]. + */ + memmove(&cranges[idx+1], &cranges[idx+2], + (ncranges - (idx + 2)) * sizeof(CombineRange)); + + /* + * Decrease the number of ranges, and repeat (with the same range, + * as it might overlap with additional ranges thanks to the merge). + */ + ncranges--; + } + + /* TODO: add assert checking the ordering etc. of produced ranges */ + + return ncranges; +} + +/* + * Represents a distance between two ranges (identified by index into + * an array of combine ranges). + */ +typedef struct DistanceValue +{ + int index; + double value; +} DistanceValue; + +/* + * Simple comparator for distance values, comparing the double value. + */ +static int +compare_distances(const void *a, const void *b) +{ + DistanceValue *da = (DistanceValue *)a; + DistanceValue *db = (DistanceValue *)b; + + if (da->value < db->value) + return -1; + else if (da->value > db->value) + return 1; + + return 0; +} + +/* + * Given an array of combine ranges, compute distance of the gaps betwen + * the ranges - for ncranges there are (ncranges-1) gaps. + * + * We simply call the "distance" function to compute the (min-max) for pairs + * of consecutive ganges. The function may be fairly expensive, so we do that + * just once (and then use it to pick as many ranges to merge as possible). + * + * See reduce_combine_ranges for details. + */ +static DistanceValue * +build_distances(FmgrInfo *distanceFn, Oid colloid, + CombineRange *cranges, int ncranges) +{ + int i; + DistanceValue *distances; + + Assert(ncranges >= 2); + + distances = (DistanceValue *) palloc0(sizeof(DistanceValue) * (ncranges - 1)); + + /* + * Walk though the ranges once and compute distance between the ranges + * so that we can sort them once. + */ + for (i = 0; i < (ncranges-1); i++) + { + Datum a1, a2, r; + + a1 = cranges[i].maxval; + a2 = cranges[i+1].minval; + + /* compute length of the empty gap (distance between max/min) */ + r = FunctionCall2Coll(distanceFn, colloid, a1, a2); + + /* remember the index */ + distances[i].index = i; + distances[i].value = DatumGetFloat8(r); + } + + /* sort the distances in ascending order */ + pg_qsort(distances, (ncranges-1), sizeof(DistanceValue), compare_distances); + + return distances; +} + +/* + * Builds combine ranges for the existing ranges (and single-point ranges), + * and also the new value (which did not fit into the array). + * + * XXX We do perform qsort on all the values, but we could also leverage + * the fact that the input data is already sorted and do merge sort. + */ +static CombineRange * +build_combine_ranges(FmgrInfo *cmp, Oid colloid, Ranges *ranges, + Datum newvalue, int *nranges) +{ + int ncranges; + CombineRange *cranges; + + /* now do the actual merge sort */ + ncranges = ranges->nranges + ranges->nvalues + 1; + cranges = (CombineRange *) palloc0(ncranges * sizeof(CombineRange)); + *nranges = ncranges; + + /* put the new value at the beginning */ + cranges[0].minval = newvalue; + cranges[0].maxval = newvalue; + cranges[0].collapsed = true; + + /* then the regular and collapsed ranges */ + fill_combine_ranges(&cranges[1], ncranges-1, ranges); + + /* and sort the ranges */ + sort_combine_ranges(cmp, colloid, cranges, ncranges); + + return cranges; +} + +/* + * Counts bondary values needed to store the ranges. Each single-point + * range is stored using a single value, each regular range needs two. + */ +static int +count_values(CombineRange *cranges, int ncranges) +{ + int i; + int count; + + count = 0; + for (i = 0; i < ncranges; i++) + { + if (cranges[i].collapsed) + count += 1; + else + count += 2; + } + + return count; +} + +/* + * Combines ranges until the number of boundary values drops below 75% + * of the capacity (MINMAX_MAX_VALUES). + * + * XXX The ranges to merge are selected solely using the distance. But + * that may not be the best strategy, for example when multiple gaps + * are of equal (or very similar) length. + * + * Consider for example points 1, 2, 3, .., 64, which have gaps of the + * same length 1 of course. In that case we tend to pick the first + * gap of that length, which leads to this: + * + * step 1: [1, 2], 3, 4, 5, .., 64 + * step 2: [1, 3], 4, 5, .., 64 + * step 3: [1, 4], 5, .., 64 + * ... + * + * So in the end we'll have one "large" range and multiple small points. + * That may be fine, but it seems a bit strange and non-optimal. Maybe + * we should consider other things when picking ranges to merge - e.g. + * length of the ranges? Or perhaps randomize the choice of ranges, with + * probability inversely proportional to the distance (the gap lengths + * may be very close, but not exactly the same). + */ +static int +reduce_combine_ranges(CombineRange *cranges, int ncranges, + DistanceValue *distances) +{ + int i; + int ndistances = (ncranges - 1); + + /* + * We have one fewer 'gaps' than the ranges. We'll be decrementing + * the number of combine ranges (reduction is the primary goal of + * this function), so we must use a separate value. + */ + for (i = 0; i < ndistances; i++) + { + int j; + int shortest; + + if (count_values(cranges, ncranges) <= MINMAX_MAX_VALUES * 0.75) + break; + + shortest = distances[i].index; + + /* + * The index must be still valid with respect to the current size + * of cranges array (and it always points to the first range, so + * never to the last one - hence the -1 in the condition). + */ + Assert(shortest < (ncranges - 1)); + + /* + * Move the values to join the two selected ranges. The new range is + * definiely not collapsed but a regular range. + */ + cranges[shortest].maxval = cranges[shortest+1].maxval; + cranges[shortest].collapsed = false; + + /* shuffle the subsequent combine ranges */ + memmove(&cranges[shortest+1], &cranges[shortest+2], + (ncranges - shortest - 2) * sizeof(CombineRange)); + + /* also, shuffle all higher indexes (we've just moved the ranges) */ + for (j = i; j < ndistances; j++) + { + if (distances[j].index > shortest) + distances[j].index--; + } + + ncranges--; + + Assert(ncranges > 0); + } + + return ncranges; +} + +/* + * Store the boundary values from CombineRanges back into Range (using + * only the minimal number of values needed). + */ +static void +store_combine_ranges(Ranges *ranges, CombineRange *cranges, int ncranges) +{ + int i; + int idx = 0; + + /* first copy in the regular ranges */ + ranges->nranges = 0; + for (i = 0; i < ncranges; i++) + { + if (!cranges[i].collapsed) + { + ranges->values[idx++] = cranges[i].minval; + ranges->values[idx++] = cranges[i].maxval; + ranges->nranges++; + } + } + + /* now copy in the collapsed ones */ + ranges->nvalues = 0; + for (i = 0; i < ncranges; i++) + { + if (cranges[i].collapsed) + { + ranges->values[idx++] = cranges[i].minval; + ranges->nvalues++; + } + } +} + +/* + * range_add_value + * Add the new value to the multi-minmax range. + */ +static bool +range_add_value(BrinDesc *bdesc, Oid colloid, + AttrNumber attno, Form_pg_attribute attr, + Ranges *ranges, Datum newval) +{ + FmgrInfo *cmpFn, + *distanceFn; + + /* combine ranges */ + CombineRange *cranges; + int ncranges; + DistanceValue *distances; + + MemoryContext ctx; + MemoryContext oldctx; + + Assert(2*ranges->nranges + ranges->nvalues <= MINMAX_MAX_VALUES); + + /* + * Bail out if the value already is covered by the range. + * + * We could also add values until we hit MINMAX_MAX_VALUES, and then + * do the deduplication in a batch, hoping for better efficiency. But + * that would mean we actually modify the range every time, which means + * having to serialize the value, which does palloc, walks the values, + * copies them, etc. Not exactly cheap. + * + * So instead we do the check, which should be fairly cheap - assuming + * the comparator function is not very expensive. + * + * This also implies means the values array can't contain duplicities. + */ + if (range_contains_value(bdesc, colloid, attno, attr, ranges, newval)) + return false; + + /* + * If there's space in the values array, copy it in and we're done. + * + * We do want to keep the values sorted (to speed up searches), so we + * do a simple insertion sort. We could do something more elaborate, + * e.g. by sorting the values only now and then, but for small counts + * (e.g. when MINMAX_MAX_VALUES is 64) this should be fine. + */ + if (2*ranges->nranges + ranges->nvalues < MINMAX_MAX_VALUES) + { + FmgrInfo *cmpFn; + Datum *values; + + cmpFn = minmax_multi_get_strategy_procinfo(bdesc, attno, attr->atttypid, + BTLessStrategyNumber); + + /* beginning of the 'single value' part (for convenience) */ + values = &ranges->values[2*ranges->nranges]; + + insert_value(cmpFn, colloid, values, ranges->nvalues, newval); + + ranges->nvalues++; + + /* + * Check we haven't broken the ordering of boundary values (checks + * both parts, but that doesn't hurt). + */ + AssertRangeOrdering(cmpFn, colloid, ranges); + + /* yep, we've modified the range */ + return true; + } + + /* + * Damn - the new value is not in the range yet, but we don't have space + * to just insert it. So we need to combine some of the existing ranges, + * to reduce the number of values we need to store (joining two intervals + * reduces the number of boundaries to store by 2). + * + * To do that we first construct an array of CombineRange items - each + * combine range tracks if it's a regular range or collapsed range, where + * "collapsed" means "single point." + * + * Existing ranges (we have ranges->nranges of them) map to combine ranges + * directly, while single points (ranges->nvalues of them) have to be + * expanded. We neet the combine ranges to be sorted, and we do that by + * performing a merge sort of ranges, values and new value. + */ + + /* we'll certainly need the comparator, so just look it up now */ + cmpFn = minmax_multi_get_strategy_procinfo(bdesc, attno, attr->atttypid, + BTLessStrategyNumber); + + /* Check the ordering invariants are not violated (for both parts). */ + AssertArrayOrder(cmpFn, colloid, ranges->values, ranges->nranges*2); + AssertArrayOrder(cmpFn, colloid, &ranges->values[ranges->nranges*2], + ranges->nvalues); + + /* and we'll also need the 'distance' procedure */ + distanceFn = minmax_multi_get_procinfo(bdesc, attno, PROCNUM_DISTANCE); + + /* + * The distanceFn calls (which may internally call e.g. numeric_le) may + * allocate quite a bit of memory, and we must not leak it. Otherwise + * we'd have problems e.g. when building indexes. So we create a local + * memory context and make sure we free the memory before leaving this + * function (not after every call). + */ + ctx = AllocSetContextCreate(CurrentMemoryContext, + "minmax-multi context", + ALLOCSET_DEFAULT_SIZES); + + oldctx = MemoryContextSwitchTo(ctx); + + /* OK build the combine ranges */ + cranges = build_combine_ranges(cmpFn, colloid, ranges, newval, &ncranges); + + /* build array of gap distances and sort them in ascending order */ + distances = build_distances(distanceFn, colloid, cranges, ncranges); + + /* + * Combine ranges until we release at least 25% of the space. This + * threshold is somewhat arbitrary, perhaps needs tuning. We must not + * use too low or high value. + */ + ncranges = reduce_combine_ranges(cranges, ncranges, distances); + + Assert(count_values(cranges, ncranges) <= MINMAX_MAX_VALUES * 0.75); + + /* decompose the combine ranges into regular ranges and single values */ + store_combine_ranges(ranges, cranges, ncranges); + + MemoryContextSwitchTo(oldctx); + MemoryContextDelete(ctx); + + /* Check the ordering invariants are not violated (for both parts). */ + AssertArrayOrder(cmpFn, colloid, ranges->values, ranges->nranges*2); + AssertArrayOrder(cmpFn, colloid, &ranges->values[ranges->nranges*2], + ranges->nvalues); + + return true; +} + + +Datum +brin_minmax_multi_opcinfo(PG_FUNCTION_ARGS) +{ + BrinOpcInfo *result; + + /* + * opaque->strategy_procinfos is initialized lazily; here it is set to + * all-uninitialized by palloc0 which sets fn_oid to InvalidOid. + */ + + result = palloc0(MAXALIGN(SizeofBrinOpcInfo(1)) + + sizeof(MinmaxMultiOpaque)); + result->oi_nstored = 1; + result->oi_opaque = (MinmaxMultiOpaque *) + MAXALIGN((char *) result + SizeofBrinOpcInfo(1)); + result->oi_typcache[0] = lookup_type_cache(BYTEAOID, 0); + + PG_RETURN_POINTER(result); +} + +/* + * Compute distance between two float4 values (plain subtraction). + */ +Datum +brin_minmax_multi_distance_float4(PG_FUNCTION_ARGS) +{ + float a1 = PG_GETARG_FLOAT4(0); + float a2 = PG_GETARG_FLOAT4(1); + + /* + * We know the values are range boundaries, but the range may be + * collapsed (i.e. single points), with equal values. + */ + Assert(a1 <= a2); + + PG_RETURN_FLOAT8((double) a2 - (double) a1); +} + +/* + * Compute distance between two float8 values (plain subtraction). + */ +Datum +brin_minmax_multi_distance_float8(PG_FUNCTION_ARGS) +{ + double a1 = PG_GETARG_FLOAT8(0); + double a2 = PG_GETARG_FLOAT8(1); + + /* + * We know the values are range boundaries, but the range may be + * collapsed (i.e. single points), with equal values. + */ + Assert(a1 <= a2); + + PG_RETURN_FLOAT8(a2 - a1); +} + +/* + * Compute distance between two int2 values (plain subtraction). + */ +Datum +brin_minmax_multi_distance_int2(PG_FUNCTION_ARGS) +{ + int16 a1 = PG_GETARG_INT16(0); + int16 a2 = PG_GETARG_INT16(1); + + /* + * We know the values are range boundaries, but the range may be + * collapsed (i.e. single points), with equal values. + */ + Assert(a1 <= a2); + + PG_RETURN_FLOAT8((double) a2 - (double) a1); +} + +/* + * Compute distance between two int4 values (plain subtraction). + */ +Datum +brin_minmax_multi_distance_int4(PG_FUNCTION_ARGS) +{ + int32 a1 = PG_GETARG_INT32(0); + int32 a2 = PG_GETARG_INT32(1); + + /* + * We know the values are range boundaries, but the range may be + * collapsed (i.e. single points), with equal values. + */ + Assert(a1 <= a2); + + PG_RETURN_FLOAT8((double) a2 - (double) a1); +} + +/* + * Compute distance between two int8 values (plain subtraction). + */ +Datum +brin_minmax_multi_distance_int8(PG_FUNCTION_ARGS) +{ + int64 a1 = PG_GETARG_INT64(0); + int64 a2 = PG_GETARG_INT64(1); + + /* + * We know the values are range boundaries, but the range may be + * collapsed (i.e. single points), with equal values. + */ + Assert(a1 <= a2); + + PG_RETURN_FLOAT8((double) a2 - (double) a1); +} + +/* + * Compute distance between two tid values (by mapping them to float8 + * and then subtracting them). + */ +Datum +brin_minmax_multi_distance_tid(PG_FUNCTION_ARGS) +{ + double da1, + da2; + + ItemPointer pa1 = (ItemPointer) PG_GETARG_DATUM(0); + ItemPointer pa2 = (ItemPointer) PG_GETARG_DATUM(1); + + /* + * We know the values are range boundaries, but the range may be + * collapsed (i.e. single points), with equal values. + */ + Assert(ItemPointerCompare(pa1, pa2) <= 0); + + da1 = ItemPointerGetBlockNumber(pa1) * MaxHeapTuplesPerPage + + ItemPointerGetOffsetNumber(pa1); + + da2 = ItemPointerGetBlockNumber(pa2) * MaxHeapTuplesPerPage + + ItemPointerGetOffsetNumber(pa2); + + PG_RETURN_FLOAT8(da2 - da1); +} + +/* + * Comutes distance between two numeric values (plain subtraction). + */ +Datum +brin_minmax_multi_distance_numeric(PG_FUNCTION_ARGS) +{ + Datum d; + Datum a1 = PG_GETARG_DATUM(0); + Datum a2 = PG_GETARG_DATUM(1); + + /* + * We know the values are range boundaries, but the range may be + * collapsed (i.e. single points), with equal values. + */ + Assert(DatumGetBool(DirectFunctionCall2(numeric_le, a1, a2))); + + d = DirectFunctionCall2(numeric_sub, a2, a1); /* a2 - a1 */ + + PG_RETURN_FLOAT8(DirectFunctionCall1(numeric_float8, d)); +} + +/* + * Computes approximate distance between two UUID values. + * + * XXX We do not need a perfectly accurate value, so we approximate the + * deltas (which would have to be 128-bit integers) with a 64-bit float. + * The small inaccuracies do not matter in practice, in the worst case + * we'll decide to merge ranges that are not the closest ones. + */ +Datum +brin_minmax_multi_distance_uuid(PG_FUNCTION_ARGS) +{ + int i; + double delta = 0; + + Datum a1 = PG_GETARG_DATUM(0); + Datum a2 = PG_GETARG_DATUM(1); + + pg_uuid_t *u1 = DatumGetUUIDP(a1); + pg_uuid_t *u2 = DatumGetUUIDP(a2); + + /* + * We know the values are range boundaries, but the range may be + * collapsed (i.e. single points), with equal values. + */ + Assert(DatumGetBool(DirectFunctionCall2(uuid_le, a1, a2))); + + /* compute approximate delta as a double precision value */ + for (i = UUID_LEN-1; i >= 0; i--) + { + delta += (int) u2->data[i] - (int) u1->data[i]; + delta /= 256; + } + + Assert(delta >= 0); + + PG_RETURN_FLOAT8(delta); +} + +/* + * Computes approximate distance between two time (without tz) values. + * + * TimeADT is just an int64, so we simply subtract the values directly. + */ +Datum +brin_minmax_multi_distance_time(PG_FUNCTION_ARGS) +{ + double delta = 0; + + TimeADT ta = PG_GETARG_TIMEADT(0); + TimeADT tb = PG_GETARG_TIMEADT(1); + + delta = (tb - ta); + + Assert(delta >= 0); + + PG_RETURN_FLOAT8(delta); +} + +/* + * Computes approximate distance between two timetz values. + * + * Simply subtracts the TimeADT (int64) values embedded in TimeTzADT. + * + * XXX Does this need to consider the time zones? + */ +Datum +brin_minmax_multi_distance_timetz(PG_FUNCTION_ARGS) +{ + double delta = 0; + + TimeTzADT *ta = PG_GETARG_TIMETZADT_P(0); + TimeTzADT *tb = PG_GETARG_TIMETZADT_P(1); + + delta = tb->time - ta->time; + + Assert(delta >= 0); + + PG_RETURN_FLOAT8(delta); +} + +/* + * Computes distance between two interval values. + * + * Intervals are internally just 'time' values, so use the same approach + * as for in brin_minmax_multi_distance_time. + * + * XXX Do we actually need two separate functions, then? + */ +Datum +brin_minmax_multi_distance_interval(PG_FUNCTION_ARGS) +{ + double delta = 0; + + TimeADT ia = PG_GETARG_TIMEADT(0); + TimeADT ib = PG_GETARG_TIMEADT(1); + + delta = (ib - ia); + + Assert(delta >= 0); + + PG_RETURN_FLOAT8(delta); +} + +/* + * Compute distance between two pg_lsn values. + * + * LSN is just an int64 encoding position in the stream, so just subtract + * those int64 values directly. + */ +Datum +brin_minmax_multi_distance_pg_lsn(PG_FUNCTION_ARGS) +{ + double delta = 0; + + XLogRecPtr lsna = PG_GETARG_LSN(0); + XLogRecPtr lsnb = PG_GETARG_LSN(1); + + delta = (lsnb - lsna); + + Assert(delta >= 0); + + PG_RETURN_FLOAT8(delta); +} + +/* + * Compute distance between two macaddr values. + * + * mac addresses are treated as 6 unsigned chars, so do the same thing we + * already do for UUID values. + */ +Datum +brin_minmax_multi_distance_macaddr(PG_FUNCTION_ARGS) +{ + double delta; + + macaddr *a = PG_GETARG_MACADDR_P(0); + macaddr *b = PG_GETARG_MACADDR_P(1); + + delta = ((double)b->f - (double)a->f); + delta /= 256; + + delta += ((double)b->e - (double)a->e); + delta /= 256; + + delta += ((double)b->d - (double)a->d); + delta /= 256; + + delta += ((double)b->c - (double)a->c); + delta /= 256; + + delta += ((double)b->b - (double)a->b); + delta /= 256; + + delta += ((double)b->a - (double)a->a); + delta /= 256; + + Assert(delta >= 0); + + PG_RETURN_FLOAT8(delta); +} + + +/* + * Compute distance between two macaddr8 values. + * + * macaddr8 addresses are 8 unsigned chars, so do the same thing we + * already do for UUID values. + */ +Datum +brin_minmax_multi_distance_macaddr8(PG_FUNCTION_ARGS) +{ + double delta; + + macaddr8 *a = PG_GETARG_MACADDR8_P(0); + macaddr8 *b = PG_GETARG_MACADDR8_P(1); + + delta = ((double)b->h - (double)a->h); + delta /= 256; + + delta += ((double)b->g - (double)a->g); + delta /= 256; + + delta += ((double)b->f - (double)a->f); + delta /= 256; + + delta += ((double)b->e - (double)a->e); + delta /= 256; + + delta += ((double)b->d - (double)a->d); + delta /= 256; + + delta += ((double)b->c - (double)a->c); + delta /= 256; + + delta += ((double)b->b - (double)a->b); + delta /= 256; + + delta += ((double)b->a - (double)a->a); + delta /= 256; + + Assert(delta >= 0); + + PG_RETURN_FLOAT8(delta); +} + + +/* + * Compute distance between two inet values. + * + * The distance is defined as difference between 32-bit/128-bit values, + * depending on the IP version. The distance is computed by subtracting + * the bytes and normalizing it to [0,1] range for each IP family. + * Addresses from difference families are consider to be in maximum + * distance, which is 1.0. + * + * XXX Does this need to consider the mask (bits)? For now it's ignored. + */ +Datum +brin_minmax_multi_distance_inet(PG_FUNCTION_ARGS) +{ + double delta; + int i; + int len; + unsigned char *addra, + *addrb; + + inet *ipa = PG_GETARG_INET_PP(0); + inet *ipb = PG_GETARG_INET_PP(1); + + /* + * If the addresses are from different families, consider them to be + * in maximal possible distance (which is 1.0). + */ + if (ip_family(ipa) != ip_family(ipb)) + return 1.0; + + /* ipv4 or ipv6 */ + if (ip_family(ipa) == PGSQL_AF_INET) + len = 4; + else + len = 16; /* NS_IN6ADDRSZ */ + + addra = ip_addr(ipa); + addrb = ip_addr(ipb); + + delta = 0; + for (i = len-1; i >= 0; i--) + { + delta += (double)addrb[i] - (double)addra[i]; + delta /= 256; + } + + Assert((delta >= 0) && (delta <= 1)); + + PG_RETURN_FLOAT8(delta); +} + + +/* + * Compute distance between two abstime values. + * + * abstime values are int32 values, so just subtract them + */ +Datum +brin_minmax_multi_distance_abstime(PG_FUNCTION_ARGS) +{ + double delta; + + AbsoluteTime a = PG_GETARG_ABSOLUTETIME(0); + AbsoluteTime b = PG_GETARG_ABSOLUTETIME(1); + + delta = ((double)b - (double)a); + + Assert(delta >= 0); + + PG_RETURN_FLOAT8(delta); +} + + +/* + * Compute distance between two reltime values. + * + * reltime values are int32 values, so just subtract them + */ +Datum +brin_minmax_multi_distance_reltime(PG_FUNCTION_ARGS) +{ + double delta; + + RelativeTime a = PG_GETARG_ABSOLUTETIME(0); + RelativeTime b = PG_GETARG_ABSOLUTETIME(1); + + delta = ((double)b - (double)a); + + Assert(delta >= 0); + + PG_RETURN_FLOAT8(delta); +} + + +/* + * Examine the given index tuple (which contains partial status of a certain + * page range) by comparing it to the given value that comes from another heap + * tuple. If the new value is outside the min/max range specified by the + * existing tuple values, update the index tuple and return true. Otherwise, + * return false and do not modify in this case. + */ +Datum +brin_minmax_multi_add_value(PG_FUNCTION_ARGS) +{ + BrinDesc *bdesc = (BrinDesc *) PG_GETARG_POINTER(0); + BrinValues *column = (BrinValues *) PG_GETARG_POINTER(1); + Datum newval = PG_GETARG_DATUM(2); + bool isnull = PG_GETARG_DATUM(3); + Oid colloid = PG_GET_COLLATION(); + bool modified = false; + Form_pg_attribute attr; + AttrNumber attno; + Ranges *ranges; + SerializedRanges *serialized = NULL; + + /* + * If the new value is null, we record that we saw it if it's the first + * one; otherwise, there's nothing to do. + */ + if (isnull) + { + if (column->bv_hasnulls) + PG_RETURN_BOOL(false); + + column->bv_hasnulls = true; + PG_RETURN_BOOL(true); + } + + attno = column->bv_attno; + attr = TupleDescAttr(bdesc->bd_tupdesc, attno - 1); + + /* + * If this is the first non-null value, we need to initialize the range + * list. Otherwise just extract the existing range list from BrinValues. + */ + if (column->bv_allnulls) + { + ranges = minmax_multi_init(); + column->bv_allnulls = false; + modified = true; + } + else + { + serialized = (SerializedRanges *) PG_DETOAST_DATUM(column->bv_values[0]); + ranges = range_deserialize(serialized, attno, attr); + } + + /* + * Try to add the new value to the range. We need to update the modified + * flag, so that we serialize the correct value. + */ + modified |= range_add_value(bdesc, colloid, attno, attr, ranges, newval); + + if (modified) + { + SerializedRanges *s = range_serialize(ranges, attno, attr); + column->bv_values[0] = PointerGetDatum(s); + + /* + * XXX pfree must happen after range_serialize, because the Ranges value + * may reference the original serialized value. + */ + if (serialized) + pfree(serialized); + } + + pfree(ranges); + + PG_RETURN_BOOL(modified); +} + +/* + * Given an index tuple corresponding to a certain page range and a scan key, + * return whether the scan key is consistent with the index tuple's min/max + * values. Return true if so, false otherwise. + */ +Datum +brin_minmax_multi_consistent(PG_FUNCTION_ARGS) +{ + BrinDesc *bdesc = (BrinDesc *) PG_GETARG_POINTER(0); + BrinValues *column = (BrinValues *) PG_GETARG_POINTER(1); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(), + subtype; + AttrNumber attno; + Datum value; + FmgrInfo *finfo; + SerializedRanges *serialized; + Ranges *ranges; + int keyno; + int rangeno; + int i; + Form_pg_attribute attr; + + attno = column->bv_attno; + attr = TupleDescAttr(bdesc->bd_tupdesc, attno - 1); + + serialized = (SerializedRanges *) PG_DETOAST_DATUM(column->bv_values[0]); + ranges = range_deserialize(serialized, attno, attr); + + /* inspect the ranges, and for each one evaluate the scan keys */ + for (rangeno = 0; rangeno < ranges->nranges; rangeno++) + { + Datum minval = ranges->values[2*rangeno]; + Datum maxval = ranges->values[2*rangeno+1]; + + /* assume the range is matching, and we'll try to prove otherwise */ + bool matching = true; + + for (keyno = 0; keyno < nkeys; keyno++) + { + Datum matches; + ScanKey key = keys[keyno]; + + /* NULL keys are handled and filtered-out in bringetbitmap */ + Assert(!(key->sk_flags & SK_ISNULL)); + + attno = key->sk_attno; + subtype = key->sk_subtype; + value = key->sk_argument; + switch (key->sk_strategy) + { + case BTLessStrategyNumber: + case BTLessEqualStrategyNumber: + finfo = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype, + key->sk_strategy); + /* first value from the array */ + matches = FunctionCall2Coll(finfo, colloid, minval, value); + break; + + case BTEqualStrategyNumber: + { + Datum compar; + FmgrInfo *cmpFn; + + /* by default this range does not match */ + matches = false; + + /* + * Otherwise, need to compare the new value with boundaries of all + * the ranges. First check if it's less than the absolute minimum, + * which is the first value in the array. + */ + cmpFn = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype, + BTLessStrategyNumber); + compar = FunctionCall2Coll(cmpFn, colloid, value, minval); + + /* smaller than the smallest value in this range */ + if (DatumGetBool(compar)) + break; + + cmpFn = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype, + BTGreaterStrategyNumber); + compar = FunctionCall2Coll(cmpFn, colloid, value, maxval); + + /* larger than the largest value in this range */ + if (DatumGetBool(compar)) + break; + + /* haven't managed to eliminate this range, so consider it matching */ + matches = true; + + break; + } + case BTGreaterEqualStrategyNumber: + case BTGreaterStrategyNumber: + finfo = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype, + key->sk_strategy); + /* last value from the array */ + matches = FunctionCall2Coll(finfo, colloid, maxval, value); + break; + + default: + /* shouldn't happen */ + elog(ERROR, "invalid strategy number %d", key->sk_strategy); + matches = 0; + break; + } + + /* the range has to match all the scan keys */ + matching &= DatumGetBool(matches); + + /* once we find a non-matching key, we're done */ + if (! matching) + break; + } + + /* have we found a range matching all scan keys? if yes, we're + * done */ + if (matching) + PG_RETURN_DATUM(BoolGetDatum(true)); + } + + /* and now inspect the values */ + for (i = 0; i < ranges->nvalues; i++) + { + Datum val = ranges->values[2*ranges->nranges + i]; + + /* assume the range is matching, and we'll try to prove otherwise */ + bool matching = true; + + for (keyno = 0; keyno < nkeys; keyno++) + { + Datum matches; + ScanKey key = keys[keyno]; + + /* we've already dealt with NULL keys at the beginning */ + if (key->sk_flags & SK_ISNULL) + continue; + + attno = key->sk_attno; + subtype = key->sk_subtype; + value = key->sk_argument; + switch (key->sk_strategy) + { + case BTLessStrategyNumber: + case BTLessEqualStrategyNumber: + case BTEqualStrategyNumber: + case BTGreaterEqualStrategyNumber: + case BTGreaterStrategyNumber: + + finfo = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype, + key->sk_strategy); + matches = FunctionCall2Coll(finfo, colloid, val, value); + break; + + default: + /* shouldn't happen */ + elog(ERROR, "invalid strategy number %d", key->sk_strategy); + matches = 0; + break; + } + + /* the range has to match all the scan keys */ + matching &= DatumGetBool(matches); + + /* once we find a non-matching key, we're done */ + if (! matching) + break; + } + + /* have we found a range matching all scan keys? if yes, we're + * done */ + if (matching) + PG_RETURN_DATUM(BoolGetDatum(true)); + } + + PG_RETURN_DATUM(BoolGetDatum(false)); +} + +/* + * Given two BrinValues, update the first of them as a union of the summary + * values contained in both. The second one is untouched. + */ +Datum +brin_minmax_multi_union(PG_FUNCTION_ARGS) +{ + BrinDesc *bdesc = (BrinDesc *) PG_GETARG_POINTER(0); + BrinValues *col_a = (BrinValues *) PG_GETARG_POINTER(1); + BrinValues *col_b = (BrinValues *) PG_GETARG_POINTER(2); + Oid colloid = PG_GET_COLLATION(); + SerializedRanges *serialized_a; + SerializedRanges *serialized_b; + Ranges *ranges_a; + Ranges *ranges_b; + AttrNumber attno; + Form_pg_attribute attr; + CombineRange *cranges; + int ncranges; + FmgrInfo *cmpFn, + *distanceFn; + DistanceValue *distances; + MemoryContext ctx; + MemoryContext oldctx; + + Assert(col_a->bv_attno == col_b->bv_attno); + + /* Adjust "hasnulls" */ + if (!col_a->bv_hasnulls && col_b->bv_hasnulls) + col_a->bv_hasnulls = true; + + /* If there are no values in B, there's nothing left to do */ + if (col_b->bv_allnulls) + PG_RETURN_VOID(); + + attno = col_a->bv_attno; + attr = TupleDescAttr(bdesc->bd_tupdesc, attno - 1); + + /* + * Adjust "allnulls". If A doesn't have values, just copy the values from + * B into A, and we're done. We cannot run the operators in this case, + * because values in A might contain garbage. Note we already established + * that B contains values. + */ + if (col_a->bv_allnulls) + { + col_a->bv_allnulls = false; + col_a->bv_values[0] = datumCopy(col_b->bv_values[0], false, -1); + PG_RETURN_VOID(); + } + + serialized_a = (SerializedRanges *) PG_DETOAST_DATUM(col_a->bv_values[0]); + serialized_b = (SerializedRanges *) PG_DETOAST_DATUM(col_b->bv_values[0]); + + ranges_a = range_deserialize(serialized_a, attno, attr); + ranges_b = range_deserialize(serialized_b, attno, attr); + + /* make sure neither of the ranges is NULL */ + Assert(ranges_a && ranges_b); + + ncranges = (ranges_a->nranges + ranges_a->nvalues) + + (ranges_b->nranges + ranges_b->nvalues); + + /* + * The distanceFn calls (which may internally call e.g. numeric_le) may + * allocate quite a bit of memory, and we must not leak it. Otherwise + * we'd have problems e.g. when building indexes. So we create a local + * memory context and make sure we free the memory before leaving this + * function (not after every call). + */ + ctx = AllocSetContextCreate(CurrentMemoryContext, + "minmax-multi context", + ALLOCSET_DEFAULT_SIZES); + + oldctx = MemoryContextSwitchTo(ctx); + + /* allocate and fill */ + cranges = (CombineRange *)palloc0(ncranges * sizeof(CombineRange)); + + /* fill the combine ranges with entries for the first range */ + fill_combine_ranges(cranges, ranges_a->nranges + ranges_a->nvalues, + ranges_a); + + /* and now add combine ranges for the second range */ + fill_combine_ranges(&cranges[ranges_a->nranges + ranges_a->nvalues], + ranges_b->nranges + ranges_b->nvalues, + ranges_b); + + cmpFn = minmax_multi_get_strategy_procinfo(bdesc, attno, attr->atttypid, + BTLessStrategyNumber); + + /* sort the combine ranges */ + sort_combine_ranges(cmpFn, colloid, cranges, ncranges); + + /* + * We've merged two different lists of ranges, so some of them may be + * overlapping. So walk through them and merge them. + */ + ncranges = merge_combine_ranges(cmpFn, colloid, cranges, ncranges); + + /* check that the combine ranges are correct (no overlaps, ordering) */ + AssertValidCombineRanges(bdesc, colloid, attno, attr, cranges, ncranges); + + /* build array of gap distances and sort them in ascending order */ + distanceFn = minmax_multi_get_procinfo(bdesc, attno, PROCNUM_DISTANCE); + distances = build_distances(distanceFn, colloid, cranges, ncranges); + + /* + * See how many values would be needed to store the current ranges, and if + * needed combine as many off them to get below the MINMAX_MAX_VALUES + * threshold. The collapsed ranges will be stored as a single value. + */ + ncranges = reduce_combine_ranges(cranges, ncranges, distances); + + /* update the first range summary */ + store_combine_ranges(ranges_a, cranges, ncranges); + + MemoryContextSwitchTo(oldctx); + MemoryContextDelete(ctx); + + /* cleanup and update the serialized value */ + pfree(serialized_a); + col_a->bv_values[0] = PointerGetDatum(range_serialize(ranges_a, attno, attr)); + + PG_RETURN_VOID(); +} + +/* + * Cache and return minmax multi opclass support procedure + * + * Return the procedure corresponding to the given function support number + * or null if it is not exists. + */ +static FmgrInfo * +minmax_multi_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum) +{ + MinmaxMultiOpaque *opaque; + uint16 basenum = procnum - PROCNUM_BASE; + + /* + * We cache these in the opaque struct, to avoid repetitive syscache + * lookups. + */ + opaque = (MinmaxMultiOpaque *) bdesc->bd_info[attno - 1]->oi_opaque; + + /* + * If we already searched for this proc and didn't find it, don't bother + * searching again. + */ + if (opaque->extra_proc_missing[basenum]) + return NULL; + + if (opaque->extra_procinfos[basenum].fn_oid == InvalidOid) + { + if (RegProcedureIsValid(index_getprocid(bdesc->bd_index, attno, + procnum))) + { + fmgr_info_copy(&opaque->extra_procinfos[basenum], + index_getprocinfo(bdesc->bd_index, attno, procnum), + bdesc->bd_context); + } + else + { + opaque->extra_proc_missing[basenum] = true; + return NULL; + } + } + + return &opaque->extra_procinfos[basenum]; +} + +/* + * Cache and return the procedure for the given strategy. + * + * Note: this function mirrors minmax_multi_get_strategy_procinfo; see notes + * there. If changes are made here, see that function too. + */ +static FmgrInfo * +minmax_multi_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, + uint16 strategynum) +{ + MinmaxMultiOpaque *opaque; + + Assert(strategynum >= 1 && + strategynum <= BTMaxStrategyNumber); + + opaque = (MinmaxMultiOpaque *) bdesc->bd_info[attno - 1]->oi_opaque; + + /* + * We cache the procedures for the previous subtype in the opaque struct, + * to avoid repetitive syscache lookups. If the subtype changed, + * invalidate all the cached entries. + */ + if (opaque->cached_subtype != subtype) + { + uint16 i; + + for (i = 1; i <= BTMaxStrategyNumber; i++) + opaque->strategy_procinfos[i - 1].fn_oid = InvalidOid; + opaque->cached_subtype = subtype; + } + + if (opaque->strategy_procinfos[strategynum - 1].fn_oid == InvalidOid) + { + Form_pg_attribute attr; + HeapTuple tuple; + Oid opfamily, + oprid; + bool isNull; + + opfamily = bdesc->bd_index->rd_opfamily[attno - 1]; + attr = TupleDescAttr(bdesc->bd_tupdesc, attno - 1); + tuple = SearchSysCache4(AMOPSTRATEGY, ObjectIdGetDatum(opfamily), + ObjectIdGetDatum(attr->atttypid), + ObjectIdGetDatum(subtype), + Int16GetDatum(strategynum)); + + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "missing operator %d(%u,%u) in opfamily %u", + strategynum, attr->atttypid, subtype, opfamily); + + oprid = DatumGetObjectId(SysCacheGetAttr(AMOPSTRATEGY, tuple, + Anum_pg_amop_amopopr, &isNull)); + ReleaseSysCache(tuple); + Assert(!isNull && RegProcedureIsValid(oprid)); + + fmgr_info_cxt(get_opcode(oprid), + &opaque->strategy_procinfos[strategynum - 1], + bdesc->bd_context); + } + + return &opaque->strategy_procinfos[strategynum - 1]; +} diff --git a/src/include/catalog/pg_amop.h b/src/include/catalog/pg_amop.h index 84dc6e3..be2eba8 100644 --- a/src/include/catalog/pg_amop.h +++ b/src/include/catalog/pg_amop.h @@ -974,6 +974,52 @@ DATA(insert ( 4054 23 20 2 s 80 3580 0 )); DATA(insert ( 4054 23 20 3 s 15 3580 0 )); DATA(insert ( 4054 23 20 4 s 82 3580 0 )); DATA(insert ( 4054 23 20 5 s 76 3580 0 )); +/* minmax multi integer */ +DATA(insert ( 4126 20 20 1 s 412 3580 0 )); +DATA(insert ( 4126 20 20 2 s 414 3580 0 )); +DATA(insert ( 4126 20 20 3 s 410 3580 0 )); +DATA(insert ( 4126 20 20 4 s 415 3580 0 )); +DATA(insert ( 4126 20 20 5 s 413 3580 0 )); +DATA(insert ( 4126 20 21 1 s 1870 3580 0 )); +DATA(insert ( 4126 20 21 2 s 1872 3580 0 )); +DATA(insert ( 4126 20 21 3 s 1868 3580 0 )); +DATA(insert ( 4126 20 21 4 s 1873 3580 0 )); +DATA(insert ( 4126 20 21 5 s 1871 3580 0 )); +DATA(insert ( 4126 20 23 1 s 418 3580 0 )); +DATA(insert ( 4126 20 23 2 s 420 3580 0 )); +DATA(insert ( 4126 20 23 3 s 416 3580 0 )); +DATA(insert ( 4126 20 23 4 s 430 3580 0 )); +DATA(insert ( 4126 20 23 5 s 419 3580 0 )); +DATA(insert ( 4126 21 21 1 s 95 3580 0 )); +DATA(insert ( 4126 21 21 2 s 522 3580 0 )); +DATA(insert ( 4126 21 21 3 s 94 3580 0 )); +DATA(insert ( 4126 21 21 4 s 524 3580 0 )); +DATA(insert ( 4126 21 21 5 s 520 3580 0 )); +DATA(insert ( 4126 21 20 1 s 1864 3580 0 )); +DATA(insert ( 4126 21 20 2 s 1866 3580 0 )); +DATA(insert ( 4126 21 20 3 s 1862 3580 0 )); +DATA(insert ( 4126 21 20 4 s 1867 3580 0 )); +DATA(insert ( 4126 21 20 5 s 1865 3580 0 )); +DATA(insert ( 4126 21 23 1 s 534 3580 0 )); +DATA(insert ( 4126 21 23 2 s 540 3580 0 )); +DATA(insert ( 4126 21 23 3 s 532 3580 0 )); +DATA(insert ( 4126 21 23 4 s 542 3580 0 )); +DATA(insert ( 4126 21 23 5 s 536 3580 0 )); +DATA(insert ( 4126 23 23 1 s 97 3580 0 )); +DATA(insert ( 4126 23 23 2 s 523 3580 0 )); +DATA(insert ( 4126 23 23 3 s 96 3580 0 )); +DATA(insert ( 4126 23 23 4 s 525 3580 0 )); +DATA(insert ( 4126 23 23 5 s 521 3580 0 )); +DATA(insert ( 4126 23 21 1 s 535 3580 0 )); +DATA(insert ( 4126 23 21 2 s 541 3580 0 )); +DATA(insert ( 4126 23 21 3 s 533 3580 0 )); +DATA(insert ( 4126 23 21 4 s 543 3580 0 )); +DATA(insert ( 4126 23 21 5 s 537 3580 0 )); +DATA(insert ( 4126 23 20 1 s 37 3580 0 )); +DATA(insert ( 4126 23 20 2 s 80 3580 0 )); +DATA(insert ( 4126 23 20 3 s 15 3580 0 )); +DATA(insert ( 4126 23 20 4 s 82 3580 0 )); +DATA(insert ( 4126 23 20 5 s 76 3580 0 )); /* bloom integer */ DATA(insert ( 5024 20 20 1 s 410 3580 0 )); DATA(insert ( 5024 20 21 1 s 1868 3580 0 )); @@ -999,6 +1045,12 @@ DATA(insert ( 4068 26 26 2 s 611 3580 0 )); DATA(insert ( 4068 26 26 3 s 607 3580 0 )); DATA(insert ( 4068 26 26 4 s 612 3580 0 )); DATA(insert ( 4068 26 26 5 s 610 3580 0 )); +/* minmax oid */ +DATA(insert ( 4127 26 26 1 s 609 3580 0 )); +DATA(insert ( 4127 26 26 2 s 611 3580 0 )); +DATA(insert ( 4127 26 26 3 s 607 3580 0 )); +DATA(insert ( 4127 26 26 4 s 612 3580 0 )); +DATA(insert ( 4127 26 26 5 s 610 3580 0 )); /* bloom oid */ DATA(insert ( 5029 26 26 1 s 607 3580 0 )); /* minmax tid */ @@ -1007,6 +1059,12 @@ DATA(insert ( 4069 27 27 2 s 2801 3580 0 )); DATA(insert ( 4069 27 27 3 s 387 3580 0 )); DATA(insert ( 4069 27 27 4 s 2802 3580 0 )); DATA(insert ( 4069 27 27 5 s 2800 3580 0 )); +/* minmax multi tid */ +DATA(insert ( 4128 27 27 1 s 2799 3580 0 )); +DATA(insert ( 4128 27 27 2 s 2801 3580 0 )); +DATA(insert ( 4128 27 27 3 s 387 3580 0 )); +DATA(insert ( 4128 27 27 4 s 2802 3580 0 )); +DATA(insert ( 4128 27 27 5 s 2800 3580 0 )); /* minmax float (float4, float8) */ DATA(insert ( 4070 700 700 1 s 622 3580 0 )); DATA(insert ( 4070 700 700 2 s 624 3580 0 )); @@ -1028,6 +1086,27 @@ DATA(insert ( 4070 701 701 2 s 673 3580 0 )); DATA(insert ( 4070 701 701 3 s 670 3580 0 )); DATA(insert ( 4070 701 701 4 s 675 3580 0 )); DATA(insert ( 4070 701 701 5 s 674 3580 0 )); +/* minmax multi (float8) */ +DATA(insert ( 4005 700 700 1 s 622 3580 0 )); +DATA(insert ( 4005 700 700 2 s 624 3580 0 )); +DATA(insert ( 4005 700 700 3 s 620 3580 0 )); +DATA(insert ( 4005 700 700 4 s 625 3580 0 )); +DATA(insert ( 4005 700 700 5 s 623 3580 0 )); +DATA(insert ( 4005 700 701 1 s 1122 3580 0 )); +DATA(insert ( 4005 700 701 2 s 1124 3580 0 )); +DATA(insert ( 4005 700 701 3 s 1120 3580 0 )); +DATA(insert ( 4005 700 701 4 s 1125 3580 0 )); +DATA(insert ( 4005 700 701 5 s 1123 3580 0 )); +DATA(insert ( 4005 701 700 1 s 1132 3580 0 )); +DATA(insert ( 4005 701 700 2 s 1134 3580 0 )); +DATA(insert ( 4005 701 700 3 s 1130 3580 0 )); +DATA(insert ( 4005 701 700 4 s 1135 3580 0 )); +DATA(insert ( 4005 701 700 5 s 1133 3580 0 )); +DATA(insert ( 4005 701 701 1 s 672 3580 0 )); +DATA(insert ( 4005 701 701 2 s 673 3580 0 )); +DATA(insert ( 4005 701 701 3 s 670 3580 0 )); +DATA(insert ( 4005 701 701 4 s 675 3580 0 )); +DATA(insert ( 4005 701 701 5 s 674 3580 0 )); /* bloom float (float4, float8) */ DATA(insert ( 5030 700 700 1 s 620 3580 0 )); DATA(insert ( 5030 700 701 1 s 1120 3580 0 )); @@ -1040,6 +1119,12 @@ DATA(insert ( 4072 702 702 2 s 564 3580 0 )); DATA(insert ( 4072 702 702 3 s 560 3580 0 )); DATA(insert ( 4072 702 702 4 s 565 3580 0 )); DATA(insert ( 4072 702 702 5 s 563 3580 0 )); +/* multi minmax abstime */ +DATA(insert ( 4146 702 702 1 s 562 3580 0 )); +DATA(insert ( 4146 702 702 2 s 564 3580 0 )); +DATA(insert ( 4146 702 702 3 s 560 3580 0 )); +DATA(insert ( 4146 702 702 4 s 565 3580 0 )); +DATA(insert ( 4146 702 702 5 s 563 3580 0 )); /* bloom abstime */ DATA(insert ( 5031 702 702 1 s 560 3580 0 )); /* minmax reltime */ @@ -1048,6 +1133,12 @@ DATA(insert ( 4073 703 703 2 s 570 3580 0 )); DATA(insert ( 4073 703 703 3 s 566 3580 0 )); DATA(insert ( 4073 703 703 4 s 571 3580 0 )); DATA(insert ( 4073 703 703 5 s 569 3580 0 )); +/* multi minmax reltime */ +DATA(insert ( 4147 703 703 1 s 568 3580 0 )); +DATA(insert ( 4147 703 703 2 s 570 3580 0 )); +DATA(insert ( 4147 703 703 3 s 566 3580 0 )); +DATA(insert ( 4147 703 703 4 s 571 3580 0 )); +DATA(insert ( 4147 703 703 5 s 569 3580 0 )); /* bloom reltime */ DATA(insert ( 5032 703 703 1 s 566 3580 0 )); /* minmax macaddr */ @@ -1056,6 +1147,12 @@ DATA(insert ( 4074 829 829 2 s 1223 3580 0 )); DATA(insert ( 4074 829 829 3 s 1220 3580 0 )); DATA(insert ( 4074 829 829 4 s 1225 3580 0 )); DATA(insert ( 4074 829 829 5 s 1224 3580 0 )); +/* multi minmax macaddr */ +DATA(insert ( 4143 829 829 1 s 1222 3580 0 )); +DATA(insert ( 4143 829 829 2 s 1223 3580 0 )); +DATA(insert ( 4143 829 829 3 s 1220 3580 0 )); +DATA(insert ( 4143 829 829 4 s 1225 3580 0 )); +DATA(insert ( 4143 829 829 5 s 1224 3580 0 )); /* bloom macaddr */ DATA(insert ( 5033 829 829 1 s 1220 3580 0 )); /* minmax macaddr8 */ @@ -1064,6 +1161,12 @@ DATA(insert ( 4109 774 774 2 s 3365 3580 0 )); DATA(insert ( 4109 774 774 3 s 3362 3580 0 )); DATA(insert ( 4109 774 774 4 s 3367 3580 0 )); DATA(insert ( 4109 774 774 5 s 3366 3580 0 )); +/* multi minmax macaddr8 */ +DATA(insert ( 4144 774 774 1 s 3364 3580 0 )); +DATA(insert ( 4144 774 774 2 s 3365 3580 0 )); +DATA(insert ( 4144 774 774 3 s 3362 3580 0 )); +DATA(insert ( 4144 774 774 4 s 3367 3580 0 )); +DATA(insert ( 4144 774 774 5 s 3366 3580 0 )); /* bloom macaddr8 */ DATA(insert ( 5034 774 774 1 s 3362 3580 0 )); /* minmax inet */ @@ -1072,6 +1175,12 @@ DATA(insert ( 4075 869 869 2 s 1204 3580 0 )); DATA(insert ( 4075 869 869 3 s 1201 3580 0 )); DATA(insert ( 4075 869 869 4 s 1206 3580 0 )); DATA(insert ( 4075 869 869 5 s 1205 3580 0 )); +/* multi minmax inet */ +DATA(insert ( 4145 869 869 1 s 1203 3580 0 )); +DATA(insert ( 4145 869 869 2 s 1204 3580 0 )); +DATA(insert ( 4145 869 869 3 s 1201 3580 0 )); +DATA(insert ( 4145 869 869 4 s 1206 3580 0 )); +DATA(insert ( 4145 869 869 5 s 1205 3580 0 )); /* inclusion inet */ DATA(insert ( 4102 869 869 3 s 3552 3580 0 )); DATA(insert ( 4102 869 869 7 s 934 3580 0 )); @@ -1095,6 +1204,12 @@ DATA(insert ( 4077 1083 1083 2 s 1111 3580 0 )); DATA(insert ( 4077 1083 1083 3 s 1108 3580 0 )); DATA(insert ( 4077 1083 1083 4 s 1113 3580 0 )); DATA(insert ( 4077 1083 1083 5 s 1112 3580 0 )); +/* multi minmax time without time zone */ +DATA(insert ( 4135 1083 1083 1 s 1110 3580 0 )); +DATA(insert ( 4135 1083 1083 2 s 1111 3580 0 )); +DATA(insert ( 4135 1083 1083 3 s 1108 3580 0 )); +DATA(insert ( 4135 1083 1083 4 s 1113 3580 0 )); +DATA(insert ( 4135 1083 1083 5 s 1112 3580 0 )); /* bloom time without time zone */ DATA(insert ( 5037 1083 1083 1 s 1108 3580 0 )); /* minmax datetime (date, timestamp, timestamptz) */ @@ -1143,6 +1258,52 @@ DATA(insert ( 4059 1184 1184 2 s 1323 3580 0 )); DATA(insert ( 4059 1184 1184 3 s 1320 3580 0 )); DATA(insert ( 4059 1184 1184 4 s 1325 3580 0 )); DATA(insert ( 4059 1184 1184 5 s 1324 3580 0 )); +/* minmax multi (timestamp, timestamptz) */ +DATA(insert ( 4006 1114 1114 1 s 2062 3580 0 )); +DATA(insert ( 4006 1114 1114 2 s 2063 3580 0 )); +DATA(insert ( 4006 1114 1114 3 s 2060 3580 0 )); +DATA(insert ( 4006 1114 1114 4 s 2065 3580 0 )); +DATA(insert ( 4006 1114 1114 5 s 2064 3580 0 )); +DATA(insert ( 4006 1114 1082 1 s 2371 3580 0 )); +DATA(insert ( 4006 1114 1082 2 s 2372 3580 0 )); +DATA(insert ( 4006 1114 1082 3 s 2373 3580 0 )); +DATA(insert ( 4006 1114 1082 4 s 2374 3580 0 )); +DATA(insert ( 4006 1114 1082 5 s 2375 3580 0 )); +DATA(insert ( 4006 1114 1184 1 s 2534 3580 0 )); +DATA(insert ( 4006 1114 1184 2 s 2535 3580 0 )); +DATA(insert ( 4006 1114 1184 3 s 2536 3580 0 )); +DATA(insert ( 4006 1114 1184 4 s 2537 3580 0 )); +DATA(insert ( 4006 1114 1184 5 s 2538 3580 0 )); +DATA(insert ( 4006 1082 1082 1 s 1095 3580 0 )); +DATA(insert ( 4006 1082 1082 2 s 1096 3580 0 )); +DATA(insert ( 4006 1082 1082 3 s 1093 3580 0 )); +DATA(insert ( 4006 1082 1082 4 s 1098 3580 0 )); +DATA(insert ( 4006 1082 1082 5 s 1097 3580 0 )); +DATA(insert ( 4006 1082 1114 1 s 2345 3580 0 )); +DATA(insert ( 4006 1082 1114 2 s 2346 3580 0 )); +DATA(insert ( 4006 1082 1114 3 s 2347 3580 0 )); +DATA(insert ( 4006 1082 1114 4 s 2348 3580 0 )); +DATA(insert ( 4006 1082 1114 5 s 2349 3580 0 )); +DATA(insert ( 4006 1082 1184 1 s 2358 3580 0 )); +DATA(insert ( 4006 1082 1184 2 s 2359 3580 0 )); +DATA(insert ( 4006 1082 1184 3 s 2360 3580 0 )); +DATA(insert ( 4006 1082 1184 4 s 2361 3580 0 )); +DATA(insert ( 4006 1082 1184 5 s 2362 3580 0 )); +DATA(insert ( 4006 1184 1082 1 s 2384 3580 0 )); +DATA(insert ( 4006 1184 1082 2 s 2385 3580 0 )); +DATA(insert ( 4006 1184 1082 3 s 2386 3580 0 )); +DATA(insert ( 4006 1184 1082 4 s 2387 3580 0 )); +DATA(insert ( 4006 1184 1082 5 s 2388 3580 0 )); +DATA(insert ( 4006 1184 1114 1 s 2540 3580 0 )); +DATA(insert ( 4006 1184 1114 2 s 2541 3580 0 )); +DATA(insert ( 4006 1184 1114 3 s 2542 3580 0 )); +DATA(insert ( 4006 1184 1114 4 s 2543 3580 0 )); +DATA(insert ( 4006 1184 1114 5 s 2544 3580 0 )); +DATA(insert ( 4006 1184 1184 1 s 1322 3580 0 )); +DATA(insert ( 4006 1184 1184 2 s 1323 3580 0 )); +DATA(insert ( 4006 1184 1184 3 s 1320 3580 0 )); +DATA(insert ( 4006 1184 1184 4 s 1325 3580 0 )); +DATA(insert ( 4006 1184 1184 5 s 1324 3580 0 )); /* bloom datetime (date, timestamp, timestamptz) */ DATA(insert ( 5038 1114 1114 1 s 2060 3580 0 )); DATA(insert ( 5038 1114 1082 1 s 2373 3580 0 )); @@ -1160,6 +1321,12 @@ DATA(insert ( 4078 1186 1186 2 s 1333 3580 0 )); DATA(insert ( 4078 1186 1186 3 s 1330 3580 0 )); DATA(insert ( 4078 1186 1186 4 s 1335 3580 0 )); DATA(insert ( 4078 1186 1186 5 s 1334 3580 0 )); +/* multi minmax interval */ +DATA(insert ( 4134 1186 1186 1 s 1332 3580 0 )); +DATA(insert ( 4134 1186 1186 2 s 1333 3580 0 )); +DATA(insert ( 4134 1186 1186 3 s 1330 3580 0 )); +DATA(insert ( 4134 1186 1186 4 s 1335 3580 0 )); +DATA(insert ( 4134 1186 1186 5 s 1334 3580 0 )); /* bloom interval */ DATA(insert ( 5041 1186 1186 1 s 1330 3580 0 )); /* minmax time with time zone */ @@ -1168,6 +1335,12 @@ DATA(insert ( 4058 1266 1266 2 s 1553 3580 0 )); DATA(insert ( 4058 1266 1266 3 s 1550 3580 0 )); DATA(insert ( 4058 1266 1266 4 s 1555 3580 0 )); DATA(insert ( 4058 1266 1266 5 s 1554 3580 0 )); +/* multi minmax time with time zone */ +DATA(insert ( 4133 1266 1266 1 s 1552 3580 0 )); +DATA(insert ( 4133 1266 1266 2 s 1553 3580 0 )); +DATA(insert ( 4133 1266 1266 3 s 1550 3580 0 )); +DATA(insert ( 4133 1266 1266 4 s 1555 3580 0 )); +DATA(insert ( 4133 1266 1266 5 s 1554 3580 0 )); /* bloom time with time zone */ DATA(insert ( 5042 1266 1266 1 s 1550 3580 0 )); /* minmax bit */ @@ -1188,6 +1361,12 @@ DATA(insert ( 4055 1700 1700 2 s 1755 3580 0 )); DATA(insert ( 4055 1700 1700 3 s 1752 3580 0 )); DATA(insert ( 4055 1700 1700 4 s 1757 3580 0 )); DATA(insert ( 4055 1700 1700 5 s 1756 3580 0 )); +/* minmax multi numeric */ +DATA(insert ( 4008 1700 1700 1 s 1754 3580 0 )); +DATA(insert ( 4008 1700 1700 2 s 1755 3580 0 )); +DATA(insert ( 4008 1700 1700 3 s 1752 3580 0 )); +DATA(insert ( 4008 1700 1700 4 s 1757 3580 0 )); +DATA(insert ( 4008 1700 1700 5 s 1756 3580 0 )); /* bloom numeric */ DATA(insert ( 5045 1700 1700 1 s 1752 3580 0 )); /* minmax uuid */ @@ -1196,6 +1375,12 @@ DATA(insert ( 4081 2950 2950 2 s 2976 3580 0 )); DATA(insert ( 4081 2950 2950 3 s 2972 3580 0 )); DATA(insert ( 4081 2950 2950 4 s 2977 3580 0 )); DATA(insert ( 4081 2950 2950 5 s 2975 3580 0 )); +/* minmax multi uuid */ +DATA(insert ( 4131 2950 2950 1 s 2974 3580 0 )); +DATA(insert ( 4131 2950 2950 2 s 2976 3580 0 )); +DATA(insert ( 4131 2950 2950 3 s 2972 3580 0 )); +DATA(insert ( 4131 2950 2950 4 s 2977 3580 0 )); +DATA(insert ( 4131 2950 2950 5 s 2975 3580 0 )); /* bloom uuid */ DATA(insert ( 5046 2950 2950 1 s 2972 3580 0 )); /* inclusion range types */ @@ -1219,6 +1404,12 @@ DATA(insert ( 4082 3220 3220 2 s 3226 3580 0 )); DATA(insert ( 4082 3220 3220 3 s 3222 3580 0 )); DATA(insert ( 4082 3220 3220 4 s 3227 3580 0 )); DATA(insert ( 4082 3220 3220 5 s 3225 3580 0 )); +/* multi minmax pg_lsn */ +DATA(insert ( 4132 3220 3220 1 s 3224 3580 0 )); +DATA(insert ( 4132 3220 3220 2 s 3226 3580 0 )); +DATA(insert ( 4132 3220 3220 3 s 3222 3580 0 )); +DATA(insert ( 4132 3220 3220 4 s 3227 3580 0 )); +DATA(insert ( 4132 3220 3220 5 s 3225 3580 0 )); /* bloom pg_lsn */ DATA(insert ( 5047 3220 3220 1 s 3222 3580 0 )); /* inclusion box */ diff --git a/src/include/catalog/pg_amproc.h b/src/include/catalog/pg_amproc.h index 4871836..8fb3405 100644 --- a/src/include/catalog/pg_amproc.h +++ b/src/include/catalog/pg_amproc.h @@ -415,6 +415,55 @@ DATA(insert ( 4054 23 21 2 3384 )); DATA(insert ( 4054 23 21 3 3385 )); DATA(insert ( 4054 23 21 4 3386 )); +/* minmax multi integer: int2, int4, int8 */ +DATA(insert ( 4126 20 20 1 4001 )); +DATA(insert ( 4126 20 20 2 4002 )); +DATA(insert ( 4126 20 20 3 4003 )); +DATA(insert ( 4126 20 20 4 4004 )); +DATA(insert ( 4126 20 20 11 4013 )); +DATA(insert ( 4126 20 21 1 4001 )); +DATA(insert ( 4126 20 21 2 4002 )); +DATA(insert ( 4126 20 21 3 4003 )); +DATA(insert ( 4126 20 21 4 4004 )); +DATA(insert ( 4126 20 21 11 4013 )); +DATA(insert ( 4126 20 23 1 4001 )); +DATA(insert ( 4126 20 23 2 4002 )); +DATA(insert ( 4126 20 23 3 4003 )); +DATA(insert ( 4126 20 23 4 4004 )); +DATA(insert ( 4126 20 23 11 4013 )); + +DATA(insert ( 4126 21 21 1 4001 )); +DATA(insert ( 4126 21 21 2 4002 )); +DATA(insert ( 4126 21 21 3 4003 )); +DATA(insert ( 4126 21 21 4 4004 )); +DATA(insert ( 4126 21 21 11 4011 )); +DATA(insert ( 4126 21 20 1 4001 )); +DATA(insert ( 4126 21 20 2 4002 )); +DATA(insert ( 4126 21 20 3 4003 )); +DATA(insert ( 4126 21 20 4 4004 )); +DATA(insert ( 4126 21 20 11 4011 )); +DATA(insert ( 4126 21 23 1 4001 )); +DATA(insert ( 4126 21 23 2 4002 )); +DATA(insert ( 4126 21 23 3 4003 )); +DATA(insert ( 4126 21 23 4 4004 )); +DATA(insert ( 4126 21 23 11 4011 )); + +DATA(insert ( 4126 23 23 1 4001 )); +DATA(insert ( 4126 23 23 2 4002 )); +DATA(insert ( 4126 23 23 3 4003 )); +DATA(insert ( 4126 23 23 4 4004 )); +DATA(insert ( 4126 23 23 11 4012 )); +DATA(insert ( 4126 23 20 1 4001 )); +DATA(insert ( 4126 23 20 2 4002 )); +DATA(insert ( 4126 23 20 3 4003 )); +DATA(insert ( 4126 23 20 4 4004 )); +DATA(insert ( 4126 23 20 11 4012 )); +DATA(insert ( 4126 23 21 1 4001 )); +DATA(insert ( 4126 23 21 2 4002 )); +DATA(insert ( 4126 23 21 3 4003 )); +DATA(insert ( 4126 23 21 4 4004 )); +DATA(insert ( 4126 23 21 11 4012 )); + /* bloom integer: int2, int4, int8 */ DATA(insert ( 5024 20 20 1 5017 )); DATA(insert ( 5024 20 20 2 5018 )); @@ -450,6 +499,12 @@ DATA(insert ( 4068 26 26 1 3383 )); DATA(insert ( 4068 26 26 2 3384 )); DATA(insert ( 4068 26 26 3 3385 )); DATA(insert ( 4068 26 26 4 3386 )); +/* minmax multi oid */ +DATA(insert ( 4127 26 26 1 4001 )); +DATA(insert ( 4127 26 26 2 4002 )); +DATA(insert ( 4127 26 26 3 4003 )); +DATA(insert ( 4127 26 26 4 4004 )); +DATA(insert ( 4127 26 26 11 4012 )); /* bloom oid */ DATA(insert ( 5029 26 26 1 5017 )); DATA(insert ( 5029 26 26 2 5018 )); @@ -461,6 +516,12 @@ DATA(insert ( 4069 27 27 1 3383 )); DATA(insert ( 4069 27 27 2 3384 )); DATA(insert ( 4069 27 27 3 3385 )); DATA(insert ( 4069 27 27 4 3386 )); +/* minmax multi tid */ +DATA(insert ( 4128 27 27 1 4001 )); +DATA(insert ( 4128 27 27 2 4002 )); +DATA(insert ( 4128 27 27 3 4003 )); +DATA(insert ( 4128 27 27 4 4004 )); +DATA(insert ( 4128 27 27 11 4129 )); /* minmax float */ DATA(insert ( 4070 700 700 1 3383 )); DATA(insert ( 4070 700 700 2 3384 )); @@ -482,6 +543,31 @@ DATA(insert ( 4070 701 700 2 3384 )); DATA(insert ( 4070 701 700 3 3385 )); DATA(insert ( 4070 701 700 4 3386 )); +/* minmax multi float */ +DATA(insert ( 4005 700 700 1 4001 )); +DATA(insert ( 4005 700 700 2 4002 )); +DATA(insert ( 4005 700 700 3 4003 )); +DATA(insert ( 4005 700 700 4 4004 )); +DATA(insert ( 4005 700 700 11 4010 )); + +DATA(insert ( 4005 700 701 1 4001 )); +DATA(insert ( 4005 700 701 2 4002 )); +DATA(insert ( 4005 700 701 3 4003 )); +DATA(insert ( 4005 700 701 4 4004 )); +DATA(insert ( 4005 700 701 11 4010 )); + +DATA(insert ( 4005 701 701 1 4001 )); +DATA(insert ( 4005 701 701 2 4002 )); +DATA(insert ( 4005 701 701 3 4003 )); +DATA(insert ( 4005 701 701 4 4004 )); +DATA(insert ( 4005 701 701 11 4007 )); + +DATA(insert ( 4005 701 700 1 4001 )); +DATA(insert ( 4005 701 700 2 4002 )); +DATA(insert ( 4005 701 700 3 4003 )); +DATA(insert ( 4005 701 700 4 4004 )); +DATA(insert ( 4005 701 700 11 4007 )); + /* bloom float */ DATA(insert ( 5030 700 700 1 5017 )); DATA(insert ( 5030 700 700 2 5018 )); @@ -500,6 +586,12 @@ DATA(insert ( 4072 702 702 1 3383 )); DATA(insert ( 4072 702 702 2 3384 )); DATA(insert ( 4072 702 702 3 3385 )); DATA(insert ( 4072 702 702 4 3386 )); +/* multi minmax abstime */ +DATA(insert ( 4146 702 702 1 4001 )); +DATA(insert ( 4146 702 702 2 4002 )); +DATA(insert ( 4146 702 702 3 4003 )); +DATA(insert ( 4146 702 702 4 4004 )); +DATA(insert ( 4146 702 702 11 4148 )); /* bloom abstime */ DATA(insert ( 5031 702 702 1 5017 )); DATA(insert ( 5031 702 702 2 5018 )); @@ -511,6 +603,12 @@ DATA(insert ( 4073 703 703 1 3383 )); DATA(insert ( 4073 703 703 2 3384 )); DATA(insert ( 4073 703 703 3 3385 )); DATA(insert ( 4073 703 703 4 3386 )); +/* multi minmax reltime */ +DATA(insert ( 4147 703 703 1 4001 )); +DATA(insert ( 4147 703 703 2 4002 )); +DATA(insert ( 4147 703 703 3 4003 )); +DATA(insert ( 4147 703 703 4 4004 )); +DATA(insert ( 4147 703 703 11 4149 )); /* bloom reltime */ DATA(insert ( 5032 703 703 1 5017 )); DATA(insert ( 5032 703 703 2 5018 )); @@ -522,6 +620,12 @@ DATA(insert ( 4074 829 829 1 3383 )); DATA(insert ( 4074 829 829 2 3384 )); DATA(insert ( 4074 829 829 3 3385 )); DATA(insert ( 4074 829 829 4 3386 )); +/* multi minmax macaddr */ +DATA(insert ( 4143 829 829 1 4001 )); +DATA(insert ( 4143 829 829 2 4002 )); +DATA(insert ( 4143 829 829 3 4003 )); +DATA(insert ( 4143 829 829 4 4004 )); +DATA(insert ( 4143 829 829 11 4140 )); /* bloom macaddr */ DATA(insert ( 5033 829 829 1 5017 )); DATA(insert ( 5033 829 829 2 5018 )); @@ -533,6 +637,12 @@ DATA(insert ( 4109 774 774 1 3383 )); DATA(insert ( 4109 774 774 2 3384 )); DATA(insert ( 4109 774 774 3 3385 )); DATA(insert ( 4109 774 774 4 3386 )); +/* minmax macaddr8 */ +DATA(insert ( 4144 774 774 1 4001 )); +DATA(insert ( 4144 774 774 2 4002 )); +DATA(insert ( 4144 774 774 3 4003 )); +DATA(insert ( 4144 774 774 4 4004 )); +DATA(insert ( 4144 774 774 11 4141 )); /* bloom macaddr8 */ DATA(insert ( 5034 774 774 1 5017 )); DATA(insert ( 5034 774 774 2 5018 )); @@ -544,6 +654,12 @@ DATA(insert ( 4075 869 869 1 3383 )); DATA(insert ( 4075 869 869 2 3384 )); DATA(insert ( 4075 869 869 3 3385 )); DATA(insert ( 4075 869 869 4 3386 )); +/* multi minmax inet */ +DATA(insert ( 4145 869 869 1 4001 )); +DATA(insert ( 4145 869 869 2 4002 )); +DATA(insert ( 4145 869 869 3 4003 )); +DATA(insert ( 4145 869 869 4 4004 )); +DATA(insert ( 4145 869 869 11 4142 )); /* inclusion inet */ DATA(insert ( 4102 869 869 1 4105 )); DATA(insert ( 4102 869 869 2 4106 )); @@ -574,6 +690,12 @@ DATA(insert ( 4077 1083 1083 1 3383 )); DATA(insert ( 4077 1083 1083 2 3384 )); DATA(insert ( 4077 1083 1083 3 3385 )); DATA(insert ( 4077 1083 1083 4 3386 )); +/* multi minmax time without time zone */ +DATA(insert ( 4135 1083 1083 1 4001 )); +DATA(insert ( 4135 1083 1083 2 4002 )); +DATA(insert ( 4135 1083 1083 3 4003 )); +DATA(insert ( 4135 1083 1083 4 4004 )); +DATA(insert ( 4135 1083 1083 11 4136 )); /* bloom time without time zone */ DATA(insert ( 5037 1083 1083 1 5017 )); DATA(insert ( 5037 1083 1083 2 5018 )); @@ -620,6 +742,55 @@ DATA(insert ( 4059 1082 1184 2 3384 )); DATA(insert ( 4059 1082 1184 3 3385 )); DATA(insert ( 4059 1082 1184 4 3386 )); +/* minmax multi (date, timestamp, timestamptz) */ +DATA(insert ( 4006 1114 1114 1 4001 )); +DATA(insert ( 4006 1114 1114 2 4002 )); +DATA(insert ( 4006 1114 1114 3 4003 )); +DATA(insert ( 4006 1114 1114 4 4004 )); +DATA(insert ( 4006 1114 1114 11 4007 )); +DATA(insert ( 4006 1114 1184 1 4001 )); +DATA(insert ( 4006 1114 1184 2 4002 )); +DATA(insert ( 4006 1114 1184 3 4003 )); +DATA(insert ( 4006 1114 1184 4 4004 )); +DATA(insert ( 4006 1114 1184 11 4007 )); +DATA(insert ( 4006 1114 1082 1 4001 )); +DATA(insert ( 4006 1114 1082 2 4002 )); +DATA(insert ( 4006 1114 1082 3 4003 )); +DATA(insert ( 4006 1114 1082 4 4004 )); +DATA(insert ( 4006 1114 1082 11 4007 )); + +DATA(insert ( 4006 1184 1184 1 4001 )); +DATA(insert ( 4006 1184 1184 2 4002 )); +DATA(insert ( 4006 1184 1184 3 4003 )); +DATA(insert ( 4006 1184 1184 4 4004 )); +DATA(insert ( 4006 1184 1184 11 4007 )); +DATA(insert ( 4006 1184 1114 1 4001 )); +DATA(insert ( 4006 1184 1114 2 4002 )); +DATA(insert ( 4006 1184 1114 3 4003 )); +DATA(insert ( 4006 1184 1114 4 4004 )); +DATA(insert ( 4006 1184 1114 11 4007 )); +DATA(insert ( 4006 1184 1082 1 4001 )); +DATA(insert ( 4006 1184 1082 2 4002 )); +DATA(insert ( 4006 1184 1082 3 4003 )); +DATA(insert ( 4006 1184 1082 4 4004 )); +DATA(insert ( 4006 1184 1082 11 4007 )); + +DATA(insert ( 4006 1082 1082 1 4001 )); +DATA(insert ( 4006 1082 1082 2 4002 )); +DATA(insert ( 4006 1082 1082 3 4003 )); +DATA(insert ( 4006 1082 1082 4 4004 )); +DATA(insert ( 4006 1082 1082 11 4007 )); +DATA(insert ( 4006 1082 1114 1 4001 )); +DATA(insert ( 4006 1082 1114 2 4002 )); +DATA(insert ( 4006 1082 1114 3 4003 )); +DATA(insert ( 4006 1082 1114 4 4004 )); +DATA(insert ( 4006 1082 1114 11 4007 )); +DATA(insert ( 4006 1082 1184 1 4001 )); +DATA(insert ( 4006 1082 1184 2 4002 )); +DATA(insert ( 4006 1082 1184 3 4003 )); +DATA(insert ( 4006 1082 1184 4 4004 )); +DATA(insert ( 4006 1082 1184 11 4007 )); + /* bloom datetime (date, timestamp, timestamptz) */ DATA(insert ( 5038 1114 1114 1 5017 )); DATA(insert ( 5038 1114 1114 2 5018 )); @@ -644,6 +815,12 @@ DATA(insert ( 4078 1186 1186 1 3383 )); DATA(insert ( 4078 1186 1186 2 3384 )); DATA(insert ( 4078 1186 1186 3 3385 )); DATA(insert ( 4078 1186 1186 4 3386 )); +/* multi minmax interval */ +DATA(insert ( 4134 1186 1186 1 4001 )); +DATA(insert ( 4134 1186 1186 2 4002 )); +DATA(insert ( 4134 1186 1186 3 4003 )); +DATA(insert ( 4134 1186 1186 4 4004 )); +DATA(insert ( 4134 1186 1186 11 4137 )); /* bloom interval */ DATA(insert ( 5041 1186 1186 1 5017 )); DATA(insert ( 5041 1186 1186 2 5018 )); @@ -655,6 +832,12 @@ DATA(insert ( 4058 1266 1266 1 3383 )); DATA(insert ( 4058 1266 1266 2 3384 )); DATA(insert ( 4058 1266 1266 3 3385 )); DATA(insert ( 4058 1266 1266 4 3386 )); +/* multi minmax time with time zone */ +DATA(insert ( 4133 1266 1266 1 4001 )); +DATA(insert ( 4133 1266 1266 2 4002 )); +DATA(insert ( 4133 1266 1266 3 4003 )); +DATA(insert ( 4133 1266 1266 4 4004 )); +DATA(insert ( 4133 1266 1266 11 4138 )); /* bloom time with time zone */ DATA(insert ( 5042 1266 1266 1 5017 )); DATA(insert ( 5042 1266 1266 2 5018 )); @@ -676,6 +859,12 @@ DATA(insert ( 4055 1700 1700 1 3383 )); DATA(insert ( 4055 1700 1700 2 3384 )); DATA(insert ( 4055 1700 1700 3 3385 )); DATA(insert ( 4055 1700 1700 4 3386 )); +/* minmax multi numeric */ +DATA(insert ( 4008 1700 1700 1 4001 )); +DATA(insert ( 4008 1700 1700 2 4002 )); +DATA(insert ( 4008 1700 1700 3 4003 )); +DATA(insert ( 4008 1700 1700 4 4004 )); +DATA(insert ( 4008 1700 1700 11 4009 )); /* bloom numeric */ DATA(insert ( 5045 1700 1700 1 5017 )); DATA(insert ( 5045 1700 1700 2 5018 )); @@ -687,6 +876,12 @@ DATA(insert ( 4081 2950 2950 1 3383 )); DATA(insert ( 4081 2950 2950 2 3384 )); DATA(insert ( 4081 2950 2950 3 3385 )); DATA(insert ( 4081 2950 2950 4 3386 )); +/* minmax multi uuid */ +DATA(insert ( 4131 2950 2950 1 4001 )); +DATA(insert ( 4131 2950 2950 2 4002 )); +DATA(insert ( 4131 2950 2950 3 4003 )); +DATA(insert ( 4131 2950 2950 4 4004 )); +DATA(insert ( 4131 2950 2950 11 4130 )); /* bloom uuid */ DATA(insert ( 5046 2950 2950 1 5017 )); DATA(insert ( 5046 2950 2950 2 5018 )); @@ -706,6 +901,12 @@ DATA(insert ( 4082 3220 3220 1 3383 )); DATA(insert ( 4082 3220 3220 2 3384 )); DATA(insert ( 4082 3220 3220 3 3385 )); DATA(insert ( 4082 3220 3220 4 3386 )); +/* multi minmax pg_lsn */ +DATA(insert ( 4132 3220 3220 1 4001 )); +DATA(insert ( 4132 3220 3220 2 4002 )); +DATA(insert ( 4132 3220 3220 3 4003 )); +DATA(insert ( 4132 3220 3220 4 4004 )); +DATA(insert ( 4132 3220 3220 11 4139 )); /* bloom pg_lsn */ DATA(insert ( 5047 3220 3220 1 5017 )); DATA(insert ( 5047 3220 3220 2 5018 )); diff --git a/src/include/catalog/pg_opclass.h b/src/include/catalog/pg_opclass.h index 7dbd4ff..4e102e4 100644 --- a/src/include/catalog/pg_opclass.h +++ b/src/include/catalog/pg_opclass.h @@ -220,54 +220,75 @@ DATA(insert ( 3580 char_bloom_ops PGNSP PGUID 5022 18 f 18 )); DATA(insert ( 3580 name_minmax_ops PGNSP PGUID 4065 19 t 19 )); DATA(insert ( 3580 name_bloom_ops PGNSP PGUID 5023 19 f 19 )); DATA(insert ( 3580 int8_minmax_ops PGNSP PGUID 4054 20 t 20 )); +DATA(insert ( 3580 int8_minmax_multi_ops PGNSP PGUID 4126 20 f 20 )); DATA(insert ( 3580 int8_bloom_ops PGNSP PGUID 5024 20 f 20 )); DATA(insert ( 3580 int2_minmax_ops PGNSP PGUID 4054 21 t 21 )); +DATA(insert ( 3580 int2_minmax_multi_ops PGNSP PGUID 4126 21 f 21 )); DATA(insert ( 3580 int2_bloom_ops PGNSP PGUID 5024 21 f 21 )); DATA(insert ( 3580 int4_minmax_ops PGNSP PGUID 4054 23 t 23 )); +DATA(insert ( 3580 int4_minmax_multi_ops PGNSP PGUID 4126 23 f 23 )); DATA(insert ( 3580 int4_bloom_ops PGNSP PGUID 5024 23 f 23 )); DATA(insert ( 3580 text_minmax_ops PGNSP PGUID 4056 25 t 25 )); DATA(insert ( 3580 text_bloom_ops PGNSP PGUID 5027 25 f 25 )); DATA(insert ( 3580 oid_minmax_ops PGNSP PGUID 4068 26 t 26 )); +DATA(insert ( 3580 oid_minmax_multi_ops PGNSP PGUID 4127 26 f 26 )); DATA(insert ( 3580 oid_bloom_ops PGNSP PGUID 5029 26 f 26 )); DATA(insert ( 3580 tid_minmax_ops PGNSP PGUID 4069 27 t 27 )); +DATA(insert ( 3580 tid_minmax_multi_ops PGNSP PGUID 4128 27 f 27 )); DATA(insert ( 3580 float4_minmax_ops PGNSP PGUID 4070 700 t 700 )); +DATA(insert ( 3580 float4_minmax_multi_ops PGNSP PGUID 4005 700 f 700 )); DATA(insert ( 3580 float4_bloom_ops PGNSP PGUID 5030 700 f 700 )); DATA(insert ( 3580 float8_minmax_ops PGNSP PGUID 4070 701 t 701 )); +DATA(insert ( 3580 float8_minmax_multi_ops PGNSP PGUID 4005 701 f 701 )); DATA(insert ( 3580 float8_bloom_ops PGNSP PGUID 5030 701 f 701 )); DATA(insert ( 3580 abstime_minmax_ops PGNSP PGUID 4072 702 t 702 )); +DATA(insert ( 3580 abstime_minmax_multi_ops PGNSP PGUID 4146 702 f 702 )); DATA(insert ( 3580 abstime_bloom_ops PGNSP PGUID 5031 702 f 702 )); DATA(insert ( 3580 reltime_minmax_ops PGNSP PGUID 4073 703 t 703 )); +DATA(insert ( 3580 reltime_minmax_multi_ops PGNSP PGUID 4147 703 f 703 )); DATA(insert ( 3580 reltime_bloom_ops PGNSP PGUID 5032 703 f 703 )); DATA(insert ( 3580 macaddr_minmax_ops PGNSP PGUID 4074 829 t 829 )); +DATA(insert ( 3580 macaddr_minmax_multi_ops PGNSP PGUID 4143 829 f 829 )); DATA(insert ( 3580 macaddr_bloom_ops PGNSP PGUID 5033 829 f 829 )); DATA(insert ( 3580 macaddr8_minmax_ops PGNSP PGUID 4109 774 t 774 )); +DATA(insert ( 3580 macaddr8_minmax_multi_ops PGNSP PGUID 4144 774 f 774 )); DATA(insert ( 3580 macaddr8_bloom_ops PGNSP PGUID 5034 774 f 774 )); DATA(insert ( 3580 inet_minmax_ops PGNSP PGUID 4075 869 f 869 )); +DATA(insert ( 3580 inet_minmax_multi_ops PGNSP PGUID 4145 869 f 869 )); DATA(insert ( 3580 inet_inclusion_ops PGNSP PGUID 4102 869 t 869 )); DATA(insert ( 3580 inet_bloom_ops PGNSP PGUID 5035 869 f 869 )); DATA(insert ( 3580 bpchar_minmax_ops PGNSP PGUID 4076 1042 t 1042 )); DATA(insert ( 3580 bpchar_bloom_ops PGNSP PGUID 5036 1042 f 1042 )); DATA(insert ( 3580 time_minmax_ops PGNSP PGUID 4077 1083 t 1083 )); +DATA(insert ( 3580 time_minmax_multi_ops PGNSP PGUID 4135 1083 f 1083 )); DATA(insert ( 3580 time_bloom_ops PGNSP PGUID 5037 1083 f 1083 )); DATA(insert ( 3580 date_minmax_ops PGNSP PGUID 4059 1082 t 1082 )); +DATA(insert ( 3580 date_minmax_multi_ops PGNSP PGUID 4006 1082 f 1082 )); DATA(insert ( 3580 date_bloom_ops PGNSP PGUID 5038 1082 f 1082 )); DATA(insert ( 3580 timestamp_minmax_ops PGNSP PGUID 4059 1114 t 1114 )); +DATA(insert ( 3580 timestamp_minmax_multi_ops PGNSP PGUID 4006 1114 f 1114 )); DATA(insert ( 3580 timestamp_bloom_ops PGNSP PGUID 5038 1114 f 1114 )); DATA(insert ( 3580 timestamptz_minmax_ops PGNSP PGUID 4059 1184 t 1184 )); +DATA(insert ( 3580 timestamptz_minmax_multi_ops PGNSP PGUID 4006 1184 f 1184 )); DATA(insert ( 3580 timestamptz_bloom_ops PGNSP PGUID 5038 1184 f 1184 )); DATA(insert ( 3580 interval_minmax_ops PGNSP PGUID 4078 1186 t 1186 )); +DATA(insert ( 3580 interval_minmax_multi_ops PGNSP PGUID 4134 1186 f 1186 )); DATA(insert ( 3580 interval_bloom_ops PGNSP PGUID 5041 1186 f 1186 )); DATA(insert ( 3580 timetz_minmax_ops PGNSP PGUID 4058 1266 t 1266 )); +DATA(insert ( 3580 timetz_minmax_multi_ops PGNSP PGUID 4133 1266 f 1266 )); DATA(insert ( 3580 timetz_bloom_ops PGNSP PGUID 5042 1266 f 1266 )); DATA(insert ( 3580 bit_minmax_ops PGNSP PGUID 4079 1560 t 1560 )); DATA(insert ( 3580 varbit_minmax_ops PGNSP PGUID 4080 1562 t 1562 )); DATA(insert ( 3580 numeric_minmax_ops PGNSP PGUID 4055 1700 t 1700 )); +DATA(insert ( 3580 numeric_minmax_multi_ops PGNSP PGUID 4008 1700 f 1700 )); DATA(insert ( 3580 numeric_bloom_ops PGNSP PGUID 5045 1700 f 1700 )); /* no brin opclass for record, anyarray */ DATA(insert ( 3580 uuid_minmax_ops PGNSP PGUID 4081 2950 t 2950 )); +DATA(insert ( 3580 uuid_minmax_multi_ops PGNSP PGUID 4131 2950 f 2950 )); DATA(insert ( 3580 uuid_bloom_ops PGNSP PGUID 5046 2950 f 2950 )); DATA(insert ( 3580 range_inclusion_ops PGNSP PGUID 4103 3831 t 3831 )); DATA(insert ( 3580 pg_lsn_minmax_ops PGNSP PGUID 4082 3220 t 3220 )); +DATA(insert ( 3580 pg_lsn_minmax_multi_ops PGNSP PGUID 4132 3220 f 3220 )); DATA(insert ( 3580 pg_lsn_bloom_ops PGNSP PGUID 5047 3220 f 3220 )); /* no brin opclass for enum, tsvector, tsquery, jsonb */ DATA(insert ( 3580 box_inclusion_ops PGNSP PGUID 4104 603 t 603 )); diff --git a/src/include/catalog/pg_opfamily.h b/src/include/catalog/pg_opfamily.h index bc22ce3..f0ce10b 100644 --- a/src/include/catalog/pg_opfamily.h +++ b/src/include/catalog/pg_opfamily.h @@ -160,14 +160,18 @@ DATA(insert OID = 4036 ( 2742 jsonb_ops PGNSP PGUID )); DATA(insert OID = 4037 ( 2742 jsonb_path_ops PGNSP PGUID )); DATA(insert OID = 4054 ( 3580 integer_minmax_ops PGNSP PGUID )); +DATA(insert OID = 4126 ( 3580 integer_minmax_multi_ops PGNSP PGUID )); DATA(insert OID = 5024 ( 3580 integer_bloom_ops PGNSP PGUID )); DATA(insert OID = 4055 ( 3580 numeric_minmax_ops PGNSP PGUID )); +DATA(insert OID = 4008 ( 3580 numeric_minmax_multi_ops PGNSP PGUID )); DATA(insert OID = 5045 ( 3580 numeric_bloom_ops PGNSP PGUID )); DATA(insert OID = 4056 ( 3580 text_minmax_ops PGNSP PGUID )); DATA(insert OID = 5027 ( 3580 text_bloom_ops PGNSP PGUID )); DATA(insert OID = 4058 ( 3580 timetz_minmax_ops PGNSP PGUID )); +DATA(insert OID = 4133 ( 3580 timetz_minmax_multi_ops PGNSP PGUID )); DATA(insert OID = 5042 ( 3580 timetz_bloom_ops PGNSP PGUID )); DATA(insert OID = 4059 ( 3580 datetime_minmax_ops PGNSP PGUID )); +DATA(insert OID = 4006 ( 3580 datetime_minmax_multi_ops PGNSP PGUID )); DATA(insert OID = 5038 ( 3580 datetime_bloom_ops PGNSP PGUID )); DATA(insert OID = 4062 ( 3580 char_minmax_ops PGNSP PGUID )); DATA(insert OID = 5022 ( 3580 char_bloom_ops PGNSP PGUID )); @@ -176,33 +180,45 @@ DATA(insert OID = 5021 ( 3580 bytea_bloom_ops PGNSP PGUID )); DATA(insert OID = 4065 ( 3580 name_minmax_ops PGNSP PGUID )); DATA(insert OID = 5023 ( 3580 name_bloom_ops PGNSP PGUID )); DATA(insert OID = 4068 ( 3580 oid_minmax_ops PGNSP PGUID )); +DATA(insert OID = 4127 ( 3580 oid_minmax_multi_ops PGNSP PGUID )); DATA(insert OID = 5029 ( 3580 oid_bloom_ops PGNSP PGUID )); DATA(insert OID = 4069 ( 3580 tid_minmax_ops PGNSP PGUID )); +DATA(insert OID = 4128 ( 3580 tid_minmax_multi_ops PGNSP PGUID )); DATA(insert OID = 4070 ( 3580 float_minmax_ops PGNSP PGUID )); +DATA(insert OID = 4005 ( 3580 float_minmax_multi_ops PGNSP PGUID )); DATA(insert OID = 5030 ( 3580 float_bloom_ops PGNSP PGUID )); DATA(insert OID = 4072 ( 3580 abstime_minmax_ops PGNSP PGUID )); +DATA(insert OID = 4146 ( 3580 abstime_minmax_multi_ops PGNSP PGUID )); DATA(insert OID = 5031 ( 3580 abstime_bloom_ops PGNSP PGUID )); DATA(insert OID = 4073 ( 3580 reltime_minmax_ops PGNSP PGUID )); +DATA(insert OID = 4147 ( 3580 reltime_minmax_multi_ops PGNSP PGUID )); DATA(insert OID = 5032 ( 3580 reltime_bloom_ops PGNSP PGUID )); DATA(insert OID = 4074 ( 3580 macaddr_minmax_ops PGNSP PGUID )); +DATA(insert OID = 4143 ( 3580 macaddr_minmax_multi_ops PGNSP PGUID )); DATA(insert OID = 5033 ( 3580 macaddr_bloom_ops PGNSP PGUID )); DATA(insert OID = 4109 ( 3580 macaddr8_minmax_ops PGNSP PGUID )); +DATA(insert OID = 4144 ( 3580 macaddr8_minmax_multi_ops PGNSP PGUID )); DATA(insert OID = 5034 ( 3580 macaddr8_bloom_ops PGNSP PGUID )); DATA(insert OID = 4075 ( 3580 network_minmax_ops PGNSP PGUID )); +DATA(insert OID = 4145 ( 3580 network_minmax_multi_ops PGNSP PGUID )); DATA(insert OID = 4102 ( 3580 network_inclusion_ops PGNSP PGUID )); DATA(insert OID = 5035 ( 3580 network_bloom_ops PGNSP PGUID )); DATA(insert OID = 4076 ( 3580 bpchar_minmax_ops PGNSP PGUID )); DATA(insert OID = 5036 ( 3580 bpchar_bloom_ops PGNSP PGUID )); DATA(insert OID = 4077 ( 3580 time_minmax_ops PGNSP PGUID )); +DATA(insert OID = 4135 ( 3580 time_minmax_multi_ops PGNSP PGUID )); DATA(insert OID = 5037 ( 3580 time_bloom_ops PGNSP PGUID )); DATA(insert OID = 4078 ( 3580 interval_minmax_ops PGNSP PGUID )); +DATA(insert OID = 4134 ( 3580 interval_minmax_multi_ops PGNSP PGUID )); DATA(insert OID = 5041 ( 3580 interval_bloom_ops PGNSP PGUID )); DATA(insert OID = 4079 ( 3580 bit_minmax_ops PGNSP PGUID )); DATA(insert OID = 4080 ( 3580 varbit_minmax_ops PGNSP PGUID )); DATA(insert OID = 4081 ( 3580 uuid_minmax_ops PGNSP PGUID )); +DATA(insert OID = 4131 ( 3580 uuid_minmax_multi_ops PGNSP PGUID )); DATA(insert OID = 5046 ( 3580 uuid_bloom_ops PGNSP PGUID )); DATA(insert OID = 4103 ( 3580 range_inclusion_ops PGNSP PGUID )); DATA(insert OID = 4082 ( 3580 pg_lsn_minmax_ops PGNSP PGUID )); +DATA(insert OID = 4132 ( 3580 pg_lsn_minmax_multi_ops PGNSP PGUID )); DATA(insert OID = 5047 ( 3580 pg_lsn_bloom_ops PGNSP PGUID )); DATA(insert OID = 4104 ( 3580 box_inclusion_ops PGNSP PGUID )); DATA(insert OID = 5000 ( 4000 box_ops PGNSP PGUID )); diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index 7154e5e..c78c49f 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -4364,6 +4364,50 @@ DESCR("BRIN minmax support"); DATA(insert OID = 3386 ( brin_minmax_union PGNSP PGUID 12 1 0 0 0 f f f f t f i s 3 0 16 "2281 2281 2281" _null_ _null_ _null_ _null_ _null_ brin_minmax_union _null_ _null_ _null_ )); DESCR("BRIN minmax support"); +/* BRIN minmax multi */ +DATA(insert OID = 4001 ( brin_minmax_multi_opcinfo PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 2281 "2281" _null_ _null_ _null_ _null_ _null_ brin_minmax_multi_opcinfo _null_ _null_ _null_ )); +DESCR("BRIN minmax support"); +DATA(insert OID = 4002 ( brin_minmax_multi_add_value PGNSP PGUID 12 1 0 0 0 f f f f t f i s 4 0 16 "2281 2281 2281 2281" _null_ _null_ _null_ _null_ _null_ brin_minmax_multi_add_value _null_ _null_ _null_ )); +DESCR("BRIN minmax support"); +DATA(insert OID = 4003 ( brin_minmax_multi_consistent PGNSP PGUID 12 1 0 0 0 f f f f t f i s 3 0 16 "2281 2281 2281" _null_ _null_ _null_ _null_ _null_ brin_minmax_multi_consistent _null_ _null_ _null_ )); +DESCR("BRIN minmax support"); +DATA(insert OID = 4004 ( brin_minmax_multi_union PGNSP PGUID 12 1 0 0 0 f f f f t f i s 3 0 16 "2281 2281 2281" _null_ _null_ _null_ _null_ _null_ brin_minmax_multi_union _null_ _null_ _null_ )); +DESCR("BRIN minmax support"); +DATA(insert OID = 4011 ( brin_minmax_multi_distance_int2 PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "2281 2281" _null_ _null_ _null_ _null_ _null_ brin_minmax_multi_distance_int2 _null_ _null_ _null_ )); +DESCR("BRIN minmax compute distance between int2 values"); +DATA(insert OID = 4012 ( brin_minmax_multi_distance_int4 PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "2281 2281" _null_ _null_ _null_ _null_ _null_ brin_minmax_multi_distance_int4 _null_ _null_ _null_ )); +DESCR("BRIN minmax compute distance between int4 values"); +DATA(insert OID = 4013 ( brin_minmax_multi_distance_int8 PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "2281 2281" _null_ _null_ _null_ _null_ _null_ brin_minmax_multi_distance_int8 _null_ _null_ _null_ )); +DESCR("BRIN minmax compute distance between int8 values"); +DATA(insert OID = 4010 ( brin_minmax_multi_distance_float4 PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "2281 2281" _null_ _null_ _null_ _null_ _null_ brin_minmax_multi_distance_float4 _null_ _null_ _null_ )); +DESCR("BRIN minmax compute distance between float4 values"); +DATA(insert OID = 4007 ( brin_minmax_multi_distance_float8 PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "2281 2281" _null_ _null_ _null_ _null_ _null_ brin_minmax_multi_distance_float8 _null_ _null_ _null_ )); +DESCR("BRIN minmax compute distance between float8 values"); +DATA(insert OID = 4009 ( brin_minmax_multi_distance_numeric PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "2281 2281" _null_ _null_ _null_ _null_ _null_ brin_minmax_multi_distance_numeric _null_ _null_ _null_ )); +DESCR("BRIN minmax compute distance between numeric values"); +DATA(insert OID = 4129 ( brin_minmax_multi_distance_tid PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "2281 2281" _null_ _null_ _null_ _null_ _null_ brin_minmax_multi_distance_tid _null_ _null_ _null_ )); +DESCR("BRIN minmax compute distance between tid values"); +DATA(insert OID = 4130 ( brin_minmax_multi_distance_uuid PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "2281 2281" _null_ _null_ _null_ _null_ _null_ brin_minmax_multi_distance_uuid _null_ _null_ _null_ )); +DESCR("BRIN minmax compute distance between uuid values"); +DATA(insert OID = 4136 ( brin_minmax_multi_distance_time PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "2281 2281" _null_ _null_ _null_ _null_ _null_ brin_minmax_multi_distance_time _null_ _null_ _null_ )); +DESCR("BRIN minmax compute distance between time values"); +DATA(insert OID = 4137 ( brin_minmax_multi_distance_interval PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "2281 2281" _null_ _null_ _null_ _null_ _null_ brin_minmax_multi_distance_interval _null_ _null_ _null_ )); +DESCR("BRIN minmax compute distance between interval values"); +DATA(insert OID = 4138 ( brin_minmax_multi_distance_timetz PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "2281 2281" _null_ _null_ _null_ _null_ _null_ brin_minmax_multi_distance_timetz _null_ _null_ _null_ )); +DESCR("BRIN minmax compute distance between timetz values"); +DATA(insert OID = 4139 ( brin_minmax_multi_distance_pg_lsn PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "2281 2281" _null_ _null_ _null_ _null_ _null_ brin_minmax_multi_distance_pg_lsn _null_ _null_ _null_ )); +DESCR("BRIN minmax compute distance between pg_lsn values"); +DATA(insert OID = 4140 ( brin_minmax_multi_distance_macaddr PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "2281 2281" _null_ _null_ _null_ _null_ _null_ brin_minmax_multi_distance_macaddr _null_ _null_ _null_ )); +DESCR("BRIN minmax compute distance between macaddr values"); +DATA(insert OID = 4141 ( brin_minmax_multi_distance_macaddr8 PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "2281 2281" _null_ _null_ _null_ _null_ _null_ brin_minmax_multi_distance_macaddr8 _null_ _null_ _null_ )); +DESCR("BRIN minmax compute distance between macaddr8 values"); +DATA(insert OID = 4142 ( brin_minmax_multi_distance_inet PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "2281 2281" _null_ _null_ _null_ _null_ _null_ brin_minmax_multi_distance_inet _null_ _null_ _null_ )); +DESCR("BRIN minmax compute distance between inet values"); +DATA(insert OID = 4148 ( brin_minmax_multi_distance_abstime PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "2281 2281" _null_ _null_ _null_ _null_ _null_ brin_minmax_multi_distance_abstime _null_ _null_ _null_ )); +DESCR("BRIN minmax compute distance between abstime values"); +DATA(insert OID = 4149 ( brin_minmax_multi_distance_reltime PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "2281 2281" _null_ _null_ _null_ _null_ _null_ brin_minmax_multi_distance_reltime _null_ _null_ _null_ )); +DESCR("BRIN minmax compute distance between reltime values"); + /* BRIN inclusion */ DATA(insert OID = 4105 ( brin_inclusion_opcinfo PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 2281 "2281" _null_ _null_ _null_ _null_ _null_ brin_inclusion_opcinfo _null_ _null_ _null_ )); DESCR("BRIN inclusion support"); diff --git a/src/test/regress/expected/brin_multi.out b/src/test/regress/expected/brin_multi.out new file mode 100644 index 0000000..a337006 --- /dev/null +++ b/src/test/regress/expected/brin_multi.out @@ -0,0 +1,408 @@ +CREATE TABLE brintest_multi ( + int8col bigint, + int2col smallint, + int4col integer, + oidcol oid, + tidcol tid, + float4col real, + float8col double precision, + macaddrcol macaddr, + inetcol inet, + cidrcol cidr, + datecol date, + timecol time without time zone, + timestampcol timestamp without time zone, + timestamptzcol timestamp with time zone, + intervalcol interval, + timetzcol time with time zone, + numericcol numeric, + uuidcol uuid, + lsncol pg_lsn +) WITH (fillfactor=10); +INSERT INTO brintest_multi SELECT + 142857 * tenthous, + thousand, + twothousand, + unique1::oid, + format('(%s,%s)', tenthous, twenty)::tid, + (four + 1.0)/(hundred+1), + odd::float8 / (tenthous + 1), + format('%s:00:%s:00:%s:00', to_hex(odd), to_hex(even), to_hex(hundred))::macaddr, + inet '10.2.3.4/24' + tenthous, + cidr '10.2.3/24' + tenthous, + date '1995-08-15' + tenthous, + time '01:20:30' + thousand * interval '18.5 second', + timestamp '1942-07-23 03:05:09' + tenthous * interval '36.38 hours', + timestamptz '1972-10-10 03:00' + thousand * interval '1 hour', + justify_days(justify_hours(tenthous * interval '12 minutes')), + timetz '01:30:20+02' + hundred * interval '15 seconds', + tenthous::numeric(36,30) * fivethous * even / (hundred + 1), + format('%s%s-%s-%s-%s-%s%s%s', to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'))::uuid, + format('%s/%s%s', odd, even, tenthous)::pg_lsn +FROM tenk1 ORDER BY unique2 LIMIT 100; +-- throw in some NULL's and different values +INSERT INTO brintest_multi (inetcol, cidrcol) SELECT + inet 'fe80::6e40:8ff:fea9:8c46' + tenthous, + cidr 'fe80::6e40:8ff:fea9:8c46' + tenthous +FROM tenk1 ORDER BY thousand, tenthous LIMIT 25; +CREATE INDEX brinidx_multi ON brintest_multi USING brin ( + int8col int8_minmax_multi_ops, + int2col int2_minmax_multi_ops, + int4col int4_minmax_multi_ops, + oidcol oid_minmax_multi_ops, + tidcol tid_minmax_multi_ops, + float4col float4_minmax_multi_ops, + float8col float8_minmax_multi_ops, + macaddrcol macaddr_minmax_multi_ops, + inetcol inet_minmax_multi_ops, + cidrcol inet_minmax_multi_ops, + datecol date_minmax_multi_ops, + timecol time_minmax_multi_ops, + timestampcol timestamp_minmax_multi_ops, + timestamptzcol timestamptz_minmax_multi_ops, + intervalcol interval_minmax_multi_ops, + timetzcol timetz_minmax_multi_ops, + numericcol numeric_minmax_multi_ops, + uuidcol uuid_minmax_multi_ops, + lsncol pg_lsn_minmax_multi_ops +) with (pages_per_range = 1); +CREATE TABLE brinopers_multi (colname name, typ text, + op text[], value text[], matches int[], + check (cardinality(op) = cardinality(value)), + check (cardinality(op) = cardinality(matches))); +INSERT INTO brinopers_multi VALUES + ('int2col', 'int2', + '{>, >=, =, <=, <}', + '{0, 0, 800, 999, 999}', + '{100, 100, 1, 100, 100}'), + ('int2col', 'int4', + '{>, >=, =, <=, <}', + '{0, 0, 800, 999, 1999}', + '{100, 100, 1, 100, 100}'), + ('int2col', 'int8', + '{>, >=, =, <=, <}', + '{0, 0, 800, 999, 1428427143}', + '{100, 100, 1, 100, 100}'), + ('int4col', 'int2', + '{>, >=, =, <=, <}', + '{0, 0, 800, 1999, 1999}', + '{100, 100, 1, 100, 100}'), + ('int4col', 'int4', + '{>, >=, =, <=, <}', + '{0, 0, 800, 1999, 1999}', + '{100, 100, 1, 100, 100}'), + ('int4col', 'int8', + '{>, >=, =, <=, <}', + '{0, 0, 800, 1999, 1428427143}', + '{100, 100, 1, 100, 100}'), + ('int8col', 'int2', + '{>, >=}', + '{0, 0}', + '{100, 100}'), + ('int8col', 'int4', + '{>, >=}', + '{0, 0}', + '{100, 100}'), + ('int8col', 'int8', + '{>, >=, =, <=, <}', + '{0, 0, 1257141600, 1428427143, 1428427143}', + '{100, 100, 1, 100, 100}'), + ('oidcol', 'oid', + '{>, >=, =, <=, <}', + '{0, 0, 8800, 9999, 9999}', + '{100, 100, 1, 100, 100}'), + ('tidcol', 'tid', + '{>, >=, =, <=, <}', + '{"(0,0)", "(0,0)", "(8800,0)", "(9999,19)", "(9999,19)"}', + '{100, 100, 1, 100, 100}'), + ('float4col', 'float4', + '{>, >=, =, <=, <}', + '{0.0103093, 0.0103093, 1, 1, 1}', + '{100, 100, 4, 100, 96}'), + ('float4col', 'float8', + '{>, >=, =, <=, <}', + '{0.0103093, 0.0103093, 1, 1, 1}', + '{100, 100, 4, 100, 96}'), + ('float8col', 'float4', + '{>, >=, =, <=, <}', + '{0, 0, 0, 1.98, 1.98}', + '{99, 100, 1, 100, 100}'), + ('float8col', 'float8', + '{>, >=, =, <=, <}', + '{0, 0, 0, 1.98, 1.98}', + '{99, 100, 1, 100, 100}'), + ('macaddrcol', 'macaddr', + '{>, >=, =, <=, <}', + '{00:00:01:00:00:00, 00:00:01:00:00:00, 2c:00:2d:00:16:00, ff:fe:00:00:00:00, ff:fe:00:00:00:00}', + '{99, 100, 2, 100, 100}'), + ('inetcol', 'inet', + '{=, <, <=, >, >=}', + '{10.2.14.231/24, 255.255.255.255, 255.255.255.255, 0.0.0.0, 0.0.0.0}', + '{1, 100, 100, 125, 125}'), + ('inetcol', 'cidr', + '{<, <=, >, >=}', + '{255.255.255.255, 255.255.255.255, 0.0.0.0, 0.0.0.0}', + '{100, 100, 125, 125}'), + ('cidrcol', 'inet', + '{=, <, <=, >, >=}', + '{10.2.14/24, 255.255.255.255, 255.255.255.255, 0.0.0.0, 0.0.0.0}', + '{2, 100, 100, 125, 125}'), + ('cidrcol', 'cidr', + '{=, <, <=, >, >=}', + '{10.2.14/24, 255.255.255.255, 255.255.255.255, 0.0.0.0, 0.0.0.0}', + '{2, 100, 100, 125, 125}'), + ('datecol', 'date', + '{>, >=, =, <=, <}', + '{1995-08-15, 1995-08-15, 2009-12-01, 2022-12-30, 2022-12-30}', + '{100, 100, 1, 100, 100}'), + ('timecol', 'time', + '{>, >=, =, <=, <}', + '{01:20:30, 01:20:30, 02:28:57, 06:28:31.5, 06:28:31.5}', + '{100, 100, 1, 100, 100}'), + ('timestampcol', 'timestamp', + '{>, >=, =, <=, <}', + '{1942-07-23 03:05:09, 1942-07-23 03:05:09, 1964-03-24 19:26:45, 1984-01-20 22:42:21, 1984-01-20 22:42:21}', + '{100, 100, 1, 100, 100}'), + ('timestampcol', 'timestamptz', + '{>, >=, =, <=, <}', + '{1942-07-23 03:05:09, 1942-07-23 03:05:09, 1964-03-24 19:26:45, 1984-01-20 22:42:21, 1984-01-20 22:42:21}', + '{100, 100, 1, 100, 100}'), + ('timestamptzcol', 'timestamptz', + '{>, >=, =, <=, <}', + '{1972-10-10 03:00:00-04, 1972-10-10 03:00:00-04, 1972-10-19 09:00:00-07, 1972-11-20 19:00:00-03, 1972-11-20 19:00:00-03}', + '{100, 100, 1, 100, 100}'), + ('intervalcol', 'interval', + '{>, >=, =, <=, <}', + '{00:00:00, 00:00:00, 1 mons 13 days 12:24, 2 mons 23 days 07:48:00, 1 year}', + '{100, 100, 1, 100, 100}'), + ('timetzcol', 'timetz', + '{>, >=, =, <=, <}', + '{01:30:20+02, 01:30:20+02, 01:35:50+02, 23:55:05+02, 23:55:05+02}', + '{99, 100, 2, 100, 100}'), + ('numericcol', 'numeric', + '{>, >=, =, <=, <}', + '{0.00, 0.01, 2268164.347826086956521739130434782609, 99470151.9, 99470151.9}', + '{100, 100, 1, 100, 100}'), + ('uuidcol', 'uuid', + '{>, >=, =, <=, <}', + '{00040004-0004-0004-0004-000400040004, 00040004-0004-0004-0004-000400040004, 52225222-5222-5222-5222-522252225222, 99989998-9998-9998-9998-999899989998, 99989998-9998-9998-9998-999899989998}', + '{100, 100, 1, 100, 100}'), + ('lsncol', 'pg_lsn', + '{>, >=, =, <=, <, IS, IS NOT}', + '{0/1200, 0/1200, 44/455222, 198/1999799, 198/1999799, NULL, NULL}', + '{100, 100, 1, 100, 100, 25, 100}'); +DO $x$ +DECLARE + r record; + r2 record; + cond text; + idx_ctids tid[]; + ss_ctids tid[]; + count int; + plan_ok bool; + plan_line text; +BEGIN + FOR r IN SELECT colname, oper, typ, value[ordinality], matches[ordinality] FROM brinopers_multi, unnest(op) WITH ORDINALITY AS oper LOOP + + -- prepare the condition + IF r.value IS NULL THEN + cond := format('%I %s %L', r.colname, r.oper, r.value); + ELSE + cond := format('%I %s %L::%s', r.colname, r.oper, r.value, r.typ); + END IF; + + -- run the query using the brin index + SET enable_seqscan = 0; + SET enable_bitmapscan = 1; + + plan_ok := false; + FOR plan_line IN EXECUTE format($y$EXPLAIN SELECT array_agg(ctid) FROM brintest_multi WHERE %s $y$, cond) LOOP + IF plan_line LIKE '%Bitmap Heap Scan on brintest_multi%' THEN + plan_ok := true; + END IF; + END LOOP; + IF NOT plan_ok THEN + RAISE WARNING 'did not get bitmap indexscan plan for %', r; + END IF; + + EXECUTE format($y$SELECT array_agg(ctid) FROM brintest_multi WHERE %s $y$, cond) + INTO idx_ctids; + + -- run the query using a seqscan + SET enable_seqscan = 1; + SET enable_bitmapscan = 0; + + plan_ok := false; + FOR plan_line IN EXECUTE format($y$EXPLAIN SELECT array_agg(ctid) FROM brintest_multi WHERE %s $y$, cond) LOOP + IF plan_line LIKE '%Seq Scan on brintest_multi%' THEN + plan_ok := true; + END IF; + END LOOP; + IF NOT plan_ok THEN + RAISE WARNING 'did not get seqscan plan for %', r; + END IF; + + EXECUTE format($y$SELECT array_agg(ctid) FROM brintest_multi WHERE %s $y$, cond) + INTO ss_ctids; + + -- make sure both return the same results + count := array_length(idx_ctids, 1); + + IF NOT (count = array_length(ss_ctids, 1) AND + idx_ctids @> ss_ctids AND + idx_ctids <@ ss_ctids) THEN + -- report the results of each scan to make the differences obvious + RAISE WARNING 'something not right in %: count %', r, count; + SET enable_seqscan = 1; + SET enable_bitmapscan = 0; + FOR r2 IN EXECUTE 'SELECT ' || r.colname || ' FROM brintest_multi WHERE ' || cond LOOP + RAISE NOTICE 'seqscan: %', r2; + END LOOP; + + SET enable_seqscan = 0; + SET enable_bitmapscan = 1; + FOR r2 IN EXECUTE 'SELECT ' || r.colname || ' FROM brintest_multi WHERE ' || cond LOOP + RAISE NOTICE 'bitmapscan: %', r2; + END LOOP; + END IF; + + -- make sure we found expected number of matches + IF count != r.matches THEN RAISE WARNING 'unexpected number of results % for %', count, r; END IF; + END LOOP; +END; +$x$; +RESET enable_seqscan; +RESET enable_bitmapscan; +INSERT INTO brintest_multi SELECT + 142857 * tenthous, + thousand, + twothousand, + unique1::oid, + format('(%s,%s)', tenthous, twenty)::tid, + (four + 1.0)/(hundred+1), + odd::float8 / (tenthous + 1), + format('%s:00:%s:00:%s:00', to_hex(odd), to_hex(even), to_hex(hundred))::macaddr, + inet '10.2.3.4' + tenthous, + cidr '10.2.3/24' + tenthous, + date '1995-08-15' + tenthous, + time '01:20:30' + thousand * interval '18.5 second', + timestamp '1942-07-23 03:05:09' + tenthous * interval '36.38 hours', + timestamptz '1972-10-10 03:00' + thousand * interval '1 hour', + justify_days(justify_hours(tenthous * interval '12 minutes')), + timetz '01:30:20' + hundred * interval '15 seconds', + tenthous::numeric(36,30) * fivethous * even / (hundred + 1), + format('%s%s-%s-%s-%s-%s%s%s', to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'))::uuid, + format('%s/%s%s', odd, even, tenthous)::pg_lsn +FROM tenk1 ORDER BY unique2 LIMIT 5 OFFSET 5; +SELECT brin_desummarize_range('brinidx_multi', 0); + brin_desummarize_range +------------------------ + +(1 row) + +VACUUM brintest_multi; -- force a summarization cycle in brinidx +UPDATE brintest_multi SET int8col = int8col * int4col; +-- Tests for brin_summarize_new_values +SELECT brin_summarize_new_values('brintest_multi'); -- error, not an index +ERROR: "brintest_multi" is not an index +SELECT brin_summarize_new_values('tenk1_unique1'); -- error, not a BRIN index +ERROR: "tenk1_unique1" is not a BRIN index +SELECT brin_summarize_new_values('brinidx_multi'); -- ok, no change expected + brin_summarize_new_values +--------------------------- + 0 +(1 row) + +-- Tests for brin_desummarize_range +SELECT brin_desummarize_range('brinidx_multi', -1); -- error, invalid range +ERROR: block number out of range: -1 +SELECT brin_desummarize_range('brinidx_multi', 0); + brin_desummarize_range +------------------------ + +(1 row) + +SELECT brin_desummarize_range('brinidx_multi', 0); + brin_desummarize_range +------------------------ + +(1 row) + +SELECT brin_desummarize_range('brinidx_multi', 100000000); + brin_desummarize_range +------------------------ + +(1 row) + +-- Test brin_summarize_range +CREATE TABLE brin_summarize_multi ( + value int +) WITH (fillfactor=10, autovacuum_enabled=false); +CREATE INDEX brin_summarize_multi_idx ON brin_summarize_multi USING brin (value) WITH (pages_per_range=2); +-- Fill a few pages +DO $$ +DECLARE curtid tid; +BEGIN + LOOP + INSERT INTO brin_summarize_multi VALUES (1) RETURNING ctid INTO curtid; + EXIT WHEN curtid > tid '(2, 0)'; + END LOOP; +END; +$$; +-- summarize one range +SELECT brin_summarize_range('brin_summarize_multi_idx', 0); + brin_summarize_range +---------------------- + 0 +(1 row) + +-- nothing: already summarized +SELECT brin_summarize_range('brin_summarize_multi_idx', 1); + brin_summarize_range +---------------------- + 0 +(1 row) + +-- summarize one range +SELECT brin_summarize_range('brin_summarize_multi_idx', 2); + brin_summarize_range +---------------------- + 1 +(1 row) + +-- nothing: page doesn't exist in table +SELECT brin_summarize_range('brin_summarize_multi_idx', 4294967295); + brin_summarize_range +---------------------- + 0 +(1 row) + +-- invalid block number values +SELECT brin_summarize_range('brin_summarize_multi_idx', -1); +ERROR: block number out of range: -1 +SELECT brin_summarize_range('brin_summarize_multi_idx', 4294967296); +ERROR: block number out of range: 4294967296 +-- test brin cost estimates behave sanely based on correlation of values +CREATE TABLE brin_test_multi (a INT, b INT); +INSERT INTO brin_test_multi SELECT x/100,x%100 FROM generate_series(1,10000) x(x); +CREATE INDEX brin_test_multi_a_idx ON brin_test_multi USING brin (a) WITH (pages_per_range = 2); +CREATE INDEX brin_test_multi_b_idx ON brin_test_multi USING brin (b) WITH (pages_per_range = 2); +VACUUM ANALYZE brin_test_multi; +-- Ensure brin index is used when columns are perfectly correlated +EXPLAIN (COSTS OFF) SELECT * FROM brin_test_multi WHERE a = 1; + QUERY PLAN +-------------------------------------------------- + Bitmap Heap Scan on brin_test_multi + Recheck Cond: (a = 1) + -> Bitmap Index Scan on brin_test_multi_a_idx + Index Cond: (a = 1) +(4 rows) + +-- Ensure brin index is not used when values are not correlated +EXPLAIN (COSTS OFF) SELECT * FROM brin_test_multi WHERE b = 1; + QUERY PLAN +----------------------------- + Seq Scan on brin_test_multi + Filter: (b = 1) +(2 rows) + diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index 149999d..2c635d5 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -84,7 +84,7 @@ test: select_into select_distinct select_distinct_on select_implicit select_havi # ---------- # Another group of parallel tests # ---------- -test: brin brin_bloom gin gist spgist privileges init_privs security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets drop_operator password +test: brin brin_bloom brin_multi gin gist spgist privileges init_privs security_label collate matview lock replica_identity rowsecurity object_address tablesample groupingsets drop_operator password # ---------- # Another group of parallel tests diff --git a/src/test/regress/sql/brin_multi.sql b/src/test/regress/sql/brin_multi.sql new file mode 100644 index 0000000..263396f --- /dev/null +++ b/src/test/regress/sql/brin_multi.sql @@ -0,0 +1,361 @@ +CREATE TABLE brintest_multi ( + int8col bigint, + int2col smallint, + int4col integer, + oidcol oid, + tidcol tid, + float4col real, + float8col double precision, + macaddrcol macaddr, + inetcol inet, + cidrcol cidr, + datecol date, + timecol time without time zone, + timestampcol timestamp without time zone, + timestamptzcol timestamp with time zone, + intervalcol interval, + timetzcol time with time zone, + numericcol numeric, + uuidcol uuid, + lsncol pg_lsn +) WITH (fillfactor=10); + +INSERT INTO brintest_multi SELECT + 142857 * tenthous, + thousand, + twothousand, + unique1::oid, + format('(%s,%s)', tenthous, twenty)::tid, + (four + 1.0)/(hundred+1), + odd::float8 / (tenthous + 1), + format('%s:00:%s:00:%s:00', to_hex(odd), to_hex(even), to_hex(hundred))::macaddr, + inet '10.2.3.4/24' + tenthous, + cidr '10.2.3/24' + tenthous, + date '1995-08-15' + tenthous, + time '01:20:30' + thousand * interval '18.5 second', + timestamp '1942-07-23 03:05:09' + tenthous * interval '36.38 hours', + timestamptz '1972-10-10 03:00' + thousand * interval '1 hour', + justify_days(justify_hours(tenthous * interval '12 minutes')), + timetz '01:30:20+02' + hundred * interval '15 seconds', + tenthous::numeric(36,30) * fivethous * even / (hundred + 1), + format('%s%s-%s-%s-%s-%s%s%s', to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'))::uuid, + format('%s/%s%s', odd, even, tenthous)::pg_lsn +FROM tenk1 ORDER BY unique2 LIMIT 100; + +-- throw in some NULL's and different values +INSERT INTO brintest_multi (inetcol, cidrcol) SELECT + inet 'fe80::6e40:8ff:fea9:8c46' + tenthous, + cidr 'fe80::6e40:8ff:fea9:8c46' + tenthous +FROM tenk1 ORDER BY thousand, tenthous LIMIT 25; + +CREATE INDEX brinidx_multi ON brintest_multi USING brin ( + int8col int8_minmax_multi_ops, + int2col int2_minmax_multi_ops, + int4col int4_minmax_multi_ops, + oidcol oid_minmax_multi_ops, + tidcol tid_minmax_multi_ops, + float4col float4_minmax_multi_ops, + float8col float8_minmax_multi_ops, + macaddrcol macaddr_minmax_multi_ops, + inetcol inet_minmax_multi_ops, + cidrcol inet_minmax_multi_ops, + datecol date_minmax_multi_ops, + timecol time_minmax_multi_ops, + timestampcol timestamp_minmax_multi_ops, + timestamptzcol timestamptz_minmax_multi_ops, + intervalcol interval_minmax_multi_ops, + timetzcol timetz_minmax_multi_ops, + numericcol numeric_minmax_multi_ops, + uuidcol uuid_minmax_multi_ops, + lsncol pg_lsn_minmax_multi_ops +) with (pages_per_range = 1); + +CREATE TABLE brinopers_multi (colname name, typ text, + op text[], value text[], matches int[], + check (cardinality(op) = cardinality(value)), + check (cardinality(op) = cardinality(matches))); + +INSERT INTO brinopers_multi VALUES + ('int2col', 'int2', + '{>, >=, =, <=, <}', + '{0, 0, 800, 999, 999}', + '{100, 100, 1, 100, 100}'), + ('int2col', 'int4', + '{>, >=, =, <=, <}', + '{0, 0, 800, 999, 1999}', + '{100, 100, 1, 100, 100}'), + ('int2col', 'int8', + '{>, >=, =, <=, <}', + '{0, 0, 800, 999, 1428427143}', + '{100, 100, 1, 100, 100}'), + ('int4col', 'int2', + '{>, >=, =, <=, <}', + '{0, 0, 800, 1999, 1999}', + '{100, 100, 1, 100, 100}'), + ('int4col', 'int4', + '{>, >=, =, <=, <}', + '{0, 0, 800, 1999, 1999}', + '{100, 100, 1, 100, 100}'), + ('int4col', 'int8', + '{>, >=, =, <=, <}', + '{0, 0, 800, 1999, 1428427143}', + '{100, 100, 1, 100, 100}'), + ('int8col', 'int2', + '{>, >=}', + '{0, 0}', + '{100, 100}'), + ('int8col', 'int4', + '{>, >=}', + '{0, 0}', + '{100, 100}'), + ('int8col', 'int8', + '{>, >=, =, <=, <}', + '{0, 0, 1257141600, 1428427143, 1428427143}', + '{100, 100, 1, 100, 100}'), + ('oidcol', 'oid', + '{>, >=, =, <=, <}', + '{0, 0, 8800, 9999, 9999}', + '{100, 100, 1, 100, 100}'), + ('tidcol', 'tid', + '{>, >=, =, <=, <}', + '{"(0,0)", "(0,0)", "(8800,0)", "(9999,19)", "(9999,19)"}', + '{100, 100, 1, 100, 100}'), + ('float4col', 'float4', + '{>, >=, =, <=, <}', + '{0.0103093, 0.0103093, 1, 1, 1}', + '{100, 100, 4, 100, 96}'), + ('float4col', 'float8', + '{>, >=, =, <=, <}', + '{0.0103093, 0.0103093, 1, 1, 1}', + '{100, 100, 4, 100, 96}'), + ('float8col', 'float4', + '{>, >=, =, <=, <}', + '{0, 0, 0, 1.98, 1.98}', + '{99, 100, 1, 100, 100}'), + ('float8col', 'float8', + '{>, >=, =, <=, <}', + '{0, 0, 0, 1.98, 1.98}', + '{99, 100, 1, 100, 100}'), + ('macaddrcol', 'macaddr', + '{>, >=, =, <=, <}', + '{00:00:01:00:00:00, 00:00:01:00:00:00, 2c:00:2d:00:16:00, ff:fe:00:00:00:00, ff:fe:00:00:00:00}', + '{99, 100, 2, 100, 100}'), + ('inetcol', 'inet', + '{=, <, <=, >, >=}', + '{10.2.14.231/24, 255.255.255.255, 255.255.255.255, 0.0.0.0, 0.0.0.0}', + '{1, 100, 100, 125, 125}'), + ('inetcol', 'cidr', + '{<, <=, >, >=}', + '{255.255.255.255, 255.255.255.255, 0.0.0.0, 0.0.0.0}', + '{100, 100, 125, 125}'), + ('cidrcol', 'inet', + '{=, <, <=, >, >=}', + '{10.2.14/24, 255.255.255.255, 255.255.255.255, 0.0.0.0, 0.0.0.0}', + '{2, 100, 100, 125, 125}'), + ('cidrcol', 'cidr', + '{=, <, <=, >, >=}', + '{10.2.14/24, 255.255.255.255, 255.255.255.255, 0.0.0.0, 0.0.0.0}', + '{2, 100, 100, 125, 125}'), + ('datecol', 'date', + '{>, >=, =, <=, <}', + '{1995-08-15, 1995-08-15, 2009-12-01, 2022-12-30, 2022-12-30}', + '{100, 100, 1, 100, 100}'), + ('timecol', 'time', + '{>, >=, =, <=, <}', + '{01:20:30, 01:20:30, 02:28:57, 06:28:31.5, 06:28:31.5}', + '{100, 100, 1, 100, 100}'), + ('timestampcol', 'timestamp', + '{>, >=, =, <=, <}', + '{1942-07-23 03:05:09, 1942-07-23 03:05:09, 1964-03-24 19:26:45, 1984-01-20 22:42:21, 1984-01-20 22:42:21}', + '{100, 100, 1, 100, 100}'), + ('timestampcol', 'timestamptz', + '{>, >=, =, <=, <}', + '{1942-07-23 03:05:09, 1942-07-23 03:05:09, 1964-03-24 19:26:45, 1984-01-20 22:42:21, 1984-01-20 22:42:21}', + '{100, 100, 1, 100, 100}'), + ('timestamptzcol', 'timestamptz', + '{>, >=, =, <=, <}', + '{1972-10-10 03:00:00-04, 1972-10-10 03:00:00-04, 1972-10-19 09:00:00-07, 1972-11-20 19:00:00-03, 1972-11-20 19:00:00-03}', + '{100, 100, 1, 100, 100}'), + ('intervalcol', 'interval', + '{>, >=, =, <=, <}', + '{00:00:00, 00:00:00, 1 mons 13 days 12:24, 2 mons 23 days 07:48:00, 1 year}', + '{100, 100, 1, 100, 100}'), + ('timetzcol', 'timetz', + '{>, >=, =, <=, <}', + '{01:30:20+02, 01:30:20+02, 01:35:50+02, 23:55:05+02, 23:55:05+02}', + '{99, 100, 2, 100, 100}'), + ('numericcol', 'numeric', + '{>, >=, =, <=, <}', + '{0.00, 0.01, 2268164.347826086956521739130434782609, 99470151.9, 99470151.9}', + '{100, 100, 1, 100, 100}'), + ('uuidcol', 'uuid', + '{>, >=, =, <=, <}', + '{00040004-0004-0004-0004-000400040004, 00040004-0004-0004-0004-000400040004, 52225222-5222-5222-5222-522252225222, 99989998-9998-9998-9998-999899989998, 99989998-9998-9998-9998-999899989998}', + '{100, 100, 1, 100, 100}'), + ('lsncol', 'pg_lsn', + '{>, >=, =, <=, <, IS, IS NOT}', + '{0/1200, 0/1200, 44/455222, 198/1999799, 198/1999799, NULL, NULL}', + '{100, 100, 1, 100, 100, 25, 100}'); + +DO $x$ +DECLARE + r record; + r2 record; + cond text; + idx_ctids tid[]; + ss_ctids tid[]; + count int; + plan_ok bool; + plan_line text; +BEGIN + FOR r IN SELECT colname, oper, typ, value[ordinality], matches[ordinality] FROM brinopers_multi, unnest(op) WITH ORDINALITY AS oper LOOP + + -- prepare the condition + IF r.value IS NULL THEN + cond := format('%I %s %L', r.colname, r.oper, r.value); + ELSE + cond := format('%I %s %L::%s', r.colname, r.oper, r.value, r.typ); + END IF; + + -- run the query using the brin index + SET enable_seqscan = 0; + SET enable_bitmapscan = 1; + + plan_ok := false; + FOR plan_line IN EXECUTE format($y$EXPLAIN SELECT array_agg(ctid) FROM brintest_multi WHERE %s $y$, cond) LOOP + IF plan_line LIKE '%Bitmap Heap Scan on brintest_multi%' THEN + plan_ok := true; + END IF; + END LOOP; + IF NOT plan_ok THEN + RAISE WARNING 'did not get bitmap indexscan plan for %', r; + END IF; + + EXECUTE format($y$SELECT array_agg(ctid) FROM brintest_multi WHERE %s $y$, cond) + INTO idx_ctids; + + -- run the query using a seqscan + SET enable_seqscan = 1; + SET enable_bitmapscan = 0; + + plan_ok := false; + FOR plan_line IN EXECUTE format($y$EXPLAIN SELECT array_agg(ctid) FROM brintest_multi WHERE %s $y$, cond) LOOP + IF plan_line LIKE '%Seq Scan on brintest_multi%' THEN + plan_ok := true; + END IF; + END LOOP; + IF NOT plan_ok THEN + RAISE WARNING 'did not get seqscan plan for %', r; + END IF; + + EXECUTE format($y$SELECT array_agg(ctid) FROM brintest_multi WHERE %s $y$, cond) + INTO ss_ctids; + + -- make sure both return the same results + count := array_length(idx_ctids, 1); + + IF NOT (count = array_length(ss_ctids, 1) AND + idx_ctids @> ss_ctids AND + idx_ctids <@ ss_ctids) THEN + -- report the results of each scan to make the differences obvious + RAISE WARNING 'something not right in %: count %', r, count; + SET enable_seqscan = 1; + SET enable_bitmapscan = 0; + FOR r2 IN EXECUTE 'SELECT ' || r.colname || ' FROM brintest_multi WHERE ' || cond LOOP + RAISE NOTICE 'seqscan: %', r2; + END LOOP; + + SET enable_seqscan = 0; + SET enable_bitmapscan = 1; + FOR r2 IN EXECUTE 'SELECT ' || r.colname || ' FROM brintest_multi WHERE ' || cond LOOP + RAISE NOTICE 'bitmapscan: %', r2; + END LOOP; + END IF; + + -- make sure we found expected number of matches + IF count != r.matches THEN RAISE WARNING 'unexpected number of results % for %', count, r; END IF; + END LOOP; +END; +$x$; + +RESET enable_seqscan; +RESET enable_bitmapscan; + +INSERT INTO brintest_multi SELECT + 142857 * tenthous, + thousand, + twothousand, + unique1::oid, + format('(%s,%s)', tenthous, twenty)::tid, + (four + 1.0)/(hundred+1), + odd::float8 / (tenthous + 1), + format('%s:00:%s:00:%s:00', to_hex(odd), to_hex(even), to_hex(hundred))::macaddr, + inet '10.2.3.4' + tenthous, + cidr '10.2.3/24' + tenthous, + date '1995-08-15' + tenthous, + time '01:20:30' + thousand * interval '18.5 second', + timestamp '1942-07-23 03:05:09' + tenthous * interval '36.38 hours', + timestamptz '1972-10-10 03:00' + thousand * interval '1 hour', + justify_days(justify_hours(tenthous * interval '12 minutes')), + timetz '01:30:20' + hundred * interval '15 seconds', + tenthous::numeric(36,30) * fivethous * even / (hundred + 1), + format('%s%s-%s-%s-%s-%s%s%s', to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'), to_char(tenthous, 'FM0000'))::uuid, + format('%s/%s%s', odd, even, tenthous)::pg_lsn +FROM tenk1 ORDER BY unique2 LIMIT 5 OFFSET 5; + +SELECT brin_desummarize_range('brinidx_multi', 0); +VACUUM brintest_multi; -- force a summarization cycle in brinidx + +UPDATE brintest_multi SET int8col = int8col * int4col; + +-- Tests for brin_summarize_new_values +SELECT brin_summarize_new_values('brintest_multi'); -- error, not an index +SELECT brin_summarize_new_values('tenk1_unique1'); -- error, not a BRIN index +SELECT brin_summarize_new_values('brinidx_multi'); -- ok, no change expected + +-- Tests for brin_desummarize_range +SELECT brin_desummarize_range('brinidx_multi', -1); -- error, invalid range +SELECT brin_desummarize_range('brinidx_multi', 0); +SELECT brin_desummarize_range('brinidx_multi', 0); +SELECT brin_desummarize_range('brinidx_multi', 100000000); + +-- Test brin_summarize_range +CREATE TABLE brin_summarize_multi ( + value int +) WITH (fillfactor=10, autovacuum_enabled=false); +CREATE INDEX brin_summarize_multi_idx ON brin_summarize_multi USING brin (value) WITH (pages_per_range=2); +-- Fill a few pages +DO $$ +DECLARE curtid tid; +BEGIN + LOOP + INSERT INTO brin_summarize_multi VALUES (1) RETURNING ctid INTO curtid; + EXIT WHEN curtid > tid '(2, 0)'; + END LOOP; +END; +$$; + +-- summarize one range +SELECT brin_summarize_range('brin_summarize_multi_idx', 0); +-- nothing: already summarized +SELECT brin_summarize_range('brin_summarize_multi_idx', 1); +-- summarize one range +SELECT brin_summarize_range('brin_summarize_multi_idx', 2); +-- nothing: page doesn't exist in table +SELECT brin_summarize_range('brin_summarize_multi_idx', 4294967295); +-- invalid block number values +SELECT brin_summarize_range('brin_summarize_multi_idx', -1); +SELECT brin_summarize_range('brin_summarize_multi_idx', 4294967296); + + +-- test brin cost estimates behave sanely based on correlation of values +CREATE TABLE brin_test_multi (a INT, b INT); +INSERT INTO brin_test_multi SELECT x/100,x%100 FROM generate_series(1,10000) x(x); +CREATE INDEX brin_test_multi_a_idx ON brin_test_multi USING brin (a) WITH (pages_per_range = 2); +CREATE INDEX brin_test_multi_b_idx ON brin_test_multi USING brin (b) WITH (pages_per_range = 2); +VACUUM ANALYZE brin_test_multi; + +-- Ensure brin index is used when columns are perfectly correlated +EXPLAIN (COSTS OFF) SELECT * FROM brin_test_multi WHERE a = 1; +-- Ensure brin index is not used when values are not correlated +EXPLAIN (COSTS OFF) SELECT * FROM brin_test_multi WHERE b = 1; -- 2.9.5 --------------53C4B41F409B92A47ECAD3E5--