Pass all scan keys to BRIN consistent function at once

This commit changes how we pass scan keys to BRIN consistent function.
Instead of passing them one by one, we now pass all scan keys for a
given attribute at once. That makes the consistent function a bit more
complex, as it has to loop through the keys, but it does allow more
elaborate opclasses that can use multiple keys to eliminate ranges much
more effectively.

The existing BRIN opclasses (minmax, inclusion) don't really benefit
from this change. The primary purpose is to allow future opclases to
benefit from seeing all keys at once.

This does change the BRIN API, because the signature of the consistent
function changes (a new parameter with number of scan keys). So this
breaks existing opclasses, and will require supporting two variants of
the code for different PostgreSQL versions. We've considered supporting
two variants of the consistent, but we've decided not to do that.
Firstly, there's another patch that moves handling of NULL values from
the opclass, which means the opclasses need to be updated anyway.
Secondly, we're not aware of any out-of-core BRIN opclasses, so it does
not seem worth the extra complexity.

Bump catversion, because of pg_proc changes.

Author: Tomas Vondra <tomas.vondra@postgresql.org>
Reviewed-by: Alvaro Herrera <alvherre@alvh.no-ip.org>
Reviewed-by: Mark Dilger <hornschnorter@gmail.com>
Reviewed-by: Alexander Korotkov <aekorotkov@gmail.com>
Reviewed-by: John Naylor <john.naylor@enterprisedb.com>
Reviewed-by: Nikita Glukhov <n.gluhov@postgrespro.ru>
Discussion: https://postgr.es/m/c1138ead-7668-f0e1-0638-c3be3237e812@2ndquadrant.com
This commit is contained in:
Tomas Vondra 2021-03-23 00:12:19 +01:00
parent bfa2cee784
commit a1c649d889
7 changed files with 266 additions and 96 deletions

View File

@ -464,12 +464,14 @@ typedef struct BrinOpcInfo
<varlistentry> <varlistentry>
<term><function>bool consistent(BrinDesc *bdesc, BrinValues *column, <term><function>bool consistent(BrinDesc *bdesc, BrinValues *column,
ScanKey key)</function></term> ScanKey *keys, int nkeys)</function></term>
<listitem> <listitem>
<para> <para>
Returns whether the ScanKey is consistent with the given indexed Returns whether all the ScanKey entries are consistent with the given
values for a range. indexed values for a range.
The attribute number to use is passed as part of the scan key. The attribute number to use is passed as part of the scan key.
Multiple scan keys for the same attribute may be passed at once, the
number of entries is determined by the <literal>nkeys</literal> parameter.
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>

View File

@ -390,6 +390,9 @@ bringetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
BrinMemTuple *dtup; BrinMemTuple *dtup;
BrinTuple *btup = NULL; BrinTuple *btup = NULL;
Size btupsz = 0; Size btupsz = 0;
ScanKey **keys;
int *nkeys;
int keyno;
opaque = (BrinOpaque *) scan->opaque; opaque = (BrinOpaque *) scan->opaque;
bdesc = opaque->bo_bdesc; bdesc = opaque->bo_bdesc;
@ -411,6 +414,65 @@ bringetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
*/ */
consistentFn = palloc0(sizeof(FmgrInfo) * bdesc->bd_tupdesc->natts); 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. We don't know which attributes have scan
* keys, so we allocate space for all attributes. That may use more memory
* but it's probably cheaper than determining which attributes are used.
*
* XXX The widest index can have 32 attributes, so the amount of wasted
* memory is negligible. We could invent a more compact approach (with
* just space for used attributes) but that would make the matching more
* complex so it's not a good trade-off.
*/
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 attribute, so init the array of keys. */
if (!keys[keyattno - 1])
{
FmgrInfo *tmp;
/*
* This is a bit of an overkill - we don't know how many scan keys
* are there for this attribute, so we simply allocate the largest
* number possible (as if all keys were for this attribute). This
* may waste a bit of memory, but we only expect small number of
* scan keys in general, so this should be negligible, and
* repeated repalloc calls are not free either.
*/
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 */ /* allocate an initial in-memory tuple, out of the per-range memcxt */
dtup = brin_new_memtuple(bdesc); dtup = brin_new_memtuple(bdesc);
@ -471,7 +533,7 @@ bringetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
} }
else else
{ {
int keyno; int attno;
/* /*
* Compare scan keys with summary values stored for the range. * Compare scan keys with summary values stored for the range.
@ -481,50 +543,38 @@ bringetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
* no keys. * no keys.
*/ */
addrange = true; addrange = true;
for (keyno = 0; keyno < scan->numberOfKeys; keyno++) for (attno = 1; attno <= bdesc->bd_tupdesc->natts; attno++)
{ {
ScanKey key = &scan->keyData[keyno]; BrinValues *bval;
AttrNumber keyattno = key->sk_attno;
BrinValues *bval = &dtup->bt_columns[keyattno - 1];
Datum add; Datum add;
Oid collation;
/* /* skip attributes without any scan keys */
* The collation of the scan key must match the collation if (nkeys[attno - 1] == 0)
* used in the index column (but only if the search is not continue;
* 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 this column? look up consistent function */ bval = &dtup->bt_columns[attno - 1];
if (consistentFn[keyattno - 1].fn_oid == InvalidOid)
{
FmgrInfo *tmp;
tmp = index_getprocinfo(idxRel, keyattno, Assert((nkeys[attno - 1] > 0) &&
BRIN_PROCNUM_CONSISTENT); (nkeys[attno - 1] <= scan->numberOfKeys));
fmgr_info_copy(&consistentFn[keyattno - 1], tmp,
CurrentMemoryContext);
}
/* /*
* Check whether the scan key is consistent with the page * Check whether the scan key is consistent with the page
* range values; if so, have the pages in the range added * range values; if so, have the pages in the range added
* to the output bitmap. * to the output bitmap.
* *
* When there are multiple scan keys, failure to meet the * XXX We simply use the collation from the first key (it
* criteria for a single one of them is enough to discard * has to be the same for all keys for the same attribue).
* 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], collation = keys[attno - 1][0]->sk_collation;
key->sk_collation,
/* Check all keys at once */
add = FunctionCall4Coll(&consistentFn[attno - 1],
collation,
PointerGetDatum(bdesc), PointerGetDatum(bdesc),
PointerGetDatum(bval), PointerGetDatum(bval),
PointerGetDatum(key)); PointerGetDatum(keys[attno - 1]),
Int32GetDatum(nkeys[attno - 1]));
addrange = DatumGetBool(add); addrange = DatumGetBool(add);
if (!addrange) if (!addrange)
break; break;

View File

@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno,
uint16 procnum); uint16 procnum);
static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno,
Oid subtype, uint16 strategynum); Oid subtype, uint16 strategynum);
static bool inclusion_consistent_key(BrinDesc *bdesc, BrinValues *column,
ScanKey key, Oid colloid);
/* /*
@ -251,6 +253,10 @@ brin_inclusion_add_value(PG_FUNCTION_ARGS)
/* /*
* BRIN inclusion consistent function * BRIN inclusion consistent function
* *
* We inspect the IS NULL scan keys first, which allows us to make a decision
* without looking at the contents of the page range. Only when the page range
* matches the IS NULL keys, we check the regular scan keys.
*
* All of the strategies are optional. * All of the strategies are optional.
*/ */
Datum Datum
@ -258,24 +264,31 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS)
{ {
BrinDesc *bdesc = (BrinDesc *) PG_GETARG_POINTER(0); BrinDesc *bdesc = (BrinDesc *) PG_GETARG_POINTER(0);
BrinValues *column = (BrinValues *) PG_GETARG_POINTER(1); BrinValues *column = (BrinValues *) PG_GETARG_POINTER(1);
ScanKey key = (ScanKey) PG_GETARG_POINTER(2); ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2);
Oid colloid = PG_GET_COLLATION(), int nkeys = PG_GETARG_INT32(3);
subtype; Oid colloid = PG_GET_COLLATION();
Datum unionval; int keyno;
AttrNumber attno; bool has_regular_keys = false;
Datum query;
FmgrInfo *finfo;
Datum result;
Assert(key->sk_attno == column->bv_attno); /* Handle IS NULL/IS NOT NULL tests */
for (keyno = 0; keyno < nkeys; keyno++)
/* Handle IS NULL/IS NOT NULL tests. */
if (key->sk_flags & SK_ISNULL)
{ {
ScanKey key = keys[keyno];
Assert(key->sk_attno == column->bv_attno);
/* Skip regular scan keys (and remember that we have some). */
if ((!key->sk_flags & SK_ISNULL))
{
has_regular_keys = true;
continue;
}
if (key->sk_flags & SK_SEARCHNULL) if (key->sk_flags & SK_SEARCHNULL)
{ {
if (column->bv_allnulls || column->bv_hasnulls) if (column->bv_allnulls || column->bv_hasnulls)
PG_RETURN_BOOL(true); continue; /* this key is fine, continue */
PG_RETURN_BOOL(false); PG_RETURN_BOOL(false);
} }
@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS)
* only nulls. * only nulls.
*/ */
if (key->sk_flags & SK_SEARCHNOTNULL) if (key->sk_flags & SK_SEARCHNOTNULL)
PG_RETURN_BOOL(!column->bv_allnulls); {
if (column->bv_allnulls)
PG_RETURN_BOOL(false);
continue;
}
/* /*
* Neither IS NULL nor IS NOT NULL was used; assume all indexable * Neither IS NULL nor IS NOT NULL was used; assume all indexable
@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS)
PG_RETURN_BOOL(false); PG_RETURN_BOOL(false);
} }
/* If it is all nulls, it cannot possibly be consistent. */ /* If there are no regular keys, the page range is considered consistent. */
if (!has_regular_keys)
PG_RETURN_BOOL(true);
/*
* If is all nulls, it cannot possibly be consistent (at this point we
* know there are at least some regular scan keys).
*/
if (column->bv_allnulls) if (column->bv_allnulls)
PG_RETURN_BOOL(false); PG_RETURN_BOOL(false);
@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS)
if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE]))
PG_RETURN_BOOL(true); PG_RETURN_BOOL(true);
attno = key->sk_attno; /* Check that the range is consistent with all regular scan keys. */
subtype = key->sk_subtype; for (keyno = 0; keyno < nkeys; keyno++)
query = key->sk_argument; {
unionval = column->bv_values[INCLUSION_UNION]; ScanKey key = keys[keyno];
/* Skip IS NULL/IS NOT NULL keys (already handled above). */
if (key->sk_flags & SK_ISNULL)
continue;
/*
* When there are multiple scan keys, failure to meet the criteria for
* a single one of them is enough to discard the range as a whole, so
* break out of the loop as soon as a false return value is obtained.
*/
if (!inclusion_consistent_key(bdesc, column, key, colloid))
PG_RETURN_BOOL(false);
}
PG_RETURN_BOOL(true);
}
/*
* inclusion_consistent_key
* Determine if the range is consistent with a single scan key.
*/
static bool
inclusion_consistent_key(BrinDesc *bdesc, BrinValues *column, ScanKey key,
Oid colloid)
{
FmgrInfo *finfo;
AttrNumber attno = key->sk_attno;
Oid subtype = key->sk_subtype;
Datum query = key->sk_argument;
Datum unionval = column->bv_values[INCLUSION_UNION];
Datum result;
/* This should be called only for regular keys, not for IS [NOT] NULL. */
Assert(!(key->sk_flags & SK_ISNULL));
switch (key->sk_strategy) switch (key->sk_strategy)
{ {
/* /*
@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS)
finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype,
RTOverRightStrategyNumber); RTOverRightStrategyNumber);
result = FunctionCall2Coll(finfo, colloid, unionval, query); result = FunctionCall2Coll(finfo, colloid, unionval, query);
PG_RETURN_BOOL(!DatumGetBool(result)); return !DatumGetBool(result);
case RTOverLeftStrategyNumber: case RTOverLeftStrategyNumber:
finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype,
RTRightStrategyNumber); RTRightStrategyNumber);
result = FunctionCall2Coll(finfo, colloid, unionval, query); result = FunctionCall2Coll(finfo, colloid, unionval, query);
PG_RETURN_BOOL(!DatumGetBool(result)); return !DatumGetBool(result);
case RTOverRightStrategyNumber: case RTOverRightStrategyNumber:
finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype,
RTLeftStrategyNumber); RTLeftStrategyNumber);
result = FunctionCall2Coll(finfo, colloid, unionval, query); result = FunctionCall2Coll(finfo, colloid, unionval, query);
PG_RETURN_BOOL(!DatumGetBool(result)); return !DatumGetBool(result);
case RTRightStrategyNumber: case RTRightStrategyNumber:
finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype,
RTOverLeftStrategyNumber); RTOverLeftStrategyNumber);
result = FunctionCall2Coll(finfo, colloid, unionval, query); result = FunctionCall2Coll(finfo, colloid, unionval, query);
PG_RETURN_BOOL(!DatumGetBool(result)); return !DatumGetBool(result);
case RTBelowStrategyNumber: case RTBelowStrategyNumber:
finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype,
RTOverAboveStrategyNumber); RTOverAboveStrategyNumber);
result = FunctionCall2Coll(finfo, colloid, unionval, query); result = FunctionCall2Coll(finfo, colloid, unionval, query);
PG_RETURN_BOOL(!DatumGetBool(result)); return !DatumGetBool(result);
case RTOverBelowStrategyNumber: case RTOverBelowStrategyNumber:
finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype,
RTAboveStrategyNumber); RTAboveStrategyNumber);
result = FunctionCall2Coll(finfo, colloid, unionval, query); result = FunctionCall2Coll(finfo, colloid, unionval, query);
PG_RETURN_BOOL(!DatumGetBool(result)); return !DatumGetBool(result);
case RTOverAboveStrategyNumber: case RTOverAboveStrategyNumber:
finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype,
RTBelowStrategyNumber); RTBelowStrategyNumber);
result = FunctionCall2Coll(finfo, colloid, unionval, query); result = FunctionCall2Coll(finfo, colloid, unionval, query);
PG_RETURN_BOOL(!DatumGetBool(result)); return !DatumGetBool(result);
case RTAboveStrategyNumber: case RTAboveStrategyNumber:
finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype,
RTOverBelowStrategyNumber); RTOverBelowStrategyNumber);
result = FunctionCall2Coll(finfo, colloid, unionval, query); result = FunctionCall2Coll(finfo, colloid, unionval, query);
PG_RETURN_BOOL(!DatumGetBool(result)); return !DatumGetBool(result);
/* /*
* Overlap and contains strategies * Overlap and contains strategies
@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS)
finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype,
key->sk_strategy); key->sk_strategy);
result = FunctionCall2Coll(finfo, colloid, unionval, query); result = FunctionCall2Coll(finfo, colloid, unionval, query);
PG_RETURN_DATUM(result); return DatumGetBool(result);
/* /*
* Contained by strategies * Contained by strategies
@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS)
RTOverlapStrategyNumber); RTOverlapStrategyNumber);
result = FunctionCall2Coll(finfo, colloid, unionval, query); result = FunctionCall2Coll(finfo, colloid, unionval, query);
if (DatumGetBool(result)) if (DatumGetBool(result))
PG_RETURN_BOOL(true); return true;
PG_RETURN_DATUM(column->bv_values[INCLUSION_CONTAINS_EMPTY]); return DatumGetBool(column->bv_values[INCLUSION_CONTAINS_EMPTY]);
/* /*
* Adjacent strategy * Adjacent strategy
@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS)
RTOverlapStrategyNumber); RTOverlapStrategyNumber);
result = FunctionCall2Coll(finfo, colloid, unionval, query); result = FunctionCall2Coll(finfo, colloid, unionval, query);
if (DatumGetBool(result)) if (DatumGetBool(result))
PG_RETURN_BOOL(true); return true;
finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype,
RTAdjacentStrategyNumber); RTAdjacentStrategyNumber);
result = FunctionCall2Coll(finfo, colloid, unionval, query); result = FunctionCall2Coll(finfo, colloid, unionval, query);
PG_RETURN_DATUM(result); return DatumGetBool(result);
/* /*
* Basic comparison strategies * Basic comparison strategies
@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS)
RTRightStrategyNumber); RTRightStrategyNumber);
result = FunctionCall2Coll(finfo, colloid, unionval, query); result = FunctionCall2Coll(finfo, colloid, unionval, query);
if (!DatumGetBool(result)) if (!DatumGetBool(result))
PG_RETURN_BOOL(true); return true;
PG_RETURN_DATUM(column->bv_values[INCLUSION_CONTAINS_EMPTY]); return DatumGetBool(column->bv_values[INCLUSION_CONTAINS_EMPTY]);
case RTSameStrategyNumber: case RTSameStrategyNumber:
case RTEqualStrategyNumber: case RTEqualStrategyNumber:
@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS)
RTContainsStrategyNumber); RTContainsStrategyNumber);
result = FunctionCall2Coll(finfo, colloid, unionval, query); result = FunctionCall2Coll(finfo, colloid, unionval, query);
if (DatumGetBool(result)) if (DatumGetBool(result))
PG_RETURN_BOOL(true); return true;
PG_RETURN_DATUM(column->bv_values[INCLUSION_CONTAINS_EMPTY]); return DatumGetBool(column->bv_values[INCLUSION_CONTAINS_EMPTY]);
case RTGreaterEqualStrategyNumber: case RTGreaterEqualStrategyNumber:
finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype,
RTLeftStrategyNumber); RTLeftStrategyNumber);
result = FunctionCall2Coll(finfo, colloid, unionval, query); result = FunctionCall2Coll(finfo, colloid, unionval, query);
if (!DatumGetBool(result)) if (!DatumGetBool(result))
PG_RETURN_BOOL(true); return true;
PG_RETURN_DATUM(column->bv_values[INCLUSION_CONTAINS_EMPTY]); return DatumGetBool(column->bv_values[INCLUSION_CONTAINS_EMPTY]);
case RTGreaterStrategyNumber: case RTGreaterStrategyNumber:
/* no need to check for empty elements */ /* no need to check for empty elements */
finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype,
RTLeftStrategyNumber); RTLeftStrategyNumber);
result = FunctionCall2Coll(finfo, colloid, unionval, query); result = FunctionCall2Coll(finfo, colloid, unionval, query);
PG_RETURN_BOOL(!DatumGetBool(result)); return !DatumGetBool(result);
default: default:
/* shouldn't happen */ /* shouldn't happen */
elog(ERROR, "invalid strategy number %d", key->sk_strategy); elog(ERROR, "invalid strategy number %d", key->sk_strategy);
PG_RETURN_BOOL(false); return false;
} }
} }

View File

@ -30,6 +30,8 @@ typedef struct MinmaxOpaque
static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno,
Oid subtype, uint16 strategynum); Oid subtype, uint16 strategynum);
static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column,
ScanKey key, Oid colloid);
Datum Datum
@ -140,29 +142,41 @@ brin_minmax_add_value(PG_FUNCTION_ARGS)
* Given an index tuple corresponding to a certain page range and a scan key, * 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 * return whether the scan key is consistent with the index tuple's min/max
* values. Return true if so, false otherwise. * values. Return true if so, false otherwise.
*
* We inspect the IS NULL scan keys first, which allows us to make a decision
* without looking at the contents of the page range. Only when the page range
* matches all those keys, we check the regular scan keys.
*/ */
Datum Datum
brin_minmax_consistent(PG_FUNCTION_ARGS) brin_minmax_consistent(PG_FUNCTION_ARGS)
{ {
BrinDesc *bdesc = (BrinDesc *) PG_GETARG_POINTER(0); BrinDesc *bdesc = (BrinDesc *) PG_GETARG_POINTER(0);
BrinValues *column = (BrinValues *) PG_GETARG_POINTER(1); BrinValues *column = (BrinValues *) PG_GETARG_POINTER(1);
ScanKey key = (ScanKey) PG_GETARG_POINTER(2); ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2);
Oid colloid = PG_GET_COLLATION(), int nkeys = PG_GETARG_INT32(3);
subtype; Oid colloid = PG_GET_COLLATION();
AttrNumber attno; int keyno;
Datum value; bool has_regular_keys = false;
Datum matches;
FmgrInfo *finfo;
Assert(key->sk_attno == column->bv_attno);
/* handle IS NULL/IS NOT NULL tests */ /* handle IS NULL/IS NOT NULL tests */
if (key->sk_flags & SK_ISNULL) for (keyno = 0; keyno < nkeys; keyno++)
{ {
ScanKey key = keys[keyno];
Assert(key->sk_attno == column->bv_attno);
/* Skip regular scan keys (and remember that we have some). */
if ((!key->sk_flags & SK_ISNULL))
{
has_regular_keys = true;
continue;
}
if (key->sk_flags & SK_SEARCHNULL) if (key->sk_flags & SK_SEARCHNULL)
{ {
if (column->bv_allnulls || column->bv_hasnulls) if (column->bv_allnulls || column->bv_hasnulls)
PG_RETURN_BOOL(true); continue; /* this key is fine, continue */
PG_RETURN_BOOL(false); PG_RETURN_BOOL(false);
} }
@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS)
* only nulls. * only nulls.
*/ */
if (key->sk_flags & SK_SEARCHNOTNULL) if (key->sk_flags & SK_SEARCHNOTNULL)
PG_RETURN_BOOL(!column->bv_allnulls); {
if (column->bv_allnulls)
PG_RETURN_BOOL(false);
continue;
}
/* /*
* Neither IS NULL nor IS NOT NULL was used; assume all indexable * Neither IS NULL nor IS NOT NULL was used; assume all indexable
@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS)
PG_RETURN_BOOL(false); PG_RETURN_BOOL(false);
} }
/* if the range is all empty, it cannot possibly be consistent */ /* If there are no regular keys, the page range is considered consistent. */
if (!has_regular_keys)
PG_RETURN_BOOL(true);
/*
* If is all nulls, it cannot possibly be consistent (at this point we
* know there are at least some regular scan keys).
*/
if (column->bv_allnulls) if (column->bv_allnulls)
PG_RETURN_BOOL(false); PG_RETURN_BOOL(false);
attno = key->sk_attno; /* Check that the range is consistent with all scan keys. */
subtype = key->sk_subtype; for (keyno = 0; keyno < nkeys; keyno++)
value = key->sk_argument; {
ScanKey key = keys[keyno];
/* ignore IS NULL/IS NOT NULL tests handled above */
if (key->sk_flags & SK_ISNULL)
continue;
/*
* When there are multiple scan keys, failure to meet the criteria for
* a single one of them is enough to discard the range as a whole, so
* break out of the loop as soon as a false return value is obtained.
*/
if (!minmax_consistent_key(bdesc, column, key, colloid))
PG_RETURN_DATUM(false);;
}
PG_RETURN_DATUM(true);
}
/*
* minmax_consistent_key
* Determine if the range is consistent with a single scan key.
*/
static bool
minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, ScanKey key,
Oid colloid)
{
FmgrInfo *finfo;
AttrNumber attno = key->sk_attno;
Oid subtype = key->sk_subtype;
Datum value = key->sk_argument;
Datum matches;
switch (key->sk_strategy) switch (key->sk_strategy)
{ {
case BTLessStrategyNumber: case BTLessStrategyNumber:
@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS)
break; break;
} }
PG_RETURN_DATUM(matches); return DatumGetBool(matches);
} }
/* /*

View File

@ -97,8 +97,8 @@ brinvalidate(Oid opclassoid)
break; break;
case BRIN_PROCNUM_CONSISTENT: case BRIN_PROCNUM_CONSISTENT:
ok = check_amproc_signature(procform->amproc, BOOLOID, true, ok = check_amproc_signature(procform->amproc, BOOLOID, true,
3, 3, INTERNALOID, INTERNALOID, 4, 4, INTERNALOID, INTERNALOID,
INTERNALOID); INTERNALOID, INT4OID);
break; break;
case BRIN_PROCNUM_UNION: case BRIN_PROCNUM_UNION:
ok = check_amproc_signature(procform->amproc, BOOLOID, true, ok = check_amproc_signature(procform->amproc, BOOLOID, true,

View File

@ -53,6 +53,6 @@
*/ */
/* yyyymmddN */ /* yyyymmddN */
#define CATALOG_VERSION_NO 202103191 #define CATALOG_VERSION_NO 202103231
#endif #endif

View File

@ -8215,7 +8215,7 @@
prosrc => 'brin_minmax_add_value' }, prosrc => 'brin_minmax_add_value' },
{ oid => '3385', descr => 'BRIN minmax support', { oid => '3385', descr => 'BRIN minmax support',
proname => 'brin_minmax_consistent', prorettype => 'bool', proname => 'brin_minmax_consistent', prorettype => 'bool',
proargtypes => 'internal internal internal', proargtypes => 'internal internal internal int4',
prosrc => 'brin_minmax_consistent' }, prosrc => 'brin_minmax_consistent' },
{ oid => '3386', descr => 'BRIN minmax support', { oid => '3386', descr => 'BRIN minmax support',
proname => 'brin_minmax_union', prorettype => 'bool', proname => 'brin_minmax_union', prorettype => 'bool',
@ -8231,7 +8231,7 @@
prosrc => 'brin_inclusion_add_value' }, prosrc => 'brin_inclusion_add_value' },
{ oid => '4107', descr => 'BRIN inclusion support', { oid => '4107', descr => 'BRIN inclusion support',
proname => 'brin_inclusion_consistent', prorettype => 'bool', proname => 'brin_inclusion_consistent', prorettype => 'bool',
proargtypes => 'internal internal internal', proargtypes => 'internal internal internal int4',
prosrc => 'brin_inclusion_consistent' }, prosrc => 'brin_inclusion_consistent' },
{ oid => '4108', descr => 'BRIN inclusion support', { oid => '4108', descr => 'BRIN inclusion support',
proname => 'brin_inclusion_union', prorettype => 'bool', proname => 'brin_inclusion_union', prorettype => 'bool',