agora inbox for [email protected]help / color / mirror / Atom feed
[PATCH 3/8] Process all scan keys in existing BRIN opclasses 75+ messages / 4 participants [nested] [flat]
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 506689d8ac..ad11a2b66f 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8206,7 +8206,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8222,7 +8222,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------556A1DC61262AF15641DB204 Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-suppor-20210308b.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-suppor-20210308b.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 506689d8ac..ad11a2b66f 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8206,7 +8206,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8222,7 +8222,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------556A1DC61262AF15641DB204 Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-suppor-20210308b.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-suppor-20210308b.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 59d2b71ca9..93bdb5ec83 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8194,7 +8194,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8210,7 +8210,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------AC3FFB734F56C5F52D422EFC Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210305.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210305.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 506689d8ac..ad11a2b66f 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8206,7 +8206,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8222,7 +8222,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------82D677E9F94AC7574E460205 Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210308.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210308.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 59d2b71ca9..93bdb5ec83 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8194,7 +8194,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8210,7 +8210,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------AC3FFB734F56C5F52D422EFC Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210305.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210305.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 506689d8ac..ad11a2b66f 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8206,7 +8206,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8222,7 +8222,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------82D677E9F94AC7574E460205 Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210308.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210308.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 506689d8ac..ad11a2b66f 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8206,7 +8206,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8222,7 +8222,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------556A1DC61262AF15641DB204 Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-suppor-20210308b.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-suppor-20210308b.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 59d2b71ca9..93bdb5ec83 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8194,7 +8194,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8210,7 +8210,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------AC3FFB734F56C5F52D422EFC Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210305.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210305.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 506689d8ac..ad11a2b66f 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8206,7 +8206,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8222,7 +8222,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------82D677E9F94AC7574E460205 Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210308.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210308.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 506689d8ac..ad11a2b66f 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8206,7 +8206,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8222,7 +8222,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------556A1DC61262AF15641DB204 Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-suppor-20210308b.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-suppor-20210308b.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 59d2b71ca9..93bdb5ec83 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8194,7 +8194,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8210,7 +8210,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------AC3FFB734F56C5F52D422EFC Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210305.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210305.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 506689d8ac..ad11a2b66f 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8206,7 +8206,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8222,7 +8222,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------82D677E9F94AC7574E460205 Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210308.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210308.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 506689d8ac..ad11a2b66f 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8206,7 +8206,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8222,7 +8222,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------556A1DC61262AF15641DB204 Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-suppor-20210308b.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-suppor-20210308b.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 59d2b71ca9..93bdb5ec83 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8194,7 +8194,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8210,7 +8210,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------AC3FFB734F56C5F52D422EFC Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210305.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210305.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 506689d8ac..ad11a2b66f 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8206,7 +8206,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8222,7 +8222,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------82D677E9F94AC7574E460205 Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210308.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210308.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 506689d8ac..ad11a2b66f 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8206,7 +8206,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8222,7 +8222,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------556A1DC61262AF15641DB204 Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-suppor-20210308b.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-suppor-20210308b.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 59d2b71ca9..93bdb5ec83 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8194,7 +8194,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8210,7 +8210,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------AC3FFB734F56C5F52D422EFC Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210305.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210305.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 506689d8ac..ad11a2b66f 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8206,7 +8206,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8222,7 +8222,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------82D677E9F94AC7574E460205 Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210308.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210308.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 506689d8ac..ad11a2b66f 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8206,7 +8206,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8222,7 +8222,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------556A1DC61262AF15641DB204 Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-suppor-20210308b.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-suppor-20210308b.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 59d2b71ca9..93bdb5ec83 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8194,7 +8194,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8210,7 +8210,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------AC3FFB734F56C5F52D422EFC Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210305.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210305.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 506689d8ac..ad11a2b66f 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8206,7 +8206,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8222,7 +8222,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------82D677E9F94AC7574E460205 Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210308.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210308.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 506689d8ac..ad11a2b66f 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8206,7 +8206,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8222,7 +8222,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------556A1DC61262AF15641DB204 Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-suppor-20210308b.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-suppor-20210308b.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 59d2b71ca9..93bdb5ec83 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8194,7 +8194,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8210,7 +8210,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------AC3FFB734F56C5F52D422EFC Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210305.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210305.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 506689d8ac..ad11a2b66f 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8206,7 +8206,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8222,7 +8222,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------82D677E9F94AC7574E460205 Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210308.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210308.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 506689d8ac..ad11a2b66f 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8206,7 +8206,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8222,7 +8222,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------556A1DC61262AF15641DB204 Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-suppor-20210308b.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-suppor-20210308b.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 59d2b71ca9..93bdb5ec83 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8194,7 +8194,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8210,7 +8210,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------AC3FFB734F56C5F52D422EFC Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210305.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210305.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 506689d8ac..ad11a2b66f 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8206,7 +8206,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8222,7 +8222,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------82D677E9F94AC7574E460205 Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210308.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210308.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 506689d8ac..ad11a2b66f 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8206,7 +8206,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8222,7 +8222,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------556A1DC61262AF15641DB204 Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-suppor-20210308b.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-suppor-20210308b.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 59d2b71ca9..93bdb5ec83 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8194,7 +8194,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8210,7 +8210,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------AC3FFB734F56C5F52D422EFC Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210305.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210305.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 506689d8ac..ad11a2b66f 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8206,7 +8206,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8222,7 +8222,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------82D677E9F94AC7574E460205 Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210308.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210308.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 506689d8ac..ad11a2b66f 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8206,7 +8206,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8222,7 +8222,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------556A1DC61262AF15641DB204 Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-suppor-20210308b.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-suppor-20210308b.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 59d2b71ca9..93bdb5ec83 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8194,7 +8194,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8210,7 +8210,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------AC3FFB734F56C5F52D422EFC Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210305.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210305.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 506689d8ac..ad11a2b66f 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8206,7 +8206,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8222,7 +8222,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------82D677E9F94AC7574E460205 Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210308.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210308.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 506689d8ac..ad11a2b66f 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8206,7 +8206,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8222,7 +8222,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------556A1DC61262AF15641DB204 Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-suppor-20210308b.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-suppor-20210308b.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 59d2b71ca9..93bdb5ec83 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8194,7 +8194,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8210,7 +8210,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------AC3FFB734F56C5F52D422EFC Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210305.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210305.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 506689d8ac..ad11a2b66f 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8206,7 +8206,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8222,7 +8222,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------82D677E9F94AC7574E460205 Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210308.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210308.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 506689d8ac..ad11a2b66f 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8206,7 +8206,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8222,7 +8222,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------556A1DC61262AF15641DB204 Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-suppor-20210308b.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-suppor-20210308b.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 59d2b71ca9..93bdb5ec83 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8194,7 +8194,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8210,7 +8210,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------AC3FFB734F56C5F52D422EFC Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210305.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210305.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 506689d8ac..ad11a2b66f 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8206,7 +8206,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8222,7 +8222,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------82D677E9F94AC7574E460205 Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210308.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210308.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 506689d8ac..ad11a2b66f 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8206,7 +8206,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8222,7 +8222,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------556A1DC61262AF15641DB204 Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-suppor-20210308b.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-suppor-20210308b.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 59d2b71ca9..93bdb5ec83 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8194,7 +8194,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8210,7 +8210,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------AC3FFB734F56C5F52D422EFC Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210305.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210305.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 506689d8ac..ad11a2b66f 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8206,7 +8206,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8222,7 +8222,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------82D677E9F94AC7574E460205 Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210308.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210308.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 506689d8ac..ad11a2b66f 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8206,7 +8206,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8222,7 +8222,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------556A1DC61262AF15641DB204 Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-suppor-20210308b.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-suppor-20210308b.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 59d2b71ca9..93bdb5ec83 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8194,7 +8194,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8210,7 +8210,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------AC3FFB734F56C5F52D422EFC Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210305.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210305.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 506689d8ac..ad11a2b66f 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8206,7 +8206,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8222,7 +8222,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------82D677E9F94AC7574E460205 Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210308.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210308.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 506689d8ac..ad11a2b66f 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8206,7 +8206,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8222,7 +8222,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------556A1DC61262AF15641DB204 Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-suppor-20210308b.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-suppor-20210308b.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 59d2b71ca9..93bdb5ec83 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8194,7 +8194,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8210,7 +8210,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------AC3FFB734F56C5F52D422EFC Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210305.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210305.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 506689d8ac..ad11a2b66f 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8206,7 +8206,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8222,7 +8222,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------82D677E9F94AC7574E460205 Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210308.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210308.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 506689d8ac..ad11a2b66f 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8206,7 +8206,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8222,7 +8222,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------556A1DC61262AF15641DB204 Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-suppor-20210308b.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-suppor-20210308b.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 59d2b71ca9..93bdb5ec83 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8194,7 +8194,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8210,7 +8210,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------AC3FFB734F56C5F52D422EFC Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210305.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210305.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 506689d8ac..ad11a2b66f 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8206,7 +8206,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8222,7 +8222,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------82D677E9F94AC7574E460205 Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210308.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210308.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 506689d8ac..ad11a2b66f 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8206,7 +8206,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8222,7 +8222,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------556A1DC61262AF15641DB204 Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-suppor-20210308b.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-suppor-20210308b.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 59d2b71ca9..93bdb5ec83 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8194,7 +8194,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8210,7 +8210,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------AC3FFB734F56C5F52D422EFC Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210305.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210305.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 506689d8ac..ad11a2b66f 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8206,7 +8206,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8222,7 +8222,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------82D677E9F94AC7574E460205 Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210308.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210308.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 506689d8ac..ad11a2b66f 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8206,7 +8206,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8222,7 +8222,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------556A1DC61262AF15641DB204 Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-suppor-20210308b.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-suppor-20210308b.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 59d2b71ca9..93bdb5ec83 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8194,7 +8194,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8210,7 +8210,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------AC3FFB734F56C5F52D422EFC Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210305.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210305.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 506689d8ac..ad11a2b66f 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8206,7 +8206,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8222,7 +8222,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------82D677E9F94AC7574E460205 Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210308.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210308.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 506689d8ac..ad11a2b66f 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8206,7 +8206,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8222,7 +8222,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------556A1DC61262AF15641DB204 Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-suppor-20210308b.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-suppor-20210308b.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 59d2b71ca9..93bdb5ec83 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8194,7 +8194,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8210,7 +8210,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------AC3FFB734F56C5F52D422EFC Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210305.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210305.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 506689d8ac..ad11a2b66f 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8206,7 +8206,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8222,7 +8222,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------82D677E9F94AC7574E460205 Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210308.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210308.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 506689d8ac..ad11a2b66f 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8206,7 +8206,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8222,7 +8222,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------556A1DC61262AF15641DB204 Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-suppor-20210308b.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-suppor-20210308b.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 59d2b71ca9..93bdb5ec83 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8194,7 +8194,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8210,7 +8210,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------AC3FFB734F56C5F52D422EFC Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210305.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210305.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 506689d8ac..ad11a2b66f 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8206,7 +8206,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8222,7 +8222,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------82D677E9F94AC7574E460205 Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210308.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210308.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 506689d8ac..ad11a2b66f 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8206,7 +8206,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8222,7 +8222,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------556A1DC61262AF15641DB204 Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-suppor-20210308b.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-suppor-20210308b.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 59d2b71ca9..93bdb5ec83 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8194,7 +8194,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8210,7 +8210,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------AC3FFB734F56C5F52D422EFC Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210305.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210305.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 506689d8ac..ad11a2b66f 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8206,7 +8206,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8222,7 +8222,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------82D677E9F94AC7574E460205 Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210308.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210308.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 506689d8ac..ad11a2b66f 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8206,7 +8206,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8222,7 +8222,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------556A1DC61262AF15641DB204 Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-suppor-20210308b.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-suppor-20210308b.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 59d2b71ca9..93bdb5ec83 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8194,7 +8194,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8210,7 +8210,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------AC3FFB734F56C5F52D422EFC Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210305.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210305.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 506689d8ac..ad11a2b66f 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8206,7 +8206,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8222,7 +8222,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------82D677E9F94AC7574E460205 Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210308.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210308.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 506689d8ac..ad11a2b66f 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8206,7 +8206,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8222,7 +8222,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------556A1DC61262AF15641DB204 Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-suppor-20210308b.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-suppor-20210308b.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 59d2b71ca9..93bdb5ec83 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8194,7 +8194,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8210,7 +8210,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------AC3FFB734F56C5F52D422EFC Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210305.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210305.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* [PATCH 3/8] Process all scan keys in existing BRIN opclasses @ 2021-03-04 23:45 Tomas Vondra <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Tomas Vondra @ 2021-03-04 23:45 UTC (permalink / raw) This modifies the existing BRIN opclasses (minmax, inclusion) although those don't really benefit from this change. The primary purpose of this is to allow more advanced opclases in the future. --- src/backend/access/brin/brin_inclusion.c | 140 ++++++++++++++++------- src/backend/access/brin/brin_minmax.c | 92 ++++++++++++--- src/include/catalog/pg_proc.dat | 4 +- 3 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c index 12e5bddd1f..a260074c91 100644 --- a/src/backend/access/brin/brin_inclusion.c +++ b/src/backend/access/brin/brin_inclusion.c @@ -85,6 +85,8 @@ static FmgrInfo *inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum); static FmgrInfo *inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, 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 * + * 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. + * * All of the strategies are optional. */ Datum @@ -258,24 +264,31 @@ 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - Datum unionval; - AttrNumber attno; - Datum query; - FmgrInfo *finfo; - Datum result; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; - /* Handle IS NULL/IS NOT NULL tests. */ - if (key->sk_flags & SK_ISNULL) + /* Handle IS NULL/IS NOT NULL tests */ + 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -284,7 +297,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -293,7 +311,14 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); @@ -301,10 +326,45 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(true); - attno = key->sk_attno; - subtype = key->sk_subtype; - query = key->sk_argument; - unionval = column->bv_values[INCLUSION_UNION]; + /* Check that the range is consistent with all regular scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { /* @@ -324,49 +384,49 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_BOOL(!DatumGetBool(result)); + return !DatumGetBool(result); /* * Overlap and contains strategies @@ -384,7 +444,7 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Contained by strategies @@ -404,9 +464,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 @@ -423,12 +483,12 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) - PG_RETURN_BOOL(true); + return true; finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); - PG_RETURN_DATUM(result); + return DatumGetBool(result); /* * Basic comparison strategies @@ -458,9 +518,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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 RTEqualStrategyNumber: @@ -468,30 +528,30 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS) RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); 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: /* 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)); + return !DatumGetBool(result); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); - PG_RETURN_BOOL(false); + return false; } } diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c index 2ffbd9bf0d..e116084a02 100644 --- a/src/backend/access/brin/brin_minmax.c +++ b/src/backend/access/brin/brin_minmax.c @@ -30,6 +30,8 @@ typedef struct MinmaxOpaque static FmgrInfo *minmax_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum); +static bool minmax_consistent_key(BrinDesc *bdesc, BrinValues *column, + ScanKey key, Oid colloid); 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, * return whether the scan key is consistent with the index tuple's min/max * 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 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); - Oid colloid = PG_GET_COLLATION(), - subtype; - AttrNumber attno; - Datum value; - Datum matches; - FmgrInfo *finfo; - - Assert(key->sk_attno == column->bv_attno); + ScanKey *keys = (ScanKey *) PG_GETARG_POINTER(2); + int nkeys = PG_GETARG_INT32(3); + Oid colloid = PG_GET_COLLATION(); + int keyno; + bool has_regular_keys = false; /* 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 (column->bv_allnulls || column->bv_hasnulls) - PG_RETURN_BOOL(true); + continue; /* this key is fine, continue */ + PG_RETURN_BOOL(false); } @@ -171,7 +185,12 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) * only nulls. */ 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 @@ -180,13 +199,52 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) 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) PG_RETURN_BOOL(false); - attno = key->sk_attno; - subtype = key->sk_subtype; - value = key->sk_argument; + /* Check that the range is consistent with all scan keys. */ + for (keyno = 0; keyno < nkeys; keyno++) + { + 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) { case BTLessStrategyNumber: @@ -229,7 +287,7 @@ brin_minmax_consistent(PG_FUNCTION_ARGS) break; } - PG_RETURN_DATUM(matches); + return DatumGetBool(matches); } /* diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 506689d8ac..ad11a2b66f 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8206,7 +8206,7 @@ prosrc => 'brin_minmax_add_value' }, { oid => '3385', descr => 'BRIN minmax support', proname => 'brin_minmax_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_minmax_consistent' }, { oid => '3386', descr => 'BRIN minmax support', proname => 'brin_minmax_union', prorettype => 'bool', @@ -8222,7 +8222,7 @@ prosrc => 'brin_inclusion_add_value' }, { oid => '4107', descr => 'BRIN inclusion support', proname => 'brin_inclusion_consistent', prorettype => 'bool', - proargtypes => 'internal internal internal', + proargtypes => 'internal internal internal int4', prosrc => 'brin_inclusion_consistent' }, { oid => '4108', descr => 'BRIN inclusion support', proname => 'brin_inclusion_union', prorettype => 'bool', -- 2.26.2 --------------82D677E9F94AC7574E460205 Content-Type: text/x-patch; charset=UTF-8; name="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210308.patch" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename*0="0004-Move-IS-NOT-NULL-handling-from-BRIN-support-20210308.pa"; filename*1="tch" ^ permalink raw reply [nested|flat] 75+ messages in thread
* Re: [PoC] Improve dead tuple storage for lazy vacuum @ 2023-01-09 08:59 John Naylor <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: John Naylor @ 2023-01-09 08:59 UTC (permalink / raw) To: Masahiko Sawada <[email protected]>; +Cc: Nathan Bossart <[email protected]>; Andres Freund <[email protected]>; Matthias van de Meent <[email protected]>; Yura Sokolov <[email protected]>; pgsql-hackers > [working on templating] In the end, I decided to base my effort on v8, and not v12 (based on one of my less-well-thought-out ideas). The latter was a good experiment, but it did not lead to an increase in readability as I had hoped. The attached v17 is still rough, but it's in good enough shape to evaluate a mostly-complete templating implementation. Part of what I didn't like about v8 was distinctions like "node" vs "nodep", which hinder readability. I've used "allocnode" for some cases where it makes sense, which is translated to "newnode" for the local pointer. Some places I just gave up and used "nodep" for parameters like in v8, just to get it done. We can revisit naming later. Not done yet: - get_handle() is not implemented - rt_attach is defined but unused - grow_node_kind() was hackishly removed, but could be turned into a macro (or function that writes to 2 pointers) - node_update_inner() is back, now that we can share a template with "search". Seems easier to read, and I suspect this is easier for the compiler. - the value type should really be a template macro, but is still hard-coded to uint64 - I think it's okay if the key is hard coded for PG16: If some use case needs more than uint64, we could consider "single-value leaves" with varlen keys as a template option. - benchmark tests not updated v13-0007 had some changes to the regression tests, but I haven't included those. The tests from v13-0003 do pass, both locally and shared. I quickly hacked together changing shared/local tests by hand (need to recompile), but it would be good for maintainability if tests could run once each with local and shmem, but use the same "expected" test output. Also, I didn't look to see if there were any changes in v14/15 that didn't have to do with precise memory accounting. At this point, Masahiko, I'd appreciate your feedback on whether this is an improvement at all (or at least a good base for improvement), especially for integrating with the TID store. I think there are some advantages to the template approach. One possible disadvantage is needing separate functions for each local and shared memory. If we go this route, I do think the TID store should invoke the template as static functions. I'm not quite comfortable with a global function that may not fit well with future use cases. One review point I'll mention: Somehow I didn't notice there is no use for the "chunk" field in the rt_node type -- it's only set to zero and copied when growing. What is the purpose? Removing it would allow the smallest node to take up only 32 bytes with a fanout of 3, by eliminating padding. Also, v17-0005 has an optimization/simplification for growing into node125 (my version needs an assertion or fallback, but works well now), found by another reading of Andres' prototype There is a lot of good engineering there, we should try to preserve it. -- John Naylor EDB: http://www.enterprisedb.com Attachments: [text/x-patch] v17-0005-Template-out-inner-and-leaf-nodes.patch (45.4K, ../../CAFBsxsFMuyUd48SXy1VMFvpVBHh6B6300_UfTSiPckKJtcwWMA@mail.gmail.com/3-v17-0005-Template-out-inner-and-leaf-nodes.patch) download | inline diff: From b1de5cbacf06dd975cc2138a498c5d9897e14df7 Mon Sep 17 00:00:00 2001 From: John Naylor <[email protected]> Date: Fri, 23 Dec 2022 14:33:49 +0700 Subject: [PATCH v17 5/9] Template out inner and leaf nodes Use a template for each insert, iteration, search, and delete functions. To optimize growing into node125, don't search for a slot each time -- just copy into the first 32 slots and set the slot index at the same time. Also set all the isset bits with a single store. Remove node_*_125_update/insert/delete functions and node_125_find_unused_slot, since they are now unused. --- src/backend/lib/radixtree.c | 863 ++---------------------- src/include/lib/radixtree_delete_impl.h | 100 +++ src/include/lib/radixtree_insert_impl.h | 293 ++++++++ src/include/lib/radixtree_iter_impl.h | 129 ++++ src/include/lib/radixtree_search_impl.h | 102 +++ src/tools/pginclude/cpluspluscheck | 6 + src/tools/pginclude/headerscheck | 6 + 7 files changed, 694 insertions(+), 805 deletions(-) create mode 100644 src/include/lib/radixtree_delete_impl.h create mode 100644 src/include/lib/radixtree_insert_impl.h create mode 100644 src/include/lib/radixtree_iter_impl.h create mode 100644 src/include/lib/radixtree_search_impl.h diff --git a/src/backend/lib/radixtree.c b/src/backend/lib/radixtree.c index 5203127f76..80cde09aaf 100644 --- a/src/backend/lib/radixtree.c +++ b/src/backend/lib/radixtree.c @@ -96,13 +96,6 @@ #define BM_IDX(x) ((x) / BITS_PER_BITMAPWORD) #define BM_BIT(x) ((x) % BITS_PER_BITMAPWORD) -/* Enum used rt_node_search() */ -typedef enum -{ - RT_ACTION_FIND = 0, /* find the key-value */ - RT_ACTION_DELETE, /* delete the key-value */ -} rt_action; - /* * Supported radix tree node kinds and size classes. * @@ -422,10 +415,8 @@ static inline void rt_init_node(rt_node *node, uint8 kind, rt_size_class size_cl bool inner); static void rt_free_node(radix_tree *tree, rt_node *node); static void rt_extend(radix_tree *tree, uint64 key); -static inline bool rt_node_search_inner(rt_node *node, uint64 key, rt_action action, - rt_node **child_p); -static inline bool rt_node_search_leaf(rt_node *node, uint64 key, rt_action action, - uint64 *value_p); +static inline bool rt_node_search_inner(rt_node *node, uint64 key, rt_node **child_p); +static inline bool rt_node_search_leaf(rt_node *node, uint64 key, uint64 *value_p); static bool rt_node_insert_inner(radix_tree *tree, rt_node *parent, rt_node *node, uint64 key, rt_node *child); static bool rt_node_insert_leaf(radix_tree *tree, rt_node *parent, rt_node *node, @@ -663,102 +654,6 @@ node_leaf_125_get_value(rt_node_leaf_125 *node, uint8 chunk) return node->values[node->base.slot_idxs[chunk]]; } -static void -node_inner_125_delete(rt_node_inner_125 *node, uint8 chunk) -{ - int slotpos = node->base.slot_idxs[chunk]; - int idx = BM_IDX(slotpos); - int bitnum = BM_BIT(slotpos); - - Assert(!NODE_IS_LEAF(node)); - - node->base.isset[idx] &= ~((bitmapword) 1 << bitnum); - node->children[node->base.slot_idxs[chunk]] = NULL; - node->base.slot_idxs[chunk] = RT_NODE_125_INVALID_IDX; -} - -static void -node_leaf_125_delete(rt_node_leaf_125 *node, uint8 chunk) -{ - int slotpos = node->base.slot_idxs[chunk]; - int idx = BM_IDX(slotpos); - int bitnum = BM_BIT(slotpos); - - Assert(NODE_IS_LEAF(node)); - node->base.isset[idx] &= ~((bitmapword) 1 << bitnum); - node->base.slot_idxs[chunk] = RT_NODE_125_INVALID_IDX; -} - -/* Return an unused slot in node-125 */ -static int -node_125_find_unused_slot(bitmapword *isset) -{ - int slotpos; - int idx; - bitmapword inverse; - - /* get the first word with at least one bit not set */ - for (idx = 0; idx < BM_IDX(128); idx++) - { - if (isset[idx] < ~((bitmapword) 0)) - break; - } - - /* To get the first unset bit in X, get the first set bit in ~X */ - inverse = ~(isset[idx]); - slotpos = idx * BITS_PER_BITMAPWORD; - slotpos += bmw_rightmost_one_pos(inverse); - - /* mark the slot used */ - isset[idx] |= bmw_rightmost_one(inverse); - - return slotpos; - } - -static inline void -node_inner_125_insert(rt_node_inner_125 *node, uint8 chunk, rt_node *child) -{ - int slotpos; - - Assert(!NODE_IS_LEAF(node)); - - slotpos = node_125_find_unused_slot(node->base.isset); - Assert(slotpos < node->base.n.fanout); - - node->base.slot_idxs[chunk] = slotpos; - node->children[slotpos] = child; -} - -/* Set the slot at the corresponding chunk */ -static inline void -node_leaf_125_insert(rt_node_leaf_125 *node, uint8 chunk, uint64 value) -{ - int slotpos; - - Assert(NODE_IS_LEAF(node)); - - slotpos = node_125_find_unused_slot(node->base.isset); - Assert(slotpos < node->base.n.fanout); - - node->base.slot_idxs[chunk] = slotpos; - node->values[slotpos] = value; -} - -/* Update the child corresponding to 'chunk' to 'child' */ -static inline void -node_inner_125_update(rt_node_inner_125 *node, uint8 chunk, rt_node *child) -{ - Assert(!NODE_IS_LEAF(node)); - node->children[node->base.slot_idxs[chunk]] = child; -} - -static inline void -node_leaf_125_update(rt_node_leaf_125 *node, uint8 chunk, uint64 value) -{ - Assert(NODE_IS_LEAF(node)); - node->values[node->base.slot_idxs[chunk]] = value; -} - /* Functions to manipulate inner and leaf node-256 */ /* Return true if the slot corresponding to the given chunk is in use */ @@ -1075,189 +970,57 @@ rt_set_extend(radix_tree *tree, uint64 key, uint64 value, rt_node *parent, } /* - * Search for the child pointer corresponding to 'key' in the given node, and - * do the specified 'action'. + * Search for the child pointer corresponding to 'key' in the given node. * * Return true if the key is found, otherwise return false. On success, the child * pointer is set to child_p. */ static inline bool -rt_node_search_inner(rt_node *node, uint64 key, rt_action action, rt_node **child_p) +rt_node_search_inner(rt_node *node, uint64 key, rt_node **child_p) { - uint8 chunk = RT_GET_KEY_CHUNK(key, node->shift); - bool found = false; - rt_node *child = NULL; - - switch (node->kind) - { - case RT_NODE_KIND_4: - { - rt_node_inner_4 *n4 = (rt_node_inner_4 *) node; - int idx = node_4_search_eq((rt_node_base_4 *) n4, chunk); - - if (idx < 0) - break; - - found = true; - - if (action == RT_ACTION_FIND) - child = n4->children[idx]; - else /* RT_ACTION_DELETE */ - chunk_children_array_delete(n4->base.chunks, n4->children, - n4->base.n.count, idx); - - break; - } - case RT_NODE_KIND_32: - { - rt_node_inner_32 *n32 = (rt_node_inner_32 *) node; - int idx = node_32_search_eq((rt_node_base_32 *) n32, chunk); - - if (idx < 0) - break; - - found = true; - if (action == RT_ACTION_FIND) - child = n32->children[idx]; - else /* RT_ACTION_DELETE */ - chunk_children_array_delete(n32->base.chunks, n32->children, - n32->base.n.count, idx); - break; - } - case RT_NODE_KIND_125: - { - rt_node_inner_125 *n125 = (rt_node_inner_125 *) node; - - if (!node_125_is_chunk_used((rt_node_base_125 *) n125, chunk)) - break; - - found = true; - - if (action == RT_ACTION_FIND) - child = node_inner_125_get_child(n125, chunk); - else /* RT_ACTION_DELETE */ - node_inner_125_delete(n125, chunk); - - break; - } - case RT_NODE_KIND_256: - { - rt_node_inner_256 *n256 = (rt_node_inner_256 *) node; - - if (!node_inner_256_is_chunk_used(n256, chunk)) - break; - - found = true; - if (action == RT_ACTION_FIND) - child = node_inner_256_get_child(n256, chunk); - else /* RT_ACTION_DELETE */ - node_inner_256_delete(n256, chunk); - - break; - } - } - - /* update statistics */ - if (action == RT_ACTION_DELETE && found) - node->count--; - - if (found && child_p) - *child_p = child; - - return found; +#define RT_NODE_LEVEL_INNER +#include "lib/radixtree_search_impl.h" +#undef RT_NODE_LEVEL_INNER } /* - * Search for the value corresponding to 'key' in the given node, and do the - * specified 'action'. + * Search for the value corresponding to 'key' in the given node. * * Return true if the key is found, otherwise return false. On success, the pointer * to the value is set to value_p. */ static inline bool -rt_node_search_leaf(rt_node *node, uint64 key, rt_action action, uint64 *value_p) +rt_node_search_leaf(rt_node *node, uint64 key, uint64 *value_p) { - uint8 chunk = RT_GET_KEY_CHUNK(key, node->shift); - bool found = false; - uint64 value = 0; - - switch (node->kind) - { - case RT_NODE_KIND_4: - { - rt_node_leaf_4 *n4 = (rt_node_leaf_4 *) node; - int idx = node_4_search_eq((rt_node_base_4 *) n4, chunk); - - if (idx < 0) - break; - - found = true; - - if (action == RT_ACTION_FIND) - value = n4->values[idx]; - else /* RT_ACTION_DELETE */ - chunk_values_array_delete(n4->base.chunks, (uint64 *) n4->values, - n4->base.n.count, idx); - - break; - } - case RT_NODE_KIND_32: - { - rt_node_leaf_32 *n32 = (rt_node_leaf_32 *) node; - int idx = node_32_search_eq((rt_node_base_32 *) n32, chunk); - - if (idx < 0) - break; - - found = true; - if (action == RT_ACTION_FIND) - value = n32->values[idx]; - else /* RT_ACTION_DELETE */ - chunk_values_array_delete(n32->base.chunks, (uint64 *) n32->values, - n32->base.n.count, idx); - break; - } - case RT_NODE_KIND_125: - { - rt_node_leaf_125 *n125 = (rt_node_leaf_125 *) node; - - if (!node_125_is_chunk_used((rt_node_base_125 *) n125, chunk)) - break; - - found = true; - - if (action == RT_ACTION_FIND) - value = node_leaf_125_get_value(n125, chunk); - else /* RT_ACTION_DELETE */ - node_leaf_125_delete(n125, chunk); - - break; - } - case RT_NODE_KIND_256: - { - rt_node_leaf_256 *n256 = (rt_node_leaf_256 *) node; - - if (!node_leaf_256_is_chunk_used(n256, chunk)) - break; - - found = true; - if (action == RT_ACTION_FIND) - value = node_leaf_256_get_value(n256, chunk); - else /* RT_ACTION_DELETE */ - node_leaf_256_delete(n256, chunk); - - break; - } - } - - /* update statistics */ - if (action == RT_ACTION_DELETE && found) - node->count--; +#define RT_NODE_LEVEL_LEAF +#include "lib/radixtree_search_impl.h" +#undef RT_NODE_LEVEL_LEAF +} - if (found && value_p) - *value_p = value; +/* + * Search for the child pointer corresponding to 'key' in the given node. + * + * Delete the node and return true if the key is found, otherwise return false. + */ +static inline bool +rt_node_delete_inner(rt_node *node, uint64 key) +{ +#define RT_NODE_LEVEL_INNER +#include "lib/radixtree_delete_impl.h" +#undef RT_NODE_LEVEL_INNER +} - return found; +/* + * Search for the value corresponding to 'key' in the given node. + * + * Delete the node and return true if the key is found, otherwise return false. + */ +static inline bool +rt_node_delete_leaf(rt_node *node, uint64 key) +{ +#define RT_NODE_LEVEL_LEAF +#include "lib/radixtree_delete_impl.h" +#undef RT_NODE_LEVEL_LEAF } /* Insert the child to the inner node */ @@ -1265,185 +1028,9 @@ static bool rt_node_insert_inner(radix_tree *tree, rt_node *parent, rt_node *node, uint64 key, rt_node *child) { - uint8 chunk = RT_GET_KEY_CHUNK(key, node->shift); - bool chunk_exists = false; - rt_node *newnode = NULL; - - Assert(!NODE_IS_LEAF(node)); - - switch (node->kind) - { - case RT_NODE_KIND_4: - { - rt_node_inner_4 *n4 = (rt_node_inner_4 *) node; - int idx; - - idx = node_4_search_eq(&n4->base, chunk); - if (idx != -1) - { - /* found the existing chunk */ - chunk_exists = true; - n4->children[idx] = child; - break; - } - - if (unlikely(!VAR_NODE_HAS_FREE_SLOT(n4))) - { - rt_node_inner_32 *new32; - - /* grow node from 4 to 32 */ - newnode = rt_grow_node_kind(tree, node, RT_NODE_KIND_32); - new32 = (rt_node_inner_32 *) newnode; - chunk_children_array_copy(n4->base.chunks, n4->children, - new32->base.chunks, new32->children); - - Assert(parent != NULL); - rt_replace_node(tree, parent, node, newnode, key); - node = newnode; - } - else - { - int insertpos = node_4_get_insertpos(&n4->base, chunk); - uint16 count = n4->base.n.count; - - /* shift chunks and children */ - if (count != 0 && insertpos < count) - chunk_children_array_shift(n4->base.chunks, n4->children, - count, insertpos); - - n4->base.chunks[insertpos] = chunk; - n4->children[insertpos] = child; - break; - } - } - /* FALLTHROUGH */ - case RT_NODE_KIND_32: - { - const rt_size_class_elem minclass = rt_size_class_info[RT_CLASS_32_PARTIAL]; - const rt_size_class_elem maxclass = rt_size_class_info[RT_CLASS_32_FULL]; - rt_node_inner_32 *n32 = (rt_node_inner_32 *) node; - int idx; - - idx = node_32_search_eq(&n32->base, chunk); - if (idx != -1) - { - /* found the existing chunk */ - chunk_exists = true; - n32->children[idx] = child; - break; - } - - if (unlikely(!VAR_NODE_HAS_FREE_SLOT(n32)) && - n32->base.n.count == minclass.fanout) - { - /* grow to the next size class of this kind */ - newnode = rt_alloc_node(tree, RT_CLASS_32_FULL, true); - memcpy(newnode, node, minclass.inner_size); - newnode->fanout = maxclass.fanout; - - Assert(parent != NULL); - rt_replace_node(tree, parent, node, newnode, key); - node = newnode; - - /* also update pointer for this kind */ - n32 = (rt_node_inner_32 *) newnode; - } - - if (unlikely(!VAR_NODE_HAS_FREE_SLOT(n32))) - { - rt_node_inner_125 *new125; - - /* grow node from 32 to 125 */ - newnode = rt_grow_node_kind(tree, node, RT_NODE_KIND_125); - new125 = (rt_node_inner_125 *) newnode; - for (int i = 0; i < n32->base.n.count; i++) - node_inner_125_insert(new125, n32->base.chunks[i], n32->children[i]); - - Assert(parent != NULL); - rt_replace_node(tree, parent, node, newnode, key); - node = newnode; - } - else - { - int insertpos = node_32_get_insertpos(&n32->base, chunk); - int16 count = n32->base.n.count; - - if (insertpos < count) - { - Assert(count > 0); - chunk_children_array_shift(n32->base.chunks, n32->children, - count, insertpos); - } - - n32->base.chunks[insertpos] = chunk; - n32->children[insertpos] = child; - break; - } - } - /* FALLTHROUGH */ - case RT_NODE_KIND_125: - { - rt_node_inner_125 *n125 = (rt_node_inner_125 *) node; - int cnt = 0; - - if (node_125_is_chunk_used(&n125->base, chunk)) - { - /* found the existing chunk */ - chunk_exists = true; - node_inner_125_update(n125, chunk, child); - break; - } - - if (unlikely(!VAR_NODE_HAS_FREE_SLOT(n125))) - { - rt_node_inner_256 *new256; - Assert(parent != NULL); - - /* grow node from 125 to 256 */ - newnode = rt_grow_node_kind(tree, node, RT_NODE_KIND_256); - new256 = (rt_node_inner_256 *) newnode; - for (int i = 0; i < RT_NODE_MAX_SLOTS && cnt < n125->base.n.count; i++) - { - if (!node_125_is_chunk_used(&n125->base, i)) - continue; - - node_inner_256_set(new256, i, node_inner_125_get_child(n125, i)); - cnt++; - } - - rt_replace_node(tree, parent, node, newnode, key); - node = newnode; - } - else - { - node_inner_125_insert(n125, chunk, child); - break; - } - } - /* FALLTHROUGH */ - case RT_NODE_KIND_256: - { - rt_node_inner_256 *n256 = (rt_node_inner_256 *) node; - - chunk_exists = node_inner_256_is_chunk_used(n256, chunk); - Assert(chunk_exists || FIXED_NODE_HAS_FREE_SLOT(n256, RT_CLASS_256)); - - node_inner_256_set(n256, chunk, child); - break; - } - } - - /* Update statistics */ - if (!chunk_exists) - node->count++; - - /* - * Done. Finally, verify the chunk and value is inserted or replaced - * properly in the node. - */ - rt_verify_node(node); - - return chunk_exists; +#define RT_NODE_LEVEL_INNER +#include "lib/radixtree_insert_impl.h" +#undef RT_NODE_LEVEL_INNER } /* Insert the value to the leaf node */ @@ -1451,190 +1038,9 @@ static bool rt_node_insert_leaf(radix_tree *tree, rt_node *parent, rt_node *node, uint64 key, uint64 value) { - uint8 chunk = RT_GET_KEY_CHUNK(key, node->shift); - bool chunk_exists = false; - - Assert(NODE_IS_LEAF(node)); - - switch (node->kind) - { - case RT_NODE_KIND_4: - { - rt_node_leaf_4 *n4 = (rt_node_leaf_4 *) node; - int idx; - - idx = node_4_search_eq((rt_node_base_4 *) n4, chunk); - if (idx != -1) - { - /* found the existing chunk */ - chunk_exists = true; - n4->values[idx] = value; - break; - } - - if (unlikely(!VAR_NODE_HAS_FREE_SLOT(n4))) - { - rt_node_leaf_32 *new32; - Assert(parent != NULL); - - /* grow node from 4 to 32 */ - new32 = (rt_node_leaf_32 *) rt_grow_node_kind(tree, (rt_node *) n4, - RT_NODE_KIND_32); - chunk_values_array_copy(n4->base.chunks, n4->values, - new32->base.chunks, new32->values); - rt_replace_node(tree, parent, (rt_node *) n4, (rt_node *) new32, key); - node = (rt_node *) new32; - } - else - { - int insertpos = node_4_get_insertpos((rt_node_base_4 *) n4, chunk); - int count = n4->base.n.count; - - /* shift chunks and values */ - if (count != 0 && insertpos < count) - chunk_values_array_shift(n4->base.chunks, n4->values, - count, insertpos); - - n4->base.chunks[insertpos] = chunk; - n4->values[insertpos] = value; - break; - } - } - /* FALLTHROUGH */ - case RT_NODE_KIND_32: - { - rt_node_leaf_32 *n32 = (rt_node_leaf_32 *) node; - int idx; - - idx = node_32_search_eq((rt_node_base_32 *) n32, chunk); - if (idx != -1) - { - /* found the existing chunk */ - chunk_exists = true; - n32->values[idx] = value; - break; - } - - if (unlikely(!VAR_NODE_HAS_FREE_SLOT(n32))) - { - Assert(parent != NULL); - - if (n32->base.n.count == rt_size_class_info[RT_CLASS_32_PARTIAL].fanout) - { - /* use the same node kind, but expand to the next size class */ - const Size size = rt_size_class_info[RT_CLASS_32_PARTIAL].leaf_size; - const int fanout = rt_size_class_info[RT_CLASS_32_FULL].fanout; - rt_node_leaf_32 *new32; - - new32 = (rt_node_leaf_32 *) rt_alloc_node(tree, RT_CLASS_32_FULL, false); - memcpy(new32, n32, size); - new32->base.n.fanout = fanout; - - rt_replace_node(tree, parent, (rt_node *) n32, (rt_node *) new32, key); - - /* must update both pointers here */ - node = (rt_node *) new32; - n32 = new32; - - goto retry_insert_leaf_32; - } - else - { - rt_node_leaf_125 *new125; - - /* grow node from 32 to 125 */ - new125 = (rt_node_leaf_125 *) rt_grow_node_kind(tree, (rt_node *) n32, - RT_NODE_KIND_125); - for (int i = 0; i < n32->base.n.count; i++) - node_leaf_125_insert(new125, n32->base.chunks[i], n32->values[i]); - - rt_replace_node(tree, parent, (rt_node *) n32, (rt_node *) new125, - key); - node = (rt_node *) new125; - } - } - else - { - retry_insert_leaf_32: - { - int insertpos = node_32_get_insertpos((rt_node_base_32 *) n32, chunk); - int count = n32->base.n.count; - - if (count != 0 && insertpos < count) - chunk_values_array_shift(n32->base.chunks, n32->values, - count, insertpos); - - n32->base.chunks[insertpos] = chunk; - n32->values[insertpos] = value; - break; - } - } - } - /* FALLTHROUGH */ - case RT_NODE_KIND_125: - { - rt_node_leaf_125 *n125 = (rt_node_leaf_125 *) node; - int cnt = 0; - - if (node_125_is_chunk_used((rt_node_base_125 *) n125, chunk)) - { - /* found the existing chunk */ - chunk_exists = true; - node_leaf_125_update(n125, chunk, value); - break; - } - - if (unlikely(!VAR_NODE_HAS_FREE_SLOT(n125))) - { - rt_node_leaf_256 *new256; - Assert(parent != NULL); - - /* grow node from 125 to 256 */ - new256 = (rt_node_leaf_256 *) rt_grow_node_kind(tree, (rt_node *) n125, - RT_NODE_KIND_256); - for (int i = 0; i < RT_NODE_MAX_SLOTS && cnt < n125->base.n.count; i++) - { - if (!node_125_is_chunk_used((rt_node_base_125 *) n125, i)) - continue; - - node_leaf_256_set(new256, i, node_leaf_125_get_value(n125, i)); - cnt++; - } - - rt_replace_node(tree, parent, (rt_node *) n125, (rt_node *) new256, - key); - node = (rt_node *) new256; - } - else - { - node_leaf_125_insert(n125, chunk, value); - break; - } - } - /* FALLTHROUGH */ - case RT_NODE_KIND_256: - { - rt_node_leaf_256 *n256 = (rt_node_leaf_256 *) node; - - chunk_exists = node_leaf_256_is_chunk_used(n256, chunk); - Assert(chunk_exists || FIXED_NODE_HAS_FREE_SLOT(n256, RT_CLASS_256)); - - node_leaf_256_set(n256, chunk, value); - break; - } - } - - /* Update statistics */ - if (!chunk_exists) - node->count++; - - /* - * Done. Finally, verify the chunk and value is inserted or replaced - * properly in the node. - */ - rt_verify_node(node); - - return chunk_exists; +#define RT_NODE_LEVEL_LEAF +#include "lib/radixtree_insert_impl.h" +#undef RT_NODE_LEVEL_LEAF } /* @@ -1723,7 +1129,7 @@ rt_set(radix_tree *tree, uint64 key, uint64 value) if (NODE_IS_LEAF(node)) break; - if (!rt_node_search_inner(node, key, RT_ACTION_FIND, &child)) + if (!rt_node_search_inner(node, key, &child)) { rt_set_extend(tree, key, value, parent, node); return false; @@ -1770,14 +1176,14 @@ rt_search(radix_tree *tree, uint64 key, uint64 *value_p) if (NODE_IS_LEAF(node)) break; - if (!rt_node_search_inner(node, key, RT_ACTION_FIND, &child)) + if (!rt_node_search_inner(node, key, &child)) return false; node = child; shift -= RT_NODE_SPAN; } - return rt_node_search_leaf(node, key, RT_ACTION_FIND, value_p); + return rt_node_search_leaf(node, key, value_p); } /* @@ -1810,7 +1216,7 @@ rt_delete(radix_tree *tree, uint64 key) /* Push the current node to the stack */ stack[++level] = node; - if (!rt_node_search_inner(node, key, RT_ACTION_FIND, &child)) + if (!rt_node_search_inner(node, key, &child)) return false; node = child; @@ -1819,7 +1225,7 @@ rt_delete(radix_tree *tree, uint64 key) /* Delete the key from the leaf node if exists */ Assert(NODE_IS_LEAF(node)); - deleted = rt_node_search_leaf(node, key, RT_ACTION_DELETE, NULL); + deleted = rt_node_delete_leaf(node, key); if (!deleted) { @@ -1845,7 +1251,7 @@ rt_delete(radix_tree *tree, uint64 key) { node = stack[level--]; - deleted = rt_node_search_inner(node, key, RT_ACTION_DELETE, NULL); + deleted = rt_node_delete_inner(node, key); Assert(deleted); /* If the node didn't become empty, we stop deleting the key */ @@ -1994,84 +1400,9 @@ rt_iter_update_key(rt_iter *iter, uint8 chunk, uint8 shift) static inline rt_node * rt_node_inner_iterate_next(rt_iter *iter, rt_node_iter *node_iter) { - rt_node *child = NULL; - bool found = false; - uint8 key_chunk; - - switch (node_iter->node->kind) - { - case RT_NODE_KIND_4: - { - rt_node_inner_4 *n4 = (rt_node_inner_4 *) node_iter->node; - - node_iter->current_idx++; - if (node_iter->current_idx >= n4->base.n.count) - break; - - child = n4->children[node_iter->current_idx]; - key_chunk = n4->base.chunks[node_iter->current_idx]; - found = true; - break; - } - case RT_NODE_KIND_32: - { - rt_node_inner_32 *n32 = (rt_node_inner_32 *) node_iter->node; - - node_iter->current_idx++; - if (node_iter->current_idx >= n32->base.n.count) - break; - - child = n32->children[node_iter->current_idx]; - key_chunk = n32->base.chunks[node_iter->current_idx]; - found = true; - break; - } - case RT_NODE_KIND_125: - { - rt_node_inner_125 *n125 = (rt_node_inner_125 *) node_iter->node; - int i; - - for (i = node_iter->current_idx + 1; i < RT_NODE_MAX_SLOTS; i++) - { - if (node_125_is_chunk_used((rt_node_base_125 *) n125, i)) - break; - } - - if (i >= RT_NODE_MAX_SLOTS) - break; - - node_iter->current_idx = i; - child = node_inner_125_get_child(n125, i); - key_chunk = i; - found = true; - break; - } - case RT_NODE_KIND_256: - { - rt_node_inner_256 *n256 = (rt_node_inner_256 *) node_iter->node; - int i; - - for (i = node_iter->current_idx + 1; i < RT_NODE_MAX_SLOTS; i++) - { - if (node_inner_256_is_chunk_used(n256, i)) - break; - } - - if (i >= RT_NODE_MAX_SLOTS) - break; - - node_iter->current_idx = i; - child = node_inner_256_get_child(n256, i); - key_chunk = i; - found = true; - break; - } - } - - if (found) - rt_iter_update_key(iter, key_chunk, node_iter->node->shift); - - return child; +#define RT_NODE_LEVEL_INNER +#include "lib/radixtree_iter_impl.h" +#undef RT_NODE_LEVEL_INNER } /* @@ -2082,88 +1413,9 @@ static inline bool rt_node_leaf_iterate_next(rt_iter *iter, rt_node_iter *node_iter, uint64 *value_p) { - rt_node *node = node_iter->node; - bool found = false; - uint64 value; - uint8 key_chunk; - - switch (node->kind) - { - case RT_NODE_KIND_4: - { - rt_node_leaf_4 *n4 = (rt_node_leaf_4 *) node_iter->node; - - node_iter->current_idx++; - if (node_iter->current_idx >= n4->base.n.count) - break; - - value = n4->values[node_iter->current_idx]; - key_chunk = n4->base.chunks[node_iter->current_idx]; - found = true; - break; - } - case RT_NODE_KIND_32: - { - rt_node_leaf_32 *n32 = (rt_node_leaf_32 *) node_iter->node; - - node_iter->current_idx++; - if (node_iter->current_idx >= n32->base.n.count) - break; - - value = n32->values[node_iter->current_idx]; - key_chunk = n32->base.chunks[node_iter->current_idx]; - found = true; - break; - } - case RT_NODE_KIND_125: - { - rt_node_leaf_125 *n125 = (rt_node_leaf_125 *) node_iter->node; - int i; - - for (i = node_iter->current_idx + 1; i < RT_NODE_MAX_SLOTS; i++) - { - if (node_125_is_chunk_used((rt_node_base_125 *) n125, i)) - break; - } - - if (i >= RT_NODE_MAX_SLOTS) - break; - - node_iter->current_idx = i; - value = node_leaf_125_get_value(n125, i); - key_chunk = i; - found = true; - break; - } - case RT_NODE_KIND_256: - { - rt_node_leaf_256 *n256 = (rt_node_leaf_256 *) node_iter->node; - int i; - - for (i = node_iter->current_idx + 1; i < RT_NODE_MAX_SLOTS; i++) - { - if (node_leaf_256_is_chunk_used(n256, i)) - break; - } - - if (i >= RT_NODE_MAX_SLOTS) - break; - - node_iter->current_idx = i; - value = node_leaf_256_get_value(n256, i); - key_chunk = i; - found = true; - break; - } - } - - if (found) - { - rt_iter_update_key(iter, key_chunk, node_iter->node->shift); - *value_p = value; - } - - return found; +#define RT_NODE_LEVEL_LEAF +#include "lib/radixtree_iter_impl.h" +#undef RT_NODE_LEVEL_LEAF } /* @@ -2229,6 +1481,7 @@ rt_verify_node(rt_node *node) for (int i = 0; i < RT_NODE_MAX_SLOTS; i++) { uint8 slot = n125->slot_idxs[i]; + int idx = BM_IDX(slot); int bitnum = BM_BIT(slot); if (!node_125_is_chunk_used(n125, i)) @@ -2236,7 +1489,7 @@ rt_verify_node(rt_node *node) /* Check if the corresponding slot is used */ Assert(slot < node->fanout); - Assert((n125->isset[i] & ((bitmapword) 1 << bitnum)) != 0); + Assert((n125->isset[idx] & ((bitmapword) 1 << bitnum)) != 0); cnt++; } @@ -2476,12 +1729,12 @@ rt_dump_search(radix_tree *tree, uint64 key) uint64 dummy; /* We reached at a leaf node, find the corresponding slot */ - rt_node_search_leaf(node, key, RT_ACTION_FIND, &dummy); + rt_node_search_leaf(node, key, &dummy); break; } - if (!rt_node_search_inner(node, key, RT_ACTION_FIND, &child)) + if (!rt_node_search_inner(node, key, &child)) break; node = child; diff --git a/src/include/lib/radixtree_delete_impl.h b/src/include/lib/radixtree_delete_impl.h new file mode 100644 index 0000000000..24fd9cc02b --- /dev/null +++ b/src/include/lib/radixtree_delete_impl.h @@ -0,0 +1,100 @@ +/* TODO: shrink nodes */ + +#if defined(RT_NODE_LEVEL_INNER) +#define RT_NODE4_TYPE rt_node_inner_4 +#define RT_NODE32_TYPE rt_node_inner_32 +#define RT_NODE125_TYPE rt_node_inner_125 +#define RT_NODE256_TYPE rt_node_inner_256 +#elif defined(RT_NODE_LEVEL_LEAF) +#define RT_NODE4_TYPE rt_node_leaf_4 +#define RT_NODE32_TYPE rt_node_leaf_32 +#define RT_NODE125_TYPE rt_node_leaf_125 +#define RT_NODE256_TYPE rt_node_leaf_256 +#else +#error node level must be either inner or leaf +#endif + + uint8 chunk = RT_GET_KEY_CHUNK(key, node->shift); + + switch (node->kind) + { + case RT_NODE_KIND_4: + { + RT_NODE4_TYPE *n4 = (RT_NODE4_TYPE *) node; + int idx = node_4_search_eq((rt_node_base_4 *) n4, chunk); + + if (idx < 0) + return false; + +#ifdef RT_NODE_LEVEL_LEAF + chunk_values_array_delete(n4->base.chunks, (uint64 *) n4->values, + n4->base.n.count, idx); +#else + chunk_children_array_delete(n4->base.chunks, n4->children, + n4->base.n.count, idx); +#endif + break; + } + case RT_NODE_KIND_32: + { + RT_NODE32_TYPE *n32 = (RT_NODE32_TYPE *) node; + int idx = node_32_search_eq((rt_node_base_32 *) n32, chunk); + + if (idx < 0) + return false; + +#ifdef RT_NODE_LEVEL_LEAF + chunk_values_array_delete(n32->base.chunks, (uint64 *) n32->values, + n32->base.n.count, idx); +#else + chunk_children_array_delete(n32->base.chunks, n32->children, + n32->base.n.count, idx); +#endif + break; + } + case RT_NODE_KIND_125: + { + RT_NODE125_TYPE *n125 = (RT_NODE125_TYPE *) node; + int slotpos = n125->base.slot_idxs[chunk]; + int idx; + int bitnum; + + if (slotpos == RT_NODE_125_INVALID_IDX) + return false; + + idx = BM_IDX(slotpos); + bitnum = BM_BIT(slotpos); + n125->base.isset[idx] &= ~((bitmapword) 1 << bitnum); + n125->base.slot_idxs[chunk] = RT_NODE_125_INVALID_IDX; + + break; + } + case RT_NODE_KIND_256: + { + RT_NODE256_TYPE *n256 = (RT_NODE256_TYPE *) node; + +#ifdef RT_NODE_LEVEL_LEAF + if (!node_leaf_256_is_chunk_used(n256, chunk)) +#else + if (!node_inner_256_is_chunk_used(n256, chunk)) +#endif + return false; + +#ifdef RT_NODE_LEVEL_LEAF + node_leaf_256_delete(n256, chunk); +#else + node_inner_256_delete(n256, chunk); +#endif + break; + } + } + + /* update statistics */ + node->count--; + + return true; + +#undef RT_NODE4_TYPE +#undef RT_NODE32_TYPE +#undef RT_NODE125_TYPE +#undef RT_NODE256_TYPE diff --git a/src/include/lib/radixtree_insert_impl.h b/src/include/lib/radixtree_insert_impl.h new file mode 100644 index 0000000000..c63fe9a3c0 --- /dev/null +++ b/src/include/lib/radixtree_insert_impl.h @@ -0,0 +1,293 @@ +#if defined(RT_NODE_LEVEL_INNER) +#define RT_NODE4_TYPE rt_node_inner_4 +#define RT_NODE32_TYPE rt_node_inner_32 +#define RT_NODE125_TYPE rt_node_inner_125 +#define RT_NODE256_TYPE rt_node_inner_256 +#elif defined(RT_NODE_LEVEL_LEAF) +#define RT_NODE4_TYPE rt_node_leaf_4 +#define RT_NODE32_TYPE rt_node_leaf_32 +#define RT_NODE125_TYPE rt_node_leaf_125 +#define RT_NODE256_TYPE rt_node_leaf_256 +#else +#error node level must be either inner or leaf +#endif + + uint8 chunk = RT_GET_KEY_CHUNK(key, node->shift); + bool chunk_exists = false; + rt_node *newnode = NULL; + +#ifdef RT_NODE_LEVEL_LEAF + Assert(NODE_IS_LEAF(node)); +#else + Assert(!NODE_IS_LEAF(node)); +#endif + + switch (node->kind) + { + case RT_NODE_KIND_4: + { + RT_NODE4_TYPE *n4 = (RT_NODE4_TYPE *) node; + int idx; + + idx = node_4_search_eq(&n4->base, chunk); + if (idx != -1) + { + /* found the existing chunk */ + chunk_exists = true; +#ifdef RT_NODE_LEVEL_LEAF + n4->values[idx] = value; +#else + n4->children[idx] = child; +#endif + break; + } + + if (unlikely(!VAR_NODE_HAS_FREE_SLOT(n4))) + { + RT_NODE32_TYPE *new32; + + /* grow node from 4 to 32 */ + newnode = rt_grow_node_kind(tree, node, RT_NODE_KIND_32); + new32 = (RT_NODE32_TYPE *) newnode; +#ifdef RT_NODE_LEVEL_LEAF + chunk_values_array_copy(n4->base.chunks, n4->values, + new32->base.chunks, new32->values); +#else + chunk_children_array_copy(n4->base.chunks, n4->children, + new32->base.chunks, new32->children); +#endif + Assert(parent != NULL); + rt_replace_node(tree, parent, node, newnode, key); + node = newnode; + } + else + { + int insertpos = node_4_get_insertpos(&n4->base, chunk); + int count = n4->base.n.count; + + /* shift chunks and children */ + if (insertpos < count) + { + Assert(count > 0); +#ifdef RT_NODE_LEVEL_LEAF + chunk_values_array_shift(n4->base.chunks, n4->values, + count, insertpos); +#else + chunk_children_array_shift(n4->base.chunks, n4->children, + count, insertpos); +#endif + } + + n4->base.chunks[insertpos] = chunk; +#ifdef RT_NODE_LEVEL_LEAF + n4->values[insertpos] = value; +#else + n4->children[insertpos] = child; +#endif + break; + } + } + /* FALLTHROUGH */ + case RT_NODE_KIND_32: + { + const rt_size_class_elem minclass = rt_size_class_info[RT_CLASS_32_PARTIAL]; + const rt_size_class_elem maxclass = rt_size_class_info[RT_CLASS_32_FULL]; + RT_NODE32_TYPE *n32 = (RT_NODE32_TYPE *) node; + int idx; + + idx = node_32_search_eq(&n32->base, chunk); + if (idx != -1) + { + /* found the existing chunk */ + chunk_exists = true; +#ifdef RT_NODE_LEVEL_LEAF + n32->values[idx] = value; +#else + n32->children[idx] = child; +#endif + break; + } + + if (unlikely(!VAR_NODE_HAS_FREE_SLOT(n32)) && + n32->base.n.fanout == minclass.fanout) + { + /* grow to the next size class of this kind */ +#ifdef RT_NODE_LEVEL_LEAF + newnode = rt_alloc_node(tree, RT_CLASS_32_FULL, false); + memcpy(newnode, node, minclass.leaf_size); +#else + newnode = rt_alloc_node(tree, RT_CLASS_32_FULL, true); + memcpy(newnode, node, minclass.inner_size); +#endif + newnode->fanout = maxclass.fanout; + + Assert(parent != NULL); + rt_replace_node(tree, parent, node, newnode, key); + node = newnode; + + /* also update pointer for this kind */ + n32 = (RT_NODE32_TYPE *) newnode; + } + + if (unlikely(!VAR_NODE_HAS_FREE_SLOT(n32))) + { + RT_NODE125_TYPE *new125; + + Assert(n32->base.n.fanout == maxclass.fanout); + + /* grow node from 32 to 125 */ + newnode = rt_grow_node_kind(tree, node, RT_NODE_KIND_125); + new125 = (RT_NODE125_TYPE *) newnode; + + for (int i = 0; i < maxclass.fanout; i++) + { + new125->base.slot_idxs[n32->base.chunks[i]] = i; +#ifdef RT_NODE_LEVEL_LEAF + new125->values[i] = n32->values[i]; +#else + new125->children[i] = n32->children[i]; +#endif + } + + Assert(maxclass.fanout <= sizeof(bitmapword) * BITS_PER_BYTE); + new125->base.isset[0] = (bitmapword) (((uint64) 1 << maxclass.fanout) - 1); + + Assert(parent != NULL); + rt_replace_node(tree, parent, node, newnode, key); + node = newnode; + } + else + { + int insertpos = node_32_get_insertpos(&n32->base, chunk); + int count = n32->base.n.count; + + if (insertpos < count) + { + Assert(count > 0); +#ifdef RT_NODE_LEVEL_LEAF + chunk_values_array_shift(n32->base.chunks, n32->values, + count, insertpos); +#else + chunk_children_array_shift(n32->base.chunks, n32->children, + count, insertpos); +#endif + } + + n32->base.chunks[insertpos] = chunk; +#ifdef RT_NODE_LEVEL_LEAF + n32->values[insertpos] = value; +#else + n32->children[insertpos] = child; +#endif + break; + } + } + /* FALLTHROUGH */ + case RT_NODE_KIND_125: + { + RT_NODE125_TYPE *n125 = (RT_NODE125_TYPE *) node; + int slotpos = n125->base.slot_idxs[chunk]; + int cnt = 0; + + if (slotpos != RT_NODE_125_INVALID_IDX) + { + /* found the existing chunk */ + chunk_exists = true; +#ifdef RT_NODE_LEVEL_LEAF + n125->values[slotpos] = value; +#else + n125->children[slotpos] = child; +#endif + break; + } + + if (unlikely(!VAR_NODE_HAS_FREE_SLOT(n125))) + { + RT_NODE256_TYPE *new256; + + /* grow node from 125 to 256 */ + newnode = rt_grow_node_kind(tree, node, RT_NODE_KIND_256); + new256 = (RT_NODE256_TYPE *) newnode; + for (int i = 0; i < RT_NODE_MAX_SLOTS && cnt < n125->base.n.count; i++) + { + if (!node_125_is_chunk_used(&n125->base, i)) + continue; +#ifdef RT_NODE_LEVEL_LEAF + node_leaf_256_set(new256, i, node_leaf_125_get_value(n125, i)); +#else + node_inner_256_set(new256, i, node_inner_125_get_child(n125, i)); +#endif + cnt++; + } + + Assert(parent != NULL); + rt_replace_node(tree, parent, node, newnode, key); + node = newnode; + } + else + { + int idx; + bitmapword inverse; + + /* get the first word with at least one bit not set */ + for (idx = 0; idx < BM_IDX(128); idx++) + { + if (n125->base.isset[idx] < ~((bitmapword) 0)) + break; + } + + /* To get the first unset bit in X, get the first set bit in ~X */ + inverse = ~(n125->base.isset[idx]); + slotpos = idx * BITS_PER_BITMAPWORD; + slotpos += bmw_rightmost_one_pos(inverse); + Assert(slotpos < node->fanout); + + /* mark the slot used */ + n125->base.isset[idx] |= bmw_rightmost_one(inverse); + n125->base.slot_idxs[chunk] = slotpos; + +#ifdef RT_NODE_LEVEL_LEAF + n125->values[slotpos] = value; +#else + n125->children[slotpos] = child; +#endif + break; + } + } + /* FALLTHROUGH */ + case RT_NODE_KIND_256: + { + RT_NODE256_TYPE *n256 = (RT_NODE256_TYPE *) node; + +#ifdef RT_NODE_LEVEL_LEAF + chunk_exists = node_leaf_256_is_chunk_used(n256, chunk); +#else + chunk_exists = node_inner_256_is_chunk_used(n256, chunk); +#endif + Assert(chunk_exists || FIXED_NODE_HAS_FREE_SLOT(n256, RT_CLASS_256)); + +#ifdef RT_NODE_LEVEL_LEAF + node_leaf_256_set(n256, chunk, value); +#else + node_inner_256_set(n256, chunk, child); +#endif + break; + } + } + + /* Update statistics */ + if (!chunk_exists) + node->count++; + + /* + * Done. Finally, verify the chunk and value is inserted or replaced + * properly in the node. + */ + rt_verify_node(node); + + return chunk_exists; + +#undef RT_NODE4_TYPE +#undef RT_NODE32_TYPE +#undef RT_NODE125_TYPE +#undef RT_NODE256_TYPE diff --git a/src/include/lib/radixtree_iter_impl.h b/src/include/lib/radixtree_iter_impl.h new file mode 100644 index 0000000000..bebf8e725a --- /dev/null +++ b/src/include/lib/radixtree_iter_impl.h @@ -0,0 +1,129 @@ +#if defined(RT_NODE_LEVEL_INNER) +#define RT_NODE4_TYPE rt_node_inner_4 +#define RT_NODE32_TYPE rt_node_inner_32 +#define RT_NODE125_TYPE rt_node_inner_125 +#define RT_NODE256_TYPE rt_node_inner_256 +#elif defined(RT_NODE_LEVEL_LEAF) +#define RT_NODE4_TYPE rt_node_leaf_4 +#define RT_NODE32_TYPE rt_node_leaf_32 +#define RT_NODE125_TYPE rt_node_leaf_125 +#define RT_NODE256_TYPE rt_node_leaf_256 +#else +#error node level must be either inner or leaf +#endif + +#ifdef RT_NODE_LEVEL_LEAF + uint64 value; +#else + rt_node *child = NULL; +#endif + bool found = false; + uint8 key_chunk; + + switch (node_iter->node->kind) + { + case RT_NODE_KIND_4: + { + RT_NODE4_TYPE *n4 = (RT_NODE4_TYPE *) node_iter->node; + + node_iter->current_idx++; + if (node_iter->current_idx >= n4->base.n.count) + break; +#ifdef RT_NODE_LEVEL_LEAF + value = n4->values[node_iter->current_idx]; +#else + child = n4->children[node_iter->current_idx]; +#endif + key_chunk = n4->base.chunks[node_iter->current_idx]; + found = true; + break; + } + case RT_NODE_KIND_32: + { + RT_NODE32_TYPE *n32 = (RT_NODE32_TYPE *) node_iter->node; + + node_iter->current_idx++; + if (node_iter->current_idx >= n32->base.n.count) + break; + +#ifdef RT_NODE_LEVEL_LEAF + value = n32->values[node_iter->current_idx]; +#else + child = n32->children[node_iter->current_idx]; +#endif + key_chunk = n32->base.chunks[node_iter->current_idx]; + found = true; + break; + } + case RT_NODE_KIND_125: + { + RT_NODE125_TYPE *n125 = (RT_NODE125_TYPE *) node_iter->node; + int i; + + for (i = node_iter->current_idx + 1; i < RT_NODE_MAX_SLOTS; i++) + { + if (node_125_is_chunk_used((rt_node_base_125 *) n125, i)) + break; + } + + if (i >= RT_NODE_MAX_SLOTS) + break; + + node_iter->current_idx = i; +#ifdef RT_NODE_LEVEL_LEAF + value = node_leaf_125_get_value(n125, i); +#else + child = node_inner_125_get_child(n125, i); +#endif + key_chunk = i; + found = true; + break; + } + case RT_NODE_KIND_256: + { + RT_NODE256_TYPE *n256 = (RT_NODE256_TYPE *) node_iter->node; + int i; + + for (i = node_iter->current_idx + 1; i < RT_NODE_MAX_SLOTS; i++) + { +#ifdef RT_NODE_LEVEL_LEAF + if (node_leaf_256_is_chunk_used(n256, i)) +#else + if (node_inner_256_is_chunk_used(n256, i)) +#endif + break; + } + + if (i >= RT_NODE_MAX_SLOTS) + break; + + node_iter->current_idx = i; +#ifdef RT_NODE_LEVEL_LEAF + value = node_leaf_256_get_value(n256, i); +#else + child = node_inner_256_get_child(n256, i); +#endif + key_chunk = i; + found = true; + break; + } + } + + if (found) + { + rt_iter_update_key(iter, key_chunk, node_iter->node->shift); +#ifdef RT_NODE_LEVEL_LEAF + *value_p = value; +#endif + } + +#ifdef RT_NODE_LEVEL_LEAF + return found; +#else + return child; +#endif + +#undef RT_NODE4_TYPE +#undef RT_NODE32_TYPE +#undef RT_NODE125_TYPE +#undef RT_NODE256_TYPE diff --git a/src/include/lib/radixtree_search_impl.h b/src/include/lib/radixtree_search_impl.h new file mode 100644 index 0000000000..d0366f9bb6 --- /dev/null +++ b/src/include/lib/radixtree_search_impl.h @@ -0,0 +1,102 @@ +#if defined(RT_NODE_LEVEL_INNER) +#define RT_NODE4_TYPE rt_node_inner_4 +#define RT_NODE32_TYPE rt_node_inner_32 +#define RT_NODE125_TYPE rt_node_inner_125 +#define RT_NODE256_TYPE rt_node_inner_256 +#elif defined(RT_NODE_LEVEL_LEAF) +#define RT_NODE4_TYPE rt_node_leaf_4 +#define RT_NODE32_TYPE rt_node_leaf_32 +#define RT_NODE125_TYPE rt_node_leaf_125 +#define RT_NODE256_TYPE rt_node_leaf_256 +#else +#error node level must be either inner or leaf +#endif + + uint8 chunk = RT_GET_KEY_CHUNK(key, node->shift); + +#ifdef RT_NODE_LEVEL_LEAF + uint64 value = 0; +#else + rt_node *child = NULL; +#endif + + switch (node->kind) + { + case RT_NODE_KIND_4: + { + RT_NODE4_TYPE *n4 = (RT_NODE4_TYPE *) node; + int idx = node_4_search_eq((rt_node_base_4 *) n4, chunk); + + if (idx < 0) + return false; + +#ifdef RT_NODE_LEVEL_LEAF + value = n4->values[idx]; +#else + child = n4->children[idx]; +#endif + break; + } + case RT_NODE_KIND_32: + { + RT_NODE32_TYPE *n32 = (RT_NODE32_TYPE *) node; + int idx = node_32_search_eq((rt_node_base_32 *) n32, chunk); + + if (idx < 0) + return false; + +#ifdef RT_NODE_LEVEL_LEAF + value = n32->values[idx]; +#else + child = n32->children[idx]; +#endif + break; + } + case RT_NODE_KIND_125: + { + RT_NODE125_TYPE *n125 = (RT_NODE125_TYPE *) node; + + if (!node_125_is_chunk_used((rt_node_base_125 *) n125, chunk)) + return false; + +#ifdef RT_NODE_LEVEL_LEAF + value = node_leaf_125_get_value(n125, chunk); +#else + child = node_inner_125_get_child(n125, chunk); +#endif + break; + } + case RT_NODE_KIND_256: + { + RT_NODE256_TYPE *n256 = (RT_NODE256_TYPE *) node; + +#ifdef RT_NODE_LEVEL_LEAF + if (!node_leaf_256_is_chunk_used(n256, chunk)) +#else + if (!node_inner_256_is_chunk_used(n256, chunk)) +#endif + return false; + +#ifdef RT_NODE_LEVEL_LEAF + value = node_leaf_256_get_value(n256, chunk); +#else + child = node_inner_256_get_child(n256, chunk); +#endif + break; + } + } + +#ifdef RT_NODE_LEVEL_LEAF + Assert(value_p != NULL); + *value_p = value; +#else + Assert(child_p != NULL); + *child_p = child; +#endif + + return true; + +#undef RT_NODE4_TYPE +#undef RT_NODE32_TYPE +#undef RT_NODE125_TYPE +#undef RT_NODE256_TYPE diff --git a/src/tools/pginclude/cpluspluscheck b/src/tools/pginclude/cpluspluscheck index e52fe9f509..09fa6e7432 100755 --- a/src/tools/pginclude/cpluspluscheck +++ b/src/tools/pginclude/cpluspluscheck @@ -101,6 +101,12 @@ do test "$f" = src/include/nodes/nodetags.h && continue test "$f" = src/backend/nodes/nodetags.h && continue + # radixtree_*_impl.h cannot be included standalone: they are just code fragments. + test "$f" = src/include/lib/radixtree_delete_impl.h && continue + test "$f" = src/include/lib/radixtree_insert_impl.h && continue + test "$f" = src/include/lib/radixtree_iter_impl.h && continue + test "$f" = src/include/lib/radixtree_search_impl.h && continue + # These files are not meant to be included standalone, because # they contain lists that might have multiple use-cases. test "$f" = src/include/access/rmgrlist.h && continue diff --git a/src/tools/pginclude/headerscheck b/src/tools/pginclude/headerscheck index abbba7aa63..d4d2f1da03 100755 --- a/src/tools/pginclude/headerscheck +++ b/src/tools/pginclude/headerscheck @@ -96,6 +96,12 @@ do test "$f" = src/include/nodes/nodetags.h && continue test "$f" = src/backend/nodes/nodetags.h && continue + # radixtree_*_impl.h cannot be included standalone: they are just code fragments. + test "$f" = src/include/lib/radixtree_delete_impl.h && continue + test "$f" = src/include/lib/radixtree_insert_impl.h && continue + test "$f" = src/include/lib/radixtree_iter_impl.h && continue + test "$f" = src/include/lib/radixtree_search_impl.h && continue + # These files are not meant to be included standalone, because # they contain lists that might have multiple use-cases. test "$f" = src/include/access/rmgrlist.h && continue -- 2.39.0 [text/x-patch] v17-0009-Implement-shared-memory.patch (38.6K, ../../CAFBsxsFMuyUd48SXy1VMFvpVBHh6B6300_UfTSiPckKJtcwWMA@mail.gmail.com/4-v17-0009-Implement-shared-memory.patch) download | inline diff: From c78f27a61d649b0981fc150c3894a0e1a992bcc0 Mon Sep 17 00:00:00 2001 From: John Naylor <[email protected]> Date: Mon, 9 Jan 2023 14:32:39 +0700 Subject: [PATCH v17 9/9] Implement shared memory --- src/backend/utils/mmgr/dsa.c | 12 + src/include/lib/radixtree.h | 376 +++++++++++++----- src/include/lib/radixtree_delete_impl.h | 6 + src/include/lib/radixtree_insert_impl.h | 43 +- src/include/lib/radixtree_iter_impl.h | 19 +- src/include/lib/radixtree_search_impl.h | 28 +- src/include/utils/dsa.h | 1 + .../modules/test_radixtree/test_radixtree.c | 43 ++ 8 files changed, 402 insertions(+), 126 deletions(-) diff --git a/src/backend/utils/mmgr/dsa.c b/src/backend/utils/mmgr/dsa.c index 604b702a91..50f0aae3ab 100644 --- a/src/backend/utils/mmgr/dsa.c +++ b/src/backend/utils/mmgr/dsa.c @@ -1024,6 +1024,18 @@ dsa_set_size_limit(dsa_area *area, size_t limit) LWLockRelease(DSA_AREA_LOCK(area)); } +size_t +dsa_get_total_size(dsa_area *area) +{ + size_t size; + + LWLockAcquire(DSA_AREA_LOCK(area), LW_SHARED); + size = area->control->total_segment_size; + LWLockRelease(DSA_AREA_LOCK(area)); + + return size; +} + /* * Aggressively free all spare memory in the hope of returning DSM segments to * the operating system. diff --git a/src/include/lib/radixtree.h b/src/include/lib/radixtree.h index b3d84da033..2b58a0cdf5 100644 --- a/src/include/lib/radixtree.h +++ b/src/include/lib/radixtree.h @@ -42,6 +42,8 @@ * - RT_DEFINE - if defined function definitions are generated * - RT_SCOPE - in which scope (e.g. extern, static inline) do function * declarations reside + * - RT_SHMEM - if defined, the radix tree is created in the DSA area + * so that multiple processes can access it simultaneously. * * Optional parameters: * - RT_DEBUG - if defined add stats tracking and debugging functions @@ -51,6 +53,9 @@ * * RT_CREATE - Create a new, empty radix tree * RT_FREE - Free the radix tree + * RT_ATTACH - Attach to the radix tree + * RT_DETACH - Detach from the radix tree + * RT_GET_HANDLE - Return the handle of the radix tree * RT_SEARCH - Search a key-value pair * RT_SET - Set a key-value pair * RT_DELETE - Delete a key-value pair @@ -80,7 +85,8 @@ #include "miscadmin.h" #include "nodes/bitmapset.h" #include "port/pg_bitutils.h" -#include "port/pg_lfind.h" +#include "port/simd.h" +#include "utils/dsa.h" #include "utils/memutils.h" /* helpers */ @@ -92,6 +98,9 @@ #define RT_CREATE RT_MAKE_NAME(create) #define RT_FREE RT_MAKE_NAME(free) #define RT_SEARCH RT_MAKE_NAME(search) +#ifdef RT_SHMEM +#define RT_ATTACH RT_MAKE_NAME(attach) +#endif #define RT_SET RT_MAKE_NAME(set) #define RT_BEGIN_ITERATE RT_MAKE_NAME(begin_iterate) #define RT_ITERATE_NEXT RT_MAKE_NAME(iterate_next) @@ -110,9 +119,11 @@ #define RT_FREE_NODE RT_MAKE_NAME(free_node) #define RT_EXTEND RT_MAKE_NAME(extend) #define RT_SET_EXTEND RT_MAKE_NAME(set_extend) -#define RT_GROW_NODE_KIND RT_MAKE_NAME(grow_node_kind) +//#define RT_GROW_NODE_KIND RT_MAKE_NAME(grow_node_kind) #define RT_COPY_NODE RT_MAKE_NAME(copy_node) #define RT_REPLACE_NODE RT_MAKE_NAME(replace_node) +#define RT_PTR_GET_LOCAL RT_MAKE_NAME(ptr_get_local) +#define RT_PTR_ALLOC_IS_VALID RT_MAKE_NAME(ptr_stored_is_valid) #define RT_NODE_4_SEARCH_EQ RT_MAKE_NAME(node_4_search_eq) #define RT_NODE_32_SEARCH_EQ RT_MAKE_NAME(node_32_search_eq) #define RT_NODE_4_GET_INSERTPOS RT_MAKE_NAME(node_4_get_insertpos) @@ -138,6 +149,7 @@ #define RT_SHIFT_GET_MAX_VAL RT_MAKE_NAME(shift_get_max_val) #define RT_NODE_SEARCH_INNER RT_MAKE_NAME(node_search_inner) #define RT_NODE_SEARCH_LEAF RT_MAKE_NAME(node_search_leaf) +#define RT_NODE_UPDATE_INNER RT_MAKE_NAME(node_update_inner) #define RT_NODE_DELETE_INNER RT_MAKE_NAME(node_delete_inner) #define RT_NODE_DELETE_LEAF RT_MAKE_NAME(node_delete_leaf) #define RT_NODE_INSERT_INNER RT_MAKE_NAME(node_insert_inner) @@ -150,6 +162,7 @@ /* type declarations */ #define RT_RADIX_TREE RT_MAKE_NAME(radix_tree) +#define RT_RADIX_TREE_CONTROL RT_MAKE_NAME(radix_tree_control) #define RT_ITER RT_MAKE_NAME(iter) #define RT_NODE RT_MAKE_NAME(node) #define RT_NODE_ITER RT_MAKE_NAME(node_iter) @@ -181,8 +194,14 @@ typedef struct RT_RADIX_TREE RT_RADIX_TREE; typedef struct RT_ITER RT_ITER; +#ifdef RT_SHMEM +RT_SCOPE RT_RADIX_TREE * RT_CREATE(MemoryContext ctx, dsa_area *dsa); +RT_SCOPE RT_RADIX_TREE * RT_ATTACH(dsa_area *dsa, dsa_pointer dp); +#else RT_SCOPE RT_RADIX_TREE * RT_CREATE(MemoryContext ctx); +#endif RT_SCOPE void RT_FREE(RT_RADIX_TREE *tree); + RT_SCOPE bool RT_SEARCH(RT_RADIX_TREE *tree, uint64 key, uint64 *val_p); RT_SCOPE bool RT_SET(RT_RADIX_TREE *tree, uint64 key, uint64 val); RT_SCOPE bool RT_DELETE(RT_RADIX_TREE *tree, uint64 key); @@ -301,9 +320,21 @@ typedef struct RT_NODE uint8 kind; } RT_NODE; + #define RT_PTR_LOCAL RT_NODE * +#ifdef RT_SHMEM +#define RT_PTR_ALLOC dsa_pointer +#else #define RT_PTR_ALLOC RT_PTR_LOCAL +#endif + + +#ifdef RT_SHMEM +#define RT_INVALID_PTR_ALLOC InvalidDsaPointer +#else +#define RT_INVALID_PTR_ALLOC NULL +#endif #define NODE_IS_LEAF(n) (((RT_PTR_LOCAL) (n))->shift == 0) #define NODE_IS_EMPTY(n) (((RT_PTR_LOCAL) (n))->count == 0) @@ -512,21 +543,33 @@ static const RT_SIZE_CLASS RT_KIND_MIN_SIZE_CLASS[RT_NODE_KIND_COUNT] = { }; /* A radix tree with nodes */ -typedef struct RT_RADIX_TREE +typedef struct RT_RADIX_TREE_CONTROL { - MemoryContext context; - RT_PTR_ALLOC root; uint64 max_val; uint64 num_keys; - MemoryContextData *inner_slabs[RT_SIZE_CLASS_COUNT]; - MemoryContextData *leaf_slabs[RT_SIZE_CLASS_COUNT]; - /* statistics */ #ifdef RT_DEBUG int32 cnt[RT_SIZE_CLASS_COUNT]; #endif +} RT_RADIX_TREE_CONTROL; + +/* A radix tree with nodes */ +typedef struct RT_RADIX_TREE +{ + MemoryContext context; + + /* pointing to either local memory or DSA */ + RT_RADIX_TREE_CONTROL *ctl; + +#ifdef RT_SHMEM + dsa_area *dsa; + dsa_pointer ctl_dp; +#else + MemoryContextData *inner_slabs[RT_SIZE_CLASS_COUNT]; + MemoryContextData *leaf_slabs[RT_SIZE_CLASS_COUNT]; +#endif } RT_RADIX_TREE; /* @@ -542,6 +585,11 @@ typedef struct RT_RADIX_TREE * construct the key whenever updating the node iteration information, e.g., when * advancing the current index within the node or when moving to the next node * at the same level. ++ * ++ * XXX: Currently we allow only one process to do iteration. Therefore, rt_node_iter ++ * has the local pointers to nodes, rather than RT_PTR_ALLOC. ++ * We need either a safeguard to disallow other processes to begin the iteration ++ * while one process is doing or to allow multiple processes to do the iteration. */ typedef struct RT_NODE_ITER { @@ -562,14 +610,35 @@ typedef struct RT_ITER } RT_ITER; -static bool RT_NODE_INSERT_INNER(RT_RADIX_TREE *tree, RT_PTR_LOCAL parent, RT_PTR_LOCAL node, - uint64 key, RT_PTR_LOCAL child); -static bool RT_NODE_INSERT_LEAF(RT_RADIX_TREE *tree, RT_PTR_LOCAL parent, RT_PTR_LOCAL node, +static bool RT_NODE_INSERT_INNER(RT_RADIX_TREE *tree, RT_PTR_LOCAL parent, RT_PTR_ALLOC nodep, RT_PTR_LOCAL node, + uint64 key, RT_PTR_ALLOC child); +static bool RT_NODE_INSERT_LEAF(RT_RADIX_TREE *tree, RT_PTR_LOCAL parent, RT_PTR_ALLOC nodep, RT_PTR_LOCAL node, uint64 key, uint64 value); /* verification (available only with assertion) */ static void RT_VERIFY_NODE(RT_PTR_LOCAL node); +/* Get the local address of an allocated node */ +static inline RT_PTR_LOCAL +RT_PTR_GET_LOCAL(RT_RADIX_TREE *tree, RT_PTR_ALLOC node) +{ +#ifdef RT_SHMEM + return dsa_get_address(tree->dsa, (dsa_pointer) node); +#else + return node; +#endif +} + +static inline bool +RT_PTR_ALLOC_IS_VALID(RT_PTR_ALLOC ptr) +{ +#ifdef RT_SHMEM + return DsaPointerIsValid(ptr); +#else + return PointerIsValid(ptr); +#endif +} + /* * Return index of the first element in 'base' that equals 'key'. Return -1 * if there is no such element. @@ -801,7 +870,7 @@ static inline bool RT_NODE_INNER_256_IS_CHUNK_USED(RT_NODE_INNER_256 *node, uint8 chunk) { Assert(!NODE_IS_LEAF(node)); - return (node->children[chunk] != NULL); + return node->children[chunk] != RT_INVALID_PTR_ALLOC; } static inline bool @@ -855,7 +924,7 @@ static inline void RT_NODE_INNER_256_DELETE(RT_NODE_INNER_256 *node, uint8 chunk) { Assert(!NODE_IS_LEAF(node)); - node->children[chunk] = NULL; + node->children[chunk] = RT_INVALID_PTR_ALLOC; } static inline void @@ -897,21 +966,31 @@ RT_SHIFT_GET_MAX_VAL(int shift) static RT_PTR_ALLOC RT_ALLOC_NODE(RT_RADIX_TREE *tree, RT_SIZE_CLASS size_class, bool inner) { - RT_PTR_ALLOC newnode; + RT_PTR_ALLOC allocnode; + size_t allocsize; if (inner) - newnode = (RT_PTR_ALLOC) MemoryContextAlloc(tree->inner_slabs[size_class], - RT_SIZE_CLASS_INFO[size_class].inner_size); + allocsize = RT_SIZE_CLASS_INFO[size_class].inner_size; else - newnode = (RT_PTR_ALLOC) MemoryContextAlloc(tree->leaf_slabs[size_class], - RT_SIZE_CLASS_INFO[size_class].leaf_size); + allocsize = RT_SIZE_CLASS_INFO[size_class].leaf_size; + +#ifdef RT_SHMEM + allocnode = dsa_allocate(tree->dsa, allocsize); +#else + if (inner) + allocnode = (RT_PTR_ALLOC) MemoryContextAlloc(tree->inner_slabs[size_class], + allocsize); + else + allocnode = (RT_PTR_ALLOC) MemoryContextAlloc(tree->leaf_slabs[size_class], + allocsize); +#endif #ifdef RT_DEBUG /* update the statistics */ - tree->cnt[size_class]++; + tree->ctl->cnt[size_class]++; #endif - return newnode; + return allocnode; } /* Initialize the node contents */ @@ -951,13 +1030,15 @@ RT_NEW_ROOT(RT_RADIX_TREE *tree, uint64 key) { int shift = RT_KEY_GET_SHIFT(key); bool inner = shift > 0; - RT_PTR_ALLOC newnode; + RT_PTR_ALLOC allocnode; + RT_PTR_LOCAL newnode; - newnode = RT_ALLOC_NODE(tree, RT_CLASS_4_FULL, inner); + allocnode = RT_ALLOC_NODE(tree, RT_CLASS_4_FULL, inner); + newnode = RT_PTR_GET_LOCAL(tree, allocnode); RT_INIT_NODE(newnode, RT_NODE_KIND_4, RT_CLASS_4_FULL, inner); newnode->shift = shift; - tree->max_val = RT_SHIFT_GET_MAX_VAL(shift); - tree->root = newnode; + tree->ctl->max_val = RT_SHIFT_GET_MAX_VAL(shift); + tree->ctl->root = allocnode; } static inline void @@ -967,7 +1048,7 @@ RT_COPY_NODE(RT_PTR_LOCAL newnode, RT_PTR_LOCAL oldnode) newnode->chunk = oldnode->chunk; newnode->count = oldnode->count; } - +#if 0 /* * Create a new node with 'new_kind' and the same shift, chunk, and * count of 'node'. @@ -975,30 +1056,33 @@ RT_COPY_NODE(RT_PTR_LOCAL newnode, RT_PTR_LOCAL oldnode) static RT_NODE* RT_GROW_NODE_KIND(RT_RADIX_TREE *tree, RT_PTR_LOCAL node, uint8 new_kind) { - RT_PTR_ALLOC newnode; + RT_PTR_ALLOC allocnode; + RT_PTR_LOCAL newnode; bool inner = !NODE_IS_LEAF(node); - newnode = RT_ALLOC_NODE(tree, RT_KIND_MIN_SIZE_CLASS[new_kind], inner); + allocnode = RT_ALLOC_NODE(tree, RT_KIND_MIN_SIZE_CLASS[new_kind], inner); + newnode = RT_PTR_GET_LOCAL(tree, allocnode); RT_INIT_NODE(newnode, new_kind, RT_KIND_MIN_SIZE_CLASS[new_kind], inner); RT_COPY_NODE(newnode, node); return newnode; } - +#endif /* Free the given node */ static void -RT_FREE_NODE(RT_RADIX_TREE *tree, RT_PTR_ALLOC node) +RT_FREE_NODE(RT_RADIX_TREE *tree, RT_PTR_ALLOC allocnode) { /* If we're deleting the root node, make the tree empty */ - if (tree->root == node) + if (tree->ctl->root == allocnode) { - tree->root = NULL; - tree->max_val = 0; + tree->ctl->root = RT_INVALID_PTR_ALLOC; + tree->ctl->max_val = 0; } #ifdef RT_DEBUG { int i; + RT_PTR_LOCAL node = RT_PTR_GET_LOCAL(tree, allocnode); /* update the statistics */ for (i = 0; i < RT_SIZE_CLASS_COUNT; i++) @@ -1011,12 +1095,26 @@ RT_FREE_NODE(RT_RADIX_TREE *tree, RT_PTR_ALLOC node) if (i == RT_SIZE_CLASS_COUNT) i = RT_CLASS_256; - tree->cnt[i]--; - Assert(tree->cnt[i] >= 0); + tree->ctl->cnt[i]--; + Assert(tree->ctl->cnt[i] >= 0); } #endif - pfree(node); +#ifdef RT_SHMEM + dsa_free(tree->dsa, allocnode); +#else + pfree(allocnode); +#endif +} + +static inline bool +RT_NODE_UPDATE_INNER(RT_PTR_LOCAL node, uint64 key, RT_PTR_ALLOC new_child) +{ +#define RT_ACTION_UPDATE +#define RT_NODE_LEVEL_INNER +#include "lib/radixtree_search_impl.h" +#undef RT_NODE_LEVEL_INNER +#undef RT_ACTION_UPDATE } /* @@ -1026,19 +1124,25 @@ static void RT_REPLACE_NODE(RT_RADIX_TREE *tree, RT_PTR_LOCAL parent, RT_PTR_ALLOC old_child, RT_PTR_ALLOC new_child, uint64 key) { - Assert(old_child->chunk == new_child->chunk); - Assert(old_child->shift == new_child->shift); + RT_PTR_LOCAL old = RT_PTR_GET_LOCAL(tree, old_child); + +#ifdef USE_ASSERT_CHECKING + RT_PTR_LOCAL new = RT_PTR_GET_LOCAL(tree, new_child); + + Assert(old->chunk == new->chunk); + Assert(old->shift == new->shift); +#endif - if (parent == old_child) + if (parent == old) { /* Replace the root node with the new large node */ - tree->root = new_child; + tree->ctl->root = new_child; } else { - bool replaced PG_USED_FOR_ASSERTS_ONLY; + bool replaced PG_USED_FOR_ASSERTS_ONLY; - replaced = RT_NODE_INSERT_INNER(tree, NULL, parent, key, new_child); + replaced = RT_NODE_UPDATE_INNER(parent, key, new_child); Assert(replaced); } @@ -1053,7 +1157,8 @@ static void RT_EXTEND(RT_RADIX_TREE *tree, uint64 key) { int target_shift; - int shift = tree->root->shift + RT_NODE_SPAN; + RT_PTR_LOCAL root = RT_PTR_GET_LOCAL(tree, tree->ctl->root); + int shift = root->shift + RT_NODE_SPAN; target_shift = RT_KEY_GET_SHIFT(key); @@ -1065,22 +1170,23 @@ RT_EXTEND(RT_RADIX_TREE *tree, uint64 key) RT_NODE_INNER_4 *n4; allocnode = RT_ALLOC_NODE(tree, RT_CLASS_4_FULL, true); - node = (RT_PTR_LOCAL) allocnode; + node = RT_PTR_GET_LOCAL(tree, allocnode); RT_INIT_NODE(node, RT_NODE_KIND_4, RT_CLASS_4_FULL, true); node->shift = shift; node->count = 1; n4 = (RT_NODE_INNER_4 *) node; n4->base.chunks[0] = 0; - n4->children[0] = tree->root; + n4->children[0] = tree->ctl->root; - tree->root->chunk = 0; - tree->root = node; + /* Update the root */ + tree->ctl->root = allocnode; + root->chunk = 0; shift += RT_NODE_SPAN; } - tree->max_val = RT_SHIFT_GET_MAX_VAL(target_shift); + tree->ctl->max_val = RT_SHIFT_GET_MAX_VAL(target_shift); } /* @@ -1089,10 +1195,12 @@ RT_EXTEND(RT_RADIX_TREE *tree, uint64 key) */ static inline void RT_SET_EXTEND(RT_RADIX_TREE *tree, uint64 key, uint64 value, RT_PTR_LOCAL parent, - RT_PTR_LOCAL node) + RT_PTR_ALLOC nodep, RT_PTR_LOCAL node) { int shift = node->shift; + Assert(RT_PTR_GET_LOCAL(tree, nodep) == node); + while (shift >= RT_NODE_SPAN) { RT_PTR_ALLOC allocchild; @@ -1101,19 +1209,20 @@ RT_SET_EXTEND(RT_RADIX_TREE *tree, uint64 key, uint64 value, RT_PTR_LOCAL parent bool inner = newshift > 0; allocchild = RT_ALLOC_NODE(tree, RT_CLASS_4_FULL, inner); - newchild = (RT_PTR_LOCAL) allocchild; + newchild = RT_PTR_GET_LOCAL(tree, allocchild); RT_INIT_NODE(newchild, RT_NODE_KIND_4, RT_CLASS_4_FULL, inner); newchild->shift = newshift; newchild->chunk = RT_GET_KEY_CHUNK(key, node->shift); - RT_NODE_INSERT_INNER(tree, parent, node, key, newchild); + RT_NODE_INSERT_INNER(tree, parent, nodep, node, key, allocchild); parent = node; node = newchild; + nodep = allocchild; shift -= RT_NODE_SPAN; } - RT_NODE_INSERT_LEAF(tree, parent, node, key, value); - tree->num_keys++; + RT_NODE_INSERT_LEAF(tree, parent, nodep, node, key, value); + tree->ctl->num_keys++; } /* @@ -1172,8 +1281,8 @@ RT_NODE_DELETE_LEAF(RT_PTR_LOCAL node, uint64 key) /* Insert the child to the inner node */ static bool -RT_NODE_INSERT_INNER(RT_RADIX_TREE *tree, RT_PTR_LOCAL parent, RT_PTR_LOCAL node, uint64 key, - RT_PTR_ALLOC child) +RT_NODE_INSERT_INNER(RT_RADIX_TREE *tree, RT_PTR_LOCAL parent, RT_PTR_ALLOC nodep, RT_PTR_LOCAL node, + uint64 key, RT_PTR_ALLOC child) { #define RT_NODE_LEVEL_INNER #include "lib/radixtree_insert_impl.h" @@ -1182,7 +1291,7 @@ RT_NODE_INSERT_INNER(RT_RADIX_TREE *tree, RT_PTR_LOCAL parent, RT_PTR_LOCAL node /* Insert the value to the leaf node */ static bool -RT_NODE_INSERT_LEAF(RT_RADIX_TREE *tree, RT_PTR_LOCAL parent, RT_PTR_LOCAL node, +RT_NODE_INSERT_LEAF(RT_RADIX_TREE *tree, RT_PTR_LOCAL parent, RT_PTR_ALLOC nodep, RT_PTR_LOCAL node, uint64 key, uint64 value) { #define RT_NODE_LEVEL_LEAF @@ -1194,18 +1303,26 @@ RT_NODE_INSERT_LEAF(RT_RADIX_TREE *tree, RT_PTR_LOCAL parent, RT_PTR_LOCAL node, * Create the radix tree in the given memory context and return it. */ RT_SCOPE RT_RADIX_TREE * +#ifdef RT_SHMEM +RT_CREATE(MemoryContext ctx, dsa_area *dsa) +#else RT_CREATE(MemoryContext ctx) +#endif { RT_RADIX_TREE *tree; MemoryContext old_ctx; old_ctx = MemoryContextSwitchTo(ctx); - tree = palloc(sizeof(RT_RADIX_TREE)); + tree = (RT_RADIX_TREE *) palloc0(sizeof(RT_RADIX_TREE)); tree->context = ctx; - tree->root = NULL; - tree->max_val = 0; - tree->num_keys = 0; + +#ifdef RT_SHMEM + tree->dsa = dsa; + tree->ctl_dp = dsa_allocate0(dsa, sizeof(RT_RADIX_TREE_CONTROL)); + tree->ctl = (RT_RADIX_TREE_CONTROL *) dsa_get_address(dsa, tree->ctl_dp); +#else + tree->ctl = (RT_RADIX_TREE_CONTROL *) palloc0(sizeof(RT_RADIX_TREE_CONTROL)); /* Create the slab allocator for each size class */ for (int i = 0; i < RT_SIZE_CLASS_COUNT; i++) @@ -1218,27 +1335,52 @@ RT_CREATE(MemoryContext ctx) RT_SIZE_CLASS_INFO[i].name, RT_SIZE_CLASS_INFO[i].leaf_blocksize, RT_SIZE_CLASS_INFO[i].leaf_size); -#ifdef RT_DEBUG - tree->cnt[i] = 0; -#endif } +#endif + + tree->ctl->root = RT_INVALID_PTR_ALLOC; MemoryContextSwitchTo(old_ctx); return tree; } +#ifdef RT_SHMEM +RT_RADIX_TREE * +RT_ATTACH(dsa_area *dsa, dsa_pointer dp) +{ + RT_RADIX_TREE *tree; + + /* XXX: memory context support */ + tree = (RT_RADIX_TREE *) palloc0(sizeof(RT_RADIX_TREE)); + + tree->ctl_dp = dp; + tree->ctl = (RT_RADIX_TREE_CONTROL *) dsa_get_address(dsa, dp); + + /* XXX: do we need to set a callback on exit to detach dsa? */ + + return tree; +} +#endif + /* * Free the given radix tree. */ RT_SCOPE void RT_FREE(RT_RADIX_TREE *tree) { +#ifdef RT_SHMEM + dsa_free(tree->dsa, tree->ctl_dp); // XXX + dsa_detach(tree->dsa); +#else + pfree(tree->ctl); + for (int i = 0; i < RT_SIZE_CLASS_COUNT; i++) { MemoryContextDelete(tree->inner_slabs[i]); MemoryContextDelete(tree->leaf_slabs[i]); } +#endif pfree(tree); } @@ -1252,46 +1394,50 @@ RT_SET(RT_RADIX_TREE *tree, uint64 key, uint64 value) { int shift; bool updated; - RT_PTR_LOCAL node; RT_PTR_LOCAL parent; + RT_PTR_ALLOC nodep; + RT_PTR_LOCAL node; /* Empty tree, create the root */ - if (!tree->root) + if (!RT_PTR_ALLOC_IS_VALID(tree->ctl->root)) RT_NEW_ROOT(tree, key); /* Extend the tree if necessary */ - if (key > tree->max_val) + if (key > tree->ctl->max_val) RT_EXTEND(tree, key); - Assert(tree->root); + //Assert(tree->ctl->root); - shift = tree->root->shift; - node = parent = tree->root; + nodep = tree->ctl->root; + parent = RT_PTR_GET_LOCAL(tree, nodep); + shift = parent->shift; /* Descend the tree until a leaf node */ while (shift >= 0) { - RT_PTR_LOCAL child; + RT_PTR_ALLOC child; + + node = RT_PTR_GET_LOCAL(tree, nodep); if (NODE_IS_LEAF(node)) break; if (!RT_NODE_SEARCH_INNER(node, key, &child)) { - RT_SET_EXTEND(tree, key, value, parent, node); + RT_SET_EXTEND(tree, key, value, parent, nodep, node); return false; } parent = node; - node = child; + nodep = child; shift -= RT_NODE_SPAN; } - updated = RT_NODE_INSERT_LEAF(tree, parent, node, key, value); + updated = RT_NODE_INSERT_LEAF(tree, parent, nodep, node, key, value); /* Update the statistics */ if (!updated) - tree->num_keys++; + tree->ctl->num_keys++; return updated; } @@ -1309,11 +1455,11 @@ RT_SEARCH(RT_RADIX_TREE *tree, uint64 key, uint64 *value_p) Assert(value_p != NULL); - if (!tree->root || key > tree->max_val) + if (!RT_PTR_ALLOC_IS_VALID(tree->ctl->root) || key > tree->ctl->max_val) return false; - node = tree->root; - shift = tree->root->shift; + node = RT_PTR_GET_LOCAL(tree, tree->ctl->root); + shift = node->shift; /* Descend the tree until a leaf node */ while (shift >= 0) @@ -1326,7 +1472,7 @@ RT_SEARCH(RT_RADIX_TREE *tree, uint64 key, uint64 *value_p) if (!RT_NODE_SEARCH_INNER(node, key, &child)) return false; - node = child; + node = RT_PTR_GET_LOCAL(tree, child); shift -= RT_NODE_SPAN; } @@ -1341,37 +1487,40 @@ RT_SCOPE bool RT_DELETE(RT_RADIX_TREE *tree, uint64 key) { RT_PTR_LOCAL node; + RT_PTR_ALLOC allocnode; RT_PTR_ALLOC stack[RT_MAX_LEVEL] = {0}; int shift; int level; bool deleted; - if (!tree->root || key > tree->max_val) + if (!RT_PTR_ALLOC_IS_VALID(tree->ctl->root) || key > tree->ctl->max_val) return false; /* * Descend the tree to search the key while building a stack of nodes we * visited. */ - node = tree->root; - shift = tree->root->shift; + allocnode = tree->ctl->root; + node = RT_PTR_GET_LOCAL(tree, allocnode); + shift = node->shift; level = -1; while (shift > 0) { RT_PTR_ALLOC child; /* Push the current node to the stack */ - stack[++level] = node; + stack[++level] = allocnode; + node = RT_PTR_GET_LOCAL(tree, allocnode); if (!RT_NODE_SEARCH_INNER(node, key, &child)) return false; - node = child; + allocnode = child; shift -= RT_NODE_SPAN; } /* Delete the key from the leaf node if exists */ - Assert(NODE_IS_LEAF(node)); + node = RT_PTR_GET_LOCAL(tree, allocnode); deleted = RT_NODE_DELETE_LEAF(node, key); if (!deleted) @@ -1381,7 +1530,7 @@ RT_DELETE(RT_RADIX_TREE *tree, uint64 key) } /* Found the key to delete. Update the statistics */ - tree->num_keys--; + tree->ctl->num_keys--; /* * Return if the leaf node still has keys and we don't need to delete the @@ -1391,13 +1540,14 @@ RT_DELETE(RT_RADIX_TREE *tree, uint64 key) return true; /* Free the empty leaf node */ - RT_FREE_NODE(tree, node); + RT_FREE_NODE(tree, allocnode); /* Delete the key in inner nodes recursively */ while (level >= 0) { - node = stack[level--]; + allocnode = stack[level--]; + node = RT_PTR_GET_LOCAL(tree, allocnode); deleted = RT_NODE_DELETE_INNER(node, key); Assert(deleted); @@ -1406,7 +1556,7 @@ RT_DELETE(RT_RADIX_TREE *tree, uint64 key) break; /* The node became empty */ - RT_FREE_NODE(tree, node); + RT_FREE_NODE(tree, allocnode); } return true; @@ -1478,6 +1628,7 @@ RT_BEGIN_ITERATE(RT_RADIX_TREE *tree) { MemoryContext old_ctx; RT_ITER *iter; + RT_PTR_LOCAL root; int top_level; old_ctx = MemoryContextSwitchTo(tree->context); @@ -1486,17 +1637,18 @@ RT_BEGIN_ITERATE(RT_RADIX_TREE *tree) iter->tree = tree; /* empty tree */ - if (!iter->tree->root) + if (!iter->tree->ctl->root) return iter; - top_level = iter->tree->root->shift / RT_NODE_SPAN; + root = RT_PTR_GET_LOCAL(tree, iter->tree->ctl->root); + top_level = root->shift / RT_NODE_SPAN; iter->stack_len = top_level; /* * Descend to the left most leaf node from the root. The key is being * constructed while descending to the leaf. */ - RT_UPDATE_ITER_STACK(iter, iter->tree->root, top_level); + RT_UPDATE_ITER_STACK(iter, root, top_level); MemoryContextSwitchTo(old_ctx); @@ -1511,7 +1663,7 @@ RT_SCOPE bool RT_ITERATE_NEXT(RT_ITER *iter, uint64 *key_p, uint64 *value_p) { /* Empty tree */ - if (!iter->tree->root) + if (!iter->tree->ctl->root) return false; for (;;) @@ -1571,7 +1723,7 @@ RT_END_ITERATE(RT_ITER *iter) RT_SCOPE uint64 RT_NUM_ENTRIES(RT_RADIX_TREE *tree) { - return tree->num_keys; + return tree->ctl->num_keys; } /* @@ -1580,13 +1732,18 @@ RT_NUM_ENTRIES(RT_RADIX_TREE *tree) RT_SCOPE uint64 RT_MEMORY_USAGE(RT_RADIX_TREE *tree) { + // XXX is this necessary? Size total = sizeof(RT_RADIX_TREE); +#ifdef RT_SHMEM + total = dsa_get_total_size(tree->dsa); +#else for (int i = 0; i < RT_SIZE_CLASS_COUNT; i++) { total += MemoryContextMemAllocated(tree->inner_slabs[i], true); total += MemoryContextMemAllocated(tree->leaf_slabs[i], true); } +#endif return total; } @@ -1670,13 +1827,13 @@ void rt_stats(RT_RADIX_TREE *tree) { ereport(NOTICE, (errmsg("num_keys = " UINT64_FORMAT ", height = %d, n4 = %u, n15 = %u, n32 = %u, n125 = %u, n256 = %u", - tree->num_keys, - tree->root->shift / RT_NODE_SPAN, - tree->cnt[RT_CLASS_4_FULL], - tree->cnt[RT_CLASS_32_PARTIAL], - tree->cnt[RT_CLASS_32_FULL], - tree->cnt[RT_CLASS_125_FULL], - tree->cnt[RT_CLASS_256]))); + tree->ctl->num_keys, + tree->ctl->root->shift / RT_NODE_SPAN, + tree->ctl->cnt[RT_CLASS_4_FULL], + tree->ctl->cnt[RT_CLASS_32_PARTIAL], + tree->ctl->cnt[RT_CLASS_32_FULL], + tree->ctl->cnt[RT_CLASS_125_FULL], + tree->ctl->cnt[RT_CLASS_256]))); } static void @@ -1848,23 +2005,23 @@ rt_dump_search(RT_RADIX_TREE *tree, uint64 key) elog(NOTICE, "-----------------------------------------------------------"); elog(NOTICE, "max_val = " UINT64_FORMAT "(0x" UINT64_FORMAT_HEX ")", - tree->max_val, tree->max_val); + tree->ctl->max_val, tree->ctl->max_val); - if (!tree->root) + if (!tree->ctl->root) { elog(NOTICE, "tree is empty"); return; } - if (key > tree->max_val) + if (key > tree->ctl->max_val) { elog(NOTICE, "key " UINT64_FORMAT "(0x" UINT64_FORMAT_HEX ") is larger than max val", key, key); return; } - node = tree->root; - shift = tree->root->shift; + node = tree->ctl->root; + shift = tree->ctl->root->shift; while (shift >= 0) { RT_PTR_LOCAL child; @@ -1901,15 +2058,15 @@ rt_dump(RT_RADIX_TREE *tree) RT_SIZE_CLASS_INFO[i].inner_blocksize, RT_SIZE_CLASS_INFO[i].leaf_size, RT_SIZE_CLASS_INFO[i].leaf_blocksize); - fprintf(stderr, "max_val = " UINT64_FORMAT "\n", tree->max_val); + fprintf(stderr, "max_val = " UINT64_FORMAT "\n", tree->ctl->max_val); - if (!tree->root) + if (!tree->ctl->root) { fprintf(stderr, "empty tree\n"); return; } - rt_dump_node(tree->root, 0, true); + rt_dump_node(tree->ctl->root, 0, true); } #endif @@ -1931,6 +2088,7 @@ rt_dump(RT_RADIX_TREE *tree) /* type declarations */ #undef RT_RADIX_TREE +#undef RT_RADIX_TREE_CONTROL #undef RT_ITER #undef RT_NODE #undef RT_NODE_ITER @@ -1959,6 +2117,7 @@ rt_dump(RT_RADIX_TREE *tree) /* function declarations */ #undef RT_CREATE #undef RT_FREE +#undef RT_ATTACH #undef RT_SET #undef RT_BEGIN_ITERATE #undef RT_ITERATE_NEXT @@ -1980,6 +2139,8 @@ rt_dump(RT_RADIX_TREE *tree) #undef RT_GROW_NODE_KIND #undef RT_COPY_NODE #undef RT_REPLACE_NODE +#undef RT_PTR_GET_LOCAL +#undef RT_PTR_ALLOC_IS_VALID #undef RT_NODE_4_SEARCH_EQ #undef RT_NODE_32_SEARCH_EQ #undef RT_NODE_4_GET_INSERTPOS @@ -2005,6 +2166,7 @@ rt_dump(RT_RADIX_TREE *tree) #undef RT_SHIFT_GET_MAX_VAL #undef RT_NODE_SEARCH_INNER #undef RT_NODE_SEARCH_LEAF +#undef RT_NODE_UPDATE_INNER #undef RT_NODE_DELETE_INNER #undef RT_NODE_DELETE_LEAF #undef RT_NODE_INSERT_INNER diff --git a/src/include/lib/radixtree_delete_impl.h b/src/include/lib/radixtree_delete_impl.h index 6eefc63e19..eb87866b90 100644 --- a/src/include/lib/radixtree_delete_impl.h +++ b/src/include/lib/radixtree_delete_impl.h @@ -16,6 +16,12 @@ uint8 chunk = RT_GET_KEY_CHUNK(key, node->shift); +#ifdef RT_NODE_LEVEL_LEAF + Assert(NODE_IS_LEAF(node)); +#else + Assert(!NODE_IS_LEAF(node)); +#endif + switch (node->kind) { case RT_NODE_KIND_4: diff --git a/src/include/lib/radixtree_insert_impl.h b/src/include/lib/radixtree_insert_impl.h index ff76583402..e4faf54d9d 100644 --- a/src/include/lib/radixtree_insert_impl.h +++ b/src/include/lib/radixtree_insert_impl.h @@ -14,11 +14,14 @@ uint8 chunk = RT_GET_KEY_CHUNK(key, node->shift); bool chunk_exists = false; - RT_NODE *newnode = NULL; + RT_PTR_LOCAL newnode = NULL; + RT_PTR_ALLOC allocnode; #ifdef RT_NODE_LEVEL_LEAF + const bool inner = false; Assert(NODE_IS_LEAF(node)); #else + const bool inner = true; Assert(!NODE_IS_LEAF(node)); #endif @@ -45,9 +48,15 @@ if (unlikely(!VAR_NODE_HAS_FREE_SLOT(n4))) { RT_NODE32_TYPE *new32; + const uint8 new_kind = RT_NODE_KIND_32; + const RT_SIZE_CLASS new_class = RT_KIND_MIN_SIZE_CLASS[new_kind]; /* grow node from 4 to 32 */ - newnode = RT_GROW_NODE_KIND(tree, node, RT_NODE_KIND_32); + allocnode = RT_ALLOC_NODE(tree, new_class, inner); + newnode = RT_PTR_GET_LOCAL(tree, allocnode); + RT_INIT_NODE(newnode, new_kind, new_class, inner); + RT_COPY_NODE(newnode, node); + //newnode = RT_GROW_NODE_KIND(tree, node, RT_NODE_KIND_32); new32 = (RT_NODE32_TYPE *) newnode; #ifdef RT_NODE_LEVEL_LEAF RT_CHUNK_VALUES_ARRAY_COPY(n4->base.chunks, n4->values, @@ -57,7 +66,7 @@ new32->base.chunks, new32->children); #endif Assert(parent != NULL); - RT_REPLACE_NODE(tree, parent, node, newnode, key); + RT_REPLACE_NODE(tree, parent, nodep, allocnode, key); node = newnode; } else @@ -112,17 +121,19 @@ n32->base.n.fanout == class32_min.fanout) { /* grow to the next size class of this kind */ + const RT_SIZE_CLASS new_class = RT_CLASS_32_FULL; + + allocnode = RT_ALLOC_NODE(tree, new_class, inner); + newnode = RT_PTR_GET_LOCAL(tree, allocnode); #ifdef RT_NODE_LEVEL_LEAF - newnode = RT_ALLOC_NODE(tree, RT_CLASS_32_FULL, false); memcpy(newnode, node, class32_min.leaf_size); #else - newnode = RT_ALLOC_NODE(tree, RT_CLASS_32_FULL, true); memcpy(newnode, node, class32_min.inner_size); #endif newnode->fanout = class32_max.fanout; Assert(parent != NULL); - RT_REPLACE_NODE(tree, parent, node, newnode, key); + RT_REPLACE_NODE(tree, parent, nodep, allocnode, key); node = newnode; /* also update pointer for this kind */ @@ -132,11 +143,17 @@ if (unlikely(!VAR_NODE_HAS_FREE_SLOT(n32))) { RT_NODE125_TYPE *new125; + const uint8 new_kind = RT_NODE_KIND_125; + const RT_SIZE_CLASS new_class = RT_KIND_MIN_SIZE_CLASS[new_kind]; Assert(n32->base.n.fanout == class32_max.fanout); /* grow node from 32 to 125 */ - newnode = RT_GROW_NODE_KIND(tree, node, RT_NODE_KIND_125); + allocnode = RT_ALLOC_NODE(tree, new_class, inner); + newnode = RT_PTR_GET_LOCAL(tree, allocnode); + RT_INIT_NODE(newnode, new_kind, new_class, inner); + RT_COPY_NODE(newnode, node); + //newnode = RT_GROW_NODE_KIND(tree, node, RT_NODE_KIND_125); new125 = (RT_NODE125_TYPE *) newnode; for (int i = 0; i < class32_max.fanout; i++) @@ -153,7 +170,7 @@ new125->base.isset[0] = (bitmapword) (((uint64) 1 << class32_max.fanout) - 1); Assert(parent != NULL); - RT_REPLACE_NODE(tree, parent, node, newnode, key); + RT_REPLACE_NODE(tree, parent, nodep, allocnode, key); node = newnode; } else @@ -204,9 +221,15 @@ if (unlikely(!VAR_NODE_HAS_FREE_SLOT(n125))) { RT_NODE256_TYPE *new256; + const uint8 new_kind = RT_NODE_KIND_256; + const RT_SIZE_CLASS new_class = RT_KIND_MIN_SIZE_CLASS[new_kind]; /* grow node from 125 to 256 */ - newnode = RT_GROW_NODE_KIND(tree, node, RT_NODE_KIND_256); + allocnode = RT_ALLOC_NODE(tree, new_class, inner); + newnode = RT_PTR_GET_LOCAL(tree, allocnode); + RT_INIT_NODE(newnode, new_kind, new_class, inner); + RT_COPY_NODE(newnode, node); + //newnode = RT_GROW_NODE_KIND(tree, node, RT_NODE_KIND_256); new256 = (RT_NODE256_TYPE *) newnode; for (int i = 0; i < RT_NODE_MAX_SLOTS && cnt < n125->base.n.count; i++) { @@ -221,7 +244,7 @@ } Assert(parent != NULL); - RT_REPLACE_NODE(tree, parent, node, newnode, key); + RT_REPLACE_NODE(tree, parent, nodep, allocnode, key); node = newnode; } else diff --git a/src/include/lib/radixtree_iter_impl.h b/src/include/lib/radixtree_iter_impl.h index a153011376..09d2018dc0 100644 --- a/src/include/lib/radixtree_iter_impl.h +++ b/src/include/lib/radixtree_iter_impl.h @@ -12,13 +12,18 @@ #error node level must be either inner or leaf #endif + bool found = false; + uint8 key_chunk; + #ifdef RT_NODE_LEVEL_LEAF uint64 value; + + Assert(NODE_IS_LEAF(node_iter->node)); #else - RT_NODE *child = NULL; + RT_PTR_LOCAL child = NULL; + + Assert(!NODE_IS_LEAF(node_iter->node)); #endif - bool found = false; - uint8 key_chunk; switch (node_iter->node->kind) { @@ -32,7 +37,7 @@ #ifdef RT_NODE_LEVEL_LEAF value = n4->values[node_iter->current_idx]; #else - child = n4->children[node_iter->current_idx]; + child = RT_PTR_GET_LOCAL(iter->tree, n4->children[node_iter->current_idx]); #endif key_chunk = n4->base.chunks[node_iter->current_idx]; found = true; @@ -49,7 +54,7 @@ #ifdef RT_NODE_LEVEL_LEAF value = n32->values[node_iter->current_idx]; #else - child = n32->children[node_iter->current_idx]; + child = RT_PTR_GET_LOCAL(iter->tree, n32->children[node_iter->current_idx]); #endif key_chunk = n32->base.chunks[node_iter->current_idx]; found = true; @@ -73,7 +78,7 @@ #ifdef RT_NODE_LEVEL_LEAF value = RT_NODE_LEAF_125_GET_VALUE(n125, i); #else - child = RT_NODE_INNER_125_GET_CHILD(n125, i); + child = RT_PTR_GET_LOCAL(iter->tree, RT_NODE_INNER_125_GET_CHILD(n125, i)); #endif key_chunk = i; found = true; @@ -101,7 +106,7 @@ #ifdef RT_NODE_LEVEL_LEAF value = RT_NODE_LEAF_256_GET_VALUE(n256, i); #else - child = RT_NODE_INNER_256_GET_CHILD(n256, i); + child = RT_PTR_GET_LOCAL(iter->tree, RT_NODE_INNER_256_GET_CHILD(n256, i)); #endif key_chunk = i; found = true; diff --git a/src/include/lib/radixtree_search_impl.h b/src/include/lib/radixtree_search_impl.h index cbc357dcc8..3e97c31c2c 100644 --- a/src/include/lib/radixtree_search_impl.h +++ b/src/include/lib/radixtree_search_impl.h @@ -16,8 +16,13 @@ #ifdef RT_NODE_LEVEL_LEAF uint64 value = 0; + + Assert(NODE_IS_LEAF(node)); #else - RT_PTR_LOCAL child = NULL; +#ifndef RT_ACTION_UPDATE + RT_PTR_ALLOC child = RT_INVALID_PTR_ALLOC; +#endif + Assert(!NODE_IS_LEAF(node)); #endif switch (node->kind) @@ -32,8 +37,12 @@ #ifdef RT_NODE_LEVEL_LEAF value = n4->values[idx]; +#else +#ifdef RT_ACTION_UPDATE + n4->children[idx] = new_child; #else child = n4->children[idx]; +#endif #endif break; } @@ -47,22 +56,31 @@ #ifdef RT_NODE_LEVEL_LEAF value = n32->values[idx]; +#else +#ifdef RT_ACTION_UPDATE + n32->children[idx] = new_child; #else child = n32->children[idx]; +#endif #endif break; } case RT_NODE_KIND_125: { RT_NODE125_TYPE *n125 = (RT_NODE125_TYPE *) node; + int slotpos = n125->base.slot_idxs[chunk]; - if (!RT_NODE_125_IS_CHUNK_USED((RT_NODE_BASE_125 *) n125, chunk)) + if (slotpos == RT_NODE_125_INVALID_IDX) return false; #ifdef RT_NODE_LEVEL_LEAF value = RT_NODE_LEAF_125_GET_VALUE(n125, chunk); +#else +#ifdef RT_ACTION_UPDATE + n125->children[slotpos] = new_child; #else child = RT_NODE_INNER_125_GET_CHILD(n125, chunk); +#endif #endif break; } @@ -79,19 +97,25 @@ #ifdef RT_NODE_LEVEL_LEAF value = RT_NODE_LEAF_256_GET_VALUE(n256, chunk); +#else +#ifdef RT_ACTION_UPDATE + RT_NODE_INNER_256_SET(n256, chunk, new_child); #else child = RT_NODE_INNER_256_GET_CHILD(n256, chunk); +#endif #endif break; } } +#ifndef RT_ACTION_UPDATE #ifdef RT_NODE_LEVEL_LEAF Assert(value_p != NULL); *value_p = value; #else Assert(child_p != NULL); *child_p = child; +#endif #endif return true; diff --git a/src/include/utils/dsa.h b/src/include/utils/dsa.h index 104386e674..c67f936880 100644 --- a/src/include/utils/dsa.h +++ b/src/include/utils/dsa.h @@ -117,6 +117,7 @@ extern dsa_handle dsa_get_handle(dsa_area *area); extern dsa_pointer dsa_allocate_extended(dsa_area *area, size_t size, int flags); extern void dsa_free(dsa_area *area, dsa_pointer dp); extern void *dsa_get_address(dsa_area *area, dsa_pointer dp); +extern size_t dsa_get_total_size(dsa_area *area); extern void dsa_trim(dsa_area *area); extern void dsa_dump(dsa_area *area); diff --git a/src/test/modules/test_radixtree/test_radixtree.c b/src/test/modules/test_radixtree/test_radixtree.c index 2256d08100..61d842789d 100644 --- a/src/test/modules/test_radixtree/test_radixtree.c +++ b/src/test/modules/test_radixtree/test_radixtree.c @@ -18,6 +18,7 @@ #include "nodes/bitmapset.h" #include "storage/block.h" #include "storage/itemptr.h" +#include "storage/lwlock.h" #include "utils/memutils.h" #include "utils/timestamp.h" @@ -103,6 +104,8 @@ static const test_spec test_specs[] = { #define RT_SCOPE static #define RT_DECLARE #define RT_DEFINE +// WIP: compiles with warnings because rt_attach is defined but not used +// #define RT_SHMEM #include "lib/radixtree.h" @@ -119,7 +122,15 @@ test_empty(void) uint64 key; uint64 val; +#ifdef RT_SHMEM + int tranche_id = LWLockNewTrancheId(); + dsa_area *dsa; + dsa = dsa_create(tranche_id); + + radixtree = rt_create(CurrentMemoryContext, dsa); +#else radixtree = rt_create(CurrentMemoryContext); +#endif if (rt_search(radixtree, 0, &dummy)) elog(ERROR, "rt_search on empty tree returned true"); @@ -153,10 +164,20 @@ test_basic(int children, bool test_inner) uint64 *keys; int shift = test_inner ? 8 : 0; +#ifdef RT_SHMEM + int tranche_id = LWLockNewTrancheId(); + dsa_area *dsa; + dsa = dsa_create(tranche_id); +#endif + elog(NOTICE, "testing basic operations with %s node %d", test_inner ? "inner" : "leaf", children); +#ifdef RT_SHMEM + radixtree = rt_create(CurrentMemoryContext, dsa); +#else radixtree = rt_create(CurrentMemoryContext); +#endif /* prepare keys in order like 1, 32, 2, 31, 2, ... */ keys = palloc(sizeof(uint64) * children); @@ -297,9 +318,19 @@ test_node_types(uint8 shift) { rt_radix_tree *radixtree; +#ifdef RT_SHMEM + int tranche_id = LWLockNewTrancheId(); + dsa_area *dsa; + dsa = dsa_create(tranche_id); +#endif + elog(NOTICE, "testing radix tree node types with shift \"%d\"", shift); +#ifdef RT_SHMEM + radixtree = rt_create(CurrentMemoryContext, dsa); +#else radixtree = rt_create(CurrentMemoryContext); +#endif /* * Insert and search entries for every node type at the 'shift' level, @@ -332,6 +363,11 @@ test_pattern(const test_spec * spec) int patternlen; uint64 *pattern_values; uint64 pattern_num_values; +#ifdef RT_SHMEM + int tranche_id = LWLockNewTrancheId(); + dsa_area *dsa; + dsa = dsa_create(tranche_id); +#endif elog(NOTICE, "testing radix tree with pattern \"%s\"", spec->test_name); if (rt_test_stats) @@ -357,7 +393,13 @@ test_pattern(const test_spec * spec) "radixtree test", ALLOCSET_SMALL_SIZES); MemoryContextSetIdentifier(radixtree_ctx, spec->test_name); + +#ifdef RT_SHMEM + radixtree = rt_create(radixtree_ctx, dsa); +#else radixtree = rt_create(radixtree_ctx); +#endif + /* * Add values to the set. @@ -563,6 +605,7 @@ test_pattern(const test_spec * spec) elog(ERROR, "rt_num_entries returned " UINT64_FORMAT ", expected " UINT64_FORMAT "after " UINT64_FORMAT " deletion", nafter, (nbefore - ndeleted), ndeleted); + rt_free(radixtree); MemoryContextDelete(radixtree_ctx); } -- 2.39.0 [text/x-patch] v17-0008-Invent-specific-pointer-macros.patch (19.3K, ../../CAFBsxsFMuyUd48SXy1VMFvpVBHh6B6300_UfTSiPckKJtcwWMA@mail.gmail.com/5-v17-0008-Invent-specific-pointer-macros.patch) download | inline diff: From 46ac0171f5a3bd80dfea8ad4061b1567650b8061 Mon Sep 17 00:00:00 2001 From: John Naylor <[email protected]> Date: Fri, 6 Jan 2023 14:20:51 +0700 Subject: [PATCH v17 8/9] Invent specific pointer macros RT_PTR_LOCAL - a normal pointer to local memory RT_PTR_ALLOC - the result of allocation, possibly a DSA pointer RT_EXTEND and RT_SET_EXTEND have some code changes to show how these are meant to be treated differently, but most punted until later. --- src/include/lib/radixtree.h | 165 +++++++++++++----------- src/include/lib/radixtree_search_impl.h | 2 +- 2 files changed, 89 insertions(+), 78 deletions(-) diff --git a/src/include/lib/radixtree.h b/src/include/lib/radixtree.h index e4350730b7..b3d84da033 100644 --- a/src/include/lib/radixtree.h +++ b/src/include/lib/radixtree.h @@ -301,8 +301,12 @@ typedef struct RT_NODE uint8 kind; } RT_NODE; -#define NODE_IS_LEAF(n) (((RT_NODE *) (n))->shift == 0) -#define NODE_IS_EMPTY(n) (((RT_NODE *) (n))->count == 0) +#define RT_PTR_LOCAL RT_NODE * + +#define RT_PTR_ALLOC RT_PTR_LOCAL + +#define NODE_IS_LEAF(n) (((RT_PTR_LOCAL) (n))->shift == 0) +#define NODE_IS_EMPTY(n) (((RT_PTR_LOCAL) (n))->count == 0) #define VAR_NODE_HAS_FREE_SLOT(node) \ ((node)->base.n.count < (node)->base.n.fanout) #define FIXED_NODE_HAS_FREE_SLOT(node, class) \ @@ -366,7 +370,7 @@ typedef struct RT_NODE_INNER_4 RT_NODE_BASE_4 base; /* number of children depends on size class */ - RT_NODE *children[FLEXIBLE_ARRAY_MEMBER]; + RT_PTR_ALLOC children[FLEXIBLE_ARRAY_MEMBER]; } RT_NODE_INNER_4; typedef struct RT_NODE_LEAF_4 @@ -382,7 +386,7 @@ typedef struct RT_NODE_INNER_32 RT_NODE_BASE_32 base; /* number of children depends on size class */ - RT_NODE *children[FLEXIBLE_ARRAY_MEMBER]; + RT_PTR_ALLOC children[FLEXIBLE_ARRAY_MEMBER]; } RT_NODE_INNER_32; typedef struct RT_NODE_LEAF_32 @@ -398,7 +402,7 @@ typedef struct RT_NODE_INNER_125 RT_NODE_BASE_125 base; /* number of children depends on size class */ - RT_NODE *children[FLEXIBLE_ARRAY_MEMBER]; + RT_PTR_ALLOC children[FLEXIBLE_ARRAY_MEMBER]; } RT_NODE_INNER_125; typedef struct RT_NODE_LEAF_125 @@ -418,7 +422,7 @@ typedef struct RT_NODE_INNER_256 RT_NODE_BASE_256 base; /* Slots for 256 children */ - RT_NODE *children[RT_NODE_MAX_SLOTS]; + RT_PTR_ALLOC children[RT_NODE_MAX_SLOTS]; } RT_NODE_INNER_256; typedef struct RT_NODE_LEAF_256 @@ -458,33 +462,33 @@ static const RT_SIZE_CLASS_ELEM RT_SIZE_CLASS_INFO[] = { [RT_CLASS_4_FULL] = { .name = "radix tree node 4", .fanout = 4, - .inner_size = sizeof(RT_NODE_INNER_4) + 4 * sizeof(RT_NODE *), + .inner_size = sizeof(RT_NODE_INNER_4) + 4 * sizeof(RT_PTR_ALLOC), .leaf_size = sizeof(RT_NODE_LEAF_4) + 4 * sizeof(uint64), - .inner_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(RT_NODE_INNER_4) + 4 * sizeof(RT_NODE *)), + .inner_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(RT_NODE_INNER_4) + 4 * sizeof(RT_PTR_ALLOC)), .leaf_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(RT_NODE_LEAF_4) + 4 * sizeof(uint64)), }, [RT_CLASS_32_PARTIAL] = { .name = "radix tree node 15", .fanout = 15, - .inner_size = sizeof(RT_NODE_INNER_32) + 15 * sizeof(RT_NODE *), + .inner_size = sizeof(RT_NODE_INNER_32) + 15 * sizeof(RT_PTR_ALLOC), .leaf_size = sizeof(RT_NODE_LEAF_32) + 15 * sizeof(uint64), - .inner_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(RT_NODE_INNER_32) + 15 * sizeof(RT_NODE *)), + .inner_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(RT_NODE_INNER_32) + 15 * sizeof(RT_PTR_ALLOC)), .leaf_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(RT_NODE_LEAF_32) + 15 * sizeof(uint64)), }, [RT_CLASS_32_FULL] = { .name = "radix tree node 32", .fanout = 32, - .inner_size = sizeof(RT_NODE_INNER_32) + 32 * sizeof(RT_NODE *), + .inner_size = sizeof(RT_NODE_INNER_32) + 32 * sizeof(RT_PTR_ALLOC), .leaf_size = sizeof(RT_NODE_LEAF_32) + 32 * sizeof(uint64), - .inner_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(RT_NODE_INNER_32) + 32 * sizeof(RT_NODE *)), + .inner_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(RT_NODE_INNER_32) + 32 * sizeof(RT_PTR_ALLOC)), .leaf_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(RT_NODE_LEAF_32) + 32 * sizeof(uint64)), }, [RT_CLASS_125_FULL] = { .name = "radix tree node 125", .fanout = 125, - .inner_size = sizeof(RT_NODE_INNER_125) + 125 * sizeof(RT_NODE *), + .inner_size = sizeof(RT_NODE_INNER_125) + 125 * sizeof(RT_PTR_ALLOC), .leaf_size = sizeof(RT_NODE_LEAF_125) + 125 * sizeof(uint64), - .inner_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(RT_NODE_INNER_125) + 125 * sizeof(RT_NODE *)), + .inner_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(RT_NODE_INNER_125) + 125 * sizeof(RT_PTR_ALLOC)), .leaf_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(RT_NODE_LEAF_125) + 125 * sizeof(uint64)), }, [RT_CLASS_256] = { @@ -512,7 +516,7 @@ typedef struct RT_RADIX_TREE { MemoryContext context; - RT_NODE *root; + RT_PTR_ALLOC root; uint64 max_val; uint64 num_keys; @@ -541,7 +545,7 @@ typedef struct RT_RADIX_TREE */ typedef struct RT_NODE_ITER { - RT_NODE *node; /* current node being iterated */ + RT_PTR_LOCAL node; /* current node being iterated */ int current_idx; /* current position. -1 for initial value */ } RT_NODE_ITER; @@ -558,13 +562,13 @@ typedef struct RT_ITER } RT_ITER; -static bool RT_NODE_INSERT_INNER(RT_RADIX_TREE *tree, RT_NODE *parent, RT_NODE *node, - uint64 key, RT_NODE *child); -static bool RT_NODE_INSERT_LEAF(RT_RADIX_TREE *tree, RT_NODE *parent, RT_NODE *node, +static bool RT_NODE_INSERT_INNER(RT_RADIX_TREE *tree, RT_PTR_LOCAL parent, RT_PTR_LOCAL node, + uint64 key, RT_PTR_LOCAL child); +static bool RT_NODE_INSERT_LEAF(RT_RADIX_TREE *tree, RT_PTR_LOCAL parent, RT_PTR_LOCAL node, uint64 key, uint64 value); /* verification (available only with assertion) */ -static void RT_VERIFY_NODE(RT_NODE *node); +static void RT_VERIFY_NODE(RT_PTR_LOCAL node); /* * Return index of the first element in 'base' that equals 'key'. Return -1 @@ -713,10 +717,10 @@ RT_NODE_32_GET_INSERTPOS(RT_NODE_BASE_32 *node, uint8 chunk) /* Shift the elements right at 'idx' by one */ static inline void -RT_CHUNK_CHILDREN_ARRAY_SHIFT(uint8 *chunks, RT_NODE **children, int count, int idx) +RT_CHUNK_CHILDREN_ARRAY_SHIFT(uint8 *chunks, RT_PTR_ALLOC *children, int count, int idx) { memmove(&(chunks[idx + 1]), &(chunks[idx]), sizeof(uint8) * (count - idx)); - memmove(&(children[idx + 1]), &(children[idx]), sizeof(RT_NODE *) * (count - idx)); + memmove(&(children[idx + 1]), &(children[idx]), sizeof(RT_PTR_ALLOC) * (count - idx)); } static inline void @@ -728,10 +732,10 @@ RT_CHUNK_VALUES_ARRAY_SHIFT(uint8 *chunks, uint64 *values, int count, int idx) /* Delete the element at 'idx' */ static inline void -RT_CHUNK_CHILDREN_ARRAY_DELETE(uint8 *chunks, RT_NODE **children, int count, int idx) +RT_CHUNK_CHILDREN_ARRAY_DELETE(uint8 *chunks, RT_PTR_ALLOC *children, int count, int idx) { memmove(&(chunks[idx]), &(chunks[idx + 1]), sizeof(uint8) * (count - idx - 1)); - memmove(&(children[idx]), &(children[idx + 1]), sizeof(RT_NODE *) * (count - idx - 1)); + memmove(&(children[idx]), &(children[idx + 1]), sizeof(RT_PTR_ALLOC) * (count - idx - 1)); } static inline void @@ -743,12 +747,12 @@ RT_CHUNK_VALUES_ARRAY_DELETE(uint8 *chunks, uint64 *values, int count, int idx) /* Copy both chunks and children/values arrays */ static inline void -RT_CHUNK_CHILDREN_ARRAY_COPY(uint8 *src_chunks, RT_NODE **src_children, - uint8 *dst_chunks, RT_NODE **dst_children) +RT_CHUNK_CHILDREN_ARRAY_COPY(uint8 *src_chunks, RT_PTR_ALLOC *src_children, + uint8 *dst_chunks, RT_PTR_ALLOC *dst_children) { const int fanout = RT_SIZE_CLASS_INFO[RT_CLASS_4_FULL].fanout; const Size chunk_size = sizeof(uint8) * fanout; - const Size children_size = sizeof(RT_NODE *) * fanout; + const Size children_size = sizeof(RT_PTR_ALLOC) * fanout; memcpy(dst_chunks, src_chunks, chunk_size); memcpy(dst_children, src_children, children_size); @@ -775,7 +779,7 @@ RT_NODE_125_IS_CHUNK_USED(RT_NODE_BASE_125 *node, uint8 chunk) return node->slot_idxs[chunk] != RT_NODE_125_INVALID_IDX; } -static inline RT_NODE * +static inline RT_PTR_ALLOC RT_NODE_INNER_125_GET_CHILD(RT_NODE_INNER_125 *node, uint8 chunk) { Assert(!NODE_IS_LEAF(node)); @@ -810,7 +814,7 @@ RT_NODE_LEAF_256_IS_CHUNK_USED(RT_NODE_LEAF_256 *node, uint8 chunk) return (node->isset[idx] & ((bitmapword) 1 << bitnum)) != 0; } -static inline RT_NODE * +static inline RT_PTR_ALLOC RT_NODE_INNER_256_GET_CHILD(RT_NODE_INNER_256 *node, uint8 chunk) { Assert(!NODE_IS_LEAF(node)); @@ -828,7 +832,7 @@ RT_NODE_LEAF_256_GET_VALUE(RT_NODE_LEAF_256 *node, uint8 chunk) /* Set the child in the node-256 */ static inline void -RT_NODE_INNER_256_SET(RT_NODE_INNER_256 *node, uint8 chunk, RT_NODE *child) +RT_NODE_INNER_256_SET(RT_NODE_INNER_256 *node, uint8 chunk, RT_PTR_ALLOC child) { Assert(!NODE_IS_LEAF(node)); node->children[chunk] = child; @@ -890,16 +894,16 @@ RT_SHIFT_GET_MAX_VAL(int shift) /* * Allocate a new node with the given node kind. */ -static RT_NODE * +static RT_PTR_ALLOC RT_ALLOC_NODE(RT_RADIX_TREE *tree, RT_SIZE_CLASS size_class, bool inner) { - RT_NODE *newnode; + RT_PTR_ALLOC newnode; if (inner) - newnode = (RT_NODE *) MemoryContextAlloc(tree->inner_slabs[size_class], + newnode = (RT_PTR_ALLOC) MemoryContextAlloc(tree->inner_slabs[size_class], RT_SIZE_CLASS_INFO[size_class].inner_size); else - newnode = (RT_NODE *) MemoryContextAlloc(tree->leaf_slabs[size_class], + newnode = (RT_PTR_ALLOC) MemoryContextAlloc(tree->leaf_slabs[size_class], RT_SIZE_CLASS_INFO[size_class].leaf_size); #ifdef RT_DEBUG @@ -912,7 +916,7 @@ RT_ALLOC_NODE(RT_RADIX_TREE *tree, RT_SIZE_CLASS size_class, bool inner) /* Initialize the node contents */ static inline void -RT_INIT_NODE(RT_NODE *node, uint8 kind, RT_SIZE_CLASS size_class, bool inner) +RT_INIT_NODE(RT_PTR_LOCAL node, uint8 kind, RT_SIZE_CLASS size_class, bool inner) { if (inner) MemSet(node, 0, RT_SIZE_CLASS_INFO[size_class].inner_size); @@ -947,7 +951,7 @@ RT_NEW_ROOT(RT_RADIX_TREE *tree, uint64 key) { int shift = RT_KEY_GET_SHIFT(key); bool inner = shift > 0; - RT_NODE *newnode; + RT_PTR_ALLOC newnode; newnode = RT_ALLOC_NODE(tree, RT_CLASS_4_FULL, inner); RT_INIT_NODE(newnode, RT_NODE_KIND_4, RT_CLASS_4_FULL, inner); @@ -957,7 +961,7 @@ RT_NEW_ROOT(RT_RADIX_TREE *tree, uint64 key) } static inline void -RT_COPY_NODE(RT_NODE *newnode, RT_NODE *oldnode) +RT_COPY_NODE(RT_PTR_LOCAL newnode, RT_PTR_LOCAL oldnode) { newnode->shift = oldnode->shift; newnode->chunk = oldnode->chunk; @@ -969,9 +973,9 @@ RT_COPY_NODE(RT_NODE *newnode, RT_NODE *oldnode) * count of 'node'. */ static RT_NODE* -RT_GROW_NODE_KIND(RT_RADIX_TREE *tree, RT_NODE *node, uint8 new_kind) +RT_GROW_NODE_KIND(RT_RADIX_TREE *tree, RT_PTR_LOCAL node, uint8 new_kind) { - RT_NODE *newnode; + RT_PTR_ALLOC newnode; bool inner = !NODE_IS_LEAF(node); newnode = RT_ALLOC_NODE(tree, RT_KIND_MIN_SIZE_CLASS[new_kind], inner); @@ -983,7 +987,7 @@ RT_GROW_NODE_KIND(RT_RADIX_TREE *tree, RT_NODE *node, uint8 new_kind) /* Free the given node */ static void -RT_FREE_NODE(RT_RADIX_TREE *tree, RT_NODE *node) +RT_FREE_NODE(RT_RADIX_TREE *tree, RT_PTR_ALLOC node) { /* If we're deleting the root node, make the tree empty */ if (tree->root == node) @@ -1019,8 +1023,8 @@ RT_FREE_NODE(RT_RADIX_TREE *tree, RT_NODE *node) * Replace old_child with new_child, and free the old one. */ static void -RT_REPLACE_NODE(RT_RADIX_TREE *tree, RT_NODE *parent, RT_NODE *old_child, - RT_NODE *new_child, uint64 key) +RT_REPLACE_NODE(RT_RADIX_TREE *tree, RT_PTR_LOCAL parent, RT_PTR_ALLOC old_child, + RT_PTR_ALLOC new_child, uint64 key) { Assert(old_child->chunk == new_child->chunk); Assert(old_child->shift == new_child->shift); @@ -1056,17 +1060,22 @@ RT_EXTEND(RT_RADIX_TREE *tree, uint64 key) /* Grow tree from 'shift' to 'target_shift' */ while (shift <= target_shift) { - RT_NODE_INNER_4 *node; + RT_PTR_ALLOC allocnode; + RT_PTR_LOCAL node; + RT_NODE_INNER_4 *n4; + + allocnode = RT_ALLOC_NODE(tree, RT_CLASS_4_FULL, true); + node = (RT_PTR_LOCAL) allocnode; + RT_INIT_NODE(node, RT_NODE_KIND_4, RT_CLASS_4_FULL, true); + node->shift = shift; + node->count = 1; - node = (RT_NODE_INNER_4 *) RT_ALLOC_NODE(tree, RT_CLASS_4_FULL, true); - RT_INIT_NODE((RT_NODE *) node, RT_NODE_KIND_4, RT_CLASS_4_FULL, true); - node->base.n.shift = shift; - node->base.n.count = 1; - node->base.chunks[0] = 0; - node->children[0] = tree->root; + n4 = (RT_NODE_INNER_4 *) node; + n4->base.chunks[0] = 0; + n4->children[0] = tree->root; tree->root->chunk = 0; - tree->root = (RT_NODE *) node; + tree->root = node; shift += RT_NODE_SPAN; } @@ -1079,18 +1088,20 @@ RT_EXTEND(RT_RADIX_TREE *tree, uint64 key) * Insert inner and leaf nodes from 'node' to bottom. */ static inline void -RT_SET_EXTEND(RT_RADIX_TREE *tree, uint64 key, uint64 value, RT_NODE *parent, - RT_NODE *node) +RT_SET_EXTEND(RT_RADIX_TREE *tree, uint64 key, uint64 value, RT_PTR_LOCAL parent, + RT_PTR_LOCAL node) { int shift = node->shift; while (shift >= RT_NODE_SPAN) { - RT_NODE *newchild; + RT_PTR_ALLOC allocchild; + RT_PTR_LOCAL newchild; int newshift = shift - RT_NODE_SPAN; bool inner = newshift > 0; - newchild = RT_ALLOC_NODE(tree, RT_CLASS_4_FULL, inner); + allocchild = RT_ALLOC_NODE(tree, RT_CLASS_4_FULL, inner); + newchild = (RT_PTR_LOCAL) allocchild; RT_INIT_NODE(newchild, RT_NODE_KIND_4, RT_CLASS_4_FULL, inner); newchild->shift = newshift; newchild->chunk = RT_GET_KEY_CHUNK(key, node->shift); @@ -1112,7 +1123,7 @@ RT_SET_EXTEND(RT_RADIX_TREE *tree, uint64 key, uint64 value, RT_NODE *parent, * pointer is set to child_p. */ static inline bool -RT_NODE_SEARCH_INNER(RT_NODE *node, uint64 key, RT_NODE **child_p) +RT_NODE_SEARCH_INNER(RT_PTR_LOCAL node, uint64 key, RT_PTR_ALLOC *child_p) { #define RT_NODE_LEVEL_INNER #include "lib/radixtree_search_impl.h" @@ -1126,7 +1137,7 @@ RT_NODE_SEARCH_INNER(RT_NODE *node, uint64 key, RT_NODE **child_p) * to the value is set to value_p. */ static inline bool -RT_NODE_SEARCH_LEAF(RT_NODE *node, uint64 key, uint64 *value_p) +RT_NODE_SEARCH_LEAF(RT_PTR_LOCAL node, uint64 key, uint64 *value_p) { #define RT_NODE_LEVEL_LEAF #include "lib/radixtree_search_impl.h" @@ -1139,7 +1150,7 @@ RT_NODE_SEARCH_LEAF(RT_NODE *node, uint64 key, uint64 *value_p) * Delete the node and return true if the key is found, otherwise return false. */ static inline bool -RT_NODE_DELETE_INNER(RT_NODE *node, uint64 key) +RT_NODE_DELETE_INNER(RT_PTR_LOCAL node, uint64 key) { #define RT_NODE_LEVEL_INNER #include "lib/radixtree_delete_impl.h" @@ -1152,7 +1163,7 @@ RT_NODE_DELETE_INNER(RT_NODE *node, uint64 key) * Delete the node and return true if the key is found, otherwise return false. */ static inline bool -RT_NODE_DELETE_LEAF(RT_NODE *node, uint64 key) +RT_NODE_DELETE_LEAF(RT_PTR_LOCAL node, uint64 key) { #define RT_NODE_LEVEL_LEAF #include "lib/radixtree_delete_impl.h" @@ -1161,8 +1172,8 @@ RT_NODE_DELETE_LEAF(RT_NODE *node, uint64 key) /* Insert the child to the inner node */ static bool -RT_NODE_INSERT_INNER(RT_RADIX_TREE *tree, RT_NODE *parent, RT_NODE *node, uint64 key, - RT_NODE *child) +RT_NODE_INSERT_INNER(RT_RADIX_TREE *tree, RT_PTR_LOCAL parent, RT_PTR_LOCAL node, uint64 key, + RT_PTR_ALLOC child) { #define RT_NODE_LEVEL_INNER #include "lib/radixtree_insert_impl.h" @@ -1171,7 +1182,7 @@ RT_NODE_INSERT_INNER(RT_RADIX_TREE *tree, RT_NODE *parent, RT_NODE *node, uint64 /* Insert the value to the leaf node */ static bool -RT_NODE_INSERT_LEAF(RT_RADIX_TREE *tree, RT_NODE *parent, RT_NODE *node, +RT_NODE_INSERT_LEAF(RT_RADIX_TREE *tree, RT_PTR_LOCAL parent, RT_PTR_LOCAL node, uint64 key, uint64 value) { #define RT_NODE_LEVEL_LEAF @@ -1241,8 +1252,8 @@ RT_SET(RT_RADIX_TREE *tree, uint64 key, uint64 value) { int shift; bool updated; - RT_NODE *node; - RT_NODE *parent; + RT_PTR_LOCAL node; + RT_PTR_LOCAL parent; /* Empty tree, create the root */ if (!tree->root) @@ -1260,7 +1271,7 @@ RT_SET(RT_RADIX_TREE *tree, uint64 key, uint64 value) /* Descend the tree until a leaf node */ while (shift >= 0) { - RT_NODE *child; + RT_PTR_LOCAL child; if (NODE_IS_LEAF(node)) break; @@ -1293,7 +1304,7 @@ RT_SET(RT_RADIX_TREE *tree, uint64 key, uint64 value) RT_SCOPE bool RT_SEARCH(RT_RADIX_TREE *tree, uint64 key, uint64 *value_p) { - RT_NODE *node; + RT_PTR_LOCAL node; int shift; Assert(value_p != NULL); @@ -1307,7 +1318,7 @@ RT_SEARCH(RT_RADIX_TREE *tree, uint64 key, uint64 *value_p) /* Descend the tree until a leaf node */ while (shift >= 0) { - RT_NODE *child; + RT_PTR_ALLOC child; if (NODE_IS_LEAF(node)) break; @@ -1329,8 +1340,8 @@ RT_SEARCH(RT_RADIX_TREE *tree, uint64 key, uint64 *value_p) RT_SCOPE bool RT_DELETE(RT_RADIX_TREE *tree, uint64 key) { - RT_NODE *node; - RT_NODE *stack[RT_MAX_LEVEL] = {0}; + RT_PTR_LOCAL node; + RT_PTR_ALLOC stack[RT_MAX_LEVEL] = {0}; int shift; int level; bool deleted; @@ -1347,7 +1358,7 @@ RT_DELETE(RT_RADIX_TREE *tree, uint64 key) level = -1; while (shift > 0) { - RT_NODE *child; + RT_PTR_ALLOC child; /* Push the current node to the stack */ stack[++level] = node; @@ -1412,7 +1423,7 @@ RT_ITER_UPDATE_KEY(RT_ITER *iter, uint8 chunk, uint8 shift) * Advance the slot in the inner node. Return the child if exists, otherwise * null. */ -static inline RT_NODE * +static inline RT_PTR_LOCAL RT_NODE_INNER_ITERATE_NEXT(RT_ITER *iter, RT_NODE_ITER *node_iter) { #define RT_NODE_LEVEL_INNER @@ -1437,10 +1448,10 @@ RT_NODE_LEAF_ITERATE_NEXT(RT_ITER *iter, RT_NODE_ITER *node_iter, * Update each node_iter for inner nodes in the iterator node stack. */ static void -RT_UPDATE_ITER_STACK(RT_ITER *iter, RT_NODE *from_node, int from) +RT_UPDATE_ITER_STACK(RT_ITER *iter, RT_PTR_LOCAL from_node, int from) { int level = from; - RT_NODE *node = from_node; + RT_PTR_LOCAL node = from_node; for (;;) { @@ -1505,7 +1516,7 @@ RT_ITERATE_NEXT(RT_ITER *iter, uint64 *key_p, uint64 *value_p) for (;;) { - RT_NODE *child = NULL; + RT_PTR_LOCAL child = NULL; uint64 value; int level; bool found; @@ -1584,7 +1595,7 @@ RT_MEMORY_USAGE(RT_RADIX_TREE *tree) * Verify the radix tree node. */ static void -RT_VERIFY_NODE(RT_NODE *node) +RT_VERIFY_NODE(RT_PTR_LOCAL node) { #ifdef USE_ASSERT_CHECKING Assert(node->count >= 0); @@ -1669,7 +1680,7 @@ rt_stats(RT_RADIX_TREE *tree) } static void -rt_dump_node(RT_NODE *node, int level, bool recurse) +rt_dump_node(RT_PTR_LOCAL node, int level, bool recurse) { char space[125] = {0}; @@ -1831,7 +1842,7 @@ rt_dump_node(RT_NODE *node, int level, bool recurse) void rt_dump_search(RT_RADIX_TREE *tree, uint64 key) { - RT_NODE *node; + RT_PTR_LOCAL node; int shift; int level = 0; @@ -1856,7 +1867,7 @@ rt_dump_search(RT_RADIX_TREE *tree, uint64 key) shift = tree->root->shift; while (shift >= 0) { - RT_NODE *child; + RT_PTR_LOCAL child; rt_dump_node(node, level, false); diff --git a/src/include/lib/radixtree_search_impl.h b/src/include/lib/radixtree_search_impl.h index 1a0d2d3f1f..cbc357dcc8 100644 --- a/src/include/lib/radixtree_search_impl.h +++ b/src/include/lib/radixtree_search_impl.h @@ -17,7 +17,7 @@ #ifdef RT_NODE_LEVEL_LEAF uint64 value = 0; #else - RT_NODE *child = NULL; + RT_PTR_LOCAL child = NULL; #endif switch (node->kind) -- 2.39.0 [text/x-patch] v17-0007-Convert-radixtree.h-into-a-template.patch (82.4K, ../../CAFBsxsFMuyUd48SXy1VMFvpVBHh6B6300_UfTSiPckKJtcwWMA@mail.gmail.com/6-v17-0007-Convert-radixtree.h-into-a-template.patch) download | inline diff: From b4857416c4030057a79cf52cdd7ffff88f55f73c Mon Sep 17 00:00:00 2001 From: John Naylor <[email protected]> Date: Wed, 4 Jan 2023 14:43:17 +0700 Subject: [PATCH v17 7/9] Convert radixtree.h into a template The only thing configurable at this point is function scope and prefix, since the point is to see if this makes a shared memory implementation clear and maintainable. The key and value type are still hard-coded to uint64. To make this more useful, at least value type should be configurable. It might be good at some point to offer a different tree type, e.g. "single-value leaves" to allow for variable length keys and values, giving full flexibility to developers. --- src/include/lib/radixtree.h | 987 +++++++++++------- src/include/lib/radixtree_delete_impl.h | 36 +- src/include/lib/radixtree_insert_impl.h | 92 +- src/include/lib/radixtree_iter_impl.h | 34 +- src/include/lib/radixtree_search_impl.h | 36 +- .../modules/test_radixtree/test_radixtree.c | 23 +- 6 files changed, 718 insertions(+), 490 deletions(-) diff --git a/src/include/lib/radixtree.h b/src/include/lib/radixtree.h index fe517793f4..e4350730b7 100644 --- a/src/include/lib/radixtree.h +++ b/src/include/lib/radixtree.h @@ -29,24 +29,41 @@ * * XXX: the radix tree node never be shrunk. * + * To generate a radix tree and associated functions for a use case several + * macros have to be #define'ed before this file is included. Including + * the file #undef's all those, so a new radix tree can be generated + * afterwards. + * The relevant parameters are: + * - RT_PREFIX - prefix for all symbol names generated. A prefix of 'foo' + * will result in radix tree type 'foo_radix_tree' and functions like + * 'foo_create'/'foo_free' and so forth. + * - RT_DECLARE - if defined function prototypes and type declarations are + * generated + * - RT_DEFINE - if defined function definitions are generated + * - RT_SCOPE - in which scope (e.g. extern, static inline) do function + * declarations reside + * + * Optional parameters: + * - RT_DEBUG - if defined add stats tracking and debugging functions + * * Interface * --------- * - * rt_create - Create a new, empty radix tree - * rt_free - Free the radix tree - * rt_search - Search a key-value pair - * rt_set - Set a key-value pair - * rt_delete - Delete a key-value pair - * rt_begin_iterate - Begin iterating through all key-value pairs - * rt_iterate_next - Return next key-value pair, if any - * rt_end_iter - End iteration - * rt_memory_usage - Get the memory usage - * rt_num_entries - Get the number of key-value pairs + * RT_CREATE - Create a new, empty radix tree + * RT_FREE - Free the radix tree + * RT_SEARCH - Search a key-value pair + * RT_SET - Set a key-value pair + * RT_DELETE - Delete a key-value pair + * RT_BEGIN_ITERATE - Begin iterating through all key-value pairs + * RT_ITERATE_NEXT - Return next key-value pair, if any + * RT_END_ITER - End iteration + * RT_MEMORY_USAGE - Get the memory usage + * RT_NUM_ENTRIES - Get the number of key-value pairs * - * rt_create() creates an empty radix tree in the given memory context + * RT_CREATE() creates an empty radix tree in the given memory context * and memory contexts for all kinds of radix tree node under the memory context. * - * rt_iterate_next() ensures returning key-value pairs in the ascending + * RT_ITERATE_NEXT() ensures returning key-value pairs in the ascending * order of the key. * * Copyright (c) 2022, PostgreSQL Global Development Group @@ -66,6 +83,133 @@ #include "port/pg_lfind.h" #include "utils/memutils.h" +/* helpers */ +#define RT_MAKE_PREFIX(a) CppConcat(a,_) +#define RT_MAKE_NAME(name) RT_MAKE_NAME_(RT_MAKE_PREFIX(RT_PREFIX),name) +#define RT_MAKE_NAME_(a,b) CppConcat(a,b) + +/* function declarations */ +#define RT_CREATE RT_MAKE_NAME(create) +#define RT_FREE RT_MAKE_NAME(free) +#define RT_SEARCH RT_MAKE_NAME(search) +#define RT_SET RT_MAKE_NAME(set) +#define RT_BEGIN_ITERATE RT_MAKE_NAME(begin_iterate) +#define RT_ITERATE_NEXT RT_MAKE_NAME(iterate_next) +#define RT_END_ITERATE RT_MAKE_NAME(end_iterate) +#define RT_DELETE RT_MAKE_NAME(delete) +#define RT_MEMORY_USAGE RT_MAKE_NAME(memory_usage) +#define RT_NUM_ENTRIES RT_MAKE_NAME(num_entries) +#define RT_DUMP RT_MAKE_NAME(dump) +#define RT_DUMP_SEARCH RT_MAKE_NAME(dump_search) +#define RT_STATS RT_MAKE_NAME(stats) + +/* internal helper functions (no externally visible prototypes) */ +#define RT_NEW_ROOT RT_MAKE_NAME(new_root) +#define RT_ALLOC_NODE RT_MAKE_NAME(alloc_node) +#define RT_INIT_NODE RT_MAKE_NAME(init_node) +#define RT_FREE_NODE RT_MAKE_NAME(free_node) +#define RT_EXTEND RT_MAKE_NAME(extend) +#define RT_SET_EXTEND RT_MAKE_NAME(set_extend) +#define RT_GROW_NODE_KIND RT_MAKE_NAME(grow_node_kind) +#define RT_COPY_NODE RT_MAKE_NAME(copy_node) +#define RT_REPLACE_NODE RT_MAKE_NAME(replace_node) +#define RT_NODE_4_SEARCH_EQ RT_MAKE_NAME(node_4_search_eq) +#define RT_NODE_32_SEARCH_EQ RT_MAKE_NAME(node_32_search_eq) +#define RT_NODE_4_GET_INSERTPOS RT_MAKE_NAME(node_4_get_insertpos) +#define RT_NODE_32_GET_INSERTPOS RT_MAKE_NAME(node_32_get_insertpos) +#define RT_CHUNK_CHILDREN_ARRAY_SHIFT RT_MAKE_NAME(chunk_children_array_shift) +#define RT_CHUNK_VALUES_ARRAY_SHIFT RT_MAKE_NAME(chunk_values_array_shift) +#define RT_CHUNK_CHILDREN_ARRAY_DELETE RT_MAKE_NAME(chunk_children_array_delete) +#define RT_CHUNK_VALUES_ARRAY_DELETE RT_MAKE_NAME(chunk_values_array_delete) +#define RT_CHUNK_CHILDREN_ARRAY_COPY RT_MAKE_NAME(chunk_children_array_copy) +#define RT_CHUNK_VALUES_ARRAY_COPY RT_MAKE_NAME(chunk_values_array_copy) +#define RT_NODE_125_IS_CHUNK_USED RT_MAKE_NAME(node_125_is_chunk_used) +#define RT_NODE_INNER_125_GET_CHILD RT_MAKE_NAME(node_inner_125_get_child) +#define RT_NODE_LEAF_125_GET_VALUE RT_MAKE_NAME(node_leaf_125_get_value) +#define RT_NODE_INNER_256_IS_CHUNK_USED RT_MAKE_NAME(node_inner_256_is_chunk_used) +#define RT_NODE_LEAF_256_IS_CHUNK_USED RT_MAKE_NAME(node_leaf_256_is_chunk_used) +#define RT_NODE_INNER_256_GET_CHILD RT_MAKE_NAME(node_inner_256_get_child) +#define RT_NODE_LEAF_256_GET_VALUE RT_MAKE_NAME(node_leaf_256_get_value) +#define RT_NODE_INNER_256_SET RT_MAKE_NAME(node_inner_256_set) +#define RT_NODE_LEAF_256_SET RT_MAKE_NAME(node_leaf_256_set) +#define RT_NODE_INNER_256_DELETE RT_MAKE_NAME(node_inner_256_delete) +#define RT_NODE_LEAF_256_DELETE RT_MAKE_NAME(node_leaf_256_delete) +#define RT_KEY_GET_SHIFT RT_MAKE_NAME(key_get_shift) +#define RT_SHIFT_GET_MAX_VAL RT_MAKE_NAME(shift_get_max_val) +#define RT_NODE_SEARCH_INNER RT_MAKE_NAME(node_search_inner) +#define RT_NODE_SEARCH_LEAF RT_MAKE_NAME(node_search_leaf) +#define RT_NODE_DELETE_INNER RT_MAKE_NAME(node_delete_inner) +#define RT_NODE_DELETE_LEAF RT_MAKE_NAME(node_delete_leaf) +#define RT_NODE_INSERT_INNER RT_MAKE_NAME(node_insert_inner) +#define RT_NODE_INSERT_LEAF RT_MAKE_NAME(node_insert_leaf) +#define RT_NODE_INNER_ITERATE_NEXT RT_MAKE_NAME(node_inner_iterate_next) +#define RT_NODE_LEAF_ITERATE_NEXT RT_MAKE_NAME(node_leaf_iterate_next) +#define RT_UPDATE_ITER_STACK RT_MAKE_NAME(update_iter_stack) +#define RT_ITER_UPDATE_KEY RT_MAKE_NAME(iter_update_key) +#define RT_VERIFY_NODE RT_MAKE_NAME(verify_node) + +/* type declarations */ +#define RT_RADIX_TREE RT_MAKE_NAME(radix_tree) +#define RT_ITER RT_MAKE_NAME(iter) +#define RT_NODE RT_MAKE_NAME(node) +#define RT_NODE_ITER RT_MAKE_NAME(node_iter) +#define RT_NODE_BASE_4 RT_MAKE_NAME(node_base_4) +#define RT_NODE_BASE_32 RT_MAKE_NAME(node_base_32) +#define RT_NODE_BASE_125 RT_MAKE_NAME(node_base_125) +#define RT_NODE_BASE_256 RT_MAKE_NAME(node_base_256) +#define RT_NODE_INNER_4 RT_MAKE_NAME(node_inner_4) +#define RT_NODE_INNER_32 RT_MAKE_NAME(node_inner_32) +#define RT_NODE_INNER_125 RT_MAKE_NAME(node_inner_125) +#define RT_NODE_INNER_256 RT_MAKE_NAME(node_inner_256) +#define RT_NODE_LEAF_4 RT_MAKE_NAME(node_leaf_4) +#define RT_NODE_LEAF_32 RT_MAKE_NAME(node_leaf_32) +#define RT_NODE_LEAF_125 RT_MAKE_NAME(node_leaf_125) +#define RT_NODE_LEAF_256 RT_MAKE_NAME(node_leaf_256) +#define RT_SIZE_CLASS RT_MAKE_NAME(size_class) +#define RT_SIZE_CLASS_ELEM RT_MAKE_NAME(size_class_elem) +#define RT_SIZE_CLASS_INFO RT_MAKE_NAME(size_class_info) +#define RT_CLASS_4_FULL RT_MAKE_NAME(class_4_full) +#define RT_CLASS_32_PARTIAL RT_MAKE_NAME(class_32_partial) +#define RT_CLASS_32_FULL RT_MAKE_NAME(class_32_full) +#define RT_CLASS_125_FULL RT_MAKE_NAME(class_125_full) +#define RT_CLASS_256 RT_MAKE_NAME(class_256) +#define RT_KIND_MIN_SIZE_CLASS RT_MAKE_NAME(kind_min_size_class) + +/* generate forward declarations necessary to use the radix tree */ +#ifdef RT_DECLARE + +typedef struct RT_RADIX_TREE RT_RADIX_TREE; +typedef struct RT_ITER RT_ITER; + +RT_SCOPE RT_RADIX_TREE * RT_CREATE(MemoryContext ctx); +RT_SCOPE void RT_FREE(RT_RADIX_TREE *tree); +RT_SCOPE bool RT_SEARCH(RT_RADIX_TREE *tree, uint64 key, uint64 *val_p); +RT_SCOPE bool RT_SET(RT_RADIX_TREE *tree, uint64 key, uint64 val); +RT_SCOPE bool RT_DELETE(RT_RADIX_TREE *tree, uint64 key); + +RT_SCOPE RT_ITER * RT_BEGIN_ITERATE(RT_RADIX_TREE *tree); +RT_SCOPE bool RT_ITERATE_NEXT(RT_ITER *iter, uint64 *key_p, uint64 *value_p); +RT_SCOPE void RT_END_ITERATE(RT_ITER *iter); + +RT_SCOPE uint64 RT_MEMORY_USAGE(RT_RADIX_TREE *tree); +RT_SCOPE uint64 RT_NUM_ENTRIES(RT_RADIX_TREE *tree); + +#ifdef RT_DEBUG +RT_SCOPE void RT_DUMP(RT_RADIX_TREE *tree); +RT_SCOPE void RT_DUMP_SEARCH(RT_RADIX_TREE *tree, uint64 key); +RT_SCOPE void RT_STATS(RT_RADIX_TREE *tree); +#endif + +#endif /* RT_DECLARE */ + + +/* generate implementation of the radix tree */ +#ifdef RT_DEFINE + +/* macros and types common to all implementations */ +#ifndef RT_COMMON +#define RT_COMMON + #ifdef RT_DEBUG #define UINT64_FORMAT_HEX "%" INT64_MODIFIER "X" #endif @@ -80,7 +224,7 @@ #define RT_CHUNK_MASK ((1 << RT_NODE_SPAN) - 1) /* Maximum shift the radix tree uses */ -#define RT_MAX_SHIFT key_get_shift(UINT64_MAX) +#define RT_MAX_SHIFT RT_KEY_GET_SHIFT(UINT64_MAX) /* Tree level the radix tree uses */ #define RT_MAX_LEVEL ((sizeof(uint64) * BITS_PER_BYTE) / RT_NODE_SPAN) @@ -101,7 +245,7 @@ * There are 4 node kinds and each node kind have one or two size classes, * partial and full. The size classes in the same node kind have the same * node structure but have the different number of fanout that is stored - * in 'fanout' of rt_node. For example in size class 15, when a 16th element + * in 'fanout' of RT_NODE. For example in size class 15, when a 16th element * is to be inserted, we allocate a larger area and memcpy the entire old * node to it. * @@ -119,19 +263,20 @@ #define RT_NODE_KIND_256 0x03 #define RT_NODE_KIND_COUNT 4 -typedef enum rt_size_class +#endif /* RT_COMMON */ + + +typedef enum RT_SIZE_CLASS { RT_CLASS_4_FULL = 0, RT_CLASS_32_PARTIAL, RT_CLASS_32_FULL, RT_CLASS_125_FULL, RT_CLASS_256 - -#define RT_SIZE_CLASS_COUNT (RT_CLASS_256 + 1) -} rt_size_class; +} RT_SIZE_CLASS; /* Common type for all nodes types */ -typedef struct rt_node +typedef struct RT_NODE { /* * Number of children. We use uint16 to be able to indicate 256 children @@ -154,53 +299,54 @@ typedef struct rt_node /* Node kind, one per search/set algorithm */ uint8 kind; -} rt_node; -#define NODE_IS_LEAF(n) (((rt_node *) (n))->shift == 0) -#define NODE_IS_EMPTY(n) (((rt_node *) (n))->count == 0) +} RT_NODE; + +#define NODE_IS_LEAF(n) (((RT_NODE *) (n))->shift == 0) +#define NODE_IS_EMPTY(n) (((RT_NODE *) (n))->count == 0) #define VAR_NODE_HAS_FREE_SLOT(node) \ ((node)->base.n.count < (node)->base.n.fanout) #define FIXED_NODE_HAS_FREE_SLOT(node, class) \ - ((node)->base.n.count < rt_size_class_info[class].fanout) + ((node)->base.n.count < RT_SIZE_CLASS_INFO[class].fanout) /* Base type of each node kinds for leaf and inner nodes */ /* The base types must be a be able to accommodate the largest size class for variable-sized node kinds*/ -typedef struct rt_node_base_4 +typedef struct RT_NODE_BASE_4 { - rt_node n; + RT_NODE n; /* 4 children, for key chunks */ uint8 chunks[4]; -} rt_node_base_4; +} RT_NODE_BASE_4; -typedef struct rt_node_base32 +typedef struct RT_NODE_BASE_32 { - rt_node n; + RT_NODE n; /* 32 children, for key chunks */ uint8 chunks[32]; -} rt_node_base_32; +} RT_NODE_BASE_32; /* * node-125 uses slot_idx array, an array of RT_NODE_MAX_SLOTS length, typically * 256, to store indexes into a second array that contains up to 125 values (or * child pointers in inner nodes). */ -typedef struct rt_node_base125 +typedef struct RT_NODE_BASE_125 { - rt_node n; + RT_NODE n; /* The index of slots for each fanout */ uint8 slot_idxs[RT_NODE_MAX_SLOTS]; /* isset is a bitmap to track which slot is in use */ bitmapword isset[BM_IDX(128)]; -} rt_node_base_125; +} RT_NODE_BASE_125; -typedef struct rt_node_base256 +typedef struct RT_NODE_BASE_256 { - rt_node n; -} rt_node_base_256; + RT_NODE n; +} RT_NODE_BASE_256; /* * Inner and leaf nodes. @@ -215,79 +361,79 @@ typedef struct rt_node_base256 * good. It might be better to just indicate non-existing entries the same way * in inner nodes. */ -typedef struct rt_node_inner_4 +typedef struct RT_NODE_INNER_4 { - rt_node_base_4 base; + RT_NODE_BASE_4 base; /* number of children depends on size class */ - rt_node *children[FLEXIBLE_ARRAY_MEMBER]; -} rt_node_inner_4; + RT_NODE *children[FLEXIBLE_ARRAY_MEMBER]; +} RT_NODE_INNER_4; -typedef struct rt_node_leaf_4 +typedef struct RT_NODE_LEAF_4 { - rt_node_base_4 base; + RT_NODE_BASE_4 base; /* number of values depends on size class */ uint64 values[FLEXIBLE_ARRAY_MEMBER]; -} rt_node_leaf_4; +} RT_NODE_LEAF_4; -typedef struct rt_node_inner_32 +typedef struct RT_NODE_INNER_32 { - rt_node_base_32 base; + RT_NODE_BASE_32 base; /* number of children depends on size class */ - rt_node *children[FLEXIBLE_ARRAY_MEMBER]; -} rt_node_inner_32; + RT_NODE *children[FLEXIBLE_ARRAY_MEMBER]; +} RT_NODE_INNER_32; -typedef struct rt_node_leaf_32 +typedef struct RT_NODE_LEAF_32 { - rt_node_base_32 base; + RT_NODE_BASE_32 base; /* number of values depends on size class */ uint64 values[FLEXIBLE_ARRAY_MEMBER]; -} rt_node_leaf_32; +} RT_NODE_LEAF_32; -typedef struct rt_node_inner_125 +typedef struct RT_NODE_INNER_125 { - rt_node_base_125 base; + RT_NODE_BASE_125 base; /* number of children depends on size class */ - rt_node *children[FLEXIBLE_ARRAY_MEMBER]; -} rt_node_inner_125; + RT_NODE *children[FLEXIBLE_ARRAY_MEMBER]; +} RT_NODE_INNER_125; -typedef struct rt_node_leaf_125 +typedef struct RT_NODE_LEAF_125 { - rt_node_base_125 base; + RT_NODE_BASE_125 base; /* number of values depends on size class */ uint64 values[FLEXIBLE_ARRAY_MEMBER]; -} rt_node_leaf_125; +} RT_NODE_LEAF_125; /* * node-256 is the largest node type. This node has RT_NODE_MAX_SLOTS length array * for directly storing values (or child pointers in inner nodes). */ -typedef struct rt_node_inner_256 +typedef struct RT_NODE_INNER_256 { - rt_node_base_256 base; + RT_NODE_BASE_256 base; /* Slots for 256 children */ - rt_node *children[RT_NODE_MAX_SLOTS]; -} rt_node_inner_256; + RT_NODE *children[RT_NODE_MAX_SLOTS]; +} RT_NODE_INNER_256; -typedef struct rt_node_leaf_256 +typedef struct RT_NODE_LEAF_256 { - rt_node_base_256 base; + RT_NODE_BASE_256 base; /* isset is a bitmap to track which slot is in use */ bitmapword isset[BM_IDX(RT_NODE_MAX_SLOTS)]; /* Slots for 256 values */ uint64 values[RT_NODE_MAX_SLOTS]; -} rt_node_leaf_256; +} RT_NODE_LEAF_256; /* Information for each size class */ -typedef struct rt_size_class_elem +typedef struct RT_SIZE_CLASS_ELEM { const char *name; int fanout; @@ -299,7 +445,7 @@ typedef struct rt_size_class_elem /* slab block size */ Size inner_blocksize; Size leaf_blocksize; -} rt_size_class_elem; +} RT_SIZE_CLASS_ELEM; /* * Calculate the slab blocksize so that we can allocate at least 32 chunks @@ -307,51 +453,54 @@ typedef struct rt_size_class_elem */ #define NODE_SLAB_BLOCK_SIZE(size) \ Max((SLAB_DEFAULT_BLOCK_SIZE / (size)) * (size), (size) * 32) -static rt_size_class_elem rt_size_class_info[RT_SIZE_CLASS_COUNT] = { + +static const RT_SIZE_CLASS_ELEM RT_SIZE_CLASS_INFO[] = { [RT_CLASS_4_FULL] = { .name = "radix tree node 4", .fanout = 4, - .inner_size = sizeof(rt_node_inner_4) + 4 * sizeof(rt_node *), - .leaf_size = sizeof(rt_node_leaf_4) + 4 * sizeof(uint64), - .inner_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(rt_node_inner_4) + 4 * sizeof(rt_node *)), - .leaf_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(rt_node_leaf_4) + 4 * sizeof(uint64)), + .inner_size = sizeof(RT_NODE_INNER_4) + 4 * sizeof(RT_NODE *), + .leaf_size = sizeof(RT_NODE_LEAF_4) + 4 * sizeof(uint64), + .inner_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(RT_NODE_INNER_4) + 4 * sizeof(RT_NODE *)), + .leaf_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(RT_NODE_LEAF_4) + 4 * sizeof(uint64)), }, [RT_CLASS_32_PARTIAL] = { .name = "radix tree node 15", .fanout = 15, - .inner_size = sizeof(rt_node_inner_32) + 15 * sizeof(rt_node *), - .leaf_size = sizeof(rt_node_leaf_32) + 15 * sizeof(uint64), - .inner_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(rt_node_inner_32) + 15 * sizeof(rt_node *)), - .leaf_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(rt_node_leaf_32) + 15 * sizeof(uint64)), + .inner_size = sizeof(RT_NODE_INNER_32) + 15 * sizeof(RT_NODE *), + .leaf_size = sizeof(RT_NODE_LEAF_32) + 15 * sizeof(uint64), + .inner_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(RT_NODE_INNER_32) + 15 * sizeof(RT_NODE *)), + .leaf_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(RT_NODE_LEAF_32) + 15 * sizeof(uint64)), }, [RT_CLASS_32_FULL] = { .name = "radix tree node 32", .fanout = 32, - .inner_size = sizeof(rt_node_inner_32) + 32 * sizeof(rt_node *), - .leaf_size = sizeof(rt_node_leaf_32) + 32 * sizeof(uint64), - .inner_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(rt_node_inner_32) + 32 * sizeof(rt_node *)), - .leaf_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(rt_node_leaf_32) + 32 * sizeof(uint64)), + .inner_size = sizeof(RT_NODE_INNER_32) + 32 * sizeof(RT_NODE *), + .leaf_size = sizeof(RT_NODE_LEAF_32) + 32 * sizeof(uint64), + .inner_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(RT_NODE_INNER_32) + 32 * sizeof(RT_NODE *)), + .leaf_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(RT_NODE_LEAF_32) + 32 * sizeof(uint64)), }, [RT_CLASS_125_FULL] = { .name = "radix tree node 125", .fanout = 125, - .inner_size = sizeof(rt_node_inner_125) + 125 * sizeof(rt_node *), - .leaf_size = sizeof(rt_node_leaf_125) + 125 * sizeof(uint64), - .inner_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(rt_node_inner_125) + 125 * sizeof(rt_node *)), - .leaf_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(rt_node_leaf_125) + 125 * sizeof(uint64)), + .inner_size = sizeof(RT_NODE_INNER_125) + 125 * sizeof(RT_NODE *), + .leaf_size = sizeof(RT_NODE_LEAF_125) + 125 * sizeof(uint64), + .inner_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(RT_NODE_INNER_125) + 125 * sizeof(RT_NODE *)), + .leaf_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(RT_NODE_LEAF_125) + 125 * sizeof(uint64)), }, [RT_CLASS_256] = { .name = "radix tree node 256", .fanout = 256, - .inner_size = sizeof(rt_node_inner_256), - .leaf_size = sizeof(rt_node_leaf_256), - .inner_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(rt_node_inner_256)), - .leaf_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(rt_node_leaf_256)), + .inner_size = sizeof(RT_NODE_INNER_256), + .leaf_size = sizeof(RT_NODE_LEAF_256), + .inner_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(RT_NODE_INNER_256)), + .leaf_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(RT_NODE_LEAF_256)), }, }; +#define RT_SIZE_CLASS_COUNT lengthof(RT_SIZE_CLASS_INFO) + /* Map from the node kind to its minimum size class */ -static rt_size_class kind_min_size_class[RT_NODE_KIND_COUNT] = { +static const RT_SIZE_CLASS RT_KIND_MIN_SIZE_CLASS[RT_NODE_KIND_COUNT] = { [RT_NODE_KIND_4] = RT_CLASS_4_FULL, [RT_NODE_KIND_32] = RT_CLASS_32_PARTIAL, [RT_NODE_KIND_125] = RT_CLASS_125_FULL, @@ -359,11 +508,11 @@ static rt_size_class kind_min_size_class[RT_NODE_KIND_COUNT] = { }; /* A radix tree with nodes */ -typedef struct radix_tree +typedef struct RT_RADIX_TREE { MemoryContext context; - rt_node *root; + RT_NODE *root; uint64 max_val; uint64 num_keys; @@ -374,7 +523,7 @@ typedef struct radix_tree #ifdef RT_DEBUG int32 cnt[RT_SIZE_CLASS_COUNT]; #endif -} radix_tree; +} RT_RADIX_TREE; /* * Iteration support. @@ -382,79 +531,47 @@ typedef struct radix_tree * Iterating the radix tree returns each pair of key and value in the ascending * order of the key. To support this, the we iterate nodes of each level. * - * rt_node_iter struct is used to track the iteration within a node. + * RT_NODE_ITER struct is used to track the iteration within a node. * - * rt_iter is the struct for iteration of the radix tree, and uses rt_node_iter + * RT_ITER is the struct for iteration of the radix tree, and uses RT_NODE_ITER * in order to track the iteration of each level. During the iteration, we also * construct the key whenever updating the node iteration information, e.g., when * advancing the current index within the node or when moving to the next node * at the same level. */ -typedef struct rt_node_iter +typedef struct RT_NODE_ITER { - rt_node *node; /* current node being iterated */ + RT_NODE *node; /* current node being iterated */ int current_idx; /* current position. -1 for initial value */ -} rt_node_iter; +} RT_NODE_ITER; -typedef struct rt_iter +typedef struct RT_ITER { - radix_tree *tree; + RT_RADIX_TREE *tree; /* Track the iteration on nodes of each level */ - rt_node_iter stack[RT_MAX_LEVEL]; + RT_NODE_ITER stack[RT_MAX_LEVEL]; int stack_len; /* The key is being constructed during the iteration */ uint64 key; -} rt_iter; - -extern radix_tree *rt_create(MemoryContext ctx); -extern void rt_free(radix_tree *tree); -extern bool rt_search(radix_tree *tree, uint64 key, uint64 *val_p); -extern bool rt_set(radix_tree *tree, uint64 key, uint64 val); -extern rt_iter *rt_begin_iterate(radix_tree *tree); +} RT_ITER; -extern bool rt_iterate_next(rt_iter *iter, uint64 *key_p, uint64 *value_p); -extern void rt_end_iterate(rt_iter *iter); -extern bool rt_delete(radix_tree *tree, uint64 key); -extern uint64 rt_memory_usage(radix_tree *tree); -extern uint64 rt_num_entries(radix_tree *tree); - -#ifdef RT_DEBUG -extern void rt_dump(radix_tree *tree); -extern void rt_dump_search(radix_tree *tree, uint64 key); -extern void rt_stats(radix_tree *tree); -#endif - - -static void rt_new_root(radix_tree *tree, uint64 key); -static rt_node *rt_alloc_node(radix_tree *tree, rt_size_class size_class, bool inner); -static inline void rt_init_node(rt_node *node, uint8 kind, rt_size_class size_class, - bool inner); -static void rt_free_node(radix_tree *tree, rt_node *node); -static void rt_extend(radix_tree *tree, uint64 key); -static inline bool rt_node_search_inner(rt_node *node, uint64 key, rt_node **child_p); -static inline bool rt_node_search_leaf(rt_node *node, uint64 key, uint64 *value_p); -static bool rt_node_insert_inner(radix_tree *tree, rt_node *parent, rt_node *node, - uint64 key, rt_node *child); -static bool rt_node_insert_leaf(radix_tree *tree, rt_node *parent, rt_node *node, +static bool RT_NODE_INSERT_INNER(RT_RADIX_TREE *tree, RT_NODE *parent, RT_NODE *node, + uint64 key, RT_NODE *child); +static bool RT_NODE_INSERT_LEAF(RT_RADIX_TREE *tree, RT_NODE *parent, RT_NODE *node, uint64 key, uint64 value); -static inline rt_node *rt_node_inner_iterate_next(rt_iter *iter, rt_node_iter *node_iter); -static inline bool rt_node_leaf_iterate_next(rt_iter *iter, rt_node_iter *node_iter, - uint64 *value_p); -static void rt_update_iter_stack(rt_iter *iter, rt_node *from_node, int from); -static inline void rt_iter_update_key(rt_iter *iter, uint8 chunk, uint8 shift); /* verification (available only with assertion) */ -static void rt_verify_node(rt_node *node); +static void RT_VERIFY_NODE(RT_NODE *node); /* * Return index of the first element in 'base' that equals 'key'. Return -1 * if there is no such element. */ static inline int -node_4_search_eq(rt_node_base_4 *node, uint8 chunk) +RT_NODE_4_SEARCH_EQ(RT_NODE_BASE_4 *node, uint8 chunk) { int idx = -1; @@ -474,7 +591,7 @@ node_4_search_eq(rt_node_base_4 *node, uint8 chunk) * Return index of the chunk to insert into chunks in the given node. */ static inline int -node_4_get_insertpos(rt_node_base_4 *node, uint8 chunk) +RT_NODE_4_GET_INSERTPOS(RT_NODE_BASE_4 *node, uint8 chunk) { int idx; @@ -492,7 +609,7 @@ node_4_get_insertpos(rt_node_base_4 *node, uint8 chunk) * if there is no such element. */ static inline int -node_32_search_eq(rt_node_base_32 *node, uint8 chunk) +RT_NODE_32_SEARCH_EQ(RT_NODE_BASE_32 *node, uint8 chunk) { int count = node->n.count; #ifndef USE_NO_SIMD @@ -541,7 +658,7 @@ node_32_search_eq(rt_node_base_32 *node, uint8 chunk) * Return index of the chunk to insert into chunks in the given node. */ static inline int -node_32_get_insertpos(rt_node_base_32 *node, uint8 chunk) +RT_NODE_32_GET_INSERTPOS(RT_NODE_BASE_32 *node, uint8 chunk) { int count = node->n.count; #ifndef USE_NO_SIMD @@ -596,14 +713,14 @@ node_32_get_insertpos(rt_node_base_32 *node, uint8 chunk) /* Shift the elements right at 'idx' by one */ static inline void -chunk_children_array_shift(uint8 *chunks, rt_node **children, int count, int idx) +RT_CHUNK_CHILDREN_ARRAY_SHIFT(uint8 *chunks, RT_NODE **children, int count, int idx) { memmove(&(chunks[idx + 1]), &(chunks[idx]), sizeof(uint8) * (count - idx)); - memmove(&(children[idx + 1]), &(children[idx]), sizeof(rt_node *) * (count - idx)); + memmove(&(children[idx + 1]), &(children[idx]), sizeof(RT_NODE *) * (count - idx)); } static inline void -chunk_values_array_shift(uint8 *chunks, uint64 *values, int count, int idx) +RT_CHUNK_VALUES_ARRAY_SHIFT(uint8 *chunks, uint64 *values, int count, int idx) { memmove(&(chunks[idx + 1]), &(chunks[idx]), sizeof(uint8) * (count - idx)); memmove(&(values[idx + 1]), &(values[idx]), sizeof(uint64 *) * (count - idx)); @@ -611,14 +728,14 @@ chunk_values_array_shift(uint8 *chunks, uint64 *values, int count, int idx) /* Delete the element at 'idx' */ static inline void -chunk_children_array_delete(uint8 *chunks, rt_node **children, int count, int idx) +RT_CHUNK_CHILDREN_ARRAY_DELETE(uint8 *chunks, RT_NODE **children, int count, int idx) { memmove(&(chunks[idx]), &(chunks[idx + 1]), sizeof(uint8) * (count - idx - 1)); - memmove(&(children[idx]), &(children[idx + 1]), sizeof(rt_node *) * (count - idx - 1)); + memmove(&(children[idx]), &(children[idx + 1]), sizeof(RT_NODE *) * (count - idx - 1)); } static inline void -chunk_values_array_delete(uint8 *chunks, uint64 *values, int count, int idx) +RT_CHUNK_VALUES_ARRAY_DELETE(uint8 *chunks, uint64 *values, int count, int idx) { memmove(&(chunks[idx]), &(chunks[idx + 1]), sizeof(uint8) * (count - idx - 1)); memmove(&(values[idx]), &(values[idx + 1]), sizeof(uint64) * (count - idx - 1)); @@ -626,22 +743,22 @@ chunk_values_array_delete(uint8 *chunks, uint64 *values, int count, int idx) /* Copy both chunks and children/values arrays */ static inline void -chunk_children_array_copy(uint8 *src_chunks, rt_node **src_children, - uint8 *dst_chunks, rt_node **dst_children) +RT_CHUNK_CHILDREN_ARRAY_COPY(uint8 *src_chunks, RT_NODE **src_children, + uint8 *dst_chunks, RT_NODE **dst_children) { - const int fanout = rt_size_class_info[RT_CLASS_4_FULL].fanout; + const int fanout = RT_SIZE_CLASS_INFO[RT_CLASS_4_FULL].fanout; const Size chunk_size = sizeof(uint8) * fanout; - const Size children_size = sizeof(rt_node *) * fanout; + const Size children_size = sizeof(RT_NODE *) * fanout; memcpy(dst_chunks, src_chunks, chunk_size); memcpy(dst_children, src_children, children_size); } static inline void -chunk_values_array_copy(uint8 *src_chunks, uint64 *src_values, +RT_CHUNK_VALUES_ARRAY_COPY(uint8 *src_chunks, uint64 *src_values, uint8 *dst_chunks, uint64 *dst_values) { - const int fanout = rt_size_class_info[RT_CLASS_4_FULL].fanout; + const int fanout = RT_SIZE_CLASS_INFO[RT_CLASS_4_FULL].fanout; const Size chunk_size = sizeof(uint8) * fanout; const Size values_size = sizeof(uint64) * fanout; @@ -653,23 +770,23 @@ chunk_values_array_copy(uint8 *src_chunks, uint64 *src_values, /* Does the given chunk in the node has the value? */ static inline bool -node_125_is_chunk_used(rt_node_base_125 *node, uint8 chunk) +RT_NODE_125_IS_CHUNK_USED(RT_NODE_BASE_125 *node, uint8 chunk) { return node->slot_idxs[chunk] != RT_NODE_125_INVALID_IDX; } -static inline rt_node * -node_inner_125_get_child(rt_node_inner_125 *node, uint8 chunk) +static inline RT_NODE * +RT_NODE_INNER_125_GET_CHILD(RT_NODE_INNER_125 *node, uint8 chunk) { Assert(!NODE_IS_LEAF(node)); return node->children[node->base.slot_idxs[chunk]]; } static inline uint64 -node_leaf_125_get_value(rt_node_leaf_125 *node, uint8 chunk) +RT_NODE_LEAF_125_GET_VALUE(RT_NODE_LEAF_125 *node, uint8 chunk) { Assert(NODE_IS_LEAF(node)); - Assert(((rt_node_base_125 *) node)->slot_idxs[chunk] != RT_NODE_125_INVALID_IDX); + Assert(((RT_NODE_BASE_125 *) node)->slot_idxs[chunk] != RT_NODE_125_INVALID_IDX); return node->values[node->base.slot_idxs[chunk]]; } @@ -677,14 +794,14 @@ node_leaf_125_get_value(rt_node_leaf_125 *node, uint8 chunk) /* Return true if the slot corresponding to the given chunk is in use */ static inline bool -node_inner_256_is_chunk_used(rt_node_inner_256 *node, uint8 chunk) +RT_NODE_INNER_256_IS_CHUNK_USED(RT_NODE_INNER_256 *node, uint8 chunk) { Assert(!NODE_IS_LEAF(node)); return (node->children[chunk] != NULL); } static inline bool -node_leaf_256_is_chunk_used(rt_node_leaf_256 *node, uint8 chunk) +RT_NODE_LEAF_256_IS_CHUNK_USED(RT_NODE_LEAF_256 *node, uint8 chunk) { int idx = BM_IDX(chunk); int bitnum = BM_BIT(chunk); @@ -693,25 +810,25 @@ node_leaf_256_is_chunk_used(rt_node_leaf_256 *node, uint8 chunk) return (node->isset[idx] & ((bitmapword) 1 << bitnum)) != 0; } -static inline rt_node * -node_inner_256_get_child(rt_node_inner_256 *node, uint8 chunk) +static inline RT_NODE * +RT_NODE_INNER_256_GET_CHILD(RT_NODE_INNER_256 *node, uint8 chunk) { Assert(!NODE_IS_LEAF(node)); - Assert(node_inner_256_is_chunk_used(node, chunk)); + Assert(RT_NODE_INNER_256_IS_CHUNK_USED(node, chunk)); return node->children[chunk]; } static inline uint64 -node_leaf_256_get_value(rt_node_leaf_256 *node, uint8 chunk) +RT_NODE_LEAF_256_GET_VALUE(RT_NODE_LEAF_256 *node, uint8 chunk) { Assert(NODE_IS_LEAF(node)); - Assert(node_leaf_256_is_chunk_used(node, chunk)); + Assert(RT_NODE_LEAF_256_IS_CHUNK_USED(node, chunk)); return node->values[chunk]; } /* Set the child in the node-256 */ static inline void -node_inner_256_set(rt_node_inner_256 *node, uint8 chunk, rt_node *child) +RT_NODE_INNER_256_SET(RT_NODE_INNER_256 *node, uint8 chunk, RT_NODE *child) { Assert(!NODE_IS_LEAF(node)); node->children[chunk] = child; @@ -719,7 +836,7 @@ node_inner_256_set(rt_node_inner_256 *node, uint8 chunk, rt_node *child) /* Set the value in the node-256 */ static inline void -node_leaf_256_set(rt_node_leaf_256 *node, uint8 chunk, uint64 value) +RT_NODE_LEAF_256_SET(RT_NODE_LEAF_256 *node, uint8 chunk, uint64 value) { int idx = BM_IDX(chunk); int bitnum = BM_BIT(chunk); @@ -731,14 +848,14 @@ node_leaf_256_set(rt_node_leaf_256 *node, uint8 chunk, uint64 value) /* Set the slot at the given chunk position */ static inline void -node_inner_256_delete(rt_node_inner_256 *node, uint8 chunk) +RT_NODE_INNER_256_DELETE(RT_NODE_INNER_256 *node, uint8 chunk) { Assert(!NODE_IS_LEAF(node)); node->children[chunk] = NULL; } static inline void -node_leaf_256_delete(rt_node_leaf_256 *node, uint8 chunk) +RT_NODE_LEAF_256_DELETE(RT_NODE_LEAF_256 *node, uint8 chunk) { int idx = BM_IDX(chunk); int bitnum = BM_BIT(chunk); @@ -751,7 +868,7 @@ node_leaf_256_delete(rt_node_leaf_256 *node, uint8 chunk) * Return the shift that is satisfied to store the given key. */ static inline int -key_get_shift(uint64 key) +RT_KEY_GET_SHIFT(uint64 key) { return (key == 0) ? 0 @@ -762,7 +879,7 @@ key_get_shift(uint64 key) * Return the max value stored in a node with the given shift. */ static uint64 -shift_get_max_val(int shift) +RT_SHIFT_GET_MAX_VAL(int shift) { if (shift == RT_MAX_SHIFT) return UINT64_MAX; @@ -770,38 +887,20 @@ shift_get_max_val(int shift) return (UINT64CONST(1) << (shift + RT_NODE_SPAN)) - 1; } -/* - * Create a new node as the root. Subordinate nodes will be created during - * the insertion. - */ -static void -rt_new_root(radix_tree *tree, uint64 key) -{ - int shift = key_get_shift(key); - bool inner = shift > 0; - rt_node *newnode; - - newnode = rt_alloc_node(tree, RT_CLASS_4_FULL, inner); - rt_init_node(newnode, RT_NODE_KIND_4, RT_CLASS_4_FULL, inner); - newnode->shift = shift; - tree->max_val = shift_get_max_val(shift); - tree->root = newnode; -} - /* * Allocate a new node with the given node kind. */ -static rt_node * -rt_alloc_node(radix_tree *tree, rt_size_class size_class, bool inner) +static RT_NODE * +RT_ALLOC_NODE(RT_RADIX_TREE *tree, RT_SIZE_CLASS size_class, bool inner) { - rt_node *newnode; + RT_NODE *newnode; if (inner) - newnode = (rt_node *) MemoryContextAlloc(tree->inner_slabs[size_class], - rt_size_class_info[size_class].inner_size); + newnode = (RT_NODE *) MemoryContextAlloc(tree->inner_slabs[size_class], + RT_SIZE_CLASS_INFO[size_class].inner_size); else - newnode = (rt_node *) MemoryContextAlloc(tree->leaf_slabs[size_class], - rt_size_class_info[size_class].leaf_size); + newnode = (RT_NODE *) MemoryContextAlloc(tree->leaf_slabs[size_class], + RT_SIZE_CLASS_INFO[size_class].leaf_size); #ifdef RT_DEBUG /* update the statistics */ @@ -813,20 +912,20 @@ rt_alloc_node(radix_tree *tree, rt_size_class size_class, bool inner) /* Initialize the node contents */ static inline void -rt_init_node(rt_node *node, uint8 kind, rt_size_class size_class, bool inner) +RT_INIT_NODE(RT_NODE *node, uint8 kind, RT_SIZE_CLASS size_class, bool inner) { if (inner) - MemSet(node, 0, rt_size_class_info[size_class].inner_size); + MemSet(node, 0, RT_SIZE_CLASS_INFO[size_class].inner_size); else - MemSet(node, 0, rt_size_class_info[size_class].leaf_size); + MemSet(node, 0, RT_SIZE_CLASS_INFO[size_class].leaf_size); node->kind = kind; - node->fanout = rt_size_class_info[size_class].fanout; + node->fanout = RT_SIZE_CLASS_INFO[size_class].fanout; /* Initialize slot_idxs to invalid values */ if (kind == RT_NODE_KIND_125) { - rt_node_base_125 *n125 = (rt_node_base_125 *) node; + RT_NODE_BASE_125 *n125 = (RT_NODE_BASE_125 *) node; memset(n125->slot_idxs, RT_NODE_125_INVALID_IDX, sizeof(n125->slot_idxs)); } @@ -839,8 +938,26 @@ rt_init_node(rt_node *node, uint8 kind, rt_size_class size_class, bool inner) node->fanout = 0; } +/* + * Create a new node as the root. Subordinate nodes will be created during + * the insertion. + */ +static void +RT_NEW_ROOT(RT_RADIX_TREE *tree, uint64 key) +{ + int shift = RT_KEY_GET_SHIFT(key); + bool inner = shift > 0; + RT_NODE *newnode; + + newnode = RT_ALLOC_NODE(tree, RT_CLASS_4_FULL, inner); + RT_INIT_NODE(newnode, RT_NODE_KIND_4, RT_CLASS_4_FULL, inner); + newnode->shift = shift; + tree->max_val = RT_SHIFT_GET_MAX_VAL(shift); + tree->root = newnode; +} + static inline void -rt_copy_node(rt_node *newnode, rt_node *oldnode) +RT_COPY_NODE(RT_NODE *newnode, RT_NODE *oldnode) { newnode->shift = oldnode->shift; newnode->chunk = oldnode->chunk; @@ -851,22 +968,22 @@ rt_copy_node(rt_node *newnode, rt_node *oldnode) * Create a new node with 'new_kind' and the same shift, chunk, and * count of 'node'. */ -static rt_node* -rt_grow_node_kind(radix_tree *tree, rt_node *node, uint8 new_kind) +static RT_NODE* +RT_GROW_NODE_KIND(RT_RADIX_TREE *tree, RT_NODE *node, uint8 new_kind) { - rt_node *newnode; + RT_NODE *newnode; bool inner = !NODE_IS_LEAF(node); - newnode = rt_alloc_node(tree, kind_min_size_class[new_kind], inner); - rt_init_node(newnode, new_kind, kind_min_size_class[new_kind], inner); - rt_copy_node(newnode, node); + newnode = RT_ALLOC_NODE(tree, RT_KIND_MIN_SIZE_CLASS[new_kind], inner); + RT_INIT_NODE(newnode, new_kind, RT_KIND_MIN_SIZE_CLASS[new_kind], inner); + RT_COPY_NODE(newnode, node); return newnode; } /* Free the given node */ static void -rt_free_node(radix_tree *tree, rt_node *node) +RT_FREE_NODE(RT_RADIX_TREE *tree, RT_NODE *node) { /* If we're deleting the root node, make the tree empty */ if (tree->root == node) @@ -882,7 +999,7 @@ rt_free_node(radix_tree *tree, rt_node *node) /* update the statistics */ for (i = 0; i < RT_SIZE_CLASS_COUNT; i++) { - if (node->fanout == rt_size_class_info[i].fanout) + if (node->fanout == RT_SIZE_CLASS_INFO[i].fanout) break; } @@ -902,8 +1019,8 @@ rt_free_node(radix_tree *tree, rt_node *node) * Replace old_child with new_child, and free the old one. */ static void -rt_replace_node(radix_tree *tree, rt_node *parent, rt_node *old_child, - rt_node *new_child, uint64 key) +RT_REPLACE_NODE(RT_RADIX_TREE *tree, RT_NODE *parent, RT_NODE *old_child, + RT_NODE *new_child, uint64 key) { Assert(old_child->chunk == new_child->chunk); Assert(old_child->shift == new_child->shift); @@ -917,11 +1034,11 @@ rt_replace_node(radix_tree *tree, rt_node *parent, rt_node *old_child, { bool replaced PG_USED_FOR_ASSERTS_ONLY; - replaced = rt_node_insert_inner(tree, NULL, parent, key, new_child); + replaced = RT_NODE_INSERT_INNER(tree, NULL, parent, key, new_child); Assert(replaced); } - rt_free_node(tree, old_child); + RT_FREE_NODE(tree, old_child); } /* @@ -929,32 +1046,32 @@ rt_replace_node(radix_tree *tree, rt_node *parent, rt_node *old_child, * store the key. */ static void -rt_extend(radix_tree *tree, uint64 key) +RT_EXTEND(RT_RADIX_TREE *tree, uint64 key) { int target_shift; int shift = tree->root->shift + RT_NODE_SPAN; - target_shift = key_get_shift(key); + target_shift = RT_KEY_GET_SHIFT(key); /* Grow tree from 'shift' to 'target_shift' */ while (shift <= target_shift) { - rt_node_inner_4 *node; + RT_NODE_INNER_4 *node; - node = (rt_node_inner_4 *) rt_alloc_node(tree, RT_CLASS_4_FULL, true); - rt_init_node((rt_node *) node, RT_NODE_KIND_4, RT_CLASS_4_FULL, true); + node = (RT_NODE_INNER_4 *) RT_ALLOC_NODE(tree, RT_CLASS_4_FULL, true); + RT_INIT_NODE((RT_NODE *) node, RT_NODE_KIND_4, RT_CLASS_4_FULL, true); node->base.n.shift = shift; node->base.n.count = 1; node->base.chunks[0] = 0; node->children[0] = tree->root; tree->root->chunk = 0; - tree->root = (rt_node *) node; + tree->root = (RT_NODE *) node; shift += RT_NODE_SPAN; } - tree->max_val = shift_get_max_val(target_shift); + tree->max_val = RT_SHIFT_GET_MAX_VAL(target_shift); } /* @@ -962,29 +1079,29 @@ rt_extend(radix_tree *tree, uint64 key) * Insert inner and leaf nodes from 'node' to bottom. */ static inline void -rt_set_extend(radix_tree *tree, uint64 key, uint64 value, rt_node *parent, - rt_node *node) +RT_SET_EXTEND(RT_RADIX_TREE *tree, uint64 key, uint64 value, RT_NODE *parent, + RT_NODE *node) { int shift = node->shift; while (shift >= RT_NODE_SPAN) { - rt_node *newchild; + RT_NODE *newchild; int newshift = shift - RT_NODE_SPAN; bool inner = newshift > 0; - newchild = rt_alloc_node(tree, RT_CLASS_4_FULL, inner); - rt_init_node(newchild, RT_NODE_KIND_4, RT_CLASS_4_FULL, inner); + newchild = RT_ALLOC_NODE(tree, RT_CLASS_4_FULL, inner); + RT_INIT_NODE(newchild, RT_NODE_KIND_4, RT_CLASS_4_FULL, inner); newchild->shift = newshift; newchild->chunk = RT_GET_KEY_CHUNK(key, node->shift); - rt_node_insert_inner(tree, parent, node, key, newchild); + RT_NODE_INSERT_INNER(tree, parent, node, key, newchild); parent = node; node = newchild; shift -= RT_NODE_SPAN; } - rt_node_insert_leaf(tree, parent, node, key, value); + RT_NODE_INSERT_LEAF(tree, parent, node, key, value); tree->num_keys++; } @@ -995,7 +1112,7 @@ rt_set_extend(radix_tree *tree, uint64 key, uint64 value, rt_node *parent, * pointer is set to child_p. */ static inline bool -rt_node_search_inner(rt_node *node, uint64 key, rt_node **child_p) +RT_NODE_SEARCH_INNER(RT_NODE *node, uint64 key, RT_NODE **child_p) { #define RT_NODE_LEVEL_INNER #include "lib/radixtree_search_impl.h" @@ -1009,7 +1126,7 @@ rt_node_search_inner(rt_node *node, uint64 key, rt_node **child_p) * to the value is set to value_p. */ static inline bool -rt_node_search_leaf(rt_node *node, uint64 key, uint64 *value_p) +RT_NODE_SEARCH_LEAF(RT_NODE *node, uint64 key, uint64 *value_p) { #define RT_NODE_LEVEL_LEAF #include "lib/radixtree_search_impl.h" @@ -1022,7 +1139,7 @@ rt_node_search_leaf(rt_node *node, uint64 key, uint64 *value_p) * Delete the node and return true if the key is found, otherwise return false. */ static inline bool -rt_node_delete_inner(rt_node *node, uint64 key) +RT_NODE_DELETE_INNER(RT_NODE *node, uint64 key) { #define RT_NODE_LEVEL_INNER #include "lib/radixtree_delete_impl.h" @@ -1035,7 +1152,7 @@ rt_node_delete_inner(rt_node *node, uint64 key) * Delete the node and return true if the key is found, otherwise return false. */ static inline bool -rt_node_delete_leaf(rt_node *node, uint64 key) +RT_NODE_DELETE_LEAF(RT_NODE *node, uint64 key) { #define RT_NODE_LEVEL_LEAF #include "lib/radixtree_delete_impl.h" @@ -1044,8 +1161,8 @@ rt_node_delete_leaf(rt_node *node, uint64 key) /* Insert the child to the inner node */ static bool -rt_node_insert_inner(radix_tree *tree, rt_node *parent, rt_node *node, uint64 key, - rt_node *child) +RT_NODE_INSERT_INNER(RT_RADIX_TREE *tree, RT_NODE *parent, RT_NODE *node, uint64 key, + RT_NODE *child) { #define RT_NODE_LEVEL_INNER #include "lib/radixtree_insert_impl.h" @@ -1054,7 +1171,7 @@ rt_node_insert_inner(radix_tree *tree, rt_node *parent, rt_node *node, uint64 ke /* Insert the value to the leaf node */ static bool -rt_node_insert_leaf(radix_tree *tree, rt_node *parent, rt_node *node, +RT_NODE_INSERT_LEAF(RT_RADIX_TREE *tree, RT_NODE *parent, RT_NODE *node, uint64 key, uint64 value) { #define RT_NODE_LEVEL_LEAF @@ -1065,15 +1182,15 @@ rt_node_insert_leaf(radix_tree *tree, rt_node *parent, rt_node *node, /* * Create the radix tree in the given memory context and return it. */ -radix_tree * -rt_create(MemoryContext ctx) +RT_SCOPE RT_RADIX_TREE * +RT_CREATE(MemoryContext ctx) { - radix_tree *tree; + RT_RADIX_TREE *tree; MemoryContext old_ctx; old_ctx = MemoryContextSwitchTo(ctx); - tree = palloc(sizeof(radix_tree)); + tree = palloc(sizeof(RT_RADIX_TREE)); tree->context = ctx; tree->root = NULL; tree->max_val = 0; @@ -1083,13 +1200,13 @@ rt_create(MemoryContext ctx) for (int i = 0; i < RT_SIZE_CLASS_COUNT; i++) { tree->inner_slabs[i] = SlabContextCreate(ctx, - rt_size_class_info[i].name, - rt_size_class_info[i].inner_blocksize, - rt_size_class_info[i].inner_size); + RT_SIZE_CLASS_INFO[i].name, + RT_SIZE_CLASS_INFO[i].inner_blocksize, + RT_SIZE_CLASS_INFO[i].inner_size); tree->leaf_slabs[i] = SlabContextCreate(ctx, - rt_size_class_info[i].name, - rt_size_class_info[i].leaf_blocksize, - rt_size_class_info[i].leaf_size); + RT_SIZE_CLASS_INFO[i].name, + RT_SIZE_CLASS_INFO[i].leaf_blocksize, + RT_SIZE_CLASS_INFO[i].leaf_size); #ifdef RT_DEBUG tree->cnt[i] = 0; #endif @@ -1103,8 +1220,8 @@ rt_create(MemoryContext ctx) /* * Free the given radix tree. */ -void -rt_free(radix_tree *tree) +RT_SCOPE void +RT_FREE(RT_RADIX_TREE *tree) { for (int i = 0; i < RT_SIZE_CLASS_COUNT; i++) { @@ -1119,21 +1236,21 @@ rt_free(radix_tree *tree) * Set key to value. If the entry already exists, we update its value to 'value' * and return true. Returns false if entry doesn't yet exist. */ -bool -rt_set(radix_tree *tree, uint64 key, uint64 value) +RT_SCOPE bool +RT_SET(RT_RADIX_TREE *tree, uint64 key, uint64 value) { int shift; bool updated; - rt_node *node; - rt_node *parent; + RT_NODE *node; + RT_NODE *parent; /* Empty tree, create the root */ if (!tree->root) - rt_new_root(tree, key); + RT_NEW_ROOT(tree, key); /* Extend the tree if necessary */ if (key > tree->max_val) - rt_extend(tree, key); + RT_EXTEND(tree, key); Assert(tree->root); @@ -1143,14 +1260,14 @@ rt_set(radix_tree *tree, uint64 key, uint64 value) /* Descend the tree until a leaf node */ while (shift >= 0) { - rt_node *child; + RT_NODE *child; if (NODE_IS_LEAF(node)) break; - if (!rt_node_search_inner(node, key, &child)) + if (!RT_NODE_SEARCH_INNER(node, key, &child)) { - rt_set_extend(tree, key, value, parent, node); + RT_SET_EXTEND(tree, key, value, parent, node); return false; } @@ -1159,7 +1276,7 @@ rt_set(radix_tree *tree, uint64 key, uint64 value) shift -= RT_NODE_SPAN; } - updated = rt_node_insert_leaf(tree, parent, node, key, value); + updated = RT_NODE_INSERT_LEAF(tree, parent, node, key, value); /* Update the statistics */ if (!updated) @@ -1173,10 +1290,10 @@ rt_set(radix_tree *tree, uint64 key, uint64 value) * otherwise return false. On success, we set the value to *val_p so it must * not be NULL. */ -bool -rt_search(radix_tree *tree, uint64 key, uint64 *value_p) +RT_SCOPE bool +RT_SEARCH(RT_RADIX_TREE *tree, uint64 key, uint64 *value_p) { - rt_node *node; + RT_NODE *node; int shift; Assert(value_p != NULL); @@ -1190,30 +1307,30 @@ rt_search(radix_tree *tree, uint64 key, uint64 *value_p) /* Descend the tree until a leaf node */ while (shift >= 0) { - rt_node *child; + RT_NODE *child; if (NODE_IS_LEAF(node)) break; - if (!rt_node_search_inner(node, key, &child)) + if (!RT_NODE_SEARCH_INNER(node, key, &child)) return false; node = child; shift -= RT_NODE_SPAN; } - return rt_node_search_leaf(node, key, value_p); + return RT_NODE_SEARCH_LEAF(node, key, value_p); } /* * Delete the given key from the radix tree. Return true if the key is found (and * deleted), otherwise do nothing and return false. */ -bool -rt_delete(radix_tree *tree, uint64 key) +RT_SCOPE bool +RT_DELETE(RT_RADIX_TREE *tree, uint64 key) { - rt_node *node; - rt_node *stack[RT_MAX_LEVEL] = {0}; + RT_NODE *node; + RT_NODE *stack[RT_MAX_LEVEL] = {0}; int shift; int level; bool deleted; @@ -1230,12 +1347,12 @@ rt_delete(radix_tree *tree, uint64 key) level = -1; while (shift > 0) { - rt_node *child; + RT_NODE *child; /* Push the current node to the stack */ stack[++level] = node; - if (!rt_node_search_inner(node, key, &child)) + if (!RT_NODE_SEARCH_INNER(node, key, &child)) return false; node = child; @@ -1244,7 +1361,7 @@ rt_delete(radix_tree *tree, uint64 key) /* Delete the key from the leaf node if exists */ Assert(NODE_IS_LEAF(node)); - deleted = rt_node_delete_leaf(node, key); + deleted = RT_NODE_DELETE_LEAF(node, key); if (!deleted) { @@ -1263,14 +1380,14 @@ rt_delete(radix_tree *tree, uint64 key) return true; /* Free the empty leaf node */ - rt_free_node(tree, node); + RT_FREE_NODE(tree, node); /* Delete the key in inner nodes recursively */ while (level >= 0) { node = stack[level--]; - deleted = rt_node_delete_inner(node, key); + deleted = RT_NODE_DELETE_INNER(node, key); Assert(deleted); /* If the node didn't become empty, we stop deleting the key */ @@ -1278,55 +1395,56 @@ rt_delete(radix_tree *tree, uint64 key) break; /* The node became empty */ - rt_free_node(tree, node); + RT_FREE_NODE(tree, node); } return true; } -/* Create and return the iterator for the given radix tree */ -rt_iter * -rt_begin_iterate(radix_tree *tree) +static inline void +RT_ITER_UPDATE_KEY(RT_ITER *iter, uint8 chunk, uint8 shift) { - MemoryContext old_ctx; - rt_iter *iter; - int top_level; - - old_ctx = MemoryContextSwitchTo(tree->context); - - iter = (rt_iter *) palloc0(sizeof(rt_iter)); - iter->tree = tree; - - /* empty tree */ - if (!iter->tree->root) - return iter; - - top_level = iter->tree->root->shift / RT_NODE_SPAN; - iter->stack_len = top_level; - - /* - * Descend to the left most leaf node from the root. The key is being - * constructed while descending to the leaf. - */ - rt_update_iter_stack(iter, iter->tree->root, top_level); + iter->key &= ~(((uint64) RT_CHUNK_MASK) << shift); + iter->key |= (((uint64) chunk) << shift); +} - MemoryContextSwitchTo(old_ctx); +/* + * Advance the slot in the inner node. Return the child if exists, otherwise + * null. + */ +static inline RT_NODE * +RT_NODE_INNER_ITERATE_NEXT(RT_ITER *iter, RT_NODE_ITER *node_iter) +{ +#define RT_NODE_LEVEL_INNER +#include "lib/radixtree_iter_impl.h" +#undef RT_NODE_LEVEL_INNER +} - return iter; +/* + * Advance the slot in the leaf node. On success, return true and the value + * is set to value_p, otherwise return false. + */ +static inline bool +RT_NODE_LEAF_ITERATE_NEXT(RT_ITER *iter, RT_NODE_ITER *node_iter, + uint64 *value_p) +{ +#define RT_NODE_LEVEL_LEAF +#include "lib/radixtree_iter_impl.h" +#undef RT_NODE_LEVEL_LEAF } /* * Update each node_iter for inner nodes in the iterator node stack. */ static void -rt_update_iter_stack(rt_iter *iter, rt_node *from_node, int from) +RT_UPDATE_ITER_STACK(RT_ITER *iter, RT_NODE *from_node, int from) { int level = from; - rt_node *node = from_node; + RT_NODE *node = from_node; for (;;) { - rt_node_iter *node_iter = &(iter->stack[level--]); + RT_NODE_ITER *node_iter = &(iter->stack[level--]); node_iter->node = node; node_iter->current_idx = -1; @@ -1336,19 +1454,50 @@ rt_update_iter_stack(rt_iter *iter, rt_node *from_node, int from) return; /* Advance to the next slot in the inner node */ - node = rt_node_inner_iterate_next(iter, node_iter); + node = RT_NODE_INNER_ITERATE_NEXT(iter, node_iter); /* We must find the first children in the node */ Assert(node); } } +/* Create and return the iterator for the given radix tree */ +RT_SCOPE RT_ITER * +RT_BEGIN_ITERATE(RT_RADIX_TREE *tree) +{ + MemoryContext old_ctx; + RT_ITER *iter; + int top_level; + + old_ctx = MemoryContextSwitchTo(tree->context); + + iter = (RT_ITER *) palloc0(sizeof(RT_ITER)); + iter->tree = tree; + + /* empty tree */ + if (!iter->tree->root) + return iter; + + top_level = iter->tree->root->shift / RT_NODE_SPAN; + iter->stack_len = top_level; + + /* + * Descend to the left most leaf node from the root. The key is being + * constructed while descending to the leaf. + */ + RT_UPDATE_ITER_STACK(iter, iter->tree->root, top_level); + + MemoryContextSwitchTo(old_ctx); + + return iter; +} + /* * Return true with setting key_p and value_p if there is next key. Otherwise, * return false. */ -bool -rt_iterate_next(rt_iter *iter, uint64 *key_p, uint64 *value_p) +RT_SCOPE bool +RT_ITERATE_NEXT(RT_ITER *iter, uint64 *key_p, uint64 *value_p) { /* Empty tree */ if (!iter->tree->root) @@ -1356,13 +1505,13 @@ rt_iterate_next(rt_iter *iter, uint64 *key_p, uint64 *value_p) for (;;) { - rt_node *child = NULL; + RT_NODE *child = NULL; uint64 value; int level; bool found; /* Advance the leaf node iterator to get next key-value pair */ - found = rt_node_leaf_iterate_next(iter, &(iter->stack[0]), &value); + found = RT_NODE_LEAF_ITERATE_NEXT(iter, &(iter->stack[0]), &value); if (found) { @@ -1377,7 +1526,7 @@ rt_iterate_next(rt_iter *iter, uint64 *key_p, uint64 *value_p) */ for (level = 1; level <= iter->stack_len; level++) { - child = rt_node_inner_iterate_next(iter, &(iter->stack[level])); + child = RT_NODE_INNER_ITERATE_NEXT(iter, &(iter->stack[level])); if (child) break; @@ -1391,7 +1540,7 @@ rt_iterate_next(rt_iter *iter, uint64 *key_p, uint64 *value_p) * Set the node to the node iterator and update the iterator stack * from this node. */ - rt_update_iter_stack(iter, child, level - 1); + RT_UPDATE_ITER_STACK(iter, child, level - 1); /* Node iterators are updated, so try again from the leaf */ } @@ -1399,49 +1548,17 @@ rt_iterate_next(rt_iter *iter, uint64 *key_p, uint64 *value_p) return false; } -void -rt_end_iterate(rt_iter *iter) +RT_SCOPE void +RT_END_ITERATE(RT_ITER *iter) { pfree(iter); } -static inline void -rt_iter_update_key(rt_iter *iter, uint8 chunk, uint8 shift) -{ - iter->key &= ~(((uint64) RT_CHUNK_MASK) << shift); - iter->key |= (((uint64) chunk) << shift); -} - -/* - * Advance the slot in the inner node. Return the child if exists, otherwise - * null. - */ -static inline rt_node * -rt_node_inner_iterate_next(rt_iter *iter, rt_node_iter *node_iter) -{ -#define RT_NODE_LEVEL_INNER -#include "lib/radixtree_iter_impl.h" -#undef RT_NODE_LEVEL_INNER -} - -/* - * Advance the slot in the leaf node. On success, return true and the value - * is set to value_p, otherwise return false. - */ -static inline bool -rt_node_leaf_iterate_next(rt_iter *iter, rt_node_iter *node_iter, - uint64 *value_p) -{ -#define RT_NODE_LEVEL_LEAF -#include "lib/radixtree_iter_impl.h" -#undef RT_NODE_LEVEL_LEAF -} - /* * Return the number of keys in the radix tree. */ -uint64 -rt_num_entries(radix_tree *tree) +RT_SCOPE uint64 +RT_NUM_ENTRIES(RT_RADIX_TREE *tree) { return tree->num_keys; } @@ -1449,10 +1566,10 @@ rt_num_entries(radix_tree *tree) /* * Return the statistics of the amount of memory used by the radix tree. */ -uint64 -rt_memory_usage(radix_tree *tree) +RT_SCOPE uint64 +RT_MEMORY_USAGE(RT_RADIX_TREE *tree) { - Size total = sizeof(radix_tree); + Size total = sizeof(RT_RADIX_TREE); for (int i = 0; i < RT_SIZE_CLASS_COUNT; i++) { @@ -1467,7 +1584,7 @@ rt_memory_usage(radix_tree *tree) * Verify the radix tree node. */ static void -rt_verify_node(rt_node *node) +RT_VERIFY_NODE(RT_NODE *node) { #ifdef USE_ASSERT_CHECKING Assert(node->count >= 0); @@ -1476,7 +1593,7 @@ rt_verify_node(rt_node *node) { case RT_NODE_KIND_4: { - rt_node_base_4 *n4 = (rt_node_base_4 *) node; + RT_NODE_BASE_4 *n4 = (RT_NODE_BASE_4 *) node; for (int i = 1; i < n4->n.count; i++) Assert(n4->chunks[i - 1] < n4->chunks[i]); @@ -1485,7 +1602,7 @@ rt_verify_node(rt_node *node) } case RT_NODE_KIND_32: { - rt_node_base_32 *n32 = (rt_node_base_32 *) node; + RT_NODE_BASE_32 *n32 = (RT_NODE_BASE_32 *) node; for (int i = 1; i < n32->n.count; i++) Assert(n32->chunks[i - 1] < n32->chunks[i]); @@ -1494,7 +1611,7 @@ rt_verify_node(rt_node *node) } case RT_NODE_KIND_125: { - rt_node_base_125 *n125 = (rt_node_base_125 *) node; + RT_NODE_BASE_125 *n125 = (RT_NODE_BASE_125 *) node; int cnt = 0; for (int i = 0; i < RT_NODE_MAX_SLOTS; i++) @@ -1503,7 +1620,7 @@ rt_verify_node(rt_node *node) int idx = BM_IDX(slot); int bitnum = BM_BIT(slot); - if (!node_125_is_chunk_used(n125, i)) + if (!RT_NODE_125_IS_CHUNK_USED(n125, i)) continue; /* Check if the corresponding slot is used */ @@ -1520,7 +1637,7 @@ rt_verify_node(rt_node *node) { if (NODE_IS_LEAF(node)) { - rt_node_leaf_256 *n256 = (rt_node_leaf_256 *) node; + RT_NODE_LEAF_256 *n256 = (RT_NODE_LEAF_256 *) node; int cnt = 0; for (int i = 0; i < BM_IDX(RT_NODE_MAX_SLOTS); i++) @@ -1539,7 +1656,7 @@ rt_verify_node(rt_node *node) /***************** DEBUG FUNCTIONS *****************/ #ifdef RT_DEBUG void -rt_stats(radix_tree *tree) +rt_stats(RT_RADIX_TREE *tree) { ereport(NOTICE, (errmsg("num_keys = " UINT64_FORMAT ", height = %d, n4 = %u, n15 = %u, n32 = %u, n125 = %u, n256 = %u", tree->num_keys, @@ -1552,7 +1669,7 @@ rt_stats(radix_tree *tree) } static void -rt_dump_node(rt_node *node, int level, bool recurse) +rt_dump_node(RT_NODE *node, int level, bool recurse) { char space[125] = {0}; @@ -1575,14 +1692,14 @@ rt_dump_node(rt_node *node, int level, bool recurse) { if (NODE_IS_LEAF(node)) { - rt_node_leaf_4 *n4 = (rt_node_leaf_4 *) node; + RT_NODE_LEAF_4 *n4 = (RT_NODE_LEAF_4 *) node; fprintf(stderr, "%schunk 0x%X value 0x" UINT64_FORMAT_HEX "\n", space, n4->base.chunks[i], n4->values[i]); } else { - rt_node_inner_4 *n4 = (rt_node_inner_4 *) node; + RT_NODE_INNER_4 *n4 = (RT_NODE_INNER_4 *) node; fprintf(stderr, "%schunk 0x%X ->", space, n4->base.chunks[i]); @@ -1601,14 +1718,14 @@ rt_dump_node(rt_node *node, int level, bool recurse) { if (NODE_IS_LEAF(node)) { - rt_node_leaf_32 *n32 = (rt_node_leaf_32 *) node; + RT_NODE_LEAF_32 *n32 = (RT_NODE_LEAF_32 *) node; fprintf(stderr, "%schunk 0x%X value 0x" UINT64_FORMAT_HEX "\n", space, n32->base.chunks[i], n32->values[i]); } else { - rt_node_inner_32 *n32 = (rt_node_inner_32 *) node; + RT_NODE_INNER_32 *n32 = (RT_NODE_INNER_32 *) node; fprintf(stderr, "%schunk 0x%X ->", space, n32->base.chunks[i]); @@ -1625,19 +1742,19 @@ rt_dump_node(rt_node *node, int level, bool recurse) } case RT_NODE_KIND_125: { - rt_node_base_125 *b125 = (rt_node_base_125 *) node; + RT_NODE_BASE_125 *b125 = (RT_NODE_BASE_125 *) node; fprintf(stderr, "slot_idxs "); for (int i = 0; i < RT_NODE_MAX_SLOTS; i++) { - if (!node_125_is_chunk_used(b125, i)) + if (!RT_NODE_125_IS_CHUNK_USED(b125, i)) continue; fprintf(stderr, " [%d]=%d, ", i, b125->slot_idxs[i]); } if (NODE_IS_LEAF(node)) { - rt_node_leaf_125 *n = (rt_node_leaf_125 *) node; + RT_NODE_LEAF_125 *n = (RT_NODE_LEAF_125 *) node; fprintf(stderr, ", isset-bitmap:"); for (int i = 0; i < BM_IDX(128); i++) @@ -1649,25 +1766,25 @@ rt_dump_node(rt_node *node, int level, bool recurse) for (int i = 0; i < RT_NODE_MAX_SLOTS; i++) { - if (!node_125_is_chunk_used(b125, i)) + if (!RT_NODE_125_IS_CHUNK_USED(b125, i)) continue; if (NODE_IS_LEAF(node)) { - rt_node_leaf_125 *n125 = (rt_node_leaf_125 *) b125; + RT_NODE_LEAF_125 *n125 = (RT_NODE_LEAF_125 *) b125; fprintf(stderr, "%schunk 0x%X value 0x" UINT64_FORMAT_HEX "\n", - space, i, node_leaf_125_get_value(n125, i)); + space, i, RT_NODE_LEAF_125_GET_VALUE(n125, i)); } else { - rt_node_inner_125 *n125 = (rt_node_inner_125 *) b125; + RT_NODE_INNER_125 *n125 = (RT_NODE_INNER_125 *) b125; fprintf(stderr, "%schunk 0x%X ->", space, i); if (recurse) - rt_dump_node(node_inner_125_get_child(n125, i), + rt_dump_node(RT_NODE_INNER_125_GET_CHILD(n125, i), level + 1, recurse); else fprintf(stderr, "\n"); @@ -1681,26 +1798,26 @@ rt_dump_node(rt_node *node, int level, bool recurse) { if (NODE_IS_LEAF(node)) { - rt_node_leaf_256 *n256 = (rt_node_leaf_256 *) node; + RT_NODE_LEAF_256 *n256 = (RT_NODE_LEAF_256 *) node; - if (!node_leaf_256_is_chunk_used(n256, i)) + if (!RT_NODE_LEAF_256_IS_CHUNK_USED(n256, i)) continue; fprintf(stderr, "%schunk 0x%X value 0x" UINT64_FORMAT_HEX "\n", - space, i, node_leaf_256_get_value(n256, i)); + space, i, RT_NODE_LEAF_256_GET_VALUE(n256, i)); } else { - rt_node_inner_256 *n256 = (rt_node_inner_256 *) node; + RT_NODE_INNER_256 *n256 = (RT_NODE_INNER_256 *) node; - if (!node_inner_256_is_chunk_used(n256, i)) + if (!RT_NODE_INNER_256_IS_CHUNK_USED(n256, i)) continue; fprintf(stderr, "%schunk 0x%X ->", space, i); if (recurse) - rt_dump_node(node_inner_256_get_child(n256, i), level + 1, + rt_dump_node(RT_NODE_INNER_256_GET_CHILD(n256, i), level + 1, recurse); else fprintf(stderr, "\n"); @@ -1712,9 +1829,9 @@ rt_dump_node(rt_node *node, int level, bool recurse) } void -rt_dump_search(radix_tree *tree, uint64 key) +rt_dump_search(RT_RADIX_TREE *tree, uint64 key) { - rt_node *node; + RT_NODE *node; int shift; int level = 0; @@ -1739,7 +1856,7 @@ rt_dump_search(radix_tree *tree, uint64 key) shift = tree->root->shift; while (shift >= 0) { - rt_node *child; + RT_NODE *child; rt_dump_node(node, level, false); @@ -1748,12 +1865,12 @@ rt_dump_search(radix_tree *tree, uint64 key) uint64 dummy; /* We reached at a leaf node, find the corresponding slot */ - rt_node_search_leaf(node, key, &dummy); + RT_NODE_SEARCH_LEAF(node, key, &dummy); break; } - if (!rt_node_search_inner(node, key, &child)) + if (!RT_NODE_SEARCH_INNER(node, key, &child)) break; node = child; @@ -1763,16 +1880,16 @@ rt_dump_search(radix_tree *tree, uint64 key) } void -rt_dump(radix_tree *tree) +rt_dump(RT_RADIX_TREE *tree) { for (int i = 0; i < RT_SIZE_CLASS_COUNT; i++) fprintf(stderr, "%s\tinner_size %zu\tinner_blocksize %zu\tleaf_size %zu\tleaf_blocksize %zu\n", - rt_size_class_info[i].name, - rt_size_class_info[i].inner_size, - rt_size_class_info[i].inner_blocksize, - rt_size_class_info[i].leaf_size, - rt_size_class_info[i].leaf_blocksize); + RT_SIZE_CLASS_INFO[i].name, + RT_SIZE_CLASS_INFO[i].inner_size, + RT_SIZE_CLASS_INFO[i].inner_blocksize, + RT_SIZE_CLASS_INFO[i].leaf_size, + RT_SIZE_CLASS_INFO[i].leaf_blocksize); fprintf(stderr, "max_val = " UINT64_FORMAT "\n", tree->max_val); if (!tree->root) @@ -1784,3 +1901,107 @@ rt_dump(radix_tree *tree) rt_dump_node(tree->root, 0, true); } #endif + +#endif /* RT_DEFINE */ + + +/* undefine external parameters, so next radix tree can be defined */ +#undef RT_PREFIX +#undef RT_SCOPE +#undef RT_DECLARE +#undef RT_DEFINE + +/* locally declared macros */ +#undef NODE_IS_LEAF +#undef NODE_IS_EMPTY +#undef VAR_NODE_HAS_FREE_SLOT +#undef FIXED_NODE_HAS_FREE_SLOT +#undef RT_SIZE_CLASS_COUNT + +/* type declarations */ +#undef RT_RADIX_TREE +#undef RT_ITER +#undef RT_NODE +#undef RT_NODE_ITER +#undef RT_NODE_BASE_4 +#undef RT_NODE_BASE_32 +#undef RT_NODE_BASE_125 +#undef RT_NODE_BASE_256 +#undef RT_NODE_INNER_4 +#undef RT_NODE_INNER_32 +#undef RT_NODE_INNER_125 +#undef RT_NODE_INNER_256 +#undef RT_NODE_LEAF_4 +#undef RT_NODE_LEAF_32 +#undef RT_NODE_LEAF_125 +#undef RT_NODE_LEAF_256 +#undef RT_SIZE_CLASS +#undef RT_SIZE_CLASS_ELEM +#undef RT_SIZE_CLASS_INFO +#undef RT_CLASS_4_FULL +#undef RT_CLASS_32_PARTIAL +#undef RT_CLASS_32_FULL +#undef RT_CLASS_125_FULL +#undef RT_CLASS_256 +#undef RT_KIND_MIN_SIZE_CLASS + +/* function declarations */ +#undef RT_CREATE +#undef RT_FREE +#undef RT_SET +#undef RT_BEGIN_ITERATE +#undef RT_ITERATE_NEXT +#undef RT_END_ITERATE +#undef RT_DELETE +#undef RT_MEMORY_USAGE +#undef RT_NUM_ENTRIES +#undef RT_DUMP +#undef RT_DUMP_SEARCH +#undef RT_STATS + +/* internal helper functions */ +#undef RT_NEW_ROOT +#undef RT_ALLOC_NODE +#undef RT_INIT_NODE +#undef RT_FREE_NODE +#undef RT_EXTEND +#undef RT_SET_EXTEND +#undef RT_GROW_NODE_KIND +#undef RT_COPY_NODE +#undef RT_REPLACE_NODE +#undef RT_NODE_4_SEARCH_EQ +#undef RT_NODE_32_SEARCH_EQ +#undef RT_NODE_4_GET_INSERTPOS +#undef RT_NODE_32_GET_INSERTPOS +#undef RT_CHUNK_CHILDREN_ARRAY_SHIFT +#undef RT_CHUNK_VALUES_ARRAY_SHIFT +#undef RT_CHUNK_CHILDREN_ARRAY_DELETE +#undef RT_CHUNK_VALUES_ARRAY_DELETE +#undef RT_CHUNK_CHILDREN_ARRAY_COPY +#undef RT_CHUNK_VALUES_ARRAY_COPY +#undef RT_NODE_125_IS_CHUNK_USED +#undef RT_NODE_INNER_125_GET_CHILD +#undef RT_NODE_LEAF_125_GET_VALUE +#undef RT_NODE_INNER_256_IS_CHUNK_USED +#undef RT_NODE_LEAF_256_IS_CHUNK_USED +#undef RT_NODE_INNER_256_GET_CHILD +#undef RT_NODE_LEAF_256_GET_VALUE +#undef RT_NODE_INNER_256_SET +#undef RT_NODE_LEAF_256_SET +#undef RT_NODE_INNER_256_DELETE +#undef RT_NODE_LEAF_256_DELETE +#undef RT_KEY_GET_SHIFT +#undef RT_SHIFT_GET_MAX_VAL +#undef RT_NODE_SEARCH_INNER +#undef RT_NODE_SEARCH_LEAF +#undef RT_NODE_DELETE_INNER +#undef RT_NODE_DELETE_LEAF +#undef RT_NODE_INSERT_INNER +#undef RT_NODE_INSERT_LEAF +#undef RT_NODE_INNER_ITERATE_NEXT +#undef RT_NODE_LEAF_ITERATE_NEXT +#undef RT_UPDATE_ITER_STACK +#undef RT_ITER_UPDATE_KEY +#undef RT_VERIFY_NODE + +#undef RT_DEBUG diff --git a/src/include/lib/radixtree_delete_impl.h b/src/include/lib/radixtree_delete_impl.h index 24fd9cc02b..6eefc63e19 100644 --- a/src/include/lib/radixtree_delete_impl.h +++ b/src/include/lib/radixtree_delete_impl.h @@ -1,15 +1,15 @@ /* TODO: shrink nodes */ #if defined(RT_NODE_LEVEL_INNER) -#define RT_NODE4_TYPE rt_node_inner_4 -#define RT_NODE32_TYPE rt_node_inner_32 -#define RT_NODE125_TYPE rt_node_inner_125 -#define RT_NODE256_TYPE rt_node_inner_256 +#define RT_NODE4_TYPE RT_NODE_INNER_4 +#define RT_NODE32_TYPE RT_NODE_INNER_32 +#define RT_NODE125_TYPE RT_NODE_INNER_125 +#define RT_NODE256_TYPE RT_NODE_INNER_256 #elif defined(RT_NODE_LEVEL_LEAF) -#define RT_NODE4_TYPE rt_node_leaf_4 -#define RT_NODE32_TYPE rt_node_leaf_32 -#define RT_NODE125_TYPE rt_node_leaf_125 -#define RT_NODE256_TYPE rt_node_leaf_256 +#define RT_NODE4_TYPE RT_NODE_LEAF_4 +#define RT_NODE32_TYPE RT_NODE_LEAF_32 +#define RT_NODE125_TYPE RT_NODE_LEAF_125 +#define RT_NODE256_TYPE RT_NODE_LEAF_256 #else #error node level must be either inner or leaf #endif @@ -21,16 +21,16 @@ case RT_NODE_KIND_4: { RT_NODE4_TYPE *n4 = (RT_NODE4_TYPE *) node; - int idx = node_4_search_eq((rt_node_base_4 *) n4, chunk); + int idx = RT_NODE_4_SEARCH_EQ((RT_NODE_BASE_4 *) n4, chunk); if (idx < 0) return false; #ifdef RT_NODE_LEVEL_LEAF - chunk_values_array_delete(n4->base.chunks, (uint64 *) n4->values, + RT_CHUNK_VALUES_ARRAY_DELETE(n4->base.chunks, (uint64 *) n4->values, n4->base.n.count, idx); #else - chunk_children_array_delete(n4->base.chunks, n4->children, + RT_CHUNK_CHILDREN_ARRAY_DELETE(n4->base.chunks, n4->children, n4->base.n.count, idx); #endif break; @@ -38,16 +38,16 @@ case RT_NODE_KIND_32: { RT_NODE32_TYPE *n32 = (RT_NODE32_TYPE *) node; - int idx = node_32_search_eq((rt_node_base_32 *) n32, chunk); + int idx = RT_NODE_32_SEARCH_EQ((RT_NODE_BASE_32 *) n32, chunk); if (idx < 0) return false; #ifdef RT_NODE_LEVEL_LEAF - chunk_values_array_delete(n32->base.chunks, (uint64 *) n32->values, + RT_CHUNK_VALUES_ARRAY_DELETE(n32->base.chunks, (uint64 *) n32->values, n32->base.n.count, idx); #else - chunk_children_array_delete(n32->base.chunks, n32->children, + RT_CHUNK_CHILDREN_ARRAY_DELETE(n32->base.chunks, n32->children, n32->base.n.count, idx); #endif break; @@ -74,16 +74,16 @@ RT_NODE256_TYPE *n256 = (RT_NODE256_TYPE *) node; #ifdef RT_NODE_LEVEL_LEAF - if (!node_leaf_256_is_chunk_used(n256, chunk)) + if (!RT_NODE_LEAF_256_IS_CHUNK_USED(n256, chunk)) #else - if (!node_inner_256_is_chunk_used(n256, chunk)) + if (!RT_NODE_INNER_256_IS_CHUNK_USED(n256, chunk)) #endif return false; #ifdef RT_NODE_LEVEL_LEAF - node_leaf_256_delete(n256, chunk); + RT_NODE_LEAF_256_DELETE(n256, chunk); #else - node_inner_256_delete(n256, chunk); + RT_NODE_INNER_256_DELETE(n256, chunk); #endif break; } diff --git a/src/include/lib/radixtree_insert_impl.h b/src/include/lib/radixtree_insert_impl.h index c63fe9a3c0..ff76583402 100644 --- a/src/include/lib/radixtree_insert_impl.h +++ b/src/include/lib/radixtree_insert_impl.h @@ -1,20 +1,20 @@ #if defined(RT_NODE_LEVEL_INNER) -#define RT_NODE4_TYPE rt_node_inner_4 -#define RT_NODE32_TYPE rt_node_inner_32 -#define RT_NODE125_TYPE rt_node_inner_125 -#define RT_NODE256_TYPE rt_node_inner_256 +#define RT_NODE4_TYPE RT_NODE_INNER_4 +#define RT_NODE32_TYPE RT_NODE_INNER_32 +#define RT_NODE125_TYPE RT_NODE_INNER_125 +#define RT_NODE256_TYPE RT_NODE_INNER_256 #elif defined(RT_NODE_LEVEL_LEAF) -#define RT_NODE4_TYPE rt_node_leaf_4 -#define RT_NODE32_TYPE rt_node_leaf_32 -#define RT_NODE125_TYPE rt_node_leaf_125 -#define RT_NODE256_TYPE rt_node_leaf_256 +#define RT_NODE4_TYPE RT_NODE_LEAF_4 +#define RT_NODE32_TYPE RT_NODE_LEAF_32 +#define RT_NODE125_TYPE RT_NODE_LEAF_125 +#define RT_NODE256_TYPE RT_NODE_LEAF_256 #else #error node level must be either inner or leaf #endif uint8 chunk = RT_GET_KEY_CHUNK(key, node->shift); bool chunk_exists = false; - rt_node *newnode = NULL; + RT_NODE *newnode = NULL; #ifdef RT_NODE_LEVEL_LEAF Assert(NODE_IS_LEAF(node)); @@ -29,7 +29,7 @@ RT_NODE4_TYPE *n4 = (RT_NODE4_TYPE *) node; int idx; - idx = node_4_search_eq(&n4->base, chunk); + idx = RT_NODE_4_SEARCH_EQ(&n4->base, chunk); if (idx != -1) { /* found the existing chunk */ @@ -47,22 +47,22 @@ RT_NODE32_TYPE *new32; /* grow node from 4 to 32 */ - newnode = rt_grow_node_kind(tree, node, RT_NODE_KIND_32); + newnode = RT_GROW_NODE_KIND(tree, node, RT_NODE_KIND_32); new32 = (RT_NODE32_TYPE *) newnode; #ifdef RT_NODE_LEVEL_LEAF - chunk_values_array_copy(n4->base.chunks, n4->values, + RT_CHUNK_VALUES_ARRAY_COPY(n4->base.chunks, n4->values, new32->base.chunks, new32->values); #else - chunk_children_array_copy(n4->base.chunks, n4->children, + RT_CHUNK_CHILDREN_ARRAY_COPY(n4->base.chunks, n4->children, new32->base.chunks, new32->children); #endif Assert(parent != NULL); - rt_replace_node(tree, parent, node, newnode, key); + RT_REPLACE_NODE(tree, parent, node, newnode, key); node = newnode; } else { - int insertpos = node_4_get_insertpos(&n4->base, chunk); + int insertpos = RT_NODE_4_GET_INSERTPOS(&n4->base, chunk); int count = n4->base.n.count; /* shift chunks and children */ @@ -70,10 +70,10 @@ { Assert(count > 0); #ifdef RT_NODE_LEVEL_LEAF - chunk_values_array_shift(n4->base.chunks, n4->values, + RT_CHUNK_VALUES_ARRAY_SHIFT(n4->base.chunks, n4->values, count, insertpos); #else - chunk_children_array_shift(n4->base.chunks, n4->children, + RT_CHUNK_CHILDREN_ARRAY_SHIFT(n4->base.chunks, n4->children, count, insertpos); #endif } @@ -90,12 +90,12 @@ /* FALLTHROUGH */ case RT_NODE_KIND_32: { - const rt_size_class_elem minclass = rt_size_class_info[RT_CLASS_32_PARTIAL]; - const rt_size_class_elem maxclass = rt_size_class_info[RT_CLASS_32_FULL]; + const RT_SIZE_CLASS_ELEM class32_min = RT_SIZE_CLASS_INFO[RT_CLASS_32_PARTIAL]; + const RT_SIZE_CLASS_ELEM class32_max = RT_SIZE_CLASS_INFO[RT_CLASS_32_FULL]; RT_NODE32_TYPE *n32 = (RT_NODE32_TYPE *) node; int idx; - idx = node_32_search_eq(&n32->base, chunk); + idx = RT_NODE_32_SEARCH_EQ(&n32->base, chunk); if (idx != -1) { /* found the existing chunk */ @@ -109,20 +109,20 @@ } if (unlikely(!VAR_NODE_HAS_FREE_SLOT(n32)) && - n32->base.n.fanout == minclass.fanout) + n32->base.n.fanout == class32_min.fanout) { /* grow to the next size class of this kind */ #ifdef RT_NODE_LEVEL_LEAF - newnode = rt_alloc_node(tree, RT_CLASS_32_FULL, false); - memcpy(newnode, node, minclass.leaf_size); + newnode = RT_ALLOC_NODE(tree, RT_CLASS_32_FULL, false); + memcpy(newnode, node, class32_min.leaf_size); #else - newnode = rt_alloc_node(tree, RT_CLASS_32_FULL, true); - memcpy(newnode, node, minclass.inner_size); + newnode = RT_ALLOC_NODE(tree, RT_CLASS_32_FULL, true); + memcpy(newnode, node, class32_min.inner_size); #endif - newnode->fanout = maxclass.fanout; + newnode->fanout = class32_max.fanout; Assert(parent != NULL); - rt_replace_node(tree, parent, node, newnode, key); + RT_REPLACE_NODE(tree, parent, node, newnode, key); node = newnode; /* also update pointer for this kind */ @@ -133,13 +133,13 @@ { RT_NODE125_TYPE *new125; - Assert(n32->base.n.fanout == maxclass.fanout); + Assert(n32->base.n.fanout == class32_max.fanout); /* grow node from 32 to 125 */ - newnode = rt_grow_node_kind(tree, node, RT_NODE_KIND_125); + newnode = RT_GROW_NODE_KIND(tree, node, RT_NODE_KIND_125); new125 = (RT_NODE125_TYPE *) newnode; - for (int i = 0; i < maxclass.fanout; i++) + for (int i = 0; i < class32_max.fanout; i++) { new125->base.slot_idxs[n32->base.chunks[i]] = i; #ifdef RT_NODE_LEVEL_LEAF @@ -149,26 +149,26 @@ #endif } - Assert(maxclass.fanout <= sizeof(bitmapword) * BITS_PER_BYTE); - new125->base.isset[0] = (bitmapword) (((uint64) 1 << maxclass.fanout) - 1); + Assert(class32_max.fanout <= sizeof(bitmapword) * BITS_PER_BYTE); + new125->base.isset[0] = (bitmapword) (((uint64) 1 << class32_max.fanout) - 1); Assert(parent != NULL); - rt_replace_node(tree, parent, node, newnode, key); + RT_REPLACE_NODE(tree, parent, node, newnode, key); node = newnode; } else { - int insertpos = node_32_get_insertpos(&n32->base, chunk); + int insertpos = RT_NODE_32_GET_INSERTPOS(&n32->base, chunk); int count = n32->base.n.count; if (insertpos < count) { Assert(count > 0); #ifdef RT_NODE_LEVEL_LEAF - chunk_values_array_shift(n32->base.chunks, n32->values, + RT_CHUNK_VALUES_ARRAY_SHIFT(n32->base.chunks, n32->values, count, insertpos); #else - chunk_children_array_shift(n32->base.chunks, n32->children, + RT_CHUNK_CHILDREN_ARRAY_SHIFT(n32->base.chunks, n32->children, count, insertpos); #endif } @@ -206,22 +206,22 @@ RT_NODE256_TYPE *new256; /* grow node from 125 to 256 */ - newnode = rt_grow_node_kind(tree, node, RT_NODE_KIND_256); + newnode = RT_GROW_NODE_KIND(tree, node, RT_NODE_KIND_256); new256 = (RT_NODE256_TYPE *) newnode; for (int i = 0; i < RT_NODE_MAX_SLOTS && cnt < n125->base.n.count; i++) { - if (!node_125_is_chunk_used(&n125->base, i)) + if (!RT_NODE_125_IS_CHUNK_USED(&n125->base, i)) continue; #ifdef RT_NODE_LEVEL_LEAF - node_leaf_256_set(new256, i, node_leaf_125_get_value(n125, i)); + RT_NODE_LEAF_256_SET(new256, i, RT_NODE_LEAF_125_GET_VALUE(n125, i)); #else - node_inner_256_set(new256, i, node_inner_125_get_child(n125, i)); + RT_NODE_INNER_256_SET(new256, i, RT_NODE_INNER_125_GET_CHILD(n125, i)); #endif cnt++; } Assert(parent != NULL); - rt_replace_node(tree, parent, node, newnode, key); + RT_REPLACE_NODE(tree, parent, node, newnode, key); node = newnode; } else @@ -260,16 +260,16 @@ RT_NODE256_TYPE *n256 = (RT_NODE256_TYPE *) node; #ifdef RT_NODE_LEVEL_LEAF - chunk_exists = node_leaf_256_is_chunk_used(n256, chunk); + chunk_exists = RT_NODE_LEAF_256_IS_CHUNK_USED(n256, chunk); #else - chunk_exists = node_inner_256_is_chunk_used(n256, chunk); + chunk_exists = RT_NODE_INNER_256_IS_CHUNK_USED(n256, chunk); #endif Assert(chunk_exists || FIXED_NODE_HAS_FREE_SLOT(n256, RT_CLASS_256)); #ifdef RT_NODE_LEVEL_LEAF - node_leaf_256_set(n256, chunk, value); + RT_NODE_LEAF_256_SET(n256, chunk, value); #else - node_inner_256_set(n256, chunk, child); + RT_NODE_INNER_256_SET(n256, chunk, child); #endif break; } @@ -283,7 +283,7 @@ * Done. Finally, verify the chunk and value is inserted or replaced * properly in the node. */ - rt_verify_node(node); + RT_VERIFY_NODE(node); return chunk_exists; diff --git a/src/include/lib/radixtree_iter_impl.h b/src/include/lib/radixtree_iter_impl.h index bebf8e725a..a153011376 100644 --- a/src/include/lib/radixtree_iter_impl.h +++ b/src/include/lib/radixtree_iter_impl.h @@ -1,13 +1,13 @@ #if defined(RT_NODE_LEVEL_INNER) -#define RT_NODE4_TYPE rt_node_inner_4 -#define RT_NODE32_TYPE rt_node_inner_32 -#define RT_NODE125_TYPE rt_node_inner_125 -#define RT_NODE256_TYPE rt_node_inner_256 +#define RT_NODE4_TYPE RT_NODE_INNER_4 +#define RT_NODE32_TYPE RT_NODE_INNER_32 +#define RT_NODE125_TYPE RT_NODE_INNER_125 +#define RT_NODE256_TYPE RT_NODE_INNER_256 #elif defined(RT_NODE_LEVEL_LEAF) -#define RT_NODE4_TYPE rt_node_leaf_4 -#define RT_NODE32_TYPE rt_node_leaf_32 -#define RT_NODE125_TYPE rt_node_leaf_125 -#define RT_NODE256_TYPE rt_node_leaf_256 +#define RT_NODE4_TYPE RT_NODE_LEAF_4 +#define RT_NODE32_TYPE RT_NODE_LEAF_32 +#define RT_NODE125_TYPE RT_NODE_LEAF_125 +#define RT_NODE256_TYPE RT_NODE_LEAF_256 #else #error node level must be either inner or leaf #endif @@ -15,7 +15,7 @@ #ifdef RT_NODE_LEVEL_LEAF uint64 value; #else - rt_node *child = NULL; + RT_NODE *child = NULL; #endif bool found = false; uint8 key_chunk; @@ -62,7 +62,7 @@ for (i = node_iter->current_idx + 1; i < RT_NODE_MAX_SLOTS; i++) { - if (node_125_is_chunk_used((rt_node_base_125 *) n125, i)) + if (RT_NODE_125_IS_CHUNK_USED((RT_NODE_BASE_125 *) n125, i)) break; } @@ -71,9 +71,9 @@ node_iter->current_idx = i; #ifdef RT_NODE_LEVEL_LEAF - value = node_leaf_125_get_value(n125, i); + value = RT_NODE_LEAF_125_GET_VALUE(n125, i); #else - child = node_inner_125_get_child(n125, i); + child = RT_NODE_INNER_125_GET_CHILD(n125, i); #endif key_chunk = i; found = true; @@ -87,9 +87,9 @@ for (i = node_iter->current_idx + 1; i < RT_NODE_MAX_SLOTS; i++) { #ifdef RT_NODE_LEVEL_LEAF - if (node_leaf_256_is_chunk_used(n256, i)) + if (RT_NODE_LEAF_256_IS_CHUNK_USED(n256, i)) #else - if (node_inner_256_is_chunk_used(n256, i)) + if (RT_NODE_INNER_256_IS_CHUNK_USED(n256, i)) #endif break; } @@ -99,9 +99,9 @@ node_iter->current_idx = i; #ifdef RT_NODE_LEVEL_LEAF - value = node_leaf_256_get_value(n256, i); + value = RT_NODE_LEAF_256_GET_VALUE(n256, i); #else - child = node_inner_256_get_child(n256, i); + child = RT_NODE_INNER_256_GET_CHILD(n256, i); #endif key_chunk = i; found = true; @@ -111,7 +111,7 @@ if (found) { - rt_iter_update_key(iter, key_chunk, node_iter->node->shift); + RT_ITER_UPDATE_KEY(iter, key_chunk, node_iter->node->shift); #ifdef RT_NODE_LEVEL_LEAF *value_p = value; #endif diff --git a/src/include/lib/radixtree_search_impl.h b/src/include/lib/radixtree_search_impl.h index d0366f9bb6..1a0d2d3f1f 100644 --- a/src/include/lib/radixtree_search_impl.h +++ b/src/include/lib/radixtree_search_impl.h @@ -1,13 +1,13 @@ #if defined(RT_NODE_LEVEL_INNER) -#define RT_NODE4_TYPE rt_node_inner_4 -#define RT_NODE32_TYPE rt_node_inner_32 -#define RT_NODE125_TYPE rt_node_inner_125 -#define RT_NODE256_TYPE rt_node_inner_256 +#define RT_NODE4_TYPE RT_NODE_INNER_4 +#define RT_NODE32_TYPE RT_NODE_INNER_32 +#define RT_NODE125_TYPE RT_NODE_INNER_125 +#define RT_NODE256_TYPE RT_NODE_INNER_256 #elif defined(RT_NODE_LEVEL_LEAF) -#define RT_NODE4_TYPE rt_node_leaf_4 -#define RT_NODE32_TYPE rt_node_leaf_32 -#define RT_NODE125_TYPE rt_node_leaf_125 -#define RT_NODE256_TYPE rt_node_leaf_256 +#define RT_NODE4_TYPE RT_NODE_LEAF_4 +#define RT_NODE32_TYPE RT_NODE_LEAF_32 +#define RT_NODE125_TYPE RT_NODE_LEAF_125 +#define RT_NODE256_TYPE RT_NODE_LEAF_256 #else #error node level must be either inner or leaf #endif @@ -17,7 +17,7 @@ #ifdef RT_NODE_LEVEL_LEAF uint64 value = 0; #else - rt_node *child = NULL; + RT_NODE *child = NULL; #endif switch (node->kind) @@ -25,7 +25,7 @@ case RT_NODE_KIND_4: { RT_NODE4_TYPE *n4 = (RT_NODE4_TYPE *) node; - int idx = node_4_search_eq((rt_node_base_4 *) n4, chunk); + int idx = RT_NODE_4_SEARCH_EQ((RT_NODE_BASE_4 *) n4, chunk); if (idx < 0) return false; @@ -40,7 +40,7 @@ case RT_NODE_KIND_32: { RT_NODE32_TYPE *n32 = (RT_NODE32_TYPE *) node; - int idx = node_32_search_eq((rt_node_base_32 *) n32, chunk); + int idx = RT_NODE_32_SEARCH_EQ((RT_NODE_BASE_32 *) n32, chunk); if (idx < 0) return false; @@ -56,13 +56,13 @@ { RT_NODE125_TYPE *n125 = (RT_NODE125_TYPE *) node; - if (!node_125_is_chunk_used((rt_node_base_125 *) n125, chunk)) + if (!RT_NODE_125_IS_CHUNK_USED((RT_NODE_BASE_125 *) n125, chunk)) return false; #ifdef RT_NODE_LEVEL_LEAF - value = node_leaf_125_get_value(n125, chunk); + value = RT_NODE_LEAF_125_GET_VALUE(n125, chunk); #else - child = node_inner_125_get_child(n125, chunk); + child = RT_NODE_INNER_125_GET_CHILD(n125, chunk); #endif break; } @@ -71,16 +71,16 @@ RT_NODE256_TYPE *n256 = (RT_NODE256_TYPE *) node; #ifdef RT_NODE_LEVEL_LEAF - if (!node_leaf_256_is_chunk_used(n256, chunk)) + if (!RT_NODE_LEAF_256_IS_CHUNK_USED(n256, chunk)) #else - if (!node_inner_256_is_chunk_used(n256, chunk)) + if (!RT_NODE_INNER_256_IS_CHUNK_USED(n256, chunk)) #endif return false; #ifdef RT_NODE_LEVEL_LEAF - value = node_leaf_256_get_value(n256, chunk); + value = RT_NODE_LEAF_256_GET_VALUE(n256, chunk); #else - child = node_inner_256_get_child(n256, chunk); + child = RT_NODE_INNER_256_GET_CHILD(n256, chunk); #endif break; } diff --git a/src/test/modules/test_radixtree/test_radixtree.c b/src/test/modules/test_radixtree/test_radixtree.c index ea993e63df..2256d08100 100644 --- a/src/test/modules/test_radixtree/test_radixtree.c +++ b/src/test/modules/test_radixtree/test_radixtree.c @@ -14,7 +14,6 @@ #include "common/pg_prng.h" #include "fmgr.h" -#include "lib/radixtree.h" #include "miscadmin.h" #include "nodes/bitmapset.h" #include "storage/block.h" @@ -99,6 +98,14 @@ static const test_spec test_specs[] = { } }; +/* define the radix tree implementation to test */ +#define RT_PREFIX rt +#define RT_SCOPE static +#define RT_DECLARE +#define RT_DEFINE +#include "lib/radixtree.h" + + PG_MODULE_MAGIC; PG_FUNCTION_INFO_V1(test_radixtree); @@ -106,7 +113,7 @@ PG_FUNCTION_INFO_V1(test_radixtree); static void test_empty(void) { - radix_tree *radixtree; + rt_radix_tree *radixtree; rt_iter *iter; uint64 dummy; uint64 key; @@ -142,7 +149,7 @@ test_empty(void) static void test_basic(int children, bool test_inner) { - radix_tree *radixtree; + rt_radix_tree *radixtree; uint64 *keys; int shift = test_inner ? 8 : 0; @@ -192,7 +199,7 @@ test_basic(int children, bool test_inner) * Check if keys from start to end with the shift exist in the tree. */ static void -check_search_on_node(radix_tree *radixtree, uint8 shift, int start, int end, +check_search_on_node(rt_radix_tree *radixtree, uint8 shift, int start, int end, int incr) { for (int i = start; i < end; i++) @@ -210,7 +217,7 @@ check_search_on_node(radix_tree *radixtree, uint8 shift, int start, int end, } static void -test_node_types_insert(radix_tree *radixtree, uint8 shift, bool insert_asc) +test_node_types_insert(rt_radix_tree *radixtree, uint8 shift, bool insert_asc) { uint64 num_entries; int ninserted = 0; @@ -257,7 +264,7 @@ test_node_types_insert(radix_tree *radixtree, uint8 shift, bool insert_asc) } static void -test_node_types_delete(radix_tree *radixtree, uint8 shift) +test_node_types_delete(rt_radix_tree *radixtree, uint8 shift) { uint64 num_entries; @@ -288,7 +295,7 @@ test_node_types_delete(radix_tree *radixtree, uint8 shift) static void test_node_types(uint8 shift) { - radix_tree *radixtree; + rt_radix_tree *radixtree; elog(NOTICE, "testing radix tree node types with shift \"%d\"", shift); @@ -312,7 +319,7 @@ test_node_types(uint8 shift) static void test_pattern(const test_spec * spec) { - radix_tree *radixtree; + rt_radix_tree *radixtree; rt_iter *iter; MemoryContext radixtree_ctx; TimestampTz starttime; -- 2.39.0 [text/x-patch] v17-0006-Convert-radixtree.c-into-a-header.patch (91.9K, ../../CAFBsxsFMuyUd48SXy1VMFvpVBHh6B6300_UfTSiPckKJtcwWMA@mail.gmail.com/7-v17-0006-Convert-radixtree.c-into-a-header.patch) download | inline diff: From 45cad7dcb2c14e035ffd03ca59fcedaf51674bb4 Mon Sep 17 00:00:00 2001 From: John Naylor <[email protected]> Date: Wed, 4 Jan 2023 12:54:51 +0700 Subject: [PATCH v17 6/9] Convert radixtree.c into a header Preparation for converting to a template. --- src/backend/lib/Makefile | 1 - src/backend/lib/meson.build | 1 - src/backend/lib/radixtree.c | 1767 ----------------------------------- src/include/lib/radixtree.h | 1762 +++++++++++++++++++++++++++++++++- 4 files changed, 1753 insertions(+), 1778 deletions(-) delete mode 100644 src/backend/lib/radixtree.c diff --git a/src/backend/lib/Makefile b/src/backend/lib/Makefile index 4c1db794b6..9dad31398a 100644 --- a/src/backend/lib/Makefile +++ b/src/backend/lib/Makefile @@ -22,7 +22,6 @@ OBJS = \ integerset.o \ knapsack.o \ pairingheap.o \ - radixtree.o \ rbtree.o \ include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/lib/meson.build b/src/backend/lib/meson.build index 5f8df32c5c..974cab8776 100644 --- a/src/backend/lib/meson.build +++ b/src/backend/lib/meson.build @@ -11,5 +11,4 @@ backend_sources += files( 'knapsack.c', 'pairingheap.c', 'rbtree.c', - 'radixtree.c', ) diff --git a/src/backend/lib/radixtree.c b/src/backend/lib/radixtree.c deleted file mode 100644 index 80cde09aaf..0000000000 --- a/src/backend/lib/radixtree.c +++ /dev/null @@ -1,1767 +0,0 @@ -/*------------------------------------------------------------------------- - * - * radixtree.c - * Implementation for adaptive radix tree. - * - * This module employs the idea from the paper "The Adaptive Radix Tree: ARTful - * Indexing for Main-Memory Databases" by Viktor Leis, Alfons Kemper, and Thomas - * Neumann, 2013. The radix tree uses adaptive node sizes, a small number of node - * types, each with a different numbers of elements. Depending on the number of - * children, the appropriate node type is used. - * - * There are some differences from the proposed implementation. For instance, - * there is not support for path compression and lazy path expansion. The radix - * tree supports fixed length of the key so we don't expect the tree level - * wouldn't be high. - * - * Both the key and the value are 64-bit unsigned integer. The inner nodes and - * the leaf nodes have slightly different structure: for inner tree nodes, - * shift > 0, store the pointer to its child node as the value. The leaf nodes, - * shift == 0, have the 64-bit unsigned integer that is specified by the user as - * the value. The paper refers to this technique as "Multi-value leaves". We - * choose it to avoid an additional pointer traversal. It is the reason this code - * currently does not support variable-length keys. - * - * XXX: Most functions in this file have two variants for inner nodes and leaf - * nodes, therefore there are duplication codes. While this sometimes makes the - * code maintenance tricky, this reduces branch prediction misses when judging - * whether the node is a inner node of a leaf node. - * - * XXX: the radix tree node never be shrunk. - * - * Interface - * --------- - * - * rt_create - Create a new, empty radix tree - * rt_free - Free the radix tree - * rt_search - Search a key-value pair - * rt_set - Set a key-value pair - * rt_delete - Delete a key-value pair - * rt_begin_iterate - Begin iterating through all key-value pairs - * rt_iterate_next - Return next key-value pair, if any - * rt_end_iter - End iteration - * rt_memory_usage - Get the memory usage - * rt_num_entries - Get the number of key-value pairs - * - * rt_create() creates an empty radix tree in the given memory context - * and memory contexts for all kinds of radix tree node under the memory context. - * - * rt_iterate_next() ensures returning key-value pairs in the ascending - * order of the key. - * - * Copyright (c) 2022, PostgreSQL Global Development Group - * - * IDENTIFICATION - * src/backend/lib/radixtree.c - * - *------------------------------------------------------------------------- - */ - -#include "postgres.h" - -#include "lib/radixtree.h" -#include "lib/stringinfo.h" -#include "miscadmin.h" -#include "nodes/bitmapset.h" -#include "port/pg_bitutils.h" -#include "port/pg_lfind.h" -#include "utils/memutils.h" - -#ifdef RT_DEBUG -#define UINT64_FORMAT_HEX "%" INT64_MODIFIER "X" -#endif - -/* The number of bits encoded in one tree level */ -#define RT_NODE_SPAN BITS_PER_BYTE - -/* The number of maximum slots in the node */ -#define RT_NODE_MAX_SLOTS (1 << RT_NODE_SPAN) - -/* Mask for extracting a chunk from the key */ -#define RT_CHUNK_MASK ((1 << RT_NODE_SPAN) - 1) - -/* Maximum shift the radix tree uses */ -#define RT_MAX_SHIFT key_get_shift(UINT64_MAX) - -/* Tree level the radix tree uses */ -#define RT_MAX_LEVEL ((sizeof(uint64) * BITS_PER_BYTE) / RT_NODE_SPAN) - -/* Invalid index used in node-125 */ -#define RT_NODE_125_INVALID_IDX 0xFF - -/* Get a chunk from the key */ -#define RT_GET_KEY_CHUNK(key, shift) ((uint8) (((key) >> (shift)) & RT_CHUNK_MASK)) - -/* For accessing bitmaps */ -#define BM_IDX(x) ((x) / BITS_PER_BITMAPWORD) -#define BM_BIT(x) ((x) % BITS_PER_BITMAPWORD) - -/* - * Supported radix tree node kinds and size classes. - * - * There are 4 node kinds and each node kind have one or two size classes, - * partial and full. The size classes in the same node kind have the same - * node structure but have the different number of fanout that is stored - * in 'fanout' of rt_node. For example in size class 15, when a 16th element - * is to be inserted, we allocate a larger area and memcpy the entire old - * node to it. - * - * This technique allows us to limit the node kinds to 4, which limits the - * number of cases in switch statements. It also allows a possible future - * optimization to encode the node kind in a pointer tag. - * - * These size classes have been chose carefully so that it minimizes the - * allocator padding in both the inner and leaf nodes on DSA. - * node - */ -#define RT_NODE_KIND_4 0x00 -#define RT_NODE_KIND_32 0x01 -#define RT_NODE_KIND_125 0x02 -#define RT_NODE_KIND_256 0x03 -#define RT_NODE_KIND_COUNT 4 - -typedef enum rt_size_class -{ - RT_CLASS_4_FULL = 0, - RT_CLASS_32_PARTIAL, - RT_CLASS_32_FULL, - RT_CLASS_125_FULL, - RT_CLASS_256 - -#define RT_SIZE_CLASS_COUNT (RT_CLASS_256 + 1) -} rt_size_class; - -/* Common type for all nodes types */ -typedef struct rt_node -{ - /* - * Number of children. We use uint16 to be able to indicate 256 children - * at the fanout of 8. - */ - uint16 count; - - /* Max number of children. We can use uint8 because we never need to store 256 */ - /* WIP: if we don't have a variable sized node4, this should instead be in the base - types as needed, since saving every byte is crucial for the smallest node kind */ - uint8 fanout; - - /* - * Shift indicates which part of the key space is represented by this - * node. That is, the key is shifted by 'shift' and the lowest - * RT_NODE_SPAN bits are then represented in chunk. - */ - uint8 shift; - uint8 chunk; - - /* Node kind, one per search/set algorithm */ - uint8 kind; -} rt_node; -#define NODE_IS_LEAF(n) (((rt_node *) (n))->shift == 0) -#define NODE_IS_EMPTY(n) (((rt_node *) (n))->count == 0) -#define VAR_NODE_HAS_FREE_SLOT(node) \ - ((node)->base.n.count < (node)->base.n.fanout) -#define FIXED_NODE_HAS_FREE_SLOT(node, class) \ - ((node)->base.n.count < rt_size_class_info[class].fanout) - -/* Base type of each node kinds for leaf and inner nodes */ -/* The base types must be a be able to accommodate the largest size -class for variable-sized node kinds*/ -typedef struct rt_node_base_4 -{ - rt_node n; - - /* 4 children, for key chunks */ - uint8 chunks[4]; -} rt_node_base_4; - -typedef struct rt_node_base32 -{ - rt_node n; - - /* 32 children, for key chunks */ - uint8 chunks[32]; -} rt_node_base_32; - -/* - * node-125 uses slot_idx array, an array of RT_NODE_MAX_SLOTS length, typically - * 256, to store indexes into a second array that contains up to 125 values (or - * child pointers in inner nodes). - */ -typedef struct rt_node_base125 -{ - rt_node n; - - /* The index of slots for each fanout */ - uint8 slot_idxs[RT_NODE_MAX_SLOTS]; - - /* isset is a bitmap to track which slot is in use */ - bitmapword isset[BM_IDX(128)]; -} rt_node_base_125; - -typedef struct rt_node_base256 -{ - rt_node n; -} rt_node_base_256; - -/* - * Inner and leaf nodes. - * - * Theres are separate for two main reasons: - * - * 1) the value type might be different than something fitting into a pointer - * width type - * 2) Need to represent non-existing values in a key-type independent way. - * - * 1) is clearly worth being concerned about, but it's not clear 2) is as - * good. It might be better to just indicate non-existing entries the same way - * in inner nodes. - */ -typedef struct rt_node_inner_4 -{ - rt_node_base_4 base; - - /* number of children depends on size class */ - rt_node *children[FLEXIBLE_ARRAY_MEMBER]; -} rt_node_inner_4; - -typedef struct rt_node_leaf_4 -{ - rt_node_base_4 base; - - /* number of values depends on size class */ - uint64 values[FLEXIBLE_ARRAY_MEMBER]; -} rt_node_leaf_4; - -typedef struct rt_node_inner_32 -{ - rt_node_base_32 base; - - /* number of children depends on size class */ - rt_node *children[FLEXIBLE_ARRAY_MEMBER]; -} rt_node_inner_32; - -typedef struct rt_node_leaf_32 -{ - rt_node_base_32 base; - - /* number of values depends on size class */ - uint64 values[FLEXIBLE_ARRAY_MEMBER]; -} rt_node_leaf_32; - -typedef struct rt_node_inner_125 -{ - rt_node_base_125 base; - - /* number of children depends on size class */ - rt_node *children[FLEXIBLE_ARRAY_MEMBER]; -} rt_node_inner_125; - -typedef struct rt_node_leaf_125 -{ - rt_node_base_125 base; - - /* number of values depends on size class */ - uint64 values[FLEXIBLE_ARRAY_MEMBER]; -} rt_node_leaf_125; - -/* - * node-256 is the largest node type. This node has RT_NODE_MAX_SLOTS length array - * for directly storing values (or child pointers in inner nodes). - */ -typedef struct rt_node_inner_256 -{ - rt_node_base_256 base; - - /* Slots for 256 children */ - rt_node *children[RT_NODE_MAX_SLOTS]; -} rt_node_inner_256; - -typedef struct rt_node_leaf_256 -{ - rt_node_base_256 base; - - /* isset is a bitmap to track which slot is in use */ - bitmapword isset[BM_IDX(RT_NODE_MAX_SLOTS)]; - - /* Slots for 256 values */ - uint64 values[RT_NODE_MAX_SLOTS]; -} rt_node_leaf_256; - -/* Information for each size class */ -typedef struct rt_size_class_elem -{ - const char *name; - int fanout; - - /* slab chunk size */ - Size inner_size; - Size leaf_size; - - /* slab block size */ - Size inner_blocksize; - Size leaf_blocksize; -} rt_size_class_elem; - -/* - * Calculate the slab blocksize so that we can allocate at least 32 chunks - * from the block. - */ -#define NODE_SLAB_BLOCK_SIZE(size) \ - Max((SLAB_DEFAULT_BLOCK_SIZE / (size)) * (size), (size) * 32) -static rt_size_class_elem rt_size_class_info[RT_SIZE_CLASS_COUNT] = { - [RT_CLASS_4_FULL] = { - .name = "radix tree node 4", - .fanout = 4, - .inner_size = sizeof(rt_node_inner_4) + 4 * sizeof(rt_node *), - .leaf_size = sizeof(rt_node_leaf_4) + 4 * sizeof(uint64), - .inner_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(rt_node_inner_4) + 4 * sizeof(rt_node *)), - .leaf_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(rt_node_leaf_4) + 4 * sizeof(uint64)), - }, - [RT_CLASS_32_PARTIAL] = { - .name = "radix tree node 15", - .fanout = 15, - .inner_size = sizeof(rt_node_inner_32) + 15 * sizeof(rt_node *), - .leaf_size = sizeof(rt_node_leaf_32) + 15 * sizeof(uint64), - .inner_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(rt_node_inner_32) + 15 * sizeof(rt_node *)), - .leaf_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(rt_node_leaf_32) + 15 * sizeof(uint64)), - }, - [RT_CLASS_32_FULL] = { - .name = "radix tree node 32", - .fanout = 32, - .inner_size = sizeof(rt_node_inner_32) + 32 * sizeof(rt_node *), - .leaf_size = sizeof(rt_node_leaf_32) + 32 * sizeof(uint64), - .inner_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(rt_node_inner_32) + 32 * sizeof(rt_node *)), - .leaf_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(rt_node_leaf_32) + 32 * sizeof(uint64)), - }, - [RT_CLASS_125_FULL] = { - .name = "radix tree node 125", - .fanout = 125, - .inner_size = sizeof(rt_node_inner_125) + 125 * sizeof(rt_node *), - .leaf_size = sizeof(rt_node_leaf_125) + 125 * sizeof(uint64), - .inner_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(rt_node_inner_125) + 125 * sizeof(rt_node *)), - .leaf_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(rt_node_leaf_125) + 125 * sizeof(uint64)), - }, - [RT_CLASS_256] = { - .name = "radix tree node 256", - .fanout = 256, - .inner_size = sizeof(rt_node_inner_256), - .leaf_size = sizeof(rt_node_leaf_256), - .inner_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(rt_node_inner_256)), - .leaf_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(rt_node_leaf_256)), - }, -}; - -/* Map from the node kind to its minimum size class */ -static rt_size_class kind_min_size_class[RT_NODE_KIND_COUNT] = { - [RT_NODE_KIND_4] = RT_CLASS_4_FULL, - [RT_NODE_KIND_32] = RT_CLASS_32_PARTIAL, - [RT_NODE_KIND_125] = RT_CLASS_125_FULL, - [RT_NODE_KIND_256] = RT_CLASS_256, -}; - -/* - * Iteration support. - * - * Iterating the radix tree returns each pair of key and value in the ascending - * order of the key. To support this, the we iterate nodes of each level. - * - * rt_node_iter struct is used to track the iteration within a node. - * - * rt_iter is the struct for iteration of the radix tree, and uses rt_node_iter - * in order to track the iteration of each level. During the iteration, we also - * construct the key whenever updating the node iteration information, e.g., when - * advancing the current index within the node or when moving to the next node - * at the same level. - */ -typedef struct rt_node_iter -{ - rt_node *node; /* current node being iterated */ - int current_idx; /* current position. -1 for initial value */ -} rt_node_iter; - -struct rt_iter -{ - radix_tree *tree; - - /* Track the iteration on nodes of each level */ - rt_node_iter stack[RT_MAX_LEVEL]; - int stack_len; - - /* The key is being constructed during the iteration */ - uint64 key; -}; - -/* A radix tree with nodes */ -struct radix_tree -{ - MemoryContext context; - - rt_node *root; - uint64 max_val; - uint64 num_keys; - - MemoryContextData *inner_slabs[RT_SIZE_CLASS_COUNT]; - MemoryContextData *leaf_slabs[RT_SIZE_CLASS_COUNT]; - - /* statistics */ -#ifdef RT_DEBUG - int32 cnt[RT_SIZE_CLASS_COUNT]; -#endif -}; - -static void rt_new_root(radix_tree *tree, uint64 key); -static rt_node *rt_alloc_node(radix_tree *tree, rt_size_class size_class, bool inner); -static inline void rt_init_node(rt_node *node, uint8 kind, rt_size_class size_class, - bool inner); -static void rt_free_node(radix_tree *tree, rt_node *node); -static void rt_extend(radix_tree *tree, uint64 key); -static inline bool rt_node_search_inner(rt_node *node, uint64 key, rt_node **child_p); -static inline bool rt_node_search_leaf(rt_node *node, uint64 key, uint64 *value_p); -static bool rt_node_insert_inner(radix_tree *tree, rt_node *parent, rt_node *node, - uint64 key, rt_node *child); -static bool rt_node_insert_leaf(radix_tree *tree, rt_node *parent, rt_node *node, - uint64 key, uint64 value); -static inline rt_node *rt_node_inner_iterate_next(rt_iter *iter, rt_node_iter *node_iter); -static inline bool rt_node_leaf_iterate_next(rt_iter *iter, rt_node_iter *node_iter, - uint64 *value_p); -static void rt_update_iter_stack(rt_iter *iter, rt_node *from_node, int from); -static inline void rt_iter_update_key(rt_iter *iter, uint8 chunk, uint8 shift); - -/* verification (available only with assertion) */ -static void rt_verify_node(rt_node *node); - -/* - * Return index of the first element in 'base' that equals 'key'. Return -1 - * if there is no such element. - */ -static inline int -node_4_search_eq(rt_node_base_4 *node, uint8 chunk) -{ - int idx = -1; - - for (int i = 0; i < node->n.count; i++) - { - if (node->chunks[i] == chunk) - { - idx = i; - break; - } - } - - return idx; -} - -/* - * Return index of the chunk to insert into chunks in the given node. - */ -static inline int -node_4_get_insertpos(rt_node_base_4 *node, uint8 chunk) -{ - int idx; - - for (idx = 0; idx < node->n.count; idx++) - { - if (node->chunks[idx] >= chunk) - break; - } - - return idx; -} - -/* - * Return index of the first element in 'base' that equals 'key'. Return -1 - * if there is no such element. - */ -static inline int -node_32_search_eq(rt_node_base_32 *node, uint8 chunk) -{ - int count = node->n.count; -#ifndef USE_NO_SIMD - Vector8 spread_chunk; - Vector8 haystack1; - Vector8 haystack2; - Vector8 cmp1; - Vector8 cmp2; - uint32 bitfield; - int index_simd = -1; -#endif - -#if defined(USE_NO_SIMD) || defined(USE_ASSERT_CHECKING) - int index = -1; - - for (int i = 0; i < count; i++) - { - if (node->chunks[i] == chunk) - { - index = i; - break; - } - } -#endif - -#ifndef USE_NO_SIMD - spread_chunk = vector8_broadcast(chunk); - vector8_load(&haystack1, &node->chunks[0]); - vector8_load(&haystack2, &node->chunks[sizeof(Vector8)]); - cmp1 = vector8_eq(spread_chunk, haystack1); - cmp2 = vector8_eq(spread_chunk, haystack2); - bitfield = vector8_highbit_mask(cmp1) | (vector8_highbit_mask(cmp2) << sizeof(Vector8)); - bitfield &= ((UINT64CONST(1) << count) - 1); - - if (bitfield) - index_simd = pg_rightmost_one_pos32(bitfield); - - Assert(index_simd == index); - return index_simd; -#else - return index; -#endif -} - -/* - * Return index of the chunk to insert into chunks in the given node. - */ -static inline int -node_32_get_insertpos(rt_node_base_32 *node, uint8 chunk) -{ - int count = node->n.count; -#ifndef USE_NO_SIMD - Vector8 spread_chunk; - Vector8 haystack1; - Vector8 haystack2; - Vector8 cmp1; - Vector8 cmp2; - Vector8 min1; - Vector8 min2; - uint32 bitfield; - int index_simd; -#endif - -#if defined(USE_NO_SIMD) || defined(USE_ASSERT_CHECKING) - int index; - - for (index = 0; index < count; index++) - { - if (node->chunks[index] >= chunk) - break; - } -#endif - -#ifndef USE_NO_SIMD - spread_chunk = vector8_broadcast(chunk); - vector8_load(&haystack1, &node->chunks[0]); - vector8_load(&haystack2, &node->chunks[sizeof(Vector8)]); - min1 = vector8_min(spread_chunk, haystack1); - min2 = vector8_min(spread_chunk, haystack2); - cmp1 = vector8_eq(spread_chunk, min1); - cmp2 = vector8_eq(spread_chunk, min2); - bitfield = vector8_highbit_mask(cmp1) | (vector8_highbit_mask(cmp2) << sizeof(Vector8)); - bitfield &= ((UINT64CONST(1) << count) - 1); - - if (bitfield) - index_simd = pg_rightmost_one_pos32(bitfield); - else - index_simd = count; - - Assert(index_simd == index); - return index_simd; -#else - return index; -#endif -} - -/* - * Functions to manipulate both chunks array and children/values array. - * These are used for node-4 and node-32. - */ - -/* Shift the elements right at 'idx' by one */ -static inline void -chunk_children_array_shift(uint8 *chunks, rt_node **children, int count, int idx) -{ - memmove(&(chunks[idx + 1]), &(chunks[idx]), sizeof(uint8) * (count - idx)); - memmove(&(children[idx + 1]), &(children[idx]), sizeof(rt_node *) * (count - idx)); -} - -static inline void -chunk_values_array_shift(uint8 *chunks, uint64 *values, int count, int idx) -{ - memmove(&(chunks[idx + 1]), &(chunks[idx]), sizeof(uint8) * (count - idx)); - memmove(&(values[idx + 1]), &(values[idx]), sizeof(uint64 *) * (count - idx)); -} - -/* Delete the element at 'idx' */ -static inline void -chunk_children_array_delete(uint8 *chunks, rt_node **children, int count, int idx) -{ - memmove(&(chunks[idx]), &(chunks[idx + 1]), sizeof(uint8) * (count - idx - 1)); - memmove(&(children[idx]), &(children[idx + 1]), sizeof(rt_node *) * (count - idx - 1)); -} - -static inline void -chunk_values_array_delete(uint8 *chunks, uint64 *values, int count, int idx) -{ - memmove(&(chunks[idx]), &(chunks[idx + 1]), sizeof(uint8) * (count - idx - 1)); - memmove(&(values[idx]), &(values[idx + 1]), sizeof(uint64) * (count - idx - 1)); -} - -/* Copy both chunks and children/values arrays */ -static inline void -chunk_children_array_copy(uint8 *src_chunks, rt_node **src_children, - uint8 *dst_chunks, rt_node **dst_children) -{ - const int fanout = rt_size_class_info[RT_CLASS_4_FULL].fanout; - const Size chunk_size = sizeof(uint8) * fanout; - const Size children_size = sizeof(rt_node *) * fanout; - - memcpy(dst_chunks, src_chunks, chunk_size); - memcpy(dst_children, src_children, children_size); -} - -static inline void -chunk_values_array_copy(uint8 *src_chunks, uint64 *src_values, - uint8 *dst_chunks, uint64 *dst_values) -{ - const int fanout = rt_size_class_info[RT_CLASS_4_FULL].fanout; - const Size chunk_size = sizeof(uint8) * fanout; - const Size values_size = sizeof(uint64) * fanout; - - memcpy(dst_chunks, src_chunks, chunk_size); - memcpy(dst_values, src_values, values_size); -} - -/* Functions to manipulate inner and leaf node-125 */ - -/* Does the given chunk in the node has the value? */ -static inline bool -node_125_is_chunk_used(rt_node_base_125 *node, uint8 chunk) -{ - return node->slot_idxs[chunk] != RT_NODE_125_INVALID_IDX; -} - -static inline rt_node * -node_inner_125_get_child(rt_node_inner_125 *node, uint8 chunk) -{ - Assert(!NODE_IS_LEAF(node)); - return node->children[node->base.slot_idxs[chunk]]; -} - -static inline uint64 -node_leaf_125_get_value(rt_node_leaf_125 *node, uint8 chunk) -{ - Assert(NODE_IS_LEAF(node)); - Assert(((rt_node_base_125 *) node)->slot_idxs[chunk] != RT_NODE_125_INVALID_IDX); - return node->values[node->base.slot_idxs[chunk]]; -} - -/* Functions to manipulate inner and leaf node-256 */ - -/* Return true if the slot corresponding to the given chunk is in use */ -static inline bool -node_inner_256_is_chunk_used(rt_node_inner_256 *node, uint8 chunk) -{ - Assert(!NODE_IS_LEAF(node)); - return (node->children[chunk] != NULL); -} - -static inline bool -node_leaf_256_is_chunk_used(rt_node_leaf_256 *node, uint8 chunk) -{ - int idx = BM_IDX(chunk); - int bitnum = BM_BIT(chunk); - - Assert(NODE_IS_LEAF(node)); - return (node->isset[idx] & ((bitmapword) 1 << bitnum)) != 0; -} - -static inline rt_node * -node_inner_256_get_child(rt_node_inner_256 *node, uint8 chunk) -{ - Assert(!NODE_IS_LEAF(node)); - Assert(node_inner_256_is_chunk_used(node, chunk)); - return node->children[chunk]; -} - -static inline uint64 -node_leaf_256_get_value(rt_node_leaf_256 *node, uint8 chunk) -{ - Assert(NODE_IS_LEAF(node)); - Assert(node_leaf_256_is_chunk_used(node, chunk)); - return node->values[chunk]; -} - -/* Set the child in the node-256 */ -static inline void -node_inner_256_set(rt_node_inner_256 *node, uint8 chunk, rt_node *child) -{ - Assert(!NODE_IS_LEAF(node)); - node->children[chunk] = child; -} - -/* Set the value in the node-256 */ -static inline void -node_leaf_256_set(rt_node_leaf_256 *node, uint8 chunk, uint64 value) -{ - int idx = BM_IDX(chunk); - int bitnum = BM_BIT(chunk); - - Assert(NODE_IS_LEAF(node)); - node->isset[idx] |= ((bitmapword) 1 << bitnum); - node->values[chunk] = value; -} - -/* Set the slot at the given chunk position */ -static inline void -node_inner_256_delete(rt_node_inner_256 *node, uint8 chunk) -{ - Assert(!NODE_IS_LEAF(node)); - node->children[chunk] = NULL; -} - -static inline void -node_leaf_256_delete(rt_node_leaf_256 *node, uint8 chunk) -{ - int idx = BM_IDX(chunk); - int bitnum = BM_BIT(chunk); - - Assert(NODE_IS_LEAF(node)); - node->isset[idx] &= ~((bitmapword) 1 << bitnum); -} - -/* - * Return the shift that is satisfied to store the given key. - */ -static inline int -key_get_shift(uint64 key) -{ - return (key == 0) - ? 0 - : (pg_leftmost_one_pos64(key) / RT_NODE_SPAN) * RT_NODE_SPAN; -} - -/* - * Return the max value stored in a node with the given shift. - */ -static uint64 -shift_get_max_val(int shift) -{ - if (shift == RT_MAX_SHIFT) - return UINT64_MAX; - - return (UINT64CONST(1) << (shift + RT_NODE_SPAN)) - 1; -} - -/* - * Create a new node as the root. Subordinate nodes will be created during - * the insertion. - */ -static void -rt_new_root(radix_tree *tree, uint64 key) -{ - int shift = key_get_shift(key); - bool inner = shift > 0; - rt_node *newnode; - - newnode = rt_alloc_node(tree, RT_CLASS_4_FULL, inner); - rt_init_node(newnode, RT_NODE_KIND_4, RT_CLASS_4_FULL, inner); - newnode->shift = shift; - tree->max_val = shift_get_max_val(shift); - tree->root = newnode; -} - -/* - * Allocate a new node with the given node kind. - */ -static rt_node * -rt_alloc_node(radix_tree *tree, rt_size_class size_class, bool inner) -{ - rt_node *newnode; - - if (inner) - newnode = (rt_node *) MemoryContextAlloc(tree->inner_slabs[size_class], - rt_size_class_info[size_class].inner_size); - else - newnode = (rt_node *) MemoryContextAlloc(tree->leaf_slabs[size_class], - rt_size_class_info[size_class].leaf_size); - -#ifdef RT_DEBUG - /* update the statistics */ - tree->cnt[size_class]++; -#endif - - return newnode; -} - -/* Initialize the node contents */ -static inline void -rt_init_node(rt_node *node, uint8 kind, rt_size_class size_class, bool inner) -{ - if (inner) - MemSet(node, 0, rt_size_class_info[size_class].inner_size); - else - MemSet(node, 0, rt_size_class_info[size_class].leaf_size); - - node->kind = kind; - node->fanout = rt_size_class_info[size_class].fanout; - - /* Initialize slot_idxs to invalid values */ - if (kind == RT_NODE_KIND_125) - { - rt_node_base_125 *n125 = (rt_node_base_125 *) node; - - memset(n125->slot_idxs, RT_NODE_125_INVALID_IDX, sizeof(n125->slot_idxs)); - } - - /* - * Technically it's 256, but we cannot store that in a uint8, - * and this is the max size class to it will never grow. - */ - if (kind == RT_NODE_KIND_256) - node->fanout = 0; -} - -static inline void -rt_copy_node(rt_node *newnode, rt_node *oldnode) -{ - newnode->shift = oldnode->shift; - newnode->chunk = oldnode->chunk; - newnode->count = oldnode->count; -} - -/* - * Create a new node with 'new_kind' and the same shift, chunk, and - * count of 'node'. - */ -static rt_node* -rt_grow_node_kind(radix_tree *tree, rt_node *node, uint8 new_kind) -{ - rt_node *newnode; - bool inner = !NODE_IS_LEAF(node); - - newnode = rt_alloc_node(tree, kind_min_size_class[new_kind], inner); - rt_init_node(newnode, new_kind, kind_min_size_class[new_kind], inner); - rt_copy_node(newnode, node); - - return newnode; -} - -/* Free the given node */ -static void -rt_free_node(radix_tree *tree, rt_node *node) -{ - /* If we're deleting the root node, make the tree empty */ - if (tree->root == node) - { - tree->root = NULL; - tree->max_val = 0; - } - -#ifdef RT_DEBUG - { - int i; - - /* update the statistics */ - for (i = 0; i < RT_SIZE_CLASS_COUNT; i++) - { - if (node->fanout == rt_size_class_info[i].fanout) - break; - } - - /* fanout of node256 is intentionally 0 */ - if (i == RT_SIZE_CLASS_COUNT) - i = RT_CLASS_256; - - tree->cnt[i]--; - Assert(tree->cnt[i] >= 0); - } -#endif - - pfree(node); -} - -/* - * Replace old_child with new_child, and free the old one. - */ -static void -rt_replace_node(radix_tree *tree, rt_node *parent, rt_node *old_child, - rt_node *new_child, uint64 key) -{ - Assert(old_child->chunk == new_child->chunk); - Assert(old_child->shift == new_child->shift); - - if (parent == old_child) - { - /* Replace the root node with the new large node */ - tree->root = new_child; - } - else - { - bool replaced PG_USED_FOR_ASSERTS_ONLY; - - replaced = rt_node_insert_inner(tree, NULL, parent, key, new_child); - Assert(replaced); - } - - rt_free_node(tree, old_child); -} - -/* - * The radix tree doesn't sufficient height. Extend the radix tree so it can - * store the key. - */ -static void -rt_extend(radix_tree *tree, uint64 key) -{ - int target_shift; - int shift = tree->root->shift + RT_NODE_SPAN; - - target_shift = key_get_shift(key); - - /* Grow tree from 'shift' to 'target_shift' */ - while (shift <= target_shift) - { - rt_node_inner_4 *node; - - node = (rt_node_inner_4 *) rt_alloc_node(tree, RT_CLASS_4_FULL, true); - rt_init_node((rt_node *) node, RT_NODE_KIND_4, RT_CLASS_4_FULL, true); - node->base.n.shift = shift; - node->base.n.count = 1; - node->base.chunks[0] = 0; - node->children[0] = tree->root; - - tree->root->chunk = 0; - tree->root = (rt_node *) node; - - shift += RT_NODE_SPAN; - } - - tree->max_val = shift_get_max_val(target_shift); -} - -/* - * The radix tree doesn't have inner and leaf nodes for given key-value pair. - * Insert inner and leaf nodes from 'node' to bottom. - */ -static inline void -rt_set_extend(radix_tree *tree, uint64 key, uint64 value, rt_node *parent, - rt_node *node) -{ - int shift = node->shift; - - while (shift >= RT_NODE_SPAN) - { - rt_node *newchild; - int newshift = shift - RT_NODE_SPAN; - bool inner = newshift > 0; - - newchild = rt_alloc_node(tree, RT_CLASS_4_FULL, inner); - rt_init_node(newchild, RT_NODE_KIND_4, RT_CLASS_4_FULL, inner); - newchild->shift = newshift; - newchild->chunk = RT_GET_KEY_CHUNK(key, node->shift); - rt_node_insert_inner(tree, parent, node, key, newchild); - - parent = node; - node = newchild; - shift -= RT_NODE_SPAN; - } - - rt_node_insert_leaf(tree, parent, node, key, value); - tree->num_keys++; -} - -/* - * Search for the child pointer corresponding to 'key' in the given node. - * - * Return true if the key is found, otherwise return false. On success, the child - * pointer is set to child_p. - */ -static inline bool -rt_node_search_inner(rt_node *node, uint64 key, rt_node **child_p) -{ -#define RT_NODE_LEVEL_INNER -#include "lib/radixtree_search_impl.h" -#undef RT_NODE_LEVEL_INNER -} - -/* - * Search for the value corresponding to 'key' in the given node. - * - * Return true if the key is found, otherwise return false. On success, the pointer - * to the value is set to value_p. - */ -static inline bool -rt_node_search_leaf(rt_node *node, uint64 key, uint64 *value_p) -{ -#define RT_NODE_LEVEL_LEAF -#include "lib/radixtree_search_impl.h" -#undef RT_NODE_LEVEL_LEAF -} - -/* - * Search for the child pointer corresponding to 'key' in the given node. - * - * Delete the node and return true if the key is found, otherwise return false. - */ -static inline bool -rt_node_delete_inner(rt_node *node, uint64 key) -{ -#define RT_NODE_LEVEL_INNER -#include "lib/radixtree_delete_impl.h" -#undef RT_NODE_LEVEL_INNER -} - -/* - * Search for the value corresponding to 'key' in the given node. - * - * Delete the node and return true if the key is found, otherwise return false. - */ -static inline bool -rt_node_delete_leaf(rt_node *node, uint64 key) -{ -#define RT_NODE_LEVEL_LEAF -#include "lib/radixtree_delete_impl.h" -#undef RT_NODE_LEVEL_LEAF -} - -/* Insert the child to the inner node */ -static bool -rt_node_insert_inner(radix_tree *tree, rt_node *parent, rt_node *node, uint64 key, - rt_node *child) -{ -#define RT_NODE_LEVEL_INNER -#include "lib/radixtree_insert_impl.h" -#undef RT_NODE_LEVEL_INNER -} - -/* Insert the value to the leaf node */ -static bool -rt_node_insert_leaf(radix_tree *tree, rt_node *parent, rt_node *node, - uint64 key, uint64 value) -{ -#define RT_NODE_LEVEL_LEAF -#include "lib/radixtree_insert_impl.h" -#undef RT_NODE_LEVEL_LEAF -} - -/* - * Create the radix tree in the given memory context and return it. - */ -radix_tree * -rt_create(MemoryContext ctx) -{ - radix_tree *tree; - MemoryContext old_ctx; - - old_ctx = MemoryContextSwitchTo(ctx); - - tree = palloc(sizeof(radix_tree)); - tree->context = ctx; - tree->root = NULL; - tree->max_val = 0; - tree->num_keys = 0; - - /* Create the slab allocator for each size class */ - for (int i = 0; i < RT_SIZE_CLASS_COUNT; i++) - { - tree->inner_slabs[i] = SlabContextCreate(ctx, - rt_size_class_info[i].name, - rt_size_class_info[i].inner_blocksize, - rt_size_class_info[i].inner_size); - tree->leaf_slabs[i] = SlabContextCreate(ctx, - rt_size_class_info[i].name, - rt_size_class_info[i].leaf_blocksize, - rt_size_class_info[i].leaf_size); -#ifdef RT_DEBUG - tree->cnt[i] = 0; -#endif - } - - MemoryContextSwitchTo(old_ctx); - - return tree; -} - -/* - * Free the given radix tree. - */ -void -rt_free(radix_tree *tree) -{ - for (int i = 0; i < RT_SIZE_CLASS_COUNT; i++) - { - MemoryContextDelete(tree->inner_slabs[i]); - MemoryContextDelete(tree->leaf_slabs[i]); - } - - pfree(tree); -} - -/* - * Set key to value. If the entry already exists, we update its value to 'value' - * and return true. Returns false if entry doesn't yet exist. - */ -bool -rt_set(radix_tree *tree, uint64 key, uint64 value) -{ - int shift; - bool updated; - rt_node *node; - rt_node *parent; - - /* Empty tree, create the root */ - if (!tree->root) - rt_new_root(tree, key); - - /* Extend the tree if necessary */ - if (key > tree->max_val) - rt_extend(tree, key); - - Assert(tree->root); - - shift = tree->root->shift; - node = parent = tree->root; - - /* Descend the tree until a leaf node */ - while (shift >= 0) - { - rt_node *child; - - if (NODE_IS_LEAF(node)) - break; - - if (!rt_node_search_inner(node, key, &child)) - { - rt_set_extend(tree, key, value, parent, node); - return false; - } - - parent = node; - node = child; - shift -= RT_NODE_SPAN; - } - - updated = rt_node_insert_leaf(tree, parent, node, key, value); - - /* Update the statistics */ - if (!updated) - tree->num_keys++; - - return updated; -} - -/* - * Search the given key in the radix tree. Return true if there is the key, - * otherwise return false. On success, we set the value to *val_p so it must - * not be NULL. - */ -bool -rt_search(radix_tree *tree, uint64 key, uint64 *value_p) -{ - rt_node *node; - int shift; - - Assert(value_p != NULL); - - if (!tree->root || key > tree->max_val) - return false; - - node = tree->root; - shift = tree->root->shift; - - /* Descend the tree until a leaf node */ - while (shift >= 0) - { - rt_node *child; - - if (NODE_IS_LEAF(node)) - break; - - if (!rt_node_search_inner(node, key, &child)) - return false; - - node = child; - shift -= RT_NODE_SPAN; - } - - return rt_node_search_leaf(node, key, value_p); -} - -/* - * Delete the given key from the radix tree. Return true if the key is found (and - * deleted), otherwise do nothing and return false. - */ -bool -rt_delete(radix_tree *tree, uint64 key) -{ - rt_node *node; - rt_node *stack[RT_MAX_LEVEL] = {0}; - int shift; - int level; - bool deleted; - - if (!tree->root || key > tree->max_val) - return false; - - /* - * Descend the tree to search the key while building a stack of nodes we - * visited. - */ - node = tree->root; - shift = tree->root->shift; - level = -1; - while (shift > 0) - { - rt_node *child; - - /* Push the current node to the stack */ - stack[++level] = node; - - if (!rt_node_search_inner(node, key, &child)) - return false; - - node = child; - shift -= RT_NODE_SPAN; - } - - /* Delete the key from the leaf node if exists */ - Assert(NODE_IS_LEAF(node)); - deleted = rt_node_delete_leaf(node, key); - - if (!deleted) - { - /* no key is found in the leaf node */ - return false; - } - - /* Found the key to delete. Update the statistics */ - tree->num_keys--; - - /* - * Return if the leaf node still has keys and we don't need to delete the - * node. - */ - if (!NODE_IS_EMPTY(node)) - return true; - - /* Free the empty leaf node */ - rt_free_node(tree, node); - - /* Delete the key in inner nodes recursively */ - while (level >= 0) - { - node = stack[level--]; - - deleted = rt_node_delete_inner(node, key); - Assert(deleted); - - /* If the node didn't become empty, we stop deleting the key */ - if (!NODE_IS_EMPTY(node)) - break; - - /* The node became empty */ - rt_free_node(tree, node); - } - - return true; -} - -/* Create and return the iterator for the given radix tree */ -rt_iter * -rt_begin_iterate(radix_tree *tree) -{ - MemoryContext old_ctx; - rt_iter *iter; - int top_level; - - old_ctx = MemoryContextSwitchTo(tree->context); - - iter = (rt_iter *) palloc0(sizeof(rt_iter)); - iter->tree = tree; - - /* empty tree */ - if (!iter->tree->root) - return iter; - - top_level = iter->tree->root->shift / RT_NODE_SPAN; - iter->stack_len = top_level; - - /* - * Descend to the left most leaf node from the root. The key is being - * constructed while descending to the leaf. - */ - rt_update_iter_stack(iter, iter->tree->root, top_level); - - MemoryContextSwitchTo(old_ctx); - - return iter; -} - -/* - * Update each node_iter for inner nodes in the iterator node stack. - */ -static void -rt_update_iter_stack(rt_iter *iter, rt_node *from_node, int from) -{ - int level = from; - rt_node *node = from_node; - - for (;;) - { - rt_node_iter *node_iter = &(iter->stack[level--]); - - node_iter->node = node; - node_iter->current_idx = -1; - - /* We don't advance the leaf node iterator here */ - if (NODE_IS_LEAF(node)) - return; - - /* Advance to the next slot in the inner node */ - node = rt_node_inner_iterate_next(iter, node_iter); - - /* We must find the first children in the node */ - Assert(node); - } -} - -/* - * Return true with setting key_p and value_p if there is next key. Otherwise, - * return false. - */ -bool -rt_iterate_next(rt_iter *iter, uint64 *key_p, uint64 *value_p) -{ - /* Empty tree */ - if (!iter->tree->root) - return false; - - for (;;) - { - rt_node *child = NULL; - uint64 value; - int level; - bool found; - - /* Advance the leaf node iterator to get next key-value pair */ - found = rt_node_leaf_iterate_next(iter, &(iter->stack[0]), &value); - - if (found) - { - *key_p = iter->key; - *value_p = value; - return true; - } - - /* - * We've visited all values in the leaf node, so advance inner node - * iterators from the level=1 until we find the next child node. - */ - for (level = 1; level <= iter->stack_len; level++) - { - child = rt_node_inner_iterate_next(iter, &(iter->stack[level])); - - if (child) - break; - } - - /* the iteration finished */ - if (!child) - return false; - - /* - * Set the node to the node iterator and update the iterator stack - * from this node. - */ - rt_update_iter_stack(iter, child, level - 1); - - /* Node iterators are updated, so try again from the leaf */ - } - - return false; -} - -void -rt_end_iterate(rt_iter *iter) -{ - pfree(iter); -} - -static inline void -rt_iter_update_key(rt_iter *iter, uint8 chunk, uint8 shift) -{ - iter->key &= ~(((uint64) RT_CHUNK_MASK) << shift); - iter->key |= (((uint64) chunk) << shift); -} - -/* - * Advance the slot in the inner node. Return the child if exists, otherwise - * null. - */ -static inline rt_node * -rt_node_inner_iterate_next(rt_iter *iter, rt_node_iter *node_iter) -{ -#define RT_NODE_LEVEL_INNER -#include "lib/radixtree_iter_impl.h" -#undef RT_NODE_LEVEL_INNER -} - -/* - * Advance the slot in the leaf node. On success, return true and the value - * is set to value_p, otherwise return false. - */ -static inline bool -rt_node_leaf_iterate_next(rt_iter *iter, rt_node_iter *node_iter, - uint64 *value_p) -{ -#define RT_NODE_LEVEL_LEAF -#include "lib/radixtree_iter_impl.h" -#undef RT_NODE_LEVEL_LEAF -} - -/* - * Return the number of keys in the radix tree. - */ -uint64 -rt_num_entries(radix_tree *tree) -{ - return tree->num_keys; -} - -/* - * Return the statistics of the amount of memory used by the radix tree. - */ -uint64 -rt_memory_usage(radix_tree *tree) -{ - Size total = sizeof(radix_tree); - - for (int i = 0; i < RT_SIZE_CLASS_COUNT; i++) - { - total += MemoryContextMemAllocated(tree->inner_slabs[i], true); - total += MemoryContextMemAllocated(tree->leaf_slabs[i], true); - } - - return total; -} - -/* - * Verify the radix tree node. - */ -static void -rt_verify_node(rt_node *node) -{ -#ifdef USE_ASSERT_CHECKING - Assert(node->count >= 0); - - switch (node->kind) - { - case RT_NODE_KIND_4: - { - rt_node_base_4 *n4 = (rt_node_base_4 *) node; - - for (int i = 1; i < n4->n.count; i++) - Assert(n4->chunks[i - 1] < n4->chunks[i]); - - break; - } - case RT_NODE_KIND_32: - { - rt_node_base_32 *n32 = (rt_node_base_32 *) node; - - for (int i = 1; i < n32->n.count; i++) - Assert(n32->chunks[i - 1] < n32->chunks[i]); - - break; - } - case RT_NODE_KIND_125: - { - rt_node_base_125 *n125 = (rt_node_base_125 *) node; - int cnt = 0; - - for (int i = 0; i < RT_NODE_MAX_SLOTS; i++) - { - uint8 slot = n125->slot_idxs[i]; - int idx = BM_IDX(slot); - int bitnum = BM_BIT(slot); - - if (!node_125_is_chunk_used(n125, i)) - continue; - - /* Check if the corresponding slot is used */ - Assert(slot < node->fanout); - Assert((n125->isset[idx] & ((bitmapword) 1 << bitnum)) != 0); - - cnt++; - } - - Assert(n125->n.count == cnt); - break; - } - case RT_NODE_KIND_256: - { - if (NODE_IS_LEAF(node)) - { - rt_node_leaf_256 *n256 = (rt_node_leaf_256 *) node; - int cnt = 0; - - for (int i = 0; i < BM_IDX(RT_NODE_MAX_SLOTS); i++) - cnt += bmw_popcount(n256->isset[i]); - - /* Check if the number of used chunk matches */ - Assert(n256->base.n.count == cnt); - - break; - } - } - } -#endif -} - -/***************** DEBUG FUNCTIONS *****************/ -#ifdef RT_DEBUG -void -rt_stats(radix_tree *tree) -{ - ereport(NOTICE, (errmsg("num_keys = " UINT64_FORMAT ", height = %d, n4 = %u, n15 = %u, n32 = %u, n125 = %u, n256 = %u", - tree->num_keys, - tree->root->shift / RT_NODE_SPAN, - tree->cnt[RT_CLASS_4_FULL], - tree->cnt[RT_CLASS_32_PARTIAL], - tree->cnt[RT_CLASS_32_FULL], - tree->cnt[RT_CLASS_125_FULL], - tree->cnt[RT_CLASS_256]))); -} - -static void -rt_dump_node(rt_node *node, int level, bool recurse) -{ - char space[125] = {0}; - - fprintf(stderr, "[%s] kind %d, fanout %d, count %u, shift %u, chunk 0x%X:\n", - NODE_IS_LEAF(node) ? "LEAF" : "INNR", - (node->kind == RT_NODE_KIND_4) ? 4 : - (node->kind == RT_NODE_KIND_32) ? 32 : - (node->kind == RT_NODE_KIND_125) ? 125 : 256, - node->fanout == 0 ? 256 : node->fanout, - node->count, node->shift, node->chunk); - - if (level > 0) - sprintf(space, "%*c", level * 4, ' '); - - switch (node->kind) - { - case RT_NODE_KIND_4: - { - for (int i = 0; i < node->count; i++) - { - if (NODE_IS_LEAF(node)) - { - rt_node_leaf_4 *n4 = (rt_node_leaf_4 *) node; - - fprintf(stderr, "%schunk 0x%X value 0x" UINT64_FORMAT_HEX "\n", - space, n4->base.chunks[i], n4->values[i]); - } - else - { - rt_node_inner_4 *n4 = (rt_node_inner_4 *) node; - - fprintf(stderr, "%schunk 0x%X ->", - space, n4->base.chunks[i]); - - if (recurse) - rt_dump_node(n4->children[i], level + 1, recurse); - else - fprintf(stderr, "\n"); - } - } - break; - } - case RT_NODE_KIND_32: - { - for (int i = 0; i < node->count; i++) - { - if (NODE_IS_LEAF(node)) - { - rt_node_leaf_32 *n32 = (rt_node_leaf_32 *) node; - - fprintf(stderr, "%schunk 0x%X value 0x" UINT64_FORMAT_HEX "\n", - space, n32->base.chunks[i], n32->values[i]); - } - else - { - rt_node_inner_32 *n32 = (rt_node_inner_32 *) node; - - fprintf(stderr, "%schunk 0x%X ->", - space, n32->base.chunks[i]); - - if (recurse) - { - rt_dump_node(n32->children[i], level + 1, recurse); - } - else - fprintf(stderr, "\n"); - } - } - break; - } - case RT_NODE_KIND_125: - { - rt_node_base_125 *b125 = (rt_node_base_125 *) node; - - fprintf(stderr, "slot_idxs "); - for (int i = 0; i < RT_NODE_MAX_SLOTS; i++) - { - if (!node_125_is_chunk_used(b125, i)) - continue; - - fprintf(stderr, " [%d]=%d, ", i, b125->slot_idxs[i]); - } - if (NODE_IS_LEAF(node)) - { - rt_node_leaf_125 *n = (rt_node_leaf_125 *) node; - - fprintf(stderr, ", isset-bitmap:"); - for (int i = 0; i < BM_IDX(128); i++) - { - fprintf(stderr, UINT64_FORMAT_HEX " ", (uint64) n->base.isset[i]); - } - fprintf(stderr, "\n"); - } - - for (int i = 0; i < RT_NODE_MAX_SLOTS; i++) - { - if (!node_125_is_chunk_used(b125, i)) - continue; - - if (NODE_IS_LEAF(node)) - { - rt_node_leaf_125 *n125 = (rt_node_leaf_125 *) b125; - - fprintf(stderr, "%schunk 0x%X value 0x" UINT64_FORMAT_HEX "\n", - space, i, node_leaf_125_get_value(n125, i)); - } - else - { - rt_node_inner_125 *n125 = (rt_node_inner_125 *) b125; - - fprintf(stderr, "%schunk 0x%X ->", - space, i); - - if (recurse) - rt_dump_node(node_inner_125_get_child(n125, i), - level + 1, recurse); - else - fprintf(stderr, "\n"); - } - } - break; - } - case RT_NODE_KIND_256: - { - for (int i = 0; i < RT_NODE_MAX_SLOTS; i++) - { - if (NODE_IS_LEAF(node)) - { - rt_node_leaf_256 *n256 = (rt_node_leaf_256 *) node; - - if (!node_leaf_256_is_chunk_used(n256, i)) - continue; - - fprintf(stderr, "%schunk 0x%X value 0x" UINT64_FORMAT_HEX "\n", - space, i, node_leaf_256_get_value(n256, i)); - } - else - { - rt_node_inner_256 *n256 = (rt_node_inner_256 *) node; - - if (!node_inner_256_is_chunk_used(n256, i)) - continue; - - fprintf(stderr, "%schunk 0x%X ->", - space, i); - - if (recurse) - rt_dump_node(node_inner_256_get_child(n256, i), level + 1, - recurse); - else - fprintf(stderr, "\n"); - } - } - break; - } - } -} - -void -rt_dump_search(radix_tree *tree, uint64 key) -{ - rt_node *node; - int shift; - int level = 0; - - elog(NOTICE, "-----------------------------------------------------------"); - elog(NOTICE, "max_val = " UINT64_FORMAT "(0x" UINT64_FORMAT_HEX ")", - tree->max_val, tree->max_val); - - if (!tree->root) - { - elog(NOTICE, "tree is empty"); - return; - } - - if (key > tree->max_val) - { - elog(NOTICE, "key " UINT64_FORMAT "(0x" UINT64_FORMAT_HEX ") is larger than max val", - key, key); - return; - } - - node = tree->root; - shift = tree->root->shift; - while (shift >= 0) - { - rt_node *child; - - rt_dump_node(node, level, false); - - if (NODE_IS_LEAF(node)) - { - uint64 dummy; - - /* We reached at a leaf node, find the corresponding slot */ - rt_node_search_leaf(node, key, &dummy); - - break; - } - - if (!rt_node_search_inner(node, key, &child)) - break; - - node = child; - shift -= RT_NODE_SPAN; - level++; - } -} - -void -rt_dump(radix_tree *tree) -{ - - for (int i = 0; i < RT_SIZE_CLASS_COUNT; i++) - fprintf(stderr, "%s\tinner_size %zu\tinner_blocksize %zu\tleaf_size %zu\tleaf_blocksize %zu\n", - rt_size_class_info[i].name, - rt_size_class_info[i].inner_size, - rt_size_class_info[i].inner_blocksize, - rt_size_class_info[i].leaf_size, - rt_size_class_info[i].leaf_blocksize); - fprintf(stderr, "max_val = " UINT64_FORMAT "\n", tree->max_val); - - if (!tree->root) - { - fprintf(stderr, "empty tree\n"); - return; - } - - rt_dump_node(tree->root, 0, true); -} -#endif diff --git a/src/include/lib/radixtree.h b/src/include/lib/radixtree.h index d5d7668617..fe517793f4 100644 --- a/src/include/lib/radixtree.h +++ b/src/include/lib/radixtree.h @@ -1,24 +1,412 @@ /*------------------------------------------------------------------------- * - * radixtree.h - * Interface for radix tree. + * radixtree.c + * Implementation for adaptive radix tree. + * + * This module employs the idea from the paper "The Adaptive Radix Tree: ARTful + * Indexing for Main-Memory Databases" by Viktor Leis, Alfons Kemper, and Thomas + * Neumann, 2013. The radix tree uses adaptive node sizes, a small number of node + * types, each with a different numbers of elements. Depending on the number of + * children, the appropriate node type is used. + * + * There are some differences from the proposed implementation. For instance, + * there is not support for path compression and lazy path expansion. The radix + * tree supports fixed length of the key so we don't expect the tree level + * wouldn't be high. + * + * Both the key and the value are 64-bit unsigned integer. The inner nodes and + * the leaf nodes have slightly different structure: for inner tree nodes, + * shift > 0, store the pointer to its child node as the value. The leaf nodes, + * shift == 0, have the 64-bit unsigned integer that is specified by the user as + * the value. The paper refers to this technique as "Multi-value leaves". We + * choose it to avoid an additional pointer traversal. It is the reason this code + * currently does not support variable-length keys. + * + * XXX: Most functions in this file have two variants for inner nodes and leaf + * nodes, therefore there are duplication codes. While this sometimes makes the + * code maintenance tricky, this reduces branch prediction misses when judging + * whether the node is a inner node of a leaf node. + * + * XXX: the radix tree node never be shrunk. + * + * Interface + * --------- + * + * rt_create - Create a new, empty radix tree + * rt_free - Free the radix tree + * rt_search - Search a key-value pair + * rt_set - Set a key-value pair + * rt_delete - Delete a key-value pair + * rt_begin_iterate - Begin iterating through all key-value pairs + * rt_iterate_next - Return next key-value pair, if any + * rt_end_iter - End iteration + * rt_memory_usage - Get the memory usage + * rt_num_entries - Get the number of key-value pairs + * + * rt_create() creates an empty radix tree in the given memory context + * and memory contexts for all kinds of radix tree node under the memory context. + * + * rt_iterate_next() ensures returning key-value pairs in the ascending + * order of the key. * * Copyright (c) 2022, PostgreSQL Global Development Group * * IDENTIFICATION - * src/include/lib/radixtree.h + * src/backend/lib/radixtree.c * *------------------------------------------------------------------------- */ -#ifndef RADIXTREE_H -#define RADIXTREE_H #include "postgres.h" -#define RT_DEBUG 1 +#include "lib/stringinfo.h" +#include "miscadmin.h" +#include "nodes/bitmapset.h" +#include "port/pg_bitutils.h" +#include "port/pg_lfind.h" +#include "utils/memutils.h" + +#ifdef RT_DEBUG +#define UINT64_FORMAT_HEX "%" INT64_MODIFIER "X" +#endif + +/* The number of bits encoded in one tree level */ +#define RT_NODE_SPAN BITS_PER_BYTE + +/* The number of maximum slots in the node */ +#define RT_NODE_MAX_SLOTS (1 << RT_NODE_SPAN) + +/* Mask for extracting a chunk from the key */ +#define RT_CHUNK_MASK ((1 << RT_NODE_SPAN) - 1) + +/* Maximum shift the radix tree uses */ +#define RT_MAX_SHIFT key_get_shift(UINT64_MAX) + +/* Tree level the radix tree uses */ +#define RT_MAX_LEVEL ((sizeof(uint64) * BITS_PER_BYTE) / RT_NODE_SPAN) + +/* Invalid index used in node-125 */ +#define RT_NODE_125_INVALID_IDX 0xFF + +/* Get a chunk from the key */ +#define RT_GET_KEY_CHUNK(key, shift) ((uint8) (((key) >> (shift)) & RT_CHUNK_MASK)) + +/* For accessing bitmaps */ +#define BM_IDX(x) ((x) / BITS_PER_BITMAPWORD) +#define BM_BIT(x) ((x) % BITS_PER_BITMAPWORD) + +/* + * Supported radix tree node kinds and size classes. + * + * There are 4 node kinds and each node kind have one or two size classes, + * partial and full. The size classes in the same node kind have the same + * node structure but have the different number of fanout that is stored + * in 'fanout' of rt_node. For example in size class 15, when a 16th element + * is to be inserted, we allocate a larger area and memcpy the entire old + * node to it. + * + * This technique allows us to limit the node kinds to 4, which limits the + * number of cases in switch statements. It also allows a possible future + * optimization to encode the node kind in a pointer tag. + * + * These size classes have been chose carefully so that it minimizes the + * allocator padding in both the inner and leaf nodes on DSA. + * node + */ +#define RT_NODE_KIND_4 0x00 +#define RT_NODE_KIND_32 0x01 +#define RT_NODE_KIND_125 0x02 +#define RT_NODE_KIND_256 0x03 +#define RT_NODE_KIND_COUNT 4 + +typedef enum rt_size_class +{ + RT_CLASS_4_FULL = 0, + RT_CLASS_32_PARTIAL, + RT_CLASS_32_FULL, + RT_CLASS_125_FULL, + RT_CLASS_256 + +#define RT_SIZE_CLASS_COUNT (RT_CLASS_256 + 1) +} rt_size_class; + +/* Common type for all nodes types */ +typedef struct rt_node +{ + /* + * Number of children. We use uint16 to be able to indicate 256 children + * at the fanout of 8. + */ + uint16 count; + + /* Max number of children. We can use uint8 because we never need to store 256 */ + /* WIP: if we don't have a variable sized node4, this should instead be in the base + types as needed, since saving every byte is crucial for the smallest node kind */ + uint8 fanout; + + /* + * Shift indicates which part of the key space is represented by this + * node. That is, the key is shifted by 'shift' and the lowest + * RT_NODE_SPAN bits are then represented in chunk. + */ + uint8 shift; + uint8 chunk; + + /* Node kind, one per search/set algorithm */ + uint8 kind; +} rt_node; +#define NODE_IS_LEAF(n) (((rt_node *) (n))->shift == 0) +#define NODE_IS_EMPTY(n) (((rt_node *) (n))->count == 0) +#define VAR_NODE_HAS_FREE_SLOT(node) \ + ((node)->base.n.count < (node)->base.n.fanout) +#define FIXED_NODE_HAS_FREE_SLOT(node, class) \ + ((node)->base.n.count < rt_size_class_info[class].fanout) + +/* Base type of each node kinds for leaf and inner nodes */ +/* The base types must be a be able to accommodate the largest size +class for variable-sized node kinds*/ +typedef struct rt_node_base_4 +{ + rt_node n; + + /* 4 children, for key chunks */ + uint8 chunks[4]; +} rt_node_base_4; + +typedef struct rt_node_base32 +{ + rt_node n; + + /* 32 children, for key chunks */ + uint8 chunks[32]; +} rt_node_base_32; + +/* + * node-125 uses slot_idx array, an array of RT_NODE_MAX_SLOTS length, typically + * 256, to store indexes into a second array that contains up to 125 values (or + * child pointers in inner nodes). + */ +typedef struct rt_node_base125 +{ + rt_node n; + + /* The index of slots for each fanout */ + uint8 slot_idxs[RT_NODE_MAX_SLOTS]; + + /* isset is a bitmap to track which slot is in use */ + bitmapword isset[BM_IDX(128)]; +} rt_node_base_125; + +typedef struct rt_node_base256 +{ + rt_node n; +} rt_node_base_256; + +/* + * Inner and leaf nodes. + * + * Theres are separate for two main reasons: + * + * 1) the value type might be different than something fitting into a pointer + * width type + * 2) Need to represent non-existing values in a key-type independent way. + * + * 1) is clearly worth being concerned about, but it's not clear 2) is as + * good. It might be better to just indicate non-existing entries the same way + * in inner nodes. + */ +typedef struct rt_node_inner_4 +{ + rt_node_base_4 base; + + /* number of children depends on size class */ + rt_node *children[FLEXIBLE_ARRAY_MEMBER]; +} rt_node_inner_4; + +typedef struct rt_node_leaf_4 +{ + rt_node_base_4 base; + + /* number of values depends on size class */ + uint64 values[FLEXIBLE_ARRAY_MEMBER]; +} rt_node_leaf_4; + +typedef struct rt_node_inner_32 +{ + rt_node_base_32 base; + + /* number of children depends on size class */ + rt_node *children[FLEXIBLE_ARRAY_MEMBER]; +} rt_node_inner_32; + +typedef struct rt_node_leaf_32 +{ + rt_node_base_32 base; + + /* number of values depends on size class */ + uint64 values[FLEXIBLE_ARRAY_MEMBER]; +} rt_node_leaf_32; + +typedef struct rt_node_inner_125 +{ + rt_node_base_125 base; + + /* number of children depends on size class */ + rt_node *children[FLEXIBLE_ARRAY_MEMBER]; +} rt_node_inner_125; + +typedef struct rt_node_leaf_125 +{ + rt_node_base_125 base; + + /* number of values depends on size class */ + uint64 values[FLEXIBLE_ARRAY_MEMBER]; +} rt_node_leaf_125; + +/* + * node-256 is the largest node type. This node has RT_NODE_MAX_SLOTS length array + * for directly storing values (or child pointers in inner nodes). + */ +typedef struct rt_node_inner_256 +{ + rt_node_base_256 base; + + /* Slots for 256 children */ + rt_node *children[RT_NODE_MAX_SLOTS]; +} rt_node_inner_256; + +typedef struct rt_node_leaf_256 +{ + rt_node_base_256 base; + + /* isset is a bitmap to track which slot is in use */ + bitmapword isset[BM_IDX(RT_NODE_MAX_SLOTS)]; + + /* Slots for 256 values */ + uint64 values[RT_NODE_MAX_SLOTS]; +} rt_node_leaf_256; + +/* Information for each size class */ +typedef struct rt_size_class_elem +{ + const char *name; + int fanout; + + /* slab chunk size */ + Size inner_size; + Size leaf_size; + + /* slab block size */ + Size inner_blocksize; + Size leaf_blocksize; +} rt_size_class_elem; + +/* + * Calculate the slab blocksize so that we can allocate at least 32 chunks + * from the block. + */ +#define NODE_SLAB_BLOCK_SIZE(size) \ + Max((SLAB_DEFAULT_BLOCK_SIZE / (size)) * (size), (size) * 32) +static rt_size_class_elem rt_size_class_info[RT_SIZE_CLASS_COUNT] = { + [RT_CLASS_4_FULL] = { + .name = "radix tree node 4", + .fanout = 4, + .inner_size = sizeof(rt_node_inner_4) + 4 * sizeof(rt_node *), + .leaf_size = sizeof(rt_node_leaf_4) + 4 * sizeof(uint64), + .inner_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(rt_node_inner_4) + 4 * sizeof(rt_node *)), + .leaf_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(rt_node_leaf_4) + 4 * sizeof(uint64)), + }, + [RT_CLASS_32_PARTIAL] = { + .name = "radix tree node 15", + .fanout = 15, + .inner_size = sizeof(rt_node_inner_32) + 15 * sizeof(rt_node *), + .leaf_size = sizeof(rt_node_leaf_32) + 15 * sizeof(uint64), + .inner_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(rt_node_inner_32) + 15 * sizeof(rt_node *)), + .leaf_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(rt_node_leaf_32) + 15 * sizeof(uint64)), + }, + [RT_CLASS_32_FULL] = { + .name = "radix tree node 32", + .fanout = 32, + .inner_size = sizeof(rt_node_inner_32) + 32 * sizeof(rt_node *), + .leaf_size = sizeof(rt_node_leaf_32) + 32 * sizeof(uint64), + .inner_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(rt_node_inner_32) + 32 * sizeof(rt_node *)), + .leaf_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(rt_node_leaf_32) + 32 * sizeof(uint64)), + }, + [RT_CLASS_125_FULL] = { + .name = "radix tree node 125", + .fanout = 125, + .inner_size = sizeof(rt_node_inner_125) + 125 * sizeof(rt_node *), + .leaf_size = sizeof(rt_node_leaf_125) + 125 * sizeof(uint64), + .inner_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(rt_node_inner_125) + 125 * sizeof(rt_node *)), + .leaf_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(rt_node_leaf_125) + 125 * sizeof(uint64)), + }, + [RT_CLASS_256] = { + .name = "radix tree node 256", + .fanout = 256, + .inner_size = sizeof(rt_node_inner_256), + .leaf_size = sizeof(rt_node_leaf_256), + .inner_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(rt_node_inner_256)), + .leaf_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(rt_node_leaf_256)), + }, +}; + +/* Map from the node kind to its minimum size class */ +static rt_size_class kind_min_size_class[RT_NODE_KIND_COUNT] = { + [RT_NODE_KIND_4] = RT_CLASS_4_FULL, + [RT_NODE_KIND_32] = RT_CLASS_32_PARTIAL, + [RT_NODE_KIND_125] = RT_CLASS_125_FULL, + [RT_NODE_KIND_256] = RT_CLASS_256, +}; + +/* A radix tree with nodes */ +typedef struct radix_tree +{ + MemoryContext context; + + rt_node *root; + uint64 max_val; + uint64 num_keys; + + MemoryContextData *inner_slabs[RT_SIZE_CLASS_COUNT]; + MemoryContextData *leaf_slabs[RT_SIZE_CLASS_COUNT]; + + /* statistics */ +#ifdef RT_DEBUG + int32 cnt[RT_SIZE_CLASS_COUNT]; +#endif +} radix_tree; + +/* + * Iteration support. + * + * Iterating the radix tree returns each pair of key and value in the ascending + * order of the key. To support this, the we iterate nodes of each level. + * + * rt_node_iter struct is used to track the iteration within a node. + * + * rt_iter is the struct for iteration of the radix tree, and uses rt_node_iter + * in order to track the iteration of each level. During the iteration, we also + * construct the key whenever updating the node iteration information, e.g., when + * advancing the current index within the node or when moving to the next node + * at the same level. + */ +typedef struct rt_node_iter +{ + rt_node *node; /* current node being iterated */ + int current_idx; /* current position. -1 for initial value */ +} rt_node_iter; + +typedef struct rt_iter +{ + radix_tree *tree; -typedef struct radix_tree radix_tree; -typedef struct rt_iter rt_iter; + /* Track the iteration on nodes of each level */ + rt_node_iter stack[RT_MAX_LEVEL]; + int stack_len; + + /* The key is being constructed during the iteration */ + uint64 key; +} rt_iter; extern radix_tree *rt_create(MemoryContext ctx); extern void rt_free(radix_tree *tree); @@ -39,4 +427,1360 @@ extern void rt_dump_search(radix_tree *tree, uint64 key); extern void rt_stats(radix_tree *tree); #endif -#endif /* RADIXTREE_H */ + +static void rt_new_root(radix_tree *tree, uint64 key); +static rt_node *rt_alloc_node(radix_tree *tree, rt_size_class size_class, bool inner); +static inline void rt_init_node(rt_node *node, uint8 kind, rt_size_class size_class, + bool inner); +static void rt_free_node(radix_tree *tree, rt_node *node); +static void rt_extend(radix_tree *tree, uint64 key); +static inline bool rt_node_search_inner(rt_node *node, uint64 key, rt_node **child_p); +static inline bool rt_node_search_leaf(rt_node *node, uint64 key, uint64 *value_p); +static bool rt_node_insert_inner(radix_tree *tree, rt_node *parent, rt_node *node, + uint64 key, rt_node *child); +static bool rt_node_insert_leaf(radix_tree *tree, rt_node *parent, rt_node *node, + uint64 key, uint64 value); +static inline rt_node *rt_node_inner_iterate_next(rt_iter *iter, rt_node_iter *node_iter); +static inline bool rt_node_leaf_iterate_next(rt_iter *iter, rt_node_iter *node_iter, + uint64 *value_p); +static void rt_update_iter_stack(rt_iter *iter, rt_node *from_node, int from); +static inline void rt_iter_update_key(rt_iter *iter, uint8 chunk, uint8 shift); + +/* verification (available only with assertion) */ +static void rt_verify_node(rt_node *node); + +/* + * Return index of the first element in 'base' that equals 'key'. Return -1 + * if there is no such element. + */ +static inline int +node_4_search_eq(rt_node_base_4 *node, uint8 chunk) +{ + int idx = -1; + + for (int i = 0; i < node->n.count; i++) + { + if (node->chunks[i] == chunk) + { + idx = i; + break; + } + } + + return idx; +} + +/* + * Return index of the chunk to insert into chunks in the given node. + */ +static inline int +node_4_get_insertpos(rt_node_base_4 *node, uint8 chunk) +{ + int idx; + + for (idx = 0; idx < node->n.count; idx++) + { + if (node->chunks[idx] >= chunk) + break; + } + + return idx; +} + +/* + * Return index of the first element in 'base' that equals 'key'. Return -1 + * if there is no such element. + */ +static inline int +node_32_search_eq(rt_node_base_32 *node, uint8 chunk) +{ + int count = node->n.count; +#ifndef USE_NO_SIMD + Vector8 spread_chunk; + Vector8 haystack1; + Vector8 haystack2; + Vector8 cmp1; + Vector8 cmp2; + uint32 bitfield; + int index_simd = -1; +#endif + +#if defined(USE_NO_SIMD) || defined(USE_ASSERT_CHECKING) + int index = -1; + + for (int i = 0; i < count; i++) + { + if (node->chunks[i] == chunk) + { + index = i; + break; + } + } +#endif + +#ifndef USE_NO_SIMD + spread_chunk = vector8_broadcast(chunk); + vector8_load(&haystack1, &node->chunks[0]); + vector8_load(&haystack2, &node->chunks[sizeof(Vector8)]); + cmp1 = vector8_eq(spread_chunk, haystack1); + cmp2 = vector8_eq(spread_chunk, haystack2); + bitfield = vector8_highbit_mask(cmp1) | (vector8_highbit_mask(cmp2) << sizeof(Vector8)); + bitfield &= ((UINT64CONST(1) << count) - 1); + + if (bitfield) + index_simd = pg_rightmost_one_pos32(bitfield); + + Assert(index_simd == index); + return index_simd; +#else + return index; +#endif +} + +/* + * Return index of the chunk to insert into chunks in the given node. + */ +static inline int +node_32_get_insertpos(rt_node_base_32 *node, uint8 chunk) +{ + int count = node->n.count; +#ifndef USE_NO_SIMD + Vector8 spread_chunk; + Vector8 haystack1; + Vector8 haystack2; + Vector8 cmp1; + Vector8 cmp2; + Vector8 min1; + Vector8 min2; + uint32 bitfield; + int index_simd; +#endif + +#if defined(USE_NO_SIMD) || defined(USE_ASSERT_CHECKING) + int index; + + for (index = 0; index < count; index++) + { + if (node->chunks[index] >= chunk) + break; + } +#endif + +#ifndef USE_NO_SIMD + spread_chunk = vector8_broadcast(chunk); + vector8_load(&haystack1, &node->chunks[0]); + vector8_load(&haystack2, &node->chunks[sizeof(Vector8)]); + min1 = vector8_min(spread_chunk, haystack1); + min2 = vector8_min(spread_chunk, haystack2); + cmp1 = vector8_eq(spread_chunk, min1); + cmp2 = vector8_eq(spread_chunk, min2); + bitfield = vector8_highbit_mask(cmp1) | (vector8_highbit_mask(cmp2) << sizeof(Vector8)); + bitfield &= ((UINT64CONST(1) << count) - 1); + + if (bitfield) + index_simd = pg_rightmost_one_pos32(bitfield); + else + index_simd = count; + + Assert(index_simd == index); + return index_simd; +#else + return index; +#endif +} + +/* + * Functions to manipulate both chunks array and children/values array. + * These are used for node-4 and node-32. + */ + +/* Shift the elements right at 'idx' by one */ +static inline void +chunk_children_array_shift(uint8 *chunks, rt_node **children, int count, int idx) +{ + memmove(&(chunks[idx + 1]), &(chunks[idx]), sizeof(uint8) * (count - idx)); + memmove(&(children[idx + 1]), &(children[idx]), sizeof(rt_node *) * (count - idx)); +} + +static inline void +chunk_values_array_shift(uint8 *chunks, uint64 *values, int count, int idx) +{ + memmove(&(chunks[idx + 1]), &(chunks[idx]), sizeof(uint8) * (count - idx)); + memmove(&(values[idx + 1]), &(values[idx]), sizeof(uint64 *) * (count - idx)); +} + +/* Delete the element at 'idx' */ +static inline void +chunk_children_array_delete(uint8 *chunks, rt_node **children, int count, int idx) +{ + memmove(&(chunks[idx]), &(chunks[idx + 1]), sizeof(uint8) * (count - idx - 1)); + memmove(&(children[idx]), &(children[idx + 1]), sizeof(rt_node *) * (count - idx - 1)); +} + +static inline void +chunk_values_array_delete(uint8 *chunks, uint64 *values, int count, int idx) +{ + memmove(&(chunks[idx]), &(chunks[idx + 1]), sizeof(uint8) * (count - idx - 1)); + memmove(&(values[idx]), &(values[idx + 1]), sizeof(uint64) * (count - idx - 1)); +} + +/* Copy both chunks and children/values arrays */ +static inline void +chunk_children_array_copy(uint8 *src_chunks, rt_node **src_children, + uint8 *dst_chunks, rt_node **dst_children) +{ + const int fanout = rt_size_class_info[RT_CLASS_4_FULL].fanout; + const Size chunk_size = sizeof(uint8) * fanout; + const Size children_size = sizeof(rt_node *) * fanout; + + memcpy(dst_chunks, src_chunks, chunk_size); + memcpy(dst_children, src_children, children_size); +} + +static inline void +chunk_values_array_copy(uint8 *src_chunks, uint64 *src_values, + uint8 *dst_chunks, uint64 *dst_values) +{ + const int fanout = rt_size_class_info[RT_CLASS_4_FULL].fanout; + const Size chunk_size = sizeof(uint8) * fanout; + const Size values_size = sizeof(uint64) * fanout; + + memcpy(dst_chunks, src_chunks, chunk_size); + memcpy(dst_values, src_values, values_size); +} + +/* Functions to manipulate inner and leaf node-125 */ + +/* Does the given chunk in the node has the value? */ +static inline bool +node_125_is_chunk_used(rt_node_base_125 *node, uint8 chunk) +{ + return node->slot_idxs[chunk] != RT_NODE_125_INVALID_IDX; +} + +static inline rt_node * +node_inner_125_get_child(rt_node_inner_125 *node, uint8 chunk) +{ + Assert(!NODE_IS_LEAF(node)); + return node->children[node->base.slot_idxs[chunk]]; +} + +static inline uint64 +node_leaf_125_get_value(rt_node_leaf_125 *node, uint8 chunk) +{ + Assert(NODE_IS_LEAF(node)); + Assert(((rt_node_base_125 *) node)->slot_idxs[chunk] != RT_NODE_125_INVALID_IDX); + return node->values[node->base.slot_idxs[chunk]]; +} + +/* Functions to manipulate inner and leaf node-256 */ + +/* Return true if the slot corresponding to the given chunk is in use */ +static inline bool +node_inner_256_is_chunk_used(rt_node_inner_256 *node, uint8 chunk) +{ + Assert(!NODE_IS_LEAF(node)); + return (node->children[chunk] != NULL); +} + +static inline bool +node_leaf_256_is_chunk_used(rt_node_leaf_256 *node, uint8 chunk) +{ + int idx = BM_IDX(chunk); + int bitnum = BM_BIT(chunk); + + Assert(NODE_IS_LEAF(node)); + return (node->isset[idx] & ((bitmapword) 1 << bitnum)) != 0; +} + +static inline rt_node * +node_inner_256_get_child(rt_node_inner_256 *node, uint8 chunk) +{ + Assert(!NODE_IS_LEAF(node)); + Assert(node_inner_256_is_chunk_used(node, chunk)); + return node->children[chunk]; +} + +static inline uint64 +node_leaf_256_get_value(rt_node_leaf_256 *node, uint8 chunk) +{ + Assert(NODE_IS_LEAF(node)); + Assert(node_leaf_256_is_chunk_used(node, chunk)); + return node->values[chunk]; +} + +/* Set the child in the node-256 */ +static inline void +node_inner_256_set(rt_node_inner_256 *node, uint8 chunk, rt_node *child) +{ + Assert(!NODE_IS_LEAF(node)); + node->children[chunk] = child; +} + +/* Set the value in the node-256 */ +static inline void +node_leaf_256_set(rt_node_leaf_256 *node, uint8 chunk, uint64 value) +{ + int idx = BM_IDX(chunk); + int bitnum = BM_BIT(chunk); + + Assert(NODE_IS_LEAF(node)); + node->isset[idx] |= ((bitmapword) 1 << bitnum); + node->values[chunk] = value; +} + +/* Set the slot at the given chunk position */ +static inline void +node_inner_256_delete(rt_node_inner_256 *node, uint8 chunk) +{ + Assert(!NODE_IS_LEAF(node)); + node->children[chunk] = NULL; +} + +static inline void +node_leaf_256_delete(rt_node_leaf_256 *node, uint8 chunk) +{ + int idx = BM_IDX(chunk); + int bitnum = BM_BIT(chunk); + + Assert(NODE_IS_LEAF(node)); + node->isset[idx] &= ~((bitmapword) 1 << bitnum); +} + +/* + * Return the shift that is satisfied to store the given key. + */ +static inline int +key_get_shift(uint64 key) +{ + return (key == 0) + ? 0 + : (pg_leftmost_one_pos64(key) / RT_NODE_SPAN) * RT_NODE_SPAN; +} + +/* + * Return the max value stored in a node with the given shift. + */ +static uint64 +shift_get_max_val(int shift) +{ + if (shift == RT_MAX_SHIFT) + return UINT64_MAX; + + return (UINT64CONST(1) << (shift + RT_NODE_SPAN)) - 1; +} + +/* + * Create a new node as the root. Subordinate nodes will be created during + * the insertion. + */ +static void +rt_new_root(radix_tree *tree, uint64 key) +{ + int shift = key_get_shift(key); + bool inner = shift > 0; + rt_node *newnode; + + newnode = rt_alloc_node(tree, RT_CLASS_4_FULL, inner); + rt_init_node(newnode, RT_NODE_KIND_4, RT_CLASS_4_FULL, inner); + newnode->shift = shift; + tree->max_val = shift_get_max_val(shift); + tree->root = newnode; +} + +/* + * Allocate a new node with the given node kind. + */ +static rt_node * +rt_alloc_node(radix_tree *tree, rt_size_class size_class, bool inner) +{ + rt_node *newnode; + + if (inner) + newnode = (rt_node *) MemoryContextAlloc(tree->inner_slabs[size_class], + rt_size_class_info[size_class].inner_size); + else + newnode = (rt_node *) MemoryContextAlloc(tree->leaf_slabs[size_class], + rt_size_class_info[size_class].leaf_size); + +#ifdef RT_DEBUG + /* update the statistics */ + tree->cnt[size_class]++; +#endif + + return newnode; +} + +/* Initialize the node contents */ +static inline void +rt_init_node(rt_node *node, uint8 kind, rt_size_class size_class, bool inner) +{ + if (inner) + MemSet(node, 0, rt_size_class_info[size_class].inner_size); + else + MemSet(node, 0, rt_size_class_info[size_class].leaf_size); + + node->kind = kind; + node->fanout = rt_size_class_info[size_class].fanout; + + /* Initialize slot_idxs to invalid values */ + if (kind == RT_NODE_KIND_125) + { + rt_node_base_125 *n125 = (rt_node_base_125 *) node; + + memset(n125->slot_idxs, RT_NODE_125_INVALID_IDX, sizeof(n125->slot_idxs)); + } + + /* + * Technically it's 256, but we cannot store that in a uint8, + * and this is the max size class to it will never grow. + */ + if (kind == RT_NODE_KIND_256) + node->fanout = 0; +} + +static inline void +rt_copy_node(rt_node *newnode, rt_node *oldnode) +{ + newnode->shift = oldnode->shift; + newnode->chunk = oldnode->chunk; + newnode->count = oldnode->count; +} + +/* + * Create a new node with 'new_kind' and the same shift, chunk, and + * count of 'node'. + */ +static rt_node* +rt_grow_node_kind(radix_tree *tree, rt_node *node, uint8 new_kind) +{ + rt_node *newnode; + bool inner = !NODE_IS_LEAF(node); + + newnode = rt_alloc_node(tree, kind_min_size_class[new_kind], inner); + rt_init_node(newnode, new_kind, kind_min_size_class[new_kind], inner); + rt_copy_node(newnode, node); + + return newnode; +} + +/* Free the given node */ +static void +rt_free_node(radix_tree *tree, rt_node *node) +{ + /* If we're deleting the root node, make the tree empty */ + if (tree->root == node) + { + tree->root = NULL; + tree->max_val = 0; + } + +#ifdef RT_DEBUG + { + int i; + + /* update the statistics */ + for (i = 0; i < RT_SIZE_CLASS_COUNT; i++) + { + if (node->fanout == rt_size_class_info[i].fanout) + break; + } + + /* fanout of node256 is intentionally 0 */ + if (i == RT_SIZE_CLASS_COUNT) + i = RT_CLASS_256; + + tree->cnt[i]--; + Assert(tree->cnt[i] >= 0); + } +#endif + + pfree(node); +} + +/* + * Replace old_child with new_child, and free the old one. + */ +static void +rt_replace_node(radix_tree *tree, rt_node *parent, rt_node *old_child, + rt_node *new_child, uint64 key) +{ + Assert(old_child->chunk == new_child->chunk); + Assert(old_child->shift == new_child->shift); + + if (parent == old_child) + { + /* Replace the root node with the new large node */ + tree->root = new_child; + } + else + { + bool replaced PG_USED_FOR_ASSERTS_ONLY; + + replaced = rt_node_insert_inner(tree, NULL, parent, key, new_child); + Assert(replaced); + } + + rt_free_node(tree, old_child); +} + +/* + * The radix tree doesn't sufficient height. Extend the radix tree so it can + * store the key. + */ +static void +rt_extend(radix_tree *tree, uint64 key) +{ + int target_shift; + int shift = tree->root->shift + RT_NODE_SPAN; + + target_shift = key_get_shift(key); + + /* Grow tree from 'shift' to 'target_shift' */ + while (shift <= target_shift) + { + rt_node_inner_4 *node; + + node = (rt_node_inner_4 *) rt_alloc_node(tree, RT_CLASS_4_FULL, true); + rt_init_node((rt_node *) node, RT_NODE_KIND_4, RT_CLASS_4_FULL, true); + node->base.n.shift = shift; + node->base.n.count = 1; + node->base.chunks[0] = 0; + node->children[0] = tree->root; + + tree->root->chunk = 0; + tree->root = (rt_node *) node; + + shift += RT_NODE_SPAN; + } + + tree->max_val = shift_get_max_val(target_shift); +} + +/* + * The radix tree doesn't have inner and leaf nodes for given key-value pair. + * Insert inner and leaf nodes from 'node' to bottom. + */ +static inline void +rt_set_extend(radix_tree *tree, uint64 key, uint64 value, rt_node *parent, + rt_node *node) +{ + int shift = node->shift; + + while (shift >= RT_NODE_SPAN) + { + rt_node *newchild; + int newshift = shift - RT_NODE_SPAN; + bool inner = newshift > 0; + + newchild = rt_alloc_node(tree, RT_CLASS_4_FULL, inner); + rt_init_node(newchild, RT_NODE_KIND_4, RT_CLASS_4_FULL, inner); + newchild->shift = newshift; + newchild->chunk = RT_GET_KEY_CHUNK(key, node->shift); + rt_node_insert_inner(tree, parent, node, key, newchild); + + parent = node; + node = newchild; + shift -= RT_NODE_SPAN; + } + + rt_node_insert_leaf(tree, parent, node, key, value); + tree->num_keys++; +} + +/* + * Search for the child pointer corresponding to 'key' in the given node. + * + * Return true if the key is found, otherwise return false. On success, the child + * pointer is set to child_p. + */ +static inline bool +rt_node_search_inner(rt_node *node, uint64 key, rt_node **child_p) +{ +#define RT_NODE_LEVEL_INNER +#include "lib/radixtree_search_impl.h" +#undef RT_NODE_LEVEL_INNER +} + +/* + * Search for the value corresponding to 'key' in the given node. + * + * Return true if the key is found, otherwise return false. On success, the pointer + * to the value is set to value_p. + */ +static inline bool +rt_node_search_leaf(rt_node *node, uint64 key, uint64 *value_p) +{ +#define RT_NODE_LEVEL_LEAF +#include "lib/radixtree_search_impl.h" +#undef RT_NODE_LEVEL_LEAF +} + +/* + * Search for the child pointer corresponding to 'key' in the given node. + * + * Delete the node and return true if the key is found, otherwise return false. + */ +static inline bool +rt_node_delete_inner(rt_node *node, uint64 key) +{ +#define RT_NODE_LEVEL_INNER +#include "lib/radixtree_delete_impl.h" +#undef RT_NODE_LEVEL_INNER +} + +/* + * Search for the value corresponding to 'key' in the given node. + * + * Delete the node and return true if the key is found, otherwise return false. + */ +static inline bool +rt_node_delete_leaf(rt_node *node, uint64 key) +{ +#define RT_NODE_LEVEL_LEAF +#include "lib/radixtree_delete_impl.h" +#undef RT_NODE_LEVEL_LEAF +} + +/* Insert the child to the inner node */ +static bool +rt_node_insert_inner(radix_tree *tree, rt_node *parent, rt_node *node, uint64 key, + rt_node *child) +{ +#define RT_NODE_LEVEL_INNER +#include "lib/radixtree_insert_impl.h" +#undef RT_NODE_LEVEL_INNER +} + +/* Insert the value to the leaf node */ +static bool +rt_node_insert_leaf(radix_tree *tree, rt_node *parent, rt_node *node, + uint64 key, uint64 value) +{ +#define RT_NODE_LEVEL_LEAF +#include "lib/radixtree_insert_impl.h" +#undef RT_NODE_LEVEL_LEAF +} + +/* + * Create the radix tree in the given memory context and return it. + */ +radix_tree * +rt_create(MemoryContext ctx) +{ + radix_tree *tree; + MemoryContext old_ctx; + + old_ctx = MemoryContextSwitchTo(ctx); + + tree = palloc(sizeof(radix_tree)); + tree->context = ctx; + tree->root = NULL; + tree->max_val = 0; + tree->num_keys = 0; + + /* Create the slab allocator for each size class */ + for (int i = 0; i < RT_SIZE_CLASS_COUNT; i++) + { + tree->inner_slabs[i] = SlabContextCreate(ctx, + rt_size_class_info[i].name, + rt_size_class_info[i].inner_blocksize, + rt_size_class_info[i].inner_size); + tree->leaf_slabs[i] = SlabContextCreate(ctx, + rt_size_class_info[i].name, + rt_size_class_info[i].leaf_blocksize, + rt_size_class_info[i].leaf_size); +#ifdef RT_DEBUG + tree->cnt[i] = 0; +#endif + } + + MemoryContextSwitchTo(old_ctx); + + return tree; +} + +/* + * Free the given radix tree. + */ +void +rt_free(radix_tree *tree) +{ + for (int i = 0; i < RT_SIZE_CLASS_COUNT; i++) + { + MemoryContextDelete(tree->inner_slabs[i]); + MemoryContextDelete(tree->leaf_slabs[i]); + } + + pfree(tree); +} + +/* + * Set key to value. If the entry already exists, we update its value to 'value' + * and return true. Returns false if entry doesn't yet exist. + */ +bool +rt_set(radix_tree *tree, uint64 key, uint64 value) +{ + int shift; + bool updated; + rt_node *node; + rt_node *parent; + + /* Empty tree, create the root */ + if (!tree->root) + rt_new_root(tree, key); + + /* Extend the tree if necessary */ + if (key > tree->max_val) + rt_extend(tree, key); + + Assert(tree->root); + + shift = tree->root->shift; + node = parent = tree->root; + + /* Descend the tree until a leaf node */ + while (shift >= 0) + { + rt_node *child; + + if (NODE_IS_LEAF(node)) + break; + + if (!rt_node_search_inner(node, key, &child)) + { + rt_set_extend(tree, key, value, parent, node); + return false; + } + + parent = node; + node = child; + shift -= RT_NODE_SPAN; + } + + updated = rt_node_insert_leaf(tree, parent, node, key, value); + + /* Update the statistics */ + if (!updated) + tree->num_keys++; + + return updated; +} + +/* + * Search the given key in the radix tree. Return true if there is the key, + * otherwise return false. On success, we set the value to *val_p so it must + * not be NULL. + */ +bool +rt_search(radix_tree *tree, uint64 key, uint64 *value_p) +{ + rt_node *node; + int shift; + + Assert(value_p != NULL); + + if (!tree->root || key > tree->max_val) + return false; + + node = tree->root; + shift = tree->root->shift; + + /* Descend the tree until a leaf node */ + while (shift >= 0) + { + rt_node *child; + + if (NODE_IS_LEAF(node)) + break; + + if (!rt_node_search_inner(node, key, &child)) + return false; + + node = child; + shift -= RT_NODE_SPAN; + } + + return rt_node_search_leaf(node, key, value_p); +} + +/* + * Delete the given key from the radix tree. Return true if the key is found (and + * deleted), otherwise do nothing and return false. + */ +bool +rt_delete(radix_tree *tree, uint64 key) +{ + rt_node *node; + rt_node *stack[RT_MAX_LEVEL] = {0}; + int shift; + int level; + bool deleted; + + if (!tree->root || key > tree->max_val) + return false; + + /* + * Descend the tree to search the key while building a stack of nodes we + * visited. + */ + node = tree->root; + shift = tree->root->shift; + level = -1; + while (shift > 0) + { + rt_node *child; + + /* Push the current node to the stack */ + stack[++level] = node; + + if (!rt_node_search_inner(node, key, &child)) + return false; + + node = child; + shift -= RT_NODE_SPAN; + } + + /* Delete the key from the leaf node if exists */ + Assert(NODE_IS_LEAF(node)); + deleted = rt_node_delete_leaf(node, key); + + if (!deleted) + { + /* no key is found in the leaf node */ + return false; + } + + /* Found the key to delete. Update the statistics */ + tree->num_keys--; + + /* + * Return if the leaf node still has keys and we don't need to delete the + * node. + */ + if (!NODE_IS_EMPTY(node)) + return true; + + /* Free the empty leaf node */ + rt_free_node(tree, node); + + /* Delete the key in inner nodes recursively */ + while (level >= 0) + { + node = stack[level--]; + + deleted = rt_node_delete_inner(node, key); + Assert(deleted); + + /* If the node didn't become empty, we stop deleting the key */ + if (!NODE_IS_EMPTY(node)) + break; + + /* The node became empty */ + rt_free_node(tree, node); + } + + return true; +} + +/* Create and return the iterator for the given radix tree */ +rt_iter * +rt_begin_iterate(radix_tree *tree) +{ + MemoryContext old_ctx; + rt_iter *iter; + int top_level; + + old_ctx = MemoryContextSwitchTo(tree->context); + + iter = (rt_iter *) palloc0(sizeof(rt_iter)); + iter->tree = tree; + + /* empty tree */ + if (!iter->tree->root) + return iter; + + top_level = iter->tree->root->shift / RT_NODE_SPAN; + iter->stack_len = top_level; + + /* + * Descend to the left most leaf node from the root. The key is being + * constructed while descending to the leaf. + */ + rt_update_iter_stack(iter, iter->tree->root, top_level); + + MemoryContextSwitchTo(old_ctx); + + return iter; +} + +/* + * Update each node_iter for inner nodes in the iterator node stack. + */ +static void +rt_update_iter_stack(rt_iter *iter, rt_node *from_node, int from) +{ + int level = from; + rt_node *node = from_node; + + for (;;) + { + rt_node_iter *node_iter = &(iter->stack[level--]); + + node_iter->node = node; + node_iter->current_idx = -1; + + /* We don't advance the leaf node iterator here */ + if (NODE_IS_LEAF(node)) + return; + + /* Advance to the next slot in the inner node */ + node = rt_node_inner_iterate_next(iter, node_iter); + + /* We must find the first children in the node */ + Assert(node); + } +} + +/* + * Return true with setting key_p and value_p if there is next key. Otherwise, + * return false. + */ +bool +rt_iterate_next(rt_iter *iter, uint64 *key_p, uint64 *value_p) +{ + /* Empty tree */ + if (!iter->tree->root) + return false; + + for (;;) + { + rt_node *child = NULL; + uint64 value; + int level; + bool found; + + /* Advance the leaf node iterator to get next key-value pair */ + found = rt_node_leaf_iterate_next(iter, &(iter->stack[0]), &value); + + if (found) + { + *key_p = iter->key; + *value_p = value; + return true; + } + + /* + * We've visited all values in the leaf node, so advance inner node + * iterators from the level=1 until we find the next child node. + */ + for (level = 1; level <= iter->stack_len; level++) + { + child = rt_node_inner_iterate_next(iter, &(iter->stack[level])); + + if (child) + break; + } + + /* the iteration finished */ + if (!child) + return false; + + /* + * Set the node to the node iterator and update the iterator stack + * from this node. + */ + rt_update_iter_stack(iter, child, level - 1); + + /* Node iterators are updated, so try again from the leaf */ + } + + return false; +} + +void +rt_end_iterate(rt_iter *iter) +{ + pfree(iter); +} + +static inline void +rt_iter_update_key(rt_iter *iter, uint8 chunk, uint8 shift) +{ + iter->key &= ~(((uint64) RT_CHUNK_MASK) << shift); + iter->key |= (((uint64) chunk) << shift); +} + +/* + * Advance the slot in the inner node. Return the child if exists, otherwise + * null. + */ +static inline rt_node * +rt_node_inner_iterate_next(rt_iter *iter, rt_node_iter *node_iter) +{ +#define RT_NODE_LEVEL_INNER +#include "lib/radixtree_iter_impl.h" +#undef RT_NODE_LEVEL_INNER +} + +/* + * Advance the slot in the leaf node. On success, return true and the value + * is set to value_p, otherwise return false. + */ +static inline bool +rt_node_leaf_iterate_next(rt_iter *iter, rt_node_iter *node_iter, + uint64 *value_p) +{ +#define RT_NODE_LEVEL_LEAF +#include "lib/radixtree_iter_impl.h" +#undef RT_NODE_LEVEL_LEAF +} + +/* + * Return the number of keys in the radix tree. + */ +uint64 +rt_num_entries(radix_tree *tree) +{ + return tree->num_keys; +} + +/* + * Return the statistics of the amount of memory used by the radix tree. + */ +uint64 +rt_memory_usage(radix_tree *tree) +{ + Size total = sizeof(radix_tree); + + for (int i = 0; i < RT_SIZE_CLASS_COUNT; i++) + { + total += MemoryContextMemAllocated(tree->inner_slabs[i], true); + total += MemoryContextMemAllocated(tree->leaf_slabs[i], true); + } + + return total; +} + +/* + * Verify the radix tree node. + */ +static void +rt_verify_node(rt_node *node) +{ +#ifdef USE_ASSERT_CHECKING + Assert(node->count >= 0); + + switch (node->kind) + { + case RT_NODE_KIND_4: + { + rt_node_base_4 *n4 = (rt_node_base_4 *) node; + + for (int i = 1; i < n4->n.count; i++) + Assert(n4->chunks[i - 1] < n4->chunks[i]); + + break; + } + case RT_NODE_KIND_32: + { + rt_node_base_32 *n32 = (rt_node_base_32 *) node; + + for (int i = 1; i < n32->n.count; i++) + Assert(n32->chunks[i - 1] < n32->chunks[i]); + + break; + } + case RT_NODE_KIND_125: + { + rt_node_base_125 *n125 = (rt_node_base_125 *) node; + int cnt = 0; + + for (int i = 0; i < RT_NODE_MAX_SLOTS; i++) + { + uint8 slot = n125->slot_idxs[i]; + int idx = BM_IDX(slot); + int bitnum = BM_BIT(slot); + + if (!node_125_is_chunk_used(n125, i)) + continue; + + /* Check if the corresponding slot is used */ + Assert(slot < node->fanout); + Assert((n125->isset[idx] & ((bitmapword) 1 << bitnum)) != 0); + + cnt++; + } + + Assert(n125->n.count == cnt); + break; + } + case RT_NODE_KIND_256: + { + if (NODE_IS_LEAF(node)) + { + rt_node_leaf_256 *n256 = (rt_node_leaf_256 *) node; + int cnt = 0; + + for (int i = 0; i < BM_IDX(RT_NODE_MAX_SLOTS); i++) + cnt += bmw_popcount(n256->isset[i]); + + /* Check if the number of used chunk matches */ + Assert(n256->base.n.count == cnt); + + break; + } + } + } +#endif +} + +/***************** DEBUG FUNCTIONS *****************/ +#ifdef RT_DEBUG +void +rt_stats(radix_tree *tree) +{ + ereport(NOTICE, (errmsg("num_keys = " UINT64_FORMAT ", height = %d, n4 = %u, n15 = %u, n32 = %u, n125 = %u, n256 = %u", + tree->num_keys, + tree->root->shift / RT_NODE_SPAN, + tree->cnt[RT_CLASS_4_FULL], + tree->cnt[RT_CLASS_32_PARTIAL], + tree->cnt[RT_CLASS_32_FULL], + tree->cnt[RT_CLASS_125_FULL], + tree->cnt[RT_CLASS_256]))); +} + +static void +rt_dump_node(rt_node *node, int level, bool recurse) +{ + char space[125] = {0}; + + fprintf(stderr, "[%s] kind %d, fanout %d, count %u, shift %u, chunk 0x%X:\n", + NODE_IS_LEAF(node) ? "LEAF" : "INNR", + (node->kind == RT_NODE_KIND_4) ? 4 : + (node->kind == RT_NODE_KIND_32) ? 32 : + (node->kind == RT_NODE_KIND_125) ? 125 : 256, + node->fanout == 0 ? 256 : node->fanout, + node->count, node->shift, node->chunk); + + if (level > 0) + sprintf(space, "%*c", level * 4, ' '); + + switch (node->kind) + { + case RT_NODE_KIND_4: + { + for (int i = 0; i < node->count; i++) + { + if (NODE_IS_LEAF(node)) + { + rt_node_leaf_4 *n4 = (rt_node_leaf_4 *) node; + + fprintf(stderr, "%schunk 0x%X value 0x" UINT64_FORMAT_HEX "\n", + space, n4->base.chunks[i], n4->values[i]); + } + else + { + rt_node_inner_4 *n4 = (rt_node_inner_4 *) node; + + fprintf(stderr, "%schunk 0x%X ->", + space, n4->base.chunks[i]); + + if (recurse) + rt_dump_node(n4->children[i], level + 1, recurse); + else + fprintf(stderr, "\n"); + } + } + break; + } + case RT_NODE_KIND_32: + { + for (int i = 0; i < node->count; i++) + { + if (NODE_IS_LEAF(node)) + { + rt_node_leaf_32 *n32 = (rt_node_leaf_32 *) node; + + fprintf(stderr, "%schunk 0x%X value 0x" UINT64_FORMAT_HEX "\n", + space, n32->base.chunks[i], n32->values[i]); + } + else + { + rt_node_inner_32 *n32 = (rt_node_inner_32 *) node; + + fprintf(stderr, "%schunk 0x%X ->", + space, n32->base.chunks[i]); + + if (recurse) + { + rt_dump_node(n32->children[i], level + 1, recurse); + } + else + fprintf(stderr, "\n"); + } + } + break; + } + case RT_NODE_KIND_125: + { + rt_node_base_125 *b125 = (rt_node_base_125 *) node; + + fprintf(stderr, "slot_idxs "); + for (int i = 0; i < RT_NODE_MAX_SLOTS; i++) + { + if (!node_125_is_chunk_used(b125, i)) + continue; + + fprintf(stderr, " [%d]=%d, ", i, b125->slot_idxs[i]); + } + if (NODE_IS_LEAF(node)) + { + rt_node_leaf_125 *n = (rt_node_leaf_125 *) node; + + fprintf(stderr, ", isset-bitmap:"); + for (int i = 0; i < BM_IDX(128); i++) + { + fprintf(stderr, UINT64_FORMAT_HEX " ", (uint64) n->base.isset[i]); + } + fprintf(stderr, "\n"); + } + + for (int i = 0; i < RT_NODE_MAX_SLOTS; i++) + { + if (!node_125_is_chunk_used(b125, i)) + continue; + + if (NODE_IS_LEAF(node)) + { + rt_node_leaf_125 *n125 = (rt_node_leaf_125 *) b125; + + fprintf(stderr, "%schunk 0x%X value 0x" UINT64_FORMAT_HEX "\n", + space, i, node_leaf_125_get_value(n125, i)); + } + else + { + rt_node_inner_125 *n125 = (rt_node_inner_125 *) b125; + + fprintf(stderr, "%schunk 0x%X ->", + space, i); + + if (recurse) + rt_dump_node(node_inner_125_get_child(n125, i), + level + 1, recurse); + else + fprintf(stderr, "\n"); + } + } + break; + } + case RT_NODE_KIND_256: + { + for (int i = 0; i < RT_NODE_MAX_SLOTS; i++) + { + if (NODE_IS_LEAF(node)) + { + rt_node_leaf_256 *n256 = (rt_node_leaf_256 *) node; + + if (!node_leaf_256_is_chunk_used(n256, i)) + continue; + + fprintf(stderr, "%schunk 0x%X value 0x" UINT64_FORMAT_HEX "\n", + space, i, node_leaf_256_get_value(n256, i)); + } + else + { + rt_node_inner_256 *n256 = (rt_node_inner_256 *) node; + + if (!node_inner_256_is_chunk_used(n256, i)) + continue; + + fprintf(stderr, "%schunk 0x%X ->", + space, i); + + if (recurse) + rt_dump_node(node_inner_256_get_child(n256, i), level + 1, + recurse); + else + fprintf(stderr, "\n"); + } + } + break; + } + } +} + +void +rt_dump_search(radix_tree *tree, uint64 key) +{ + rt_node *node; + int shift; + int level = 0; + + elog(NOTICE, "-----------------------------------------------------------"); + elog(NOTICE, "max_val = " UINT64_FORMAT "(0x" UINT64_FORMAT_HEX ")", + tree->max_val, tree->max_val); + + if (!tree->root) + { + elog(NOTICE, "tree is empty"); + return; + } + + if (key > tree->max_val) + { + elog(NOTICE, "key " UINT64_FORMAT "(0x" UINT64_FORMAT_HEX ") is larger than max val", + key, key); + return; + } + + node = tree->root; + shift = tree->root->shift; + while (shift >= 0) + { + rt_node *child; + + rt_dump_node(node, level, false); + + if (NODE_IS_LEAF(node)) + { + uint64 dummy; + + /* We reached at a leaf node, find the corresponding slot */ + rt_node_search_leaf(node, key, &dummy); + + break; + } + + if (!rt_node_search_inner(node, key, &child)) + break; + + node = child; + shift -= RT_NODE_SPAN; + level++; + } +} + +void +rt_dump(radix_tree *tree) +{ + + for (int i = 0; i < RT_SIZE_CLASS_COUNT; i++) + fprintf(stderr, "%s\tinner_size %zu\tinner_blocksize %zu\tleaf_size %zu\tleaf_blocksize %zu\n", + rt_size_class_info[i].name, + rt_size_class_info[i].inner_size, + rt_size_class_info[i].inner_blocksize, + rt_size_class_info[i].leaf_size, + rt_size_class_info[i].leaf_blocksize); + fprintf(stderr, "max_val = " UINT64_FORMAT "\n", tree->max_val); + + if (!tree->root) + { + fprintf(stderr, "empty tree\n"); + return; + } + + rt_dump_node(tree->root, 0, true); +} +#endif -- 2.39.0 [text/x-patch] v17-0001-introduce-vector8_min-and-vector8_highbit_mask.patch (2.6K, ../../CAFBsxsFMuyUd48SXy1VMFvpVBHh6B6300_UfTSiPckKJtcwWMA@mail.gmail.com/8-v17-0001-introduce-vector8_min-and-vector8_highbit_mask.patch) download | inline diff: From b5653e1d6ac004f5b5420d240f9c0ee142495874 Mon Sep 17 00:00:00 2001 From: Masahiko Sawada <[email protected]> Date: Mon, 24 Oct 2022 14:07:09 +0900 Subject: [PATCH v17 1/9] introduce vector8_min and vector8_highbit_mask --- src/include/port/simd.h | 47 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/include/port/simd.h b/src/include/port/simd.h index c836360d4b..84d41a340a 100644 --- a/src/include/port/simd.h +++ b/src/include/port/simd.h @@ -77,6 +77,7 @@ static inline bool vector8_has(const Vector8 v, const uint8 c); static inline bool vector8_has_zero(const Vector8 v); static inline bool vector8_has_le(const Vector8 v, const uint8 c); static inline bool vector8_is_highbit_set(const Vector8 v); +static inline uint32 vector8_highbit_mask(const Vector8 v); #ifndef USE_NO_SIMD static inline bool vector32_is_highbit_set(const Vector32 v); #endif @@ -96,6 +97,7 @@ static inline Vector8 vector8_ssub(const Vector8 v1, const Vector8 v2); */ #ifndef USE_NO_SIMD static inline Vector8 vector8_eq(const Vector8 v1, const Vector8 v2); +static inline Vector8 vector8_min(const Vector8 v1, const Vector8 v2); static inline Vector32 vector32_eq(const Vector32 v1, const Vector32 v2); #endif @@ -277,6 +279,36 @@ vector8_is_highbit_set(const Vector8 v) #endif } +/* + * Return the bitmak of the high-bit of each element. + */ +static inline uint32 +vector8_highbit_mask(const Vector8 v) +{ +#ifdef USE_SSE2 + return (uint32) _mm_movemask_epi8(v); +#elif defined(USE_NEON) + static const uint8 mask[16] = { + 1 << 0, 1 << 1, 1 << 2, 1 << 3, + 1 << 4, 1 << 5, 1 << 6, 1 << 7, + 1 << 0, 1 << 1, 1 << 2, 1 << 3, + 1 << 4, 1 << 5, 1 << 6, 1 << 7, + }; + + uint8x16_t masked = vandq_u8(vld1q_u8(mask), (uint8x16_t) vshrq_n_s8(v, 7)); + uint8x16_t maskedhi = vextq_u8(masked, masked, 8); + + return (uint32) vaddvq_u16((uint16x8_t) vzip1q_u8(masked, maskedhi)); +#else + uint32 mask = 0; + + for (Size i = 0; i < sizeof(Vector8); i++) + mask |= (((const uint8 *) &v)[i] >> 7) << i; + + return mask; +#endif +} + /* * Exactly like vector8_is_highbit_set except for the input type, so it * looks at each byte separately. @@ -372,4 +404,19 @@ vector32_eq(const Vector32 v1, const Vector32 v2) } #endif /* ! USE_NO_SIMD */ +/* + * Compare the given vectors and return the vector of minimum elements. + */ +#ifndef USE_NO_SIMD +static inline Vector8 +vector8_min(const Vector8 v1, const Vector8 v2) +{ +#ifdef USE_SSE2 + return _mm_min_epu8(v1, v2); +#elif defined(USE_NEON) + return vminq_u8(v1, v2); +#endif +} +#endif /* ! USE_NO_SIMD */ + #endif /* SIMD_H */ -- 2.39.0 [text/x-patch] v17-0002-Move-some-bitmap-logic-out-of-bitmapset.c.patch (6.1K, ../../CAFBsxsFMuyUd48SXy1VMFvpVBHh6B6300_UfTSiPckKJtcwWMA@mail.gmail.com/9-v17-0002-Move-some-bitmap-logic-out-of-bitmapset.c.patch) download | inline diff: From dd3ab2bf57b5fae0dd7c10a4a44d23db38d65140 Mon Sep 17 00:00:00 2001 From: John Naylor <[email protected]> Date: Tue, 6 Dec 2022 13:39:41 +0700 Subject: [PATCH v17 2/9] Move some bitmap logic out of bitmapset.c Add pg_rightmost_one32/64 functions. This functionality was previously private to bitmapset.c as the RIGHTMOST_ONE macro. It has practical use in other contexts, so move to pg_bitutils.h. Also move the logic for selecting appropriate pg_bitutils functions based on word size to bitmapset.h for wider visibility and add appropriate selection for bmw_rightmost_one(). Since the previous macro relied on casting to signedbitmapword, and the new functions do not, remove that typedef. Design input and review by Tom Lane Discussion: https://www.postgresql.org/message-id/CAFBsxsFW2JjTo58jtDB%2B3sZhxMx3t-3evew8%3DAcr%2BGGhC%2BkFaA%40mail.gmail.com --- src/backend/nodes/bitmapset.c | 36 ++------------------------------ src/include/nodes/bitmapset.h | 16 ++++++++++++-- src/include/port/pg_bitutils.h | 31 +++++++++++++++++++++++++++ src/tools/pgindent/typedefs.list | 1 - 4 files changed, 47 insertions(+), 37 deletions(-) diff --git a/src/backend/nodes/bitmapset.c b/src/backend/nodes/bitmapset.c index 98660524ad..fcd8e2ccbc 100644 --- a/src/backend/nodes/bitmapset.c +++ b/src/backend/nodes/bitmapset.c @@ -32,39 +32,7 @@ #define BITMAPSET_SIZE(nwords) \ (offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword)) -/*---------- - * This is a well-known cute trick for isolating the rightmost one-bit - * in a word. It assumes two's complement arithmetic. Consider any - * nonzero value, and focus attention on the rightmost one. The value is - * then something like - * xxxxxx10000 - * where x's are unspecified bits. The two's complement negative is formed - * by inverting all the bits and adding one. Inversion gives - * yyyyyy01111 - * where each y is the inverse of the corresponding x. Incrementing gives - * yyyyyy10000 - * and then ANDing with the original value gives - * 00000010000 - * This works for all cases except original value = zero, where of course - * we get zero. - *---------- - */ -#define RIGHTMOST_ONE(x) ((signedbitmapword) (x) & -((signedbitmapword) (x))) - -#define HAS_MULTIPLE_ONES(x) ((bitmapword) RIGHTMOST_ONE(x) != (x)) - -/* Select appropriate bit-twiddling functions for bitmap word size */ -#if BITS_PER_BITMAPWORD == 32 -#define bmw_leftmost_one_pos(w) pg_leftmost_one_pos32(w) -#define bmw_rightmost_one_pos(w) pg_rightmost_one_pos32(w) -#define bmw_popcount(w) pg_popcount32(w) -#elif BITS_PER_BITMAPWORD == 64 -#define bmw_leftmost_one_pos(w) pg_leftmost_one_pos64(w) -#define bmw_rightmost_one_pos(w) pg_rightmost_one_pos64(w) -#define bmw_popcount(w) pg_popcount64(w) -#else -#error "invalid BITS_PER_BITMAPWORD" -#endif +#define HAS_MULTIPLE_ONES(x) (bmw_rightmost_one(x) != (x)) /* @@ -1013,7 +981,7 @@ bms_first_member(Bitmapset *a) { int result; - w = RIGHTMOST_ONE(w); + w = bmw_rightmost_one(w); a->words[wordnum] &= ~w; result = wordnum * BITS_PER_BITMAPWORD; diff --git a/src/include/nodes/bitmapset.h b/src/include/nodes/bitmapset.h index 0dca6bc5fa..80e91fac0f 100644 --- a/src/include/nodes/bitmapset.h +++ b/src/include/nodes/bitmapset.h @@ -38,13 +38,11 @@ struct List; #define BITS_PER_BITMAPWORD 64 typedef uint64 bitmapword; /* must be an unsigned type */ -typedef int64 signedbitmapword; /* must be the matching signed type */ #else #define BITS_PER_BITMAPWORD 32 typedef uint32 bitmapword; /* must be an unsigned type */ -typedef int32 signedbitmapword; /* must be the matching signed type */ #endif @@ -75,6 +73,20 @@ typedef enum BMS_MULTIPLE /* >1 member */ } BMS_Membership; +/* Select appropriate bit-twiddling functions for bitmap word size */ +#if BITS_PER_BITMAPWORD == 32 +#define bmw_rightmost_one(w) pg_rightmost_one32(w) +#define bmw_leftmost_one_pos(w) pg_leftmost_one_pos32(w) +#define bmw_rightmost_one_pos(w) pg_rightmost_one_pos32(w) +#define bmw_popcount(w) pg_popcount32(w) +#elif BITS_PER_BITMAPWORD == 64 +#define bmw_rightmost_one(w) pg_rightmost_one64(w) +#define bmw_leftmost_one_pos(w) pg_leftmost_one_pos64(w) +#define bmw_rightmost_one_pos(w) pg_rightmost_one_pos64(w) +#define bmw_popcount(w) pg_popcount64(w) +#else +#error "invalid BITS_PER_BITMAPWORD" +#endif /* * function prototypes in nodes/bitmapset.c diff --git a/src/include/port/pg_bitutils.h b/src/include/port/pg_bitutils.h index a49a9c03d9..7235ad25ee 100644 --- a/src/include/port/pg_bitutils.h +++ b/src/include/port/pg_bitutils.h @@ -17,6 +17,37 @@ extern PGDLLIMPORT const uint8 pg_leftmost_one_pos[256]; extern PGDLLIMPORT const uint8 pg_rightmost_one_pos[256]; extern PGDLLIMPORT const uint8 pg_number_of_ones[256]; +/*---------- + * This is a well-known cute trick for isolating the rightmost one-bit + * in a word. It assumes two's complement arithmetic. Consider any + * nonzero value, and focus attention on the rightmost one. The value is + * then something like + * xxxxxx10000 + * where x's are unspecified bits. The two's complement negative is formed + * by inverting all the bits and adding one. Inversion gives + * yyyyyy01111 + * where each y is the inverse of the corresponding x. Incrementing gives + * yyyyyy10000 + * and then ANDing with the original value gives + * 00000010000 + * This works for all cases except original value = zero, where of course + * we get zero. + *---------- + */ +static inline uint32 +pg_rightmost_one32(uint32 word) +{ + int32 result = (int32) word & -((int32) word); + return (uint32) result; +} + +static inline uint64 +pg_rightmost_one64(uint64 word) +{ + int64 result = (int64) word & -((int64) word); + return (uint64) result; +} + /* * pg_leftmost_one_pos32 * Returns the position of the most significant set bit in "word", diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 50d86cb01b..e19fd2966d 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -3655,7 +3655,6 @@ shmem_request_hook_type shmem_startup_hook_type sig_atomic_t sigjmp_buf -signedbitmapword sigset_t size_t slist_head -- 2.39.0 [text/x-patch] v17-0004-tool-for-measuring-radix-tree-performance.patch (20.0K, ../../CAFBsxsFMuyUd48SXy1VMFvpVBHh6B6300_UfTSiPckKJtcwWMA@mail.gmail.com/10-v17-0004-tool-for-measuring-radix-tree-performance.patch) download | inline diff: From f510d0d88460cbebebba9c089e38e02e054c71bb Mon Sep 17 00:00:00 2001 From: Masahiko Sawada <[email protected]> Date: Fri, 16 Sep 2022 11:57:03 +0900 Subject: [PATCH v17 4/9] tool for measuring radix tree performance --- contrib/bench_radix_tree/Makefile | 21 + .../bench_radix_tree--1.0.sql | 76 +++ contrib/bench_radix_tree/bench_radix_tree.c | 635 ++++++++++++++++++ .../bench_radix_tree/bench_radix_tree.control | 6 + contrib/bench_radix_tree/expected/bench.out | 13 + contrib/bench_radix_tree/sql/bench.sql | 16 + 6 files changed, 767 insertions(+) create mode 100644 contrib/bench_radix_tree/Makefile create mode 100644 contrib/bench_radix_tree/bench_radix_tree--1.0.sql create mode 100644 contrib/bench_radix_tree/bench_radix_tree.c create mode 100644 contrib/bench_radix_tree/bench_radix_tree.control create mode 100644 contrib/bench_radix_tree/expected/bench.out create mode 100644 contrib/bench_radix_tree/sql/bench.sql diff --git a/contrib/bench_radix_tree/Makefile b/contrib/bench_radix_tree/Makefile new file mode 100644 index 0000000000..952bb0ceae --- /dev/null +++ b/contrib/bench_radix_tree/Makefile @@ -0,0 +1,21 @@ +# contrib/bench_radix_tree/Makefile + +MODULE_big = bench_radix_tree +OBJS = \ + bench_radix_tree.o + +EXTENSION = bench_radix_tree +DATA = bench_radix_tree--1.0.sql + +REGRESS = bench_fixed_height + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = contrib/bench_radix_tree +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/contrib/bench_radix_tree/bench_radix_tree--1.0.sql b/contrib/bench_radix_tree/bench_radix_tree--1.0.sql new file mode 100644 index 0000000000..2fd689aa91 --- /dev/null +++ b/contrib/bench_radix_tree/bench_radix_tree--1.0.sql @@ -0,0 +1,76 @@ +/* contrib/bench_radix_tree/bench_radix_tree--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION bench_radix_tree" to load this file. \quit + +create function bench_shuffle_search( +minblk int4, +maxblk int4, +random_block bool DEFAULT false, +OUT nkeys int8, +OUT rt_mem_allocated int8, +OUT array_mem_allocated int8, +OUT rt_load_ms int8, +OUT array_load_ms int8, +OUT rt_search_ms int8, +OUT array_serach_ms int8 +) +returns record +as 'MODULE_PATHNAME' +LANGUAGE C STRICT VOLATILE PARALLEL UNSAFE; + +create function bench_seq_search( +minblk int4, +maxblk int4, +random_block bool DEFAULT false, +OUT nkeys int8, +OUT rt_mem_allocated int8, +OUT array_mem_allocated int8, +OUT rt_load_ms int8, +OUT array_load_ms int8, +OUT rt_search_ms int8, +OUT array_serach_ms int8 +) +returns record +as 'MODULE_PATHNAME' +LANGUAGE C STRICT VOLATILE PARALLEL UNSAFE; + +create function bench_load_random_int( +cnt int8, +OUT mem_allocated int8, +OUT load_ms int8) +returns record +as 'MODULE_PATHNAME' +LANGUAGE C STRICT VOLATILE PARALLEL UNSAFE; + +create function bench_search_random_nodes( +cnt int8, +filter_str text DEFAULT NULL, +OUT mem_allocated int8, +OUT search_ms int8) +returns record +as 'MODULE_PATHNAME' +LANGUAGE C VOLATILE PARALLEL UNSAFE; + +create function bench_fixed_height_search( +fanout int4, +OUT fanout int4, +OUT nkeys int8, +OUT rt_mem_allocated int8, +OUT rt_load_ms int8, +OUT rt_search_ms int8 +) +returns record +as 'MODULE_PATHNAME' +LANGUAGE C STRICT VOLATILE PARALLEL UNSAFE; + +create function bench_node128_load( +fanout int4, +OUT fanout int4, +OUT nkeys int8, +OUT rt_mem_allocated int8, +OUT rt_sparseload_ms int8 +) +returns record +as 'MODULE_PATHNAME' +LANGUAGE C STRICT VOLATILE PARALLEL UNSAFE; diff --git a/contrib/bench_radix_tree/bench_radix_tree.c b/contrib/bench_radix_tree/bench_radix_tree.c new file mode 100644 index 0000000000..a0693695e6 --- /dev/null +++ b/contrib/bench_radix_tree/bench_radix_tree.c @@ -0,0 +1,635 @@ +/*------------------------------------------------------------------------- + * + * bench_radix_tree.c + * + * Copyright (c) 2016-2022, PostgreSQL Global Development Group + * + * contrib/bench_radix_tree/bench_radix_tree.c + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "common/pg_prng.h" +#include "fmgr.h" +#include "funcapi.h" +#include "lib/radixtree.h" +#include <math.h> +#include "miscadmin.h" +#include "storage/lwlock.h" +#include "utils/builtins.h" +#include "utils/timestamp.h" + +PG_MODULE_MAGIC; + +/* run benchmark also for binary-search case? */ +/* #define MEASURE_BINARY_SEARCH 1 */ + +#define TIDS_PER_BLOCK_FOR_LOAD 30 +#define TIDS_PER_BLOCK_FOR_LOOKUP 50 + +PG_FUNCTION_INFO_V1(bench_seq_search); +PG_FUNCTION_INFO_V1(bench_shuffle_search); +PG_FUNCTION_INFO_V1(bench_load_random_int); +PG_FUNCTION_INFO_V1(bench_fixed_height_search); +PG_FUNCTION_INFO_V1(bench_search_random_nodes); +PG_FUNCTION_INFO_V1(bench_node128_load); + +static uint64 +tid_to_key_off(ItemPointer tid, uint32 *off) +{ + uint64 upper; + uint32 shift = pg_ceil_log2_32(MaxHeapTuplesPerPage); + int64 tid_i; + + Assert(ItemPointerGetOffsetNumber(tid) < MaxHeapTuplesPerPage); + + tid_i = ItemPointerGetOffsetNumber(tid); + tid_i |= (uint64) ItemPointerGetBlockNumber(tid) << shift; + + /* log(sizeof(uint64) * BITS_PER_BYTE, 2) = log(64, 2) = 6 */ + *off = tid_i & ((1 << 6) - 1); + upper = tid_i >> 6; + Assert(*off < (sizeof(uint64) * BITS_PER_BYTE)); + + Assert(*off < 64); + + return upper; +} + +static int +shuffle_randrange(pg_prng_state *state, int lower, int upper) +{ + return (int) floor(pg_prng_double(state) * ((upper - lower) + 0.999999)) + lower; +} + +/* Naive Fisher-Yates implementation*/ +static void +shuffle_itemptrs(ItemPointer itemptr, uint64 nitems) +{ + /* reproducability */ + pg_prng_state state; + + pg_prng_seed(&state, 0); + + for (int i = 0; i < nitems - 1; i++) + { + int j = shuffle_randrange(&state, i, nitems - 1); + ItemPointerData t = itemptr[j]; + + itemptr[j] = itemptr[i]; + itemptr[i] = t; + } +} + +static ItemPointer +generate_tids(BlockNumber minblk, BlockNumber maxblk, int ntids_per_blk, uint64 *ntids_p, + bool random_block) +{ + ItemPointer tids; + uint64 maxitems; + uint64 ntids = 0; + pg_prng_state state; + + maxitems = (maxblk - minblk + 1) * ntids_per_blk; + tids = MemoryContextAllocHuge(TopTransactionContext, + sizeof(ItemPointerData) * maxitems); + + if (random_block) + pg_prng_seed(&state, 0x9E3779B185EBCA87); + + for (BlockNumber blk = minblk; blk < maxblk; blk++) + { + if (random_block && !pg_prng_bool(&state)) + continue; + + for (OffsetNumber off = FirstOffsetNumber; + off <= ntids_per_blk; off++) + { + CHECK_FOR_INTERRUPTS(); + + ItemPointerSetBlockNumber(&(tids[ntids]), blk); + ItemPointerSetOffsetNumber(&(tids[ntids]), off); + + ntids++; + } + } + + *ntids_p = ntids; + return tids; +} + +#ifdef MEASURE_BINARY_SEARCH +static int +vac_cmp_itemptr(const void *left, const void *right) +{ + BlockNumber lblk, + rblk; + OffsetNumber loff, + roff; + + lblk = ItemPointerGetBlockNumber((ItemPointer) left); + rblk = ItemPointerGetBlockNumber((ItemPointer) right); + + if (lblk < rblk) + return -1; + if (lblk > rblk) + return 1; + + loff = ItemPointerGetOffsetNumber((ItemPointer) left); + roff = ItemPointerGetOffsetNumber((ItemPointer) right); + + if (loff < roff) + return -1; + if (loff > roff) + return 1; + + return 0; +} +#endif + +static Datum +bench_search(FunctionCallInfo fcinfo, bool shuffle) +{ + BlockNumber minblk = PG_GETARG_INT32(0); + BlockNumber maxblk = PG_GETARG_INT32(1); + bool random_block = PG_GETARG_BOOL(2); + radix_tree *rt = NULL; + uint64 ntids; + uint64 key; + uint64 last_key = PG_UINT64_MAX; + uint64 val = 0; + ItemPointer tids; + TupleDesc tupdesc; + TimestampTz start_time, + end_time; + long secs; + int usecs; + int64 rt_load_ms, + rt_search_ms; + Datum values[7]; + bool nulls[7]; + + /* Build a tuple descriptor for our result type */ + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + + tids = generate_tids(minblk, maxblk, TIDS_PER_BLOCK_FOR_LOAD, &ntids, random_block); + + /* measure the load time of the radix tree */ + rt = rt_create(CurrentMemoryContext); + start_time = GetCurrentTimestamp(); + for (int i = 0; i < ntids; i++) + { + ItemPointer tid = &(tids[i]); + uint32 off; + + CHECK_FOR_INTERRUPTS(); + + key = tid_to_key_off(tid, &off); + + if (last_key != PG_UINT64_MAX && last_key != key) + { + rt_set(rt, last_key, val); + val = 0; + } + + last_key = key; + val |= (uint64) 1 << off; + } + if (last_key != PG_UINT64_MAX) + rt_set(rt, last_key, val); + + end_time = GetCurrentTimestamp(); + TimestampDifference(start_time, end_time, &secs, &usecs); + rt_load_ms = secs * 1000 + usecs / 1000; + + rt_stats(rt); + + if (shuffle) + shuffle_itemptrs(tids, ntids); + + elog(NOTICE, "sleeping for 2 seconds..."); + pg_usleep(2 * 1000000L); + + /* meaure the serach time of the radix tree */ + start_time = GetCurrentTimestamp(); + for (int i = 0; i < ntids; i++) + { + ItemPointer tid = &(tids[i]); + uint32 off; + volatile bool ret; /* prevent calling rt_search from being + * optimized out */ + + CHECK_FOR_INTERRUPTS(); + + key = tid_to_key_off(tid, &off); + + ret = rt_search(rt, key, &val); + (void) ret; + } + end_time = GetCurrentTimestamp(); + TimestampDifference(start_time, end_time, &secs, &usecs); + rt_search_ms = secs * 1000 + usecs / 1000; + + MemSet(nulls, false, sizeof(nulls)); + values[0] = Int64GetDatum(rt_num_entries(rt)); + values[1] = Int64GetDatum(rt_memory_usage(rt)); + values[2] = Int64GetDatum(sizeof(ItemPointerData) * ntids); + values[3] = Int64GetDatum(rt_load_ms); + nulls[4] = true; /* ar_load_ms */ + values[5] = Int64GetDatum(rt_search_ms); + nulls[6] = true; /* ar_search_ms */ + +#ifdef MEASURE_BINARY_SEARCH + { + ItemPointer itemptrs = NULL; + + int64 ar_load_ms, + ar_search_ms; + + /* measure the load time of the array */ + itemptrs = MemoryContextAllocHuge(CurrentMemoryContext, + sizeof(ItemPointerData) * ntids); + start_time = GetCurrentTimestamp(); + for (int i = 0; i < ntids; i++) + { + ItemPointerSetBlockNumber(&(itemptrs[i]), + ItemPointerGetBlockNumber(&(tids[i]))); + ItemPointerSetOffsetNumber(&(itemptrs[i]), + ItemPointerGetOffsetNumber(&(tids[i]))); + } + end_time = GetCurrentTimestamp(); + TimestampDifference(start_time, end_time, &secs, &usecs); + ar_load_ms = secs * 1000 + usecs / 1000; + + /* next, measure the serach time of the array */ + start_time = GetCurrentTimestamp(); + for (int i = 0; i < ntids; i++) + { + ItemPointer tid = &(tids[i]); + volatile bool ret; /* prevent calling bsearch from being + * optimized out */ + + CHECK_FOR_INTERRUPTS(); + + ret = bsearch((void *) tid, + (void *) itemptrs, + ntids, + sizeof(ItemPointerData), + vac_cmp_itemptr); + (void) ret; + } + end_time = GetCurrentTimestamp(); + TimestampDifference(start_time, end_time, &secs, &usecs); + ar_search_ms = secs * 1000 + usecs / 1000; + + /* set the result */ + nulls[4] = false; + values[4] = Int64GetDatum(ar_load_ms); + nulls[6] = false; + values[6] = Int64GetDatum(ar_search_ms); + } +#endif + + rt_free(rt); + PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls))); +} + +Datum +bench_seq_search(PG_FUNCTION_ARGS) +{ + return bench_search(fcinfo, false); +} + +Datum +bench_shuffle_search(PG_FUNCTION_ARGS) +{ + return bench_search(fcinfo, true); +} + +Datum +bench_load_random_int(PG_FUNCTION_ARGS) +{ + uint64 cnt = (uint64) PG_GETARG_INT64(0); + radix_tree *rt; + pg_prng_state state; + TupleDesc tupdesc; + TimestampTz start_time, + end_time; + long secs; + int usecs; + int64 load_time_ms; + Datum values[2]; + bool nulls[2]; + + /* Build a tuple descriptor for our result type */ + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + + pg_prng_seed(&state, 0); + rt = rt_create(CurrentMemoryContext); + + start_time = GetCurrentTimestamp(); + for (uint64 i = 0; i < cnt; i++) + { + uint64 key = pg_prng_uint64(&state); + + rt_set(rt, key, key); + } + end_time = GetCurrentTimestamp(); + + TimestampDifference(start_time, end_time, &secs, &usecs); + load_time_ms = secs * 1000 + usecs / 1000; + + rt_stats(rt); + + MemSet(nulls, false, sizeof(nulls)); + values[0] = Int64GetDatum(rt_memory_usage(rt)); + values[1] = Int64GetDatum(load_time_ms); + + rt_free(rt); + PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls))); +} + +/* copy of splitmix64() */ +static uint64 +hash64(uint64 x) +{ + x ^= x >> 30; + x *= UINT64CONST(0xbf58476d1ce4e5b9); + x ^= x >> 27; + x *= UINT64CONST(0x94d049bb133111eb); + x ^= x >> 31; + return x; +} + +/* attempts to have a relatively even population of node kinds */ +Datum +bench_search_random_nodes(PG_FUNCTION_ARGS) +{ + uint64 cnt = (uint64) PG_GETARG_INT64(0); + radix_tree *rt; + TupleDesc tupdesc; + TimestampTz start_time, + end_time; + long secs; + int usecs; + int64 search_time_ms; + Datum values[2] = {0}; + bool nulls[2] = {0}; + /* from trial and error */ + uint64 filter = (((uint64) 0x7F<<32) | (0x07<<24) | (0xFF<<16) | 0xFF); + + /* Build a tuple descriptor for our result type */ + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + + if (!PG_ARGISNULL(1)) + { + char *filter_str = text_to_cstring(PG_GETARG_TEXT_P(1)); + + if (sscanf(filter_str, "0x%lX", &filter) == 0) + elog(ERROR, "invalid filter string %s", filter_str); + } + elog(NOTICE, "bench with filter 0x%lX", filter); + + rt = rt_create(CurrentMemoryContext); + + for (uint64 i = 0; i < cnt; i++) + { + const uint64 hash = hash64(i); + const uint64 key = hash & filter; + + rt_set(rt, key, key); + } + + elog(NOTICE, "sleeping for 2 seconds..."); + pg_usleep(2 * 1000000L); + + start_time = GetCurrentTimestamp(); + for (uint64 i = 0; i < cnt; i++) + { + const uint64 hash = hash64(i); + const uint64 key = hash & filter; + uint64 val; + volatile bool ret; /* prevent calling rt_search from being + * optimized out */ + + CHECK_FOR_INTERRUPTS(); + + ret = rt_search(rt, key, &val); + (void) ret; + } + end_time = GetCurrentTimestamp(); + + TimestampDifference(start_time, end_time, &secs, &usecs); + search_time_ms = secs * 1000 + usecs / 1000; + + rt_stats(rt); + + values[0] = Int64GetDatum(rt_memory_usage(rt)); + values[1] = Int64GetDatum(search_time_ms); + + rt_free(rt); + PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls))); +} + +Datum +bench_fixed_height_search(PG_FUNCTION_ARGS) +{ + int fanout = PG_GETARG_INT32(0); + radix_tree *rt; + TupleDesc tupdesc; + TimestampTz start_time, + end_time; + long secs; + int usecs; + int64 rt_load_ms, + rt_search_ms; + Datum values[5]; + bool nulls[5]; + + /* test boundary between vector and iteration */ + const int n_keys = 5 * 16 * 16 * 16 * 16; + uint64 r, + h, + i, + j, + k; + int key_id; + + /* Build a tuple descriptor for our result type */ + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + + rt = rt_create(CurrentMemoryContext); + + start_time = GetCurrentTimestamp(); + + key_id = 0; + + /* + * lower nodes have limited fanout, the top is only limited by + * bits-per-byte + */ + for (r = 1;; r++) + { + for (h = 1; h <= fanout; h++) + { + for (i = 1; i <= fanout; i++) + { + for (j = 1; j <= fanout; j++) + { + for (k = 1; k <= fanout; k++) + { + uint64 key; + + key = (r << 32) | (h << 24) | (i << 16) | (j << 8) | (k); + + CHECK_FOR_INTERRUPTS(); + + key_id++; + if (key_id > n_keys) + goto finish_set; + + rt_set(rt, key, key_id); + } + } + } + } + } +finish_set: + end_time = GetCurrentTimestamp(); + TimestampDifference(start_time, end_time, &secs, &usecs); + rt_load_ms = secs * 1000 + usecs / 1000; + + rt_stats(rt); + + /* meaure the search time of the radix tree */ + start_time = GetCurrentTimestamp(); + + key_id = 0; + for (r = 1;; r++) + { + for (h = 1; h <= fanout; h++) + { + for (i = 1; i <= fanout; i++) + { + for (j = 1; j <= fanout; j++) + { + for (k = 1; k <= fanout; k++) + { + uint64 key, + val; + + key = (r << 32) | (h << 24) | (i << 16) | (j << 8) | (k); + + CHECK_FOR_INTERRUPTS(); + + key_id++; + if (key_id > n_keys) + goto finish_search; + + rt_search(rt, key, &val); + } + } + } + } + } +finish_search: + end_time = GetCurrentTimestamp(); + TimestampDifference(start_time, end_time, &secs, &usecs); + rt_search_ms = secs * 1000 + usecs / 1000; + + MemSet(nulls, false, sizeof(nulls)); + values[0] = Int32GetDatum(fanout); + values[1] = Int64GetDatum(rt_num_entries(rt)); + values[2] = Int64GetDatum(rt_memory_usage(rt)); + values[3] = Int64GetDatum(rt_load_ms); + values[4] = Int64GetDatum(rt_search_ms); + + rt_free(rt); + PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls))); +} + +Datum +bench_node128_load(PG_FUNCTION_ARGS) +{ + int fanout = PG_GETARG_INT32(0); + radix_tree *rt; + TupleDesc tupdesc; + TimestampTz start_time, + end_time; + long secs; + int usecs; + int64 rt_sparseload_ms; + Datum values[5]; + bool nulls[5]; + + uint64 r, + h; + int key_id; + + /* Build a tuple descriptor for our result type */ + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + + rt = rt_create(CurrentMemoryContext); + + key_id = 0; + + for (r = 1; r <= fanout; r++) + { + for (h = 1; h <= fanout; h++) + { + uint64 key; + + key = (r << 8) | (h); + + key_id++; + rt_set(rt, key, key_id); + } + } + + rt_stats(rt); + + /* measure sparse deletion and re-loading */ + start_time = GetCurrentTimestamp(); + + for (int t = 0; t<10000; t++) + { + /* delete one key in each leaf */ + for (r = 1; r <= fanout; r++) + { + uint64 key; + + key = (r << 8) | (fanout); + + rt_delete(rt, key); + } + + /* add them all back */ + key_id = 0; + for (r = 1; r <= fanout; r++) + { + uint64 key; + + key = (r << 8) | (fanout); + + key_id++; + rt_set(rt, key, key_id); + } + } + end_time = GetCurrentTimestamp(); + TimestampDifference(start_time, end_time, &secs, &usecs); + rt_sparseload_ms = secs * 1000 + usecs / 1000; + + MemSet(nulls, false, sizeof(nulls)); + values[0] = Int32GetDatum(fanout); + values[1] = Int64GetDatum(rt_num_entries(rt)); + values[2] = Int64GetDatum(rt_memory_usage(rt)); + values[3] = Int64GetDatum(rt_sparseload_ms); + + rt_free(rt); + PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls))); +} diff --git a/contrib/bench_radix_tree/bench_radix_tree.control b/contrib/bench_radix_tree/bench_radix_tree.control new file mode 100644 index 0000000000..1d988e6c9a --- /dev/null +++ b/contrib/bench_radix_tree/bench_radix_tree.control @@ -0,0 +1,6 @@ +# bench_radix_tree extension +comment = 'benchmark suits for radix tree' +default_version = '1.0' +module_pathname = '$libdir/bench_radix_tree' +relocatable = true +trusted = true diff --git a/contrib/bench_radix_tree/expected/bench.out b/contrib/bench_radix_tree/expected/bench.out new file mode 100644 index 0000000000..60c303892e --- /dev/null +++ b/contrib/bench_radix_tree/expected/bench.out @@ -0,0 +1,13 @@ +create extension bench_radix_tree; +\o seq_search.data +begin; +select * from bench_seq_search(0, 1000000); +commit; +\o shuffle_search.data +begin; +select * from bench_shuffle_search(0, 1000000); +commit; +\o random_load.data +begin; +select * from bench_load_random_int(10000000); +commit; diff --git a/contrib/bench_radix_tree/sql/bench.sql b/contrib/bench_radix_tree/sql/bench.sql new file mode 100644 index 0000000000..a46018c9d4 --- /dev/null +++ b/contrib/bench_radix_tree/sql/bench.sql @@ -0,0 +1,16 @@ +create extension bench_radix_tree; + +\o seq_search.data +begin; +select * from bench_seq_search(0, 1000000); +commit; + +\o shuffle_search.data +begin; +select * from bench_shuffle_search(0, 1000000); +commit; + +\o random_load.data +begin; +select * from bench_load_random_int(10000000); +commit; -- 2.39.0 [text/x-patch] v17-0003-Add-radix-implementation.patch (89.8K, ../../CAFBsxsFMuyUd48SXy1VMFvpVBHh6B6300_UfTSiPckKJtcwWMA@mail.gmail.com/11-v17-0003-Add-radix-implementation.patch) download | inline diff: From dfe269fb71621a6ec580ef3d8ae601bdbc0c4b91 Mon Sep 17 00:00:00 2001 From: Masahiko Sawada <[email protected]> Date: Wed, 14 Sep 2022 12:38:51 +0000 Subject: [PATCH v17 3/9] Add radix implementation. --- src/backend/lib/Makefile | 1 + src/backend/lib/meson.build | 1 + src/backend/lib/radixtree.c | 2514 +++++++++++++++++ src/include/lib/radixtree.h | 42 + src/test/modules/Makefile | 1 + src/test/modules/meson.build | 1 + src/test/modules/test_radixtree/.gitignore | 4 + src/test/modules/test_radixtree/Makefile | 23 + src/test/modules/test_radixtree/README | 7 + .../expected/test_radixtree.out | 36 + src/test/modules/test_radixtree/meson.build | 34 + .../test_radixtree/sql/test_radixtree.sql | 7 + .../test_radixtree/test_radixtree--1.0.sql | 8 + .../modules/test_radixtree/test_radixtree.c | 581 ++++ .../test_radixtree/test_radixtree.control | 4 + 15 files changed, 3264 insertions(+) create mode 100644 src/backend/lib/radixtree.c create mode 100644 src/include/lib/radixtree.h create mode 100644 src/test/modules/test_radixtree/.gitignore create mode 100644 src/test/modules/test_radixtree/Makefile create mode 100644 src/test/modules/test_radixtree/README create mode 100644 src/test/modules/test_radixtree/expected/test_radixtree.out create mode 100644 src/test/modules/test_radixtree/meson.build create mode 100644 src/test/modules/test_radixtree/sql/test_radixtree.sql create mode 100644 src/test/modules/test_radixtree/test_radixtree--1.0.sql create mode 100644 src/test/modules/test_radixtree/test_radixtree.c create mode 100644 src/test/modules/test_radixtree/test_radixtree.control diff --git a/src/backend/lib/Makefile b/src/backend/lib/Makefile index 9dad31398a..4c1db794b6 100644 --- a/src/backend/lib/Makefile +++ b/src/backend/lib/Makefile @@ -22,6 +22,7 @@ OBJS = \ integerset.o \ knapsack.o \ pairingheap.o \ + radixtree.o \ rbtree.o \ include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/lib/meson.build b/src/backend/lib/meson.build index 974cab8776..5f8df32c5c 100644 --- a/src/backend/lib/meson.build +++ b/src/backend/lib/meson.build @@ -11,4 +11,5 @@ backend_sources += files( 'knapsack.c', 'pairingheap.c', 'rbtree.c', + 'radixtree.c', ) diff --git a/src/backend/lib/radixtree.c b/src/backend/lib/radixtree.c new file mode 100644 index 0000000000..5203127f76 --- /dev/null +++ b/src/backend/lib/radixtree.c @@ -0,0 +1,2514 @@ +/*------------------------------------------------------------------------- + * + * radixtree.c + * Implementation for adaptive radix tree. + * + * This module employs the idea from the paper "The Adaptive Radix Tree: ARTful + * Indexing for Main-Memory Databases" by Viktor Leis, Alfons Kemper, and Thomas + * Neumann, 2013. The radix tree uses adaptive node sizes, a small number of node + * types, each with a different numbers of elements. Depending on the number of + * children, the appropriate node type is used. + * + * There are some differences from the proposed implementation. For instance, + * there is not support for path compression and lazy path expansion. The radix + * tree supports fixed length of the key so we don't expect the tree level + * wouldn't be high. + * + * Both the key and the value are 64-bit unsigned integer. The inner nodes and + * the leaf nodes have slightly different structure: for inner tree nodes, + * shift > 0, store the pointer to its child node as the value. The leaf nodes, + * shift == 0, have the 64-bit unsigned integer that is specified by the user as + * the value. The paper refers to this technique as "Multi-value leaves". We + * choose it to avoid an additional pointer traversal. It is the reason this code + * currently does not support variable-length keys. + * + * XXX: Most functions in this file have two variants for inner nodes and leaf + * nodes, therefore there are duplication codes. While this sometimes makes the + * code maintenance tricky, this reduces branch prediction misses when judging + * whether the node is a inner node of a leaf node. + * + * XXX: the radix tree node never be shrunk. + * + * Interface + * --------- + * + * rt_create - Create a new, empty radix tree + * rt_free - Free the radix tree + * rt_search - Search a key-value pair + * rt_set - Set a key-value pair + * rt_delete - Delete a key-value pair + * rt_begin_iterate - Begin iterating through all key-value pairs + * rt_iterate_next - Return next key-value pair, if any + * rt_end_iter - End iteration + * rt_memory_usage - Get the memory usage + * rt_num_entries - Get the number of key-value pairs + * + * rt_create() creates an empty radix tree in the given memory context + * and memory contexts for all kinds of radix tree node under the memory context. + * + * rt_iterate_next() ensures returning key-value pairs in the ascending + * order of the key. + * + * Copyright (c) 2022, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/lib/radixtree.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "lib/radixtree.h" +#include "lib/stringinfo.h" +#include "miscadmin.h" +#include "nodes/bitmapset.h" +#include "port/pg_bitutils.h" +#include "port/pg_lfind.h" +#include "utils/memutils.h" + +#ifdef RT_DEBUG +#define UINT64_FORMAT_HEX "%" INT64_MODIFIER "X" +#endif + +/* The number of bits encoded in one tree level */ +#define RT_NODE_SPAN BITS_PER_BYTE + +/* The number of maximum slots in the node */ +#define RT_NODE_MAX_SLOTS (1 << RT_NODE_SPAN) + +/* Mask for extracting a chunk from the key */ +#define RT_CHUNK_MASK ((1 << RT_NODE_SPAN) - 1) + +/* Maximum shift the radix tree uses */ +#define RT_MAX_SHIFT key_get_shift(UINT64_MAX) + +/* Tree level the radix tree uses */ +#define RT_MAX_LEVEL ((sizeof(uint64) * BITS_PER_BYTE) / RT_NODE_SPAN) + +/* Invalid index used in node-125 */ +#define RT_NODE_125_INVALID_IDX 0xFF + +/* Get a chunk from the key */ +#define RT_GET_KEY_CHUNK(key, shift) ((uint8) (((key) >> (shift)) & RT_CHUNK_MASK)) + +/* For accessing bitmaps */ +#define BM_IDX(x) ((x) / BITS_PER_BITMAPWORD) +#define BM_BIT(x) ((x) % BITS_PER_BITMAPWORD) + +/* Enum used rt_node_search() */ +typedef enum +{ + RT_ACTION_FIND = 0, /* find the key-value */ + RT_ACTION_DELETE, /* delete the key-value */ +} rt_action; + +/* + * Supported radix tree node kinds and size classes. + * + * There are 4 node kinds and each node kind have one or two size classes, + * partial and full. The size classes in the same node kind have the same + * node structure but have the different number of fanout that is stored + * in 'fanout' of rt_node. For example in size class 15, when a 16th element + * is to be inserted, we allocate a larger area and memcpy the entire old + * node to it. + * + * This technique allows us to limit the node kinds to 4, which limits the + * number of cases in switch statements. It also allows a possible future + * optimization to encode the node kind in a pointer tag. + * + * These size classes have been chose carefully so that it minimizes the + * allocator padding in both the inner and leaf nodes on DSA. + * node + */ +#define RT_NODE_KIND_4 0x00 +#define RT_NODE_KIND_32 0x01 +#define RT_NODE_KIND_125 0x02 +#define RT_NODE_KIND_256 0x03 +#define RT_NODE_KIND_COUNT 4 + +typedef enum rt_size_class +{ + RT_CLASS_4_FULL = 0, + RT_CLASS_32_PARTIAL, + RT_CLASS_32_FULL, + RT_CLASS_125_FULL, + RT_CLASS_256 + +#define RT_SIZE_CLASS_COUNT (RT_CLASS_256 + 1) +} rt_size_class; + +/* Common type for all nodes types */ +typedef struct rt_node +{ + /* + * Number of children. We use uint16 to be able to indicate 256 children + * at the fanout of 8. + */ + uint16 count; + + /* Max number of children. We can use uint8 because we never need to store 256 */ + /* WIP: if we don't have a variable sized node4, this should instead be in the base + types as needed, since saving every byte is crucial for the smallest node kind */ + uint8 fanout; + + /* + * Shift indicates which part of the key space is represented by this + * node. That is, the key is shifted by 'shift' and the lowest + * RT_NODE_SPAN bits are then represented in chunk. + */ + uint8 shift; + uint8 chunk; + + /* Node kind, one per search/set algorithm */ + uint8 kind; +} rt_node; +#define NODE_IS_LEAF(n) (((rt_node *) (n))->shift == 0) +#define NODE_IS_EMPTY(n) (((rt_node *) (n))->count == 0) +#define VAR_NODE_HAS_FREE_SLOT(node) \ + ((node)->base.n.count < (node)->base.n.fanout) +#define FIXED_NODE_HAS_FREE_SLOT(node, class) \ + ((node)->base.n.count < rt_size_class_info[class].fanout) + +/* Base type of each node kinds for leaf and inner nodes */ +/* The base types must be a be able to accommodate the largest size +class for variable-sized node kinds*/ +typedef struct rt_node_base_4 +{ + rt_node n; + + /* 4 children, for key chunks */ + uint8 chunks[4]; +} rt_node_base_4; + +typedef struct rt_node_base32 +{ + rt_node n; + + /* 32 children, for key chunks */ + uint8 chunks[32]; +} rt_node_base_32; + +/* + * node-125 uses slot_idx array, an array of RT_NODE_MAX_SLOTS length, typically + * 256, to store indexes into a second array that contains up to 125 values (or + * child pointers in inner nodes). + */ +typedef struct rt_node_base125 +{ + rt_node n; + + /* The index of slots for each fanout */ + uint8 slot_idxs[RT_NODE_MAX_SLOTS]; + + /* isset is a bitmap to track which slot is in use */ + bitmapword isset[BM_IDX(128)]; +} rt_node_base_125; + +typedef struct rt_node_base256 +{ + rt_node n; +} rt_node_base_256; + +/* + * Inner and leaf nodes. + * + * Theres are separate for two main reasons: + * + * 1) the value type might be different than something fitting into a pointer + * width type + * 2) Need to represent non-existing values in a key-type independent way. + * + * 1) is clearly worth being concerned about, but it's not clear 2) is as + * good. It might be better to just indicate non-existing entries the same way + * in inner nodes. + */ +typedef struct rt_node_inner_4 +{ + rt_node_base_4 base; + + /* number of children depends on size class */ + rt_node *children[FLEXIBLE_ARRAY_MEMBER]; +} rt_node_inner_4; + +typedef struct rt_node_leaf_4 +{ + rt_node_base_4 base; + + /* number of values depends on size class */ + uint64 values[FLEXIBLE_ARRAY_MEMBER]; +} rt_node_leaf_4; + +typedef struct rt_node_inner_32 +{ + rt_node_base_32 base; + + /* number of children depends on size class */ + rt_node *children[FLEXIBLE_ARRAY_MEMBER]; +} rt_node_inner_32; + +typedef struct rt_node_leaf_32 +{ + rt_node_base_32 base; + + /* number of values depends on size class */ + uint64 values[FLEXIBLE_ARRAY_MEMBER]; +} rt_node_leaf_32; + +typedef struct rt_node_inner_125 +{ + rt_node_base_125 base; + + /* number of children depends on size class */ + rt_node *children[FLEXIBLE_ARRAY_MEMBER]; +} rt_node_inner_125; + +typedef struct rt_node_leaf_125 +{ + rt_node_base_125 base; + + /* number of values depends on size class */ + uint64 values[FLEXIBLE_ARRAY_MEMBER]; +} rt_node_leaf_125; + +/* + * node-256 is the largest node type. This node has RT_NODE_MAX_SLOTS length array + * for directly storing values (or child pointers in inner nodes). + */ +typedef struct rt_node_inner_256 +{ + rt_node_base_256 base; + + /* Slots for 256 children */ + rt_node *children[RT_NODE_MAX_SLOTS]; +} rt_node_inner_256; + +typedef struct rt_node_leaf_256 +{ + rt_node_base_256 base; + + /* isset is a bitmap to track which slot is in use */ + bitmapword isset[BM_IDX(RT_NODE_MAX_SLOTS)]; + + /* Slots for 256 values */ + uint64 values[RT_NODE_MAX_SLOTS]; +} rt_node_leaf_256; + +/* Information for each size class */ +typedef struct rt_size_class_elem +{ + const char *name; + int fanout; + + /* slab chunk size */ + Size inner_size; + Size leaf_size; + + /* slab block size */ + Size inner_blocksize; + Size leaf_blocksize; +} rt_size_class_elem; + +/* + * Calculate the slab blocksize so that we can allocate at least 32 chunks + * from the block. + */ +#define NODE_SLAB_BLOCK_SIZE(size) \ + Max((SLAB_DEFAULT_BLOCK_SIZE / (size)) * (size), (size) * 32) +static rt_size_class_elem rt_size_class_info[RT_SIZE_CLASS_COUNT] = { + [RT_CLASS_4_FULL] = { + .name = "radix tree node 4", + .fanout = 4, + .inner_size = sizeof(rt_node_inner_4) + 4 * sizeof(rt_node *), + .leaf_size = sizeof(rt_node_leaf_4) + 4 * sizeof(uint64), + .inner_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(rt_node_inner_4) + 4 * sizeof(rt_node *)), + .leaf_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(rt_node_leaf_4) + 4 * sizeof(uint64)), + }, + [RT_CLASS_32_PARTIAL] = { + .name = "radix tree node 15", + .fanout = 15, + .inner_size = sizeof(rt_node_inner_32) + 15 * sizeof(rt_node *), + .leaf_size = sizeof(rt_node_leaf_32) + 15 * sizeof(uint64), + .inner_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(rt_node_inner_32) + 15 * sizeof(rt_node *)), + .leaf_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(rt_node_leaf_32) + 15 * sizeof(uint64)), + }, + [RT_CLASS_32_FULL] = { + .name = "radix tree node 32", + .fanout = 32, + .inner_size = sizeof(rt_node_inner_32) + 32 * sizeof(rt_node *), + .leaf_size = sizeof(rt_node_leaf_32) + 32 * sizeof(uint64), + .inner_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(rt_node_inner_32) + 32 * sizeof(rt_node *)), + .leaf_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(rt_node_leaf_32) + 32 * sizeof(uint64)), + }, + [RT_CLASS_125_FULL] = { + .name = "radix tree node 125", + .fanout = 125, + .inner_size = sizeof(rt_node_inner_125) + 125 * sizeof(rt_node *), + .leaf_size = sizeof(rt_node_leaf_125) + 125 * sizeof(uint64), + .inner_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(rt_node_inner_125) + 125 * sizeof(rt_node *)), + .leaf_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(rt_node_leaf_125) + 125 * sizeof(uint64)), + }, + [RT_CLASS_256] = { + .name = "radix tree node 256", + .fanout = 256, + .inner_size = sizeof(rt_node_inner_256), + .leaf_size = sizeof(rt_node_leaf_256), + .inner_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(rt_node_inner_256)), + .leaf_blocksize = NODE_SLAB_BLOCK_SIZE(sizeof(rt_node_leaf_256)), + }, +}; + +/* Map from the node kind to its minimum size class */ +static rt_size_class kind_min_size_class[RT_NODE_KIND_COUNT] = { + [RT_NODE_KIND_4] = RT_CLASS_4_FULL, + [RT_NODE_KIND_32] = RT_CLASS_32_PARTIAL, + [RT_NODE_KIND_125] = RT_CLASS_125_FULL, + [RT_NODE_KIND_256] = RT_CLASS_256, +}; + +/* + * Iteration support. + * + * Iterating the radix tree returns each pair of key and value in the ascending + * order of the key. To support this, the we iterate nodes of each level. + * + * rt_node_iter struct is used to track the iteration within a node. + * + * rt_iter is the struct for iteration of the radix tree, and uses rt_node_iter + * in order to track the iteration of each level. During the iteration, we also + * construct the key whenever updating the node iteration information, e.g., when + * advancing the current index within the node or when moving to the next node + * at the same level. + */ +typedef struct rt_node_iter +{ + rt_node *node; /* current node being iterated */ + int current_idx; /* current position. -1 for initial value */ +} rt_node_iter; + +struct rt_iter +{ + radix_tree *tree; + + /* Track the iteration on nodes of each level */ + rt_node_iter stack[RT_MAX_LEVEL]; + int stack_len; + + /* The key is being constructed during the iteration */ + uint64 key; +}; + +/* A radix tree with nodes */ +struct radix_tree +{ + MemoryContext context; + + rt_node *root; + uint64 max_val; + uint64 num_keys; + + MemoryContextData *inner_slabs[RT_SIZE_CLASS_COUNT]; + MemoryContextData *leaf_slabs[RT_SIZE_CLASS_COUNT]; + + /* statistics */ +#ifdef RT_DEBUG + int32 cnt[RT_SIZE_CLASS_COUNT]; +#endif +}; + +static void rt_new_root(radix_tree *tree, uint64 key); +static rt_node *rt_alloc_node(radix_tree *tree, rt_size_class size_class, bool inner); +static inline void rt_init_node(rt_node *node, uint8 kind, rt_size_class size_class, + bool inner); +static void rt_free_node(radix_tree *tree, rt_node *node); +static void rt_extend(radix_tree *tree, uint64 key); +static inline bool rt_node_search_inner(rt_node *node, uint64 key, rt_action action, + rt_node **child_p); +static inline bool rt_node_search_leaf(rt_node *node, uint64 key, rt_action action, + uint64 *value_p); +static bool rt_node_insert_inner(radix_tree *tree, rt_node *parent, rt_node *node, + uint64 key, rt_node *child); +static bool rt_node_insert_leaf(radix_tree *tree, rt_node *parent, rt_node *node, + uint64 key, uint64 value); +static inline rt_node *rt_node_inner_iterate_next(rt_iter *iter, rt_node_iter *node_iter); +static inline bool rt_node_leaf_iterate_next(rt_iter *iter, rt_node_iter *node_iter, + uint64 *value_p); +static void rt_update_iter_stack(rt_iter *iter, rt_node *from_node, int from); +static inline void rt_iter_update_key(rt_iter *iter, uint8 chunk, uint8 shift); + +/* verification (available only with assertion) */ +static void rt_verify_node(rt_node *node); + +/* + * Return index of the first element in 'base' that equals 'key'. Return -1 + * if there is no such element. + */ +static inline int +node_4_search_eq(rt_node_base_4 *node, uint8 chunk) +{ + int idx = -1; + + for (int i = 0; i < node->n.count; i++) + { + if (node->chunks[i] == chunk) + { + idx = i; + break; + } + } + + return idx; +} + +/* + * Return index of the chunk to insert into chunks in the given node. + */ +static inline int +node_4_get_insertpos(rt_node_base_4 *node, uint8 chunk) +{ + int idx; + + for (idx = 0; idx < node->n.count; idx++) + { + if (node->chunks[idx] >= chunk) + break; + } + + return idx; +} + +/* + * Return index of the first element in 'base' that equals 'key'. Return -1 + * if there is no such element. + */ +static inline int +node_32_search_eq(rt_node_base_32 *node, uint8 chunk) +{ + int count = node->n.count; +#ifndef USE_NO_SIMD + Vector8 spread_chunk; + Vector8 haystack1; + Vector8 haystack2; + Vector8 cmp1; + Vector8 cmp2; + uint32 bitfield; + int index_simd = -1; +#endif + +#if defined(USE_NO_SIMD) || defined(USE_ASSERT_CHECKING) + int index = -1; + + for (int i = 0; i < count; i++) + { + if (node->chunks[i] == chunk) + { + index = i; + break; + } + } +#endif + +#ifndef USE_NO_SIMD + spread_chunk = vector8_broadcast(chunk); + vector8_load(&haystack1, &node->chunks[0]); + vector8_load(&haystack2, &node->chunks[sizeof(Vector8)]); + cmp1 = vector8_eq(spread_chunk, haystack1); + cmp2 = vector8_eq(spread_chunk, haystack2); + bitfield = vector8_highbit_mask(cmp1) | (vector8_highbit_mask(cmp2) << sizeof(Vector8)); + bitfield &= ((UINT64CONST(1) << count) - 1); + + if (bitfield) + index_simd = pg_rightmost_one_pos32(bitfield); + + Assert(index_simd == index); + return index_simd; +#else + return index; +#endif +} + +/* + * Return index of the chunk to insert into chunks in the given node. + */ +static inline int +node_32_get_insertpos(rt_node_base_32 *node, uint8 chunk) +{ + int count = node->n.count; +#ifndef USE_NO_SIMD + Vector8 spread_chunk; + Vector8 haystack1; + Vector8 haystack2; + Vector8 cmp1; + Vector8 cmp2; + Vector8 min1; + Vector8 min2; + uint32 bitfield; + int index_simd; +#endif + +#if defined(USE_NO_SIMD) || defined(USE_ASSERT_CHECKING) + int index; + + for (index = 0; index < count; index++) + { + if (node->chunks[index] >= chunk) + break; + } +#endif + +#ifndef USE_NO_SIMD + spread_chunk = vector8_broadcast(chunk); + vector8_load(&haystack1, &node->chunks[0]); + vector8_load(&haystack2, &node->chunks[sizeof(Vector8)]); + min1 = vector8_min(spread_chunk, haystack1); + min2 = vector8_min(spread_chunk, haystack2); + cmp1 = vector8_eq(spread_chunk, min1); + cmp2 = vector8_eq(spread_chunk, min2); + bitfield = vector8_highbit_mask(cmp1) | (vector8_highbit_mask(cmp2) << sizeof(Vector8)); + bitfield &= ((UINT64CONST(1) << count) - 1); + + if (bitfield) + index_simd = pg_rightmost_one_pos32(bitfield); + else + index_simd = count; + + Assert(index_simd == index); + return index_simd; +#else + return index; +#endif +} + +/* + * Functions to manipulate both chunks array and children/values array. + * These are used for node-4 and node-32. + */ + +/* Shift the elements right at 'idx' by one */ +static inline void +chunk_children_array_shift(uint8 *chunks, rt_node **children, int count, int idx) +{ + memmove(&(chunks[idx + 1]), &(chunks[idx]), sizeof(uint8) * (count - idx)); + memmove(&(children[idx + 1]), &(children[idx]), sizeof(rt_node *) * (count - idx)); +} + +static inline void +chunk_values_array_shift(uint8 *chunks, uint64 *values, int count, int idx) +{ + memmove(&(chunks[idx + 1]), &(chunks[idx]), sizeof(uint8) * (count - idx)); + memmove(&(values[idx + 1]), &(values[idx]), sizeof(uint64 *) * (count - idx)); +} + +/* Delete the element at 'idx' */ +static inline void +chunk_children_array_delete(uint8 *chunks, rt_node **children, int count, int idx) +{ + memmove(&(chunks[idx]), &(chunks[idx + 1]), sizeof(uint8) * (count - idx - 1)); + memmove(&(children[idx]), &(children[idx + 1]), sizeof(rt_node *) * (count - idx - 1)); +} + +static inline void +chunk_values_array_delete(uint8 *chunks, uint64 *values, int count, int idx) +{ + memmove(&(chunks[idx]), &(chunks[idx + 1]), sizeof(uint8) * (count - idx - 1)); + memmove(&(values[idx]), &(values[idx + 1]), sizeof(uint64) * (count - idx - 1)); +} + +/* Copy both chunks and children/values arrays */ +static inline void +chunk_children_array_copy(uint8 *src_chunks, rt_node **src_children, + uint8 *dst_chunks, rt_node **dst_children) +{ + const int fanout = rt_size_class_info[RT_CLASS_4_FULL].fanout; + const Size chunk_size = sizeof(uint8) * fanout; + const Size children_size = sizeof(rt_node *) * fanout; + + memcpy(dst_chunks, src_chunks, chunk_size); + memcpy(dst_children, src_children, children_size); +} + +static inline void +chunk_values_array_copy(uint8 *src_chunks, uint64 *src_values, + uint8 *dst_chunks, uint64 *dst_values) +{ + const int fanout = rt_size_class_info[RT_CLASS_4_FULL].fanout; + const Size chunk_size = sizeof(uint8) * fanout; + const Size values_size = sizeof(uint64) * fanout; + + memcpy(dst_chunks, src_chunks, chunk_size); + memcpy(dst_values, src_values, values_size); +} + +/* Functions to manipulate inner and leaf node-125 */ + +/* Does the given chunk in the node has the value? */ +static inline bool +node_125_is_chunk_used(rt_node_base_125 *node, uint8 chunk) +{ + return node->slot_idxs[chunk] != RT_NODE_125_INVALID_IDX; +} + +static inline rt_node * +node_inner_125_get_child(rt_node_inner_125 *node, uint8 chunk) +{ + Assert(!NODE_IS_LEAF(node)); + return node->children[node->base.slot_idxs[chunk]]; +} + +static inline uint64 +node_leaf_125_get_value(rt_node_leaf_125 *node, uint8 chunk) +{ + Assert(NODE_IS_LEAF(node)); + Assert(((rt_node_base_125 *) node)->slot_idxs[chunk] != RT_NODE_125_INVALID_IDX); + return node->values[node->base.slot_idxs[chunk]]; +} + +static void +node_inner_125_delete(rt_node_inner_125 *node, uint8 chunk) +{ + int slotpos = node->base.slot_idxs[chunk]; + int idx = BM_IDX(slotpos); + int bitnum = BM_BIT(slotpos); + + Assert(!NODE_IS_LEAF(node)); + + node->base.isset[idx] &= ~((bitmapword) 1 << bitnum); + node->children[node->base.slot_idxs[chunk]] = NULL; + node->base.slot_idxs[chunk] = RT_NODE_125_INVALID_IDX; +} + +static void +node_leaf_125_delete(rt_node_leaf_125 *node, uint8 chunk) +{ + int slotpos = node->base.slot_idxs[chunk]; + int idx = BM_IDX(slotpos); + int bitnum = BM_BIT(slotpos); + + Assert(NODE_IS_LEAF(node)); + node->base.isset[idx] &= ~((bitmapword) 1 << bitnum); + node->base.slot_idxs[chunk] = RT_NODE_125_INVALID_IDX; +} + +/* Return an unused slot in node-125 */ +static int +node_125_find_unused_slot(bitmapword *isset) +{ + int slotpos; + int idx; + bitmapword inverse; + + /* get the first word with at least one bit not set */ + for (idx = 0; idx < BM_IDX(128); idx++) + { + if (isset[idx] < ~((bitmapword) 0)) + break; + } + + /* To get the first unset bit in X, get the first set bit in ~X */ + inverse = ~(isset[idx]); + slotpos = idx * BITS_PER_BITMAPWORD; + slotpos += bmw_rightmost_one_pos(inverse); + + /* mark the slot used */ + isset[idx] |= bmw_rightmost_one(inverse); + + return slotpos; + } + +static inline void +node_inner_125_insert(rt_node_inner_125 *node, uint8 chunk, rt_node *child) +{ + int slotpos; + + Assert(!NODE_IS_LEAF(node)); + + slotpos = node_125_find_unused_slot(node->base.isset); + Assert(slotpos < node->base.n.fanout); + + node->base.slot_idxs[chunk] = slotpos; + node->children[slotpos] = child; +} + +/* Set the slot at the corresponding chunk */ +static inline void +node_leaf_125_insert(rt_node_leaf_125 *node, uint8 chunk, uint64 value) +{ + int slotpos; + + Assert(NODE_IS_LEAF(node)); + + slotpos = node_125_find_unused_slot(node->base.isset); + Assert(slotpos < node->base.n.fanout); + + node->base.slot_idxs[chunk] = slotpos; + node->values[slotpos] = value; +} + +/* Update the child corresponding to 'chunk' to 'child' */ +static inline void +node_inner_125_update(rt_node_inner_125 *node, uint8 chunk, rt_node *child) +{ + Assert(!NODE_IS_LEAF(node)); + node->children[node->base.slot_idxs[chunk]] = child; +} + +static inline void +node_leaf_125_update(rt_node_leaf_125 *node, uint8 chunk, uint64 value) +{ + Assert(NODE_IS_LEAF(node)); + node->values[node->base.slot_idxs[chunk]] = value; +} + +/* Functions to manipulate inner and leaf node-256 */ + +/* Return true if the slot corresponding to the given chunk is in use */ +static inline bool +node_inner_256_is_chunk_used(rt_node_inner_256 *node, uint8 chunk) +{ + Assert(!NODE_IS_LEAF(node)); + return (node->children[chunk] != NULL); +} + +static inline bool +node_leaf_256_is_chunk_used(rt_node_leaf_256 *node, uint8 chunk) +{ + int idx = BM_IDX(chunk); + int bitnum = BM_BIT(chunk); + + Assert(NODE_IS_LEAF(node)); + return (node->isset[idx] & ((bitmapword) 1 << bitnum)) != 0; +} + +static inline rt_node * +node_inner_256_get_child(rt_node_inner_256 *node, uint8 chunk) +{ + Assert(!NODE_IS_LEAF(node)); + Assert(node_inner_256_is_chunk_used(node, chunk)); + return node->children[chunk]; +} + +static inline uint64 +node_leaf_256_get_value(rt_node_leaf_256 *node, uint8 chunk) +{ + Assert(NODE_IS_LEAF(node)); + Assert(node_leaf_256_is_chunk_used(node, chunk)); + return node->values[chunk]; +} + +/* Set the child in the node-256 */ +static inline void +node_inner_256_set(rt_node_inner_256 *node, uint8 chunk, rt_node *child) +{ + Assert(!NODE_IS_LEAF(node)); + node->children[chunk] = child; +} + +/* Set the value in the node-256 */ +static inline void +node_leaf_256_set(rt_node_leaf_256 *node, uint8 chunk, uint64 value) +{ + int idx = BM_IDX(chunk); + int bitnum = BM_BIT(chunk); + + Assert(NODE_IS_LEAF(node)); + node->isset[idx] |= ((bitmapword) 1 << bitnum); + node->values[chunk] = value; +} + +/* Set the slot at the given chunk position */ +static inline void +node_inner_256_delete(rt_node_inner_256 *node, uint8 chunk) +{ + Assert(!NODE_IS_LEAF(node)); + node->children[chunk] = NULL; +} + +static inline void +node_leaf_256_delete(rt_node_leaf_256 *node, uint8 chunk) +{ + int idx = BM_IDX(chunk); + int bitnum = BM_BIT(chunk); + + Assert(NODE_IS_LEAF(node)); + node->isset[idx] &= ~((bitmapword) 1 << bitnum); +} + +/* + * Return the shift that is satisfied to store the given key. + */ +static inline int +key_get_shift(uint64 key) +{ + return (key == 0) + ? 0 + : (pg_leftmost_one_pos64(key) / RT_NODE_SPAN) * RT_NODE_SPAN; +} + +/* + * Return the max value stored in a node with the given shift. + */ +static uint64 +shift_get_max_val(int shift) +{ + if (shift == RT_MAX_SHIFT) + return UINT64_MAX; + + return (UINT64CONST(1) << (shift + RT_NODE_SPAN)) - 1; +} + +/* + * Create a new node as the root. Subordinate nodes will be created during + * the insertion. + */ +static void +rt_new_root(radix_tree *tree, uint64 key) +{ + int shift = key_get_shift(key); + bool inner = shift > 0; + rt_node *newnode; + + newnode = rt_alloc_node(tree, RT_CLASS_4_FULL, inner); + rt_init_node(newnode, RT_NODE_KIND_4, RT_CLASS_4_FULL, inner); + newnode->shift = shift; + tree->max_val = shift_get_max_val(shift); + tree->root = newnode; +} + +/* + * Allocate a new node with the given node kind. + */ +static rt_node * +rt_alloc_node(radix_tree *tree, rt_size_class size_class, bool inner) +{ + rt_node *newnode; + + if (inner) + newnode = (rt_node *) MemoryContextAlloc(tree->inner_slabs[size_class], + rt_size_class_info[size_class].inner_size); + else + newnode = (rt_node *) MemoryContextAlloc(tree->leaf_slabs[size_class], + rt_size_class_info[size_class].leaf_size); + +#ifdef RT_DEBUG + /* update the statistics */ + tree->cnt[size_class]++; +#endif + + return newnode; +} + +/* Initialize the node contents */ +static inline void +rt_init_node(rt_node *node, uint8 kind, rt_size_class size_class, bool inner) +{ + if (inner) + MemSet(node, 0, rt_size_class_info[size_class].inner_size); + else + MemSet(node, 0, rt_size_class_info[size_class].leaf_size); + + node->kind = kind; + node->fanout = rt_size_class_info[size_class].fanout; + + /* Initialize slot_idxs to invalid values */ + if (kind == RT_NODE_KIND_125) + { + rt_node_base_125 *n125 = (rt_node_base_125 *) node; + + memset(n125->slot_idxs, RT_NODE_125_INVALID_IDX, sizeof(n125->slot_idxs)); + } + + /* + * Technically it's 256, but we cannot store that in a uint8, + * and this is the max size class to it will never grow. + */ + if (kind == RT_NODE_KIND_256) + node->fanout = 0; +} + +static inline void +rt_copy_node(rt_node *newnode, rt_node *oldnode) +{ + newnode->shift = oldnode->shift; + newnode->chunk = oldnode->chunk; + newnode->count = oldnode->count; +} + +/* + * Create a new node with 'new_kind' and the same shift, chunk, and + * count of 'node'. + */ +static rt_node* +rt_grow_node_kind(radix_tree *tree, rt_node *node, uint8 new_kind) +{ + rt_node *newnode; + bool inner = !NODE_IS_LEAF(node); + + newnode = rt_alloc_node(tree, kind_min_size_class[new_kind], inner); + rt_init_node(newnode, new_kind, kind_min_size_class[new_kind], inner); + rt_copy_node(newnode, node); + + return newnode; +} + +/* Free the given node */ +static void +rt_free_node(radix_tree *tree, rt_node *node) +{ + /* If we're deleting the root node, make the tree empty */ + if (tree->root == node) + { + tree->root = NULL; + tree->max_val = 0; + } + +#ifdef RT_DEBUG + { + int i; + + /* update the statistics */ + for (i = 0; i < RT_SIZE_CLASS_COUNT; i++) + { + if (node->fanout == rt_size_class_info[i].fanout) + break; + } + + /* fanout of node256 is intentionally 0 */ + if (i == RT_SIZE_CLASS_COUNT) + i = RT_CLASS_256; + + tree->cnt[i]--; + Assert(tree->cnt[i] >= 0); + } +#endif + + pfree(node); +} + +/* + * Replace old_child with new_child, and free the old one. + */ +static void +rt_replace_node(radix_tree *tree, rt_node *parent, rt_node *old_child, + rt_node *new_child, uint64 key) +{ + Assert(old_child->chunk == new_child->chunk); + Assert(old_child->shift == new_child->shift); + + if (parent == old_child) + { + /* Replace the root node with the new large node */ + tree->root = new_child; + } + else + { + bool replaced PG_USED_FOR_ASSERTS_ONLY; + + replaced = rt_node_insert_inner(tree, NULL, parent, key, new_child); + Assert(replaced); + } + + rt_free_node(tree, old_child); +} + +/* + * The radix tree doesn't sufficient height. Extend the radix tree so it can + * store the key. + */ +static void +rt_extend(radix_tree *tree, uint64 key) +{ + int target_shift; + int shift = tree->root->shift + RT_NODE_SPAN; + + target_shift = key_get_shift(key); + + /* Grow tree from 'shift' to 'target_shift' */ + while (shift <= target_shift) + { + rt_node_inner_4 *node; + + node = (rt_node_inner_4 *) rt_alloc_node(tree, RT_CLASS_4_FULL, true); + rt_init_node((rt_node *) node, RT_NODE_KIND_4, RT_CLASS_4_FULL, true); + node->base.n.shift = shift; + node->base.n.count = 1; + node->base.chunks[0] = 0; + node->children[0] = tree->root; + + tree->root->chunk = 0; + tree->root = (rt_node *) node; + + shift += RT_NODE_SPAN; + } + + tree->max_val = shift_get_max_val(target_shift); +} + +/* + * The radix tree doesn't have inner and leaf nodes for given key-value pair. + * Insert inner and leaf nodes from 'node' to bottom. + */ +static inline void +rt_set_extend(radix_tree *tree, uint64 key, uint64 value, rt_node *parent, + rt_node *node) +{ + int shift = node->shift; + + while (shift >= RT_NODE_SPAN) + { + rt_node *newchild; + int newshift = shift - RT_NODE_SPAN; + bool inner = newshift > 0; + + newchild = rt_alloc_node(tree, RT_CLASS_4_FULL, inner); + rt_init_node(newchild, RT_NODE_KIND_4, RT_CLASS_4_FULL, inner); + newchild->shift = newshift; + newchild->chunk = RT_GET_KEY_CHUNK(key, node->shift); + rt_node_insert_inner(tree, parent, node, key, newchild); + + parent = node; + node = newchild; + shift -= RT_NODE_SPAN; + } + + rt_node_insert_leaf(tree, parent, node, key, value); + tree->num_keys++; +} + +/* + * Search for the child pointer corresponding to 'key' in the given node, and + * do the specified 'action'. + * + * Return true if the key is found, otherwise return false. On success, the child + * pointer is set to child_p. + */ +static inline bool +rt_node_search_inner(rt_node *node, uint64 key, rt_action action, rt_node **child_p) +{ + uint8 chunk = RT_GET_KEY_CHUNK(key, node->shift); + bool found = false; + rt_node *child = NULL; + + switch (node->kind) + { + case RT_NODE_KIND_4: + { + rt_node_inner_4 *n4 = (rt_node_inner_4 *) node; + int idx = node_4_search_eq((rt_node_base_4 *) n4, chunk); + + if (idx < 0) + break; + + found = true; + + if (action == RT_ACTION_FIND) + child = n4->children[idx]; + else /* RT_ACTION_DELETE */ + chunk_children_array_delete(n4->base.chunks, n4->children, + n4->base.n.count, idx); + + break; + } + case RT_NODE_KIND_32: + { + rt_node_inner_32 *n32 = (rt_node_inner_32 *) node; + int idx = node_32_search_eq((rt_node_base_32 *) n32, chunk); + + if (idx < 0) + break; + + found = true; + if (action == RT_ACTION_FIND) + child = n32->children[idx]; + else /* RT_ACTION_DELETE */ + chunk_children_array_delete(n32->base.chunks, n32->children, + n32->base.n.count, idx); + break; + } + case RT_NODE_KIND_125: + { + rt_node_inner_125 *n125 = (rt_node_inner_125 *) node; + + if (!node_125_is_chunk_used((rt_node_base_125 *) n125, chunk)) + break; + + found = true; + + if (action == RT_ACTION_FIND) + child = node_inner_125_get_child(n125, chunk); + else /* RT_ACTION_DELETE */ + node_inner_125_delete(n125, chunk); + + break; + } + case RT_NODE_KIND_256: + { + rt_node_inner_256 *n256 = (rt_node_inner_256 *) node; + + if (!node_inner_256_is_chunk_used(n256, chunk)) + break; + + found = true; + if (action == RT_ACTION_FIND) + child = node_inner_256_get_child(n256, chunk); + else /* RT_ACTION_DELETE */ + node_inner_256_delete(n256, chunk); + + break; + } + } + + /* update statistics */ + if (action == RT_ACTION_DELETE && found) + node->count--; + + if (found && child_p) + *child_p = child; + + return found; +} + +/* + * Search for the value corresponding to 'key' in the given node, and do the + * specified 'action'. + * + * Return true if the key is found, otherwise return false. On success, the pointer + * to the value is set to value_p. + */ +static inline bool +rt_node_search_leaf(rt_node *node, uint64 key, rt_action action, uint64 *value_p) +{ + uint8 chunk = RT_GET_KEY_CHUNK(key, node->shift); + bool found = false; + uint64 value = 0; + + switch (node->kind) + { + case RT_NODE_KIND_4: + { + rt_node_leaf_4 *n4 = (rt_node_leaf_4 *) node; + int idx = node_4_search_eq((rt_node_base_4 *) n4, chunk); + + if (idx < 0) + break; + + found = true; + + if (action == RT_ACTION_FIND) + value = n4->values[idx]; + else /* RT_ACTION_DELETE */ + chunk_values_array_delete(n4->base.chunks, (uint64 *) n4->values, + n4->base.n.count, idx); + + break; + } + case RT_NODE_KIND_32: + { + rt_node_leaf_32 *n32 = (rt_node_leaf_32 *) node; + int idx = node_32_search_eq((rt_node_base_32 *) n32, chunk); + + if (idx < 0) + break; + + found = true; + if (action == RT_ACTION_FIND) + value = n32->values[idx]; + else /* RT_ACTION_DELETE */ + chunk_values_array_delete(n32->base.chunks, (uint64 *) n32->values, + n32->base.n.count, idx); + break; + } + case RT_NODE_KIND_125: + { + rt_node_leaf_125 *n125 = (rt_node_leaf_125 *) node; + + if (!node_125_is_chunk_used((rt_node_base_125 *) n125, chunk)) + break; + + found = true; + + if (action == RT_ACTION_FIND) + value = node_leaf_125_get_value(n125, chunk); + else /* RT_ACTION_DELETE */ + node_leaf_125_delete(n125, chunk); + + break; + } + case RT_NODE_KIND_256: + { + rt_node_leaf_256 *n256 = (rt_node_leaf_256 *) node; + + if (!node_leaf_256_is_chunk_used(n256, chunk)) + break; + + found = true; + if (action == RT_ACTION_FIND) + value = node_leaf_256_get_value(n256, chunk); + else /* RT_ACTION_DELETE */ + node_leaf_256_delete(n256, chunk); + + break; + } + } + + /* update statistics */ + if (action == RT_ACTION_DELETE && found) + node->count--; + + if (found && value_p) + *value_p = value; + + return found; +} + +/* Insert the child to the inner node */ +static bool +rt_node_insert_inner(radix_tree *tree, rt_node *parent, rt_node *node, uint64 key, + rt_node *child) +{ + uint8 chunk = RT_GET_KEY_CHUNK(key, node->shift); + bool chunk_exists = false; + rt_node *newnode = NULL; + + Assert(!NODE_IS_LEAF(node)); + + switch (node->kind) + { + case RT_NODE_KIND_4: + { + rt_node_inner_4 *n4 = (rt_node_inner_4 *) node; + int idx; + + idx = node_4_search_eq(&n4->base, chunk); + if (idx != -1) + { + /* found the existing chunk */ + chunk_exists = true; + n4->children[idx] = child; + break; + } + + if (unlikely(!VAR_NODE_HAS_FREE_SLOT(n4))) + { + rt_node_inner_32 *new32; + + /* grow node from 4 to 32 */ + newnode = rt_grow_node_kind(tree, node, RT_NODE_KIND_32); + new32 = (rt_node_inner_32 *) newnode; + chunk_children_array_copy(n4->base.chunks, n4->children, + new32->base.chunks, new32->children); + + Assert(parent != NULL); + rt_replace_node(tree, parent, node, newnode, key); + node = newnode; + } + else + { + int insertpos = node_4_get_insertpos(&n4->base, chunk); + uint16 count = n4->base.n.count; + + /* shift chunks and children */ + if (count != 0 && insertpos < count) + chunk_children_array_shift(n4->base.chunks, n4->children, + count, insertpos); + + n4->base.chunks[insertpos] = chunk; + n4->children[insertpos] = child; + break; + } + } + /* FALLTHROUGH */ + case RT_NODE_KIND_32: + { + const rt_size_class_elem minclass = rt_size_class_info[RT_CLASS_32_PARTIAL]; + const rt_size_class_elem maxclass = rt_size_class_info[RT_CLASS_32_FULL]; + rt_node_inner_32 *n32 = (rt_node_inner_32 *) node; + int idx; + + idx = node_32_search_eq(&n32->base, chunk); + if (idx != -1) + { + /* found the existing chunk */ + chunk_exists = true; + n32->children[idx] = child; + break; + } + + if (unlikely(!VAR_NODE_HAS_FREE_SLOT(n32)) && + n32->base.n.count == minclass.fanout) + { + /* grow to the next size class of this kind */ + newnode = rt_alloc_node(tree, RT_CLASS_32_FULL, true); + memcpy(newnode, node, minclass.inner_size); + newnode->fanout = maxclass.fanout; + + Assert(parent != NULL); + rt_replace_node(tree, parent, node, newnode, key); + node = newnode; + + /* also update pointer for this kind */ + n32 = (rt_node_inner_32 *) newnode; + } + + if (unlikely(!VAR_NODE_HAS_FREE_SLOT(n32))) + { + rt_node_inner_125 *new125; + + /* grow node from 32 to 125 */ + newnode = rt_grow_node_kind(tree, node, RT_NODE_KIND_125); + new125 = (rt_node_inner_125 *) newnode; + for (int i = 0; i < n32->base.n.count; i++) + node_inner_125_insert(new125, n32->base.chunks[i], n32->children[i]); + + Assert(parent != NULL); + rt_replace_node(tree, parent, node, newnode, key); + node = newnode; + } + else + { + int insertpos = node_32_get_insertpos(&n32->base, chunk); + int16 count = n32->base.n.count; + + if (insertpos < count) + { + Assert(count > 0); + chunk_children_array_shift(n32->base.chunks, n32->children, + count, insertpos); + } + + n32->base.chunks[insertpos] = chunk; + n32->children[insertpos] = child; + break; + } + } + /* FALLTHROUGH */ + case RT_NODE_KIND_125: + { + rt_node_inner_125 *n125 = (rt_node_inner_125 *) node; + int cnt = 0; + + if (node_125_is_chunk_used(&n125->base, chunk)) + { + /* found the existing chunk */ + chunk_exists = true; + node_inner_125_update(n125, chunk, child); + break; + } + + if (unlikely(!VAR_NODE_HAS_FREE_SLOT(n125))) + { + rt_node_inner_256 *new256; + Assert(parent != NULL); + + /* grow node from 125 to 256 */ + newnode = rt_grow_node_kind(tree, node, RT_NODE_KIND_256); + new256 = (rt_node_inner_256 *) newnode; + for (int i = 0; i < RT_NODE_MAX_SLOTS && cnt < n125->base.n.count; i++) + { + if (!node_125_is_chunk_used(&n125->base, i)) + continue; + + node_inner_256_set(new256, i, node_inner_125_get_child(n125, i)); + cnt++; + } + + rt_replace_node(tree, parent, node, newnode, key); + node = newnode; + } + else + { + node_inner_125_insert(n125, chunk, child); + break; + } + } + /* FALLTHROUGH */ + case RT_NODE_KIND_256: + { + rt_node_inner_256 *n256 = (rt_node_inner_256 *) node; + + chunk_exists = node_inner_256_is_chunk_used(n256, chunk); + Assert(chunk_exists || FIXED_NODE_HAS_FREE_SLOT(n256, RT_CLASS_256)); + + node_inner_256_set(n256, chunk, child); + break; + } + } + + /* Update statistics */ + if (!chunk_exists) + node->count++; + + /* + * Done. Finally, verify the chunk and value is inserted or replaced + * properly in the node. + */ + rt_verify_node(node); + + return chunk_exists; +} + +/* Insert the value to the leaf node */ +static bool +rt_node_insert_leaf(radix_tree *tree, rt_node *parent, rt_node *node, + uint64 key, uint64 value) +{ + uint8 chunk = RT_GET_KEY_CHUNK(key, node->shift); + bool chunk_exists = false; + + Assert(NODE_IS_LEAF(node)); + + switch (node->kind) + { + case RT_NODE_KIND_4: + { + rt_node_leaf_4 *n4 = (rt_node_leaf_4 *) node; + int idx; + + idx = node_4_search_eq((rt_node_base_4 *) n4, chunk); + if (idx != -1) + { + /* found the existing chunk */ + chunk_exists = true; + n4->values[idx] = value; + break; + } + + if (unlikely(!VAR_NODE_HAS_FREE_SLOT(n4))) + { + rt_node_leaf_32 *new32; + Assert(parent != NULL); + + /* grow node from 4 to 32 */ + new32 = (rt_node_leaf_32 *) rt_grow_node_kind(tree, (rt_node *) n4, + RT_NODE_KIND_32); + chunk_values_array_copy(n4->base.chunks, n4->values, + new32->base.chunks, new32->values); + rt_replace_node(tree, parent, (rt_node *) n4, (rt_node *) new32, key); + node = (rt_node *) new32; + } + else + { + int insertpos = node_4_get_insertpos((rt_node_base_4 *) n4, chunk); + int count = n4->base.n.count; + + /* shift chunks and values */ + if (count != 0 && insertpos < count) + chunk_values_array_shift(n4->base.chunks, n4->values, + count, insertpos); + + n4->base.chunks[insertpos] = chunk; + n4->values[insertpos] = value; + break; + } + } + /* FALLTHROUGH */ + case RT_NODE_KIND_32: + { + rt_node_leaf_32 *n32 = (rt_node_leaf_32 *) node; + int idx; + + idx = node_32_search_eq((rt_node_base_32 *) n32, chunk); + if (idx != -1) + { + /* found the existing chunk */ + chunk_exists = true; + n32->values[idx] = value; + break; + } + + if (unlikely(!VAR_NODE_HAS_FREE_SLOT(n32))) + { + Assert(parent != NULL); + + if (n32->base.n.count == rt_size_class_info[RT_CLASS_32_PARTIAL].fanout) + { + /* use the same node kind, but expand to the next size class */ + const Size size = rt_size_class_info[RT_CLASS_32_PARTIAL].leaf_size; + const int fanout = rt_size_class_info[RT_CLASS_32_FULL].fanout; + rt_node_leaf_32 *new32; + + new32 = (rt_node_leaf_32 *) rt_alloc_node(tree, RT_CLASS_32_FULL, false); + memcpy(new32, n32, size); + new32->base.n.fanout = fanout; + + rt_replace_node(tree, parent, (rt_node *) n32, (rt_node *) new32, key); + + /* must update both pointers here */ + node = (rt_node *) new32; + n32 = new32; + + goto retry_insert_leaf_32; + } + else + { + rt_node_leaf_125 *new125; + + /* grow node from 32 to 125 */ + new125 = (rt_node_leaf_125 *) rt_grow_node_kind(tree, (rt_node *) n32, + RT_NODE_KIND_125); + for (int i = 0; i < n32->base.n.count; i++) + node_leaf_125_insert(new125, n32->base.chunks[i], n32->values[i]); + + rt_replace_node(tree, parent, (rt_node *) n32, (rt_node *) new125, + key); + node = (rt_node *) new125; + } + } + else + { + retry_insert_leaf_32: + { + int insertpos = node_32_get_insertpos((rt_node_base_32 *) n32, chunk); + int count = n32->base.n.count; + + if (count != 0 && insertpos < count) + chunk_values_array_shift(n32->base.chunks, n32->values, + count, insertpos); + + n32->base.chunks[insertpos] = chunk; + n32->values[insertpos] = value; + break; + } + } + } + /* FALLTHROUGH */ + case RT_NODE_KIND_125: + { + rt_node_leaf_125 *n125 = (rt_node_leaf_125 *) node; + int cnt = 0; + + if (node_125_is_chunk_used((rt_node_base_125 *) n125, chunk)) + { + /* found the existing chunk */ + chunk_exists = true; + node_leaf_125_update(n125, chunk, value); + break; + } + + if (unlikely(!VAR_NODE_HAS_FREE_SLOT(n125))) + { + rt_node_leaf_256 *new256; + Assert(parent != NULL); + + /* grow node from 125 to 256 */ + new256 = (rt_node_leaf_256 *) rt_grow_node_kind(tree, (rt_node *) n125, + RT_NODE_KIND_256); + for (int i = 0; i < RT_NODE_MAX_SLOTS && cnt < n125->base.n.count; i++) + { + if (!node_125_is_chunk_used((rt_node_base_125 *) n125, i)) + continue; + + node_leaf_256_set(new256, i, node_leaf_125_get_value(n125, i)); + cnt++; + } + + rt_replace_node(tree, parent, (rt_node *) n125, (rt_node *) new256, + key); + node = (rt_node *) new256; + } + else + { + node_leaf_125_insert(n125, chunk, value); + break; + } + } + /* FALLTHROUGH */ + case RT_NODE_KIND_256: + { + rt_node_leaf_256 *n256 = (rt_node_leaf_256 *) node; + + chunk_exists = node_leaf_256_is_chunk_used(n256, chunk); + Assert(chunk_exists || FIXED_NODE_HAS_FREE_SLOT(n256, RT_CLASS_256)); + + node_leaf_256_set(n256, chunk, value); + break; + } + } + + /* Update statistics */ + if (!chunk_exists) + node->count++; + + /* + * Done. Finally, verify the chunk and value is inserted or replaced + * properly in the node. + */ + rt_verify_node(node); + + return chunk_exists; +} + +/* + * Create the radix tree in the given memory context and return it. + */ +radix_tree * +rt_create(MemoryContext ctx) +{ + radix_tree *tree; + MemoryContext old_ctx; + + old_ctx = MemoryContextSwitchTo(ctx); + + tree = palloc(sizeof(radix_tree)); + tree->context = ctx; + tree->root = NULL; + tree->max_val = 0; + tree->num_keys = 0; + + /* Create the slab allocator for each size class */ + for (int i = 0; i < RT_SIZE_CLASS_COUNT; i++) + { + tree->inner_slabs[i] = SlabContextCreate(ctx, + rt_size_class_info[i].name, + rt_size_class_info[i].inner_blocksize, + rt_size_class_info[i].inner_size); + tree->leaf_slabs[i] = SlabContextCreate(ctx, + rt_size_class_info[i].name, + rt_size_class_info[i].leaf_blocksize, + rt_size_class_info[i].leaf_size); +#ifdef RT_DEBUG + tree->cnt[i] = 0; +#endif + } + + MemoryContextSwitchTo(old_ctx); + + return tree; +} + +/* + * Free the given radix tree. + */ +void +rt_free(radix_tree *tree) +{ + for (int i = 0; i < RT_SIZE_CLASS_COUNT; i++) + { + MemoryContextDelete(tree->inner_slabs[i]); + MemoryContextDelete(tree->leaf_slabs[i]); + } + + pfree(tree); +} + +/* + * Set key to value. If the entry already exists, we update its value to 'value' + * and return true. Returns false if entry doesn't yet exist. + */ +bool +rt_set(radix_tree *tree, uint64 key, uint64 value) +{ + int shift; + bool updated; + rt_node *node; + rt_node *parent; + + /* Empty tree, create the root */ + if (!tree->root) + rt_new_root(tree, key); + + /* Extend the tree if necessary */ + if (key > tree->max_val) + rt_extend(tree, key); + + Assert(tree->root); + + shift = tree->root->shift; + node = parent = tree->root; + + /* Descend the tree until a leaf node */ + while (shift >= 0) + { + rt_node *child; + + if (NODE_IS_LEAF(node)) + break; + + if (!rt_node_search_inner(node, key, RT_ACTION_FIND, &child)) + { + rt_set_extend(tree, key, value, parent, node); + return false; + } + + parent = node; + node = child; + shift -= RT_NODE_SPAN; + } + + updated = rt_node_insert_leaf(tree, parent, node, key, value); + + /* Update the statistics */ + if (!updated) + tree->num_keys++; + + return updated; +} + +/* + * Search the given key in the radix tree. Return true if there is the key, + * otherwise return false. On success, we set the value to *val_p so it must + * not be NULL. + */ +bool +rt_search(radix_tree *tree, uint64 key, uint64 *value_p) +{ + rt_node *node; + int shift; + + Assert(value_p != NULL); + + if (!tree->root || key > tree->max_val) + return false; + + node = tree->root; + shift = tree->root->shift; + + /* Descend the tree until a leaf node */ + while (shift >= 0) + { + rt_node *child; + + if (NODE_IS_LEAF(node)) + break; + + if (!rt_node_search_inner(node, key, RT_ACTION_FIND, &child)) + return false; + + node = child; + shift -= RT_NODE_SPAN; + } + + return rt_node_search_leaf(node, key, RT_ACTION_FIND, value_p); +} + +/* + * Delete the given key from the radix tree. Return true if the key is found (and + * deleted), otherwise do nothing and return false. + */ +bool +rt_delete(radix_tree *tree, uint64 key) +{ + rt_node *node; + rt_node *stack[RT_MAX_LEVEL] = {0}; + int shift; + int level; + bool deleted; + + if (!tree->root || key > tree->max_val) + return false; + + /* + * Descend the tree to search the key while building a stack of nodes we + * visited. + */ + node = tree->root; + shift = tree->root->shift; + level = -1; + while (shift > 0) + { + rt_node *child; + + /* Push the current node to the stack */ + stack[++level] = node; + + if (!rt_node_search_inner(node, key, RT_ACTION_FIND, &child)) + return false; + + node = child; + shift -= RT_NODE_SPAN; + } + + /* Delete the key from the leaf node if exists */ + Assert(NODE_IS_LEAF(node)); + deleted = rt_node_search_leaf(node, key, RT_ACTION_DELETE, NULL); + + if (!deleted) + { + /* no key is found in the leaf node */ + return false; + } + + /* Found the key to delete. Update the statistics */ + tree->num_keys--; + + /* + * Return if the leaf node still has keys and we don't need to delete the + * node. + */ + if (!NODE_IS_EMPTY(node)) + return true; + + /* Free the empty leaf node */ + rt_free_node(tree, node); + + /* Delete the key in inner nodes recursively */ + while (level >= 0) + { + node = stack[level--]; + + deleted = rt_node_search_inner(node, key, RT_ACTION_DELETE, NULL); + Assert(deleted); + + /* If the node didn't become empty, we stop deleting the key */ + if (!NODE_IS_EMPTY(node)) + break; + + /* The node became empty */ + rt_free_node(tree, node); + } + + return true; +} + +/* Create and return the iterator for the given radix tree */ +rt_iter * +rt_begin_iterate(radix_tree *tree) +{ + MemoryContext old_ctx; + rt_iter *iter; + int top_level; + + old_ctx = MemoryContextSwitchTo(tree->context); + + iter = (rt_iter *) palloc0(sizeof(rt_iter)); + iter->tree = tree; + + /* empty tree */ + if (!iter->tree->root) + return iter; + + top_level = iter->tree->root->shift / RT_NODE_SPAN; + iter->stack_len = top_level; + + /* + * Descend to the left most leaf node from the root. The key is being + * constructed while descending to the leaf. + */ + rt_update_iter_stack(iter, iter->tree->root, top_level); + + MemoryContextSwitchTo(old_ctx); + + return iter; +} + +/* + * Update each node_iter for inner nodes in the iterator node stack. + */ +static void +rt_update_iter_stack(rt_iter *iter, rt_node *from_node, int from) +{ + int level = from; + rt_node *node = from_node; + + for (;;) + { + rt_node_iter *node_iter = &(iter->stack[level--]); + + node_iter->node = node; + node_iter->current_idx = -1; + + /* We don't advance the leaf node iterator here */ + if (NODE_IS_LEAF(node)) + return; + + /* Advance to the next slot in the inner node */ + node = rt_node_inner_iterate_next(iter, node_iter); + + /* We must find the first children in the node */ + Assert(node); + } +} + +/* + * Return true with setting key_p and value_p if there is next key. Otherwise, + * return false. + */ +bool +rt_iterate_next(rt_iter *iter, uint64 *key_p, uint64 *value_p) +{ + /* Empty tree */ + if (!iter->tree->root) + return false; + + for (;;) + { + rt_node *child = NULL; + uint64 value; + int level; + bool found; + + /* Advance the leaf node iterator to get next key-value pair */ + found = rt_node_leaf_iterate_next(iter, &(iter->stack[0]), &value); + + if (found) + { + *key_p = iter->key; + *value_p = value; + return true; + } + + /* + * We've visited all values in the leaf node, so advance inner node + * iterators from the level=1 until we find the next child node. + */ + for (level = 1; level <= iter->stack_len; level++) + { + child = rt_node_inner_iterate_next(iter, &(iter->stack[level])); + + if (child) + break; + } + + /* the iteration finished */ + if (!child) + return false; + + /* + * Set the node to the node iterator and update the iterator stack + * from this node. + */ + rt_update_iter_stack(iter, child, level - 1); + + /* Node iterators are updated, so try again from the leaf */ + } + + return false; +} + +void +rt_end_iterate(rt_iter *iter) +{ + pfree(iter); +} + +static inline void +rt_iter_update_key(rt_iter *iter, uint8 chunk, uint8 shift) +{ + iter->key &= ~(((uint64) RT_CHUNK_MASK) << shift); + iter->key |= (((uint64) chunk) << shift); +} + +/* + * Advance the slot in the inner node. Return the child if exists, otherwise + * null. + */ +static inline rt_node * +rt_node_inner_iterate_next(rt_iter *iter, rt_node_iter *node_iter) +{ + rt_node *child = NULL; + bool found = false; + uint8 key_chunk; + + switch (node_iter->node->kind) + { + case RT_NODE_KIND_4: + { + rt_node_inner_4 *n4 = (rt_node_inner_4 *) node_iter->node; + + node_iter->current_idx++; + if (node_iter->current_idx >= n4->base.n.count) + break; + + child = n4->children[node_iter->current_idx]; + key_chunk = n4->base.chunks[node_iter->current_idx]; + found = true; + break; + } + case RT_NODE_KIND_32: + { + rt_node_inner_32 *n32 = (rt_node_inner_32 *) node_iter->node; + + node_iter->current_idx++; + if (node_iter->current_idx >= n32->base.n.count) + break; + + child = n32->children[node_iter->current_idx]; + key_chunk = n32->base.chunks[node_iter->current_idx]; + found = true; + break; + } + case RT_NODE_KIND_125: + { + rt_node_inner_125 *n125 = (rt_node_inner_125 *) node_iter->node; + int i; + + for (i = node_iter->current_idx + 1; i < RT_NODE_MAX_SLOTS; i++) + { + if (node_125_is_chunk_used((rt_node_base_125 *) n125, i)) + break; + } + + if (i >= RT_NODE_MAX_SLOTS) + break; + + node_iter->current_idx = i; + child = node_inner_125_get_child(n125, i); + key_chunk = i; + found = true; + break; + } + case RT_NODE_KIND_256: + { + rt_node_inner_256 *n256 = (rt_node_inner_256 *) node_iter->node; + int i; + + for (i = node_iter->current_idx + 1; i < RT_NODE_MAX_SLOTS; i++) + { + if (node_inner_256_is_chunk_used(n256, i)) + break; + } + + if (i >= RT_NODE_MAX_SLOTS) + break; + + node_iter->current_idx = i; + child = node_inner_256_get_child(n256, i); + key_chunk = i; + found = true; + break; + } + } + + if (found) + rt_iter_update_key(iter, key_chunk, node_iter->node->shift); + + return child; +} + +/* + * Advance the slot in the leaf node. On success, return true and the value + * is set to value_p, otherwise return false. + */ +static inline bool +rt_node_leaf_iterate_next(rt_iter *iter, rt_node_iter *node_iter, + uint64 *value_p) +{ + rt_node *node = node_iter->node; + bool found = false; + uint64 value; + uint8 key_chunk; + + switch (node->kind) + { + case RT_NODE_KIND_4: + { + rt_node_leaf_4 *n4 = (rt_node_leaf_4 *) node_iter->node; + + node_iter->current_idx++; + if (node_iter->current_idx >= n4->base.n.count) + break; + + value = n4->values[node_iter->current_idx]; + key_chunk = n4->base.chunks[node_iter->current_idx]; + found = true; + break; + } + case RT_NODE_KIND_32: + { + rt_node_leaf_32 *n32 = (rt_node_leaf_32 *) node_iter->node; + + node_iter->current_idx++; + if (node_iter->current_idx >= n32->base.n.count) + break; + + value = n32->values[node_iter->current_idx]; + key_chunk = n32->base.chunks[node_iter->current_idx]; + found = true; + break; + } + case RT_NODE_KIND_125: + { + rt_node_leaf_125 *n125 = (rt_node_leaf_125 *) node_iter->node; + int i; + + for (i = node_iter->current_idx + 1; i < RT_NODE_MAX_SLOTS; i++) + { + if (node_125_is_chunk_used((rt_node_base_125 *) n125, i)) + break; + } + + if (i >= RT_NODE_MAX_SLOTS) + break; + + node_iter->current_idx = i; + value = node_leaf_125_get_value(n125, i); + key_chunk = i; + found = true; + break; + } + case RT_NODE_KIND_256: + { + rt_node_leaf_256 *n256 = (rt_node_leaf_256 *) node_iter->node; + int i; + + for (i = node_iter->current_idx + 1; i < RT_NODE_MAX_SLOTS; i++) + { + if (node_leaf_256_is_chunk_used(n256, i)) + break; + } + + if (i >= RT_NODE_MAX_SLOTS) + break; + + node_iter->current_idx = i; + value = node_leaf_256_get_value(n256, i); + key_chunk = i; + found = true; + break; + } + } + + if (found) + { + rt_iter_update_key(iter, key_chunk, node_iter->node->shift); + *value_p = value; + } + + return found; +} + +/* + * Return the number of keys in the radix tree. + */ +uint64 +rt_num_entries(radix_tree *tree) +{ + return tree->num_keys; +} + +/* + * Return the statistics of the amount of memory used by the radix tree. + */ +uint64 +rt_memory_usage(radix_tree *tree) +{ + Size total = sizeof(radix_tree); + + for (int i = 0; i < RT_SIZE_CLASS_COUNT; i++) + { + total += MemoryContextMemAllocated(tree->inner_slabs[i], true); + total += MemoryContextMemAllocated(tree->leaf_slabs[i], true); + } + + return total; +} + +/* + * Verify the radix tree node. + */ +static void +rt_verify_node(rt_node *node) +{ +#ifdef USE_ASSERT_CHECKING + Assert(node->count >= 0); + + switch (node->kind) + { + case RT_NODE_KIND_4: + { + rt_node_base_4 *n4 = (rt_node_base_4 *) node; + + for (int i = 1; i < n4->n.count; i++) + Assert(n4->chunks[i - 1] < n4->chunks[i]); + + break; + } + case RT_NODE_KIND_32: + { + rt_node_base_32 *n32 = (rt_node_base_32 *) node; + + for (int i = 1; i < n32->n.count; i++) + Assert(n32->chunks[i - 1] < n32->chunks[i]); + + break; + } + case RT_NODE_KIND_125: + { + rt_node_base_125 *n125 = (rt_node_base_125 *) node; + int cnt = 0; + + for (int i = 0; i < RT_NODE_MAX_SLOTS; i++) + { + uint8 slot = n125->slot_idxs[i]; + int bitnum = BM_BIT(slot); + + if (!node_125_is_chunk_used(n125, i)) + continue; + + /* Check if the corresponding slot is used */ + Assert(slot < node->fanout); + Assert((n125->isset[i] & ((bitmapword) 1 << bitnum)) != 0); + + cnt++; + } + + Assert(n125->n.count == cnt); + break; + } + case RT_NODE_KIND_256: + { + if (NODE_IS_LEAF(node)) + { + rt_node_leaf_256 *n256 = (rt_node_leaf_256 *) node; + int cnt = 0; + + for (int i = 0; i < BM_IDX(RT_NODE_MAX_SLOTS); i++) + cnt += bmw_popcount(n256->isset[i]); + + /* Check if the number of used chunk matches */ + Assert(n256->base.n.count == cnt); + + break; + } + } + } +#endif +} + +/***************** DEBUG FUNCTIONS *****************/ +#ifdef RT_DEBUG +void +rt_stats(radix_tree *tree) +{ + ereport(NOTICE, (errmsg("num_keys = " UINT64_FORMAT ", height = %d, n4 = %u, n15 = %u, n32 = %u, n125 = %u, n256 = %u", + tree->num_keys, + tree->root->shift / RT_NODE_SPAN, + tree->cnt[RT_CLASS_4_FULL], + tree->cnt[RT_CLASS_32_PARTIAL], + tree->cnt[RT_CLASS_32_FULL], + tree->cnt[RT_CLASS_125_FULL], + tree->cnt[RT_CLASS_256]))); +} + +static void +rt_dump_node(rt_node *node, int level, bool recurse) +{ + char space[125] = {0}; + + fprintf(stderr, "[%s] kind %d, fanout %d, count %u, shift %u, chunk 0x%X:\n", + NODE_IS_LEAF(node) ? "LEAF" : "INNR", + (node->kind == RT_NODE_KIND_4) ? 4 : + (node->kind == RT_NODE_KIND_32) ? 32 : + (node->kind == RT_NODE_KIND_125) ? 125 : 256, + node->fanout == 0 ? 256 : node->fanout, + node->count, node->shift, node->chunk); + + if (level > 0) + sprintf(space, "%*c", level * 4, ' '); + + switch (node->kind) + { + case RT_NODE_KIND_4: + { + for (int i = 0; i < node->count; i++) + { + if (NODE_IS_LEAF(node)) + { + rt_node_leaf_4 *n4 = (rt_node_leaf_4 *) node; + + fprintf(stderr, "%schunk 0x%X value 0x" UINT64_FORMAT_HEX "\n", + space, n4->base.chunks[i], n4->values[i]); + } + else + { + rt_node_inner_4 *n4 = (rt_node_inner_4 *) node; + + fprintf(stderr, "%schunk 0x%X ->", + space, n4->base.chunks[i]); + + if (recurse) + rt_dump_node(n4->children[i], level + 1, recurse); + else + fprintf(stderr, "\n"); + } + } + break; + } + case RT_NODE_KIND_32: + { + for (int i = 0; i < node->count; i++) + { + if (NODE_IS_LEAF(node)) + { + rt_node_leaf_32 *n32 = (rt_node_leaf_32 *) node; + + fprintf(stderr, "%schunk 0x%X value 0x" UINT64_FORMAT_HEX "\n", + space, n32->base.chunks[i], n32->values[i]); + } + else + { + rt_node_inner_32 *n32 = (rt_node_inner_32 *) node; + + fprintf(stderr, "%schunk 0x%X ->", + space, n32->base.chunks[i]); + + if (recurse) + { + rt_dump_node(n32->children[i], level + 1, recurse); + } + else + fprintf(stderr, "\n"); + } + } + break; + } + case RT_NODE_KIND_125: + { + rt_node_base_125 *b125 = (rt_node_base_125 *) node; + + fprintf(stderr, "slot_idxs "); + for (int i = 0; i < RT_NODE_MAX_SLOTS; i++) + { + if (!node_125_is_chunk_used(b125, i)) + continue; + + fprintf(stderr, " [%d]=%d, ", i, b125->slot_idxs[i]); + } + if (NODE_IS_LEAF(node)) + { + rt_node_leaf_125 *n = (rt_node_leaf_125 *) node; + + fprintf(stderr, ", isset-bitmap:"); + for (int i = 0; i < BM_IDX(128); i++) + { + fprintf(stderr, UINT64_FORMAT_HEX " ", (uint64) n->base.isset[i]); + } + fprintf(stderr, "\n"); + } + + for (int i = 0; i < RT_NODE_MAX_SLOTS; i++) + { + if (!node_125_is_chunk_used(b125, i)) + continue; + + if (NODE_IS_LEAF(node)) + { + rt_node_leaf_125 *n125 = (rt_node_leaf_125 *) b125; + + fprintf(stderr, "%schunk 0x%X value 0x" UINT64_FORMAT_HEX "\n", + space, i, node_leaf_125_get_value(n125, i)); + } + else + { + rt_node_inner_125 *n125 = (rt_node_inner_125 *) b125; + + fprintf(stderr, "%schunk 0x%X ->", + space, i); + + if (recurse) + rt_dump_node(node_inner_125_get_child(n125, i), + level + 1, recurse); + else + fprintf(stderr, "\n"); + } + } + break; + } + case RT_NODE_KIND_256: + { + for (int i = 0; i < RT_NODE_MAX_SLOTS; i++) + { + if (NODE_IS_LEAF(node)) + { + rt_node_leaf_256 *n256 = (rt_node_leaf_256 *) node; + + if (!node_leaf_256_is_chunk_used(n256, i)) + continue; + + fprintf(stderr, "%schunk 0x%X value 0x" UINT64_FORMAT_HEX "\n", + space, i, node_leaf_256_get_value(n256, i)); + } + else + { + rt_node_inner_256 *n256 = (rt_node_inner_256 *) node; + + if (!node_inner_256_is_chunk_used(n256, i)) + continue; + + fprintf(stderr, "%schunk 0x%X ->", + space, i); + + if (recurse) + rt_dump_node(node_inner_256_get_child(n256, i), level + 1, + recurse); + else + fprintf(stderr, "\n"); + } + } + break; + } + } +} + +void +rt_dump_search(radix_tree *tree, uint64 key) +{ + rt_node *node; + int shift; + int level = 0; + + elog(NOTICE, "-----------------------------------------------------------"); + elog(NOTICE, "max_val = " UINT64_FORMAT "(0x" UINT64_FORMAT_HEX ")", + tree->max_val, tree->max_val); + + if (!tree->root) + { + elog(NOTICE, "tree is empty"); + return; + } + + if (key > tree->max_val) + { + elog(NOTICE, "key " UINT64_FORMAT "(0x" UINT64_FORMAT_HEX ") is larger than max val", + key, key); + return; + } + + node = tree->root; + shift = tree->root->shift; + while (shift >= 0) + { + rt_node *child; + + rt_dump_node(node, level, false); + + if (NODE_IS_LEAF(node)) + { + uint64 dummy; + + /* We reached at a leaf node, find the corresponding slot */ + rt_node_search_leaf(node, key, RT_ACTION_FIND, &dummy); + + break; + } + + if (!rt_node_search_inner(node, key, RT_ACTION_FIND, &child)) + break; + + node = child; + shift -= RT_NODE_SPAN; + level++; + } +} + +void +rt_dump(radix_tree *tree) +{ + + for (int i = 0; i < RT_SIZE_CLASS_COUNT; i++) + fprintf(stderr, "%s\tinner_size %zu\tinner_blocksize %zu\tleaf_size %zu\tleaf_blocksize %zu\n", + rt_size_class_info[i].name, + rt_size_class_info[i].inner_size, + rt_size_class_info[i].inner_blocksize, + rt_size_class_info[i].leaf_size, + rt_size_class_info[i].leaf_blocksize); + fprintf(stderr, "max_val = " UINT64_FORMAT "\n", tree->max_val); + + if (!tree->root) + { + fprintf(stderr, "empty tree\n"); + return; + } + + rt_dump_node(tree->root, 0, true); +} +#endif diff --git a/src/include/lib/radixtree.h b/src/include/lib/radixtree.h new file mode 100644 index 0000000000..d5d7668617 --- /dev/null +++ b/src/include/lib/radixtree.h @@ -0,0 +1,42 @@ +/*------------------------------------------------------------------------- + * + * radixtree.h + * Interface for radix tree. + * + * Copyright (c) 2022, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/include/lib/radixtree.h + * + *------------------------------------------------------------------------- + */ +#ifndef RADIXTREE_H +#define RADIXTREE_H + +#include "postgres.h" + +#define RT_DEBUG 1 + +typedef struct radix_tree radix_tree; +typedef struct rt_iter rt_iter; + +extern radix_tree *rt_create(MemoryContext ctx); +extern void rt_free(radix_tree *tree); +extern bool rt_search(radix_tree *tree, uint64 key, uint64 *val_p); +extern bool rt_set(radix_tree *tree, uint64 key, uint64 val); +extern rt_iter *rt_begin_iterate(radix_tree *tree); + +extern bool rt_iterate_next(rt_iter *iter, uint64 *key_p, uint64 *value_p); +extern void rt_end_iterate(rt_iter *iter); +extern bool rt_delete(radix_tree *tree, uint64 key); + +extern uint64 rt_memory_usage(radix_tree *tree); +extern uint64 rt_num_entries(radix_tree *tree); + +#ifdef RT_DEBUG +extern void rt_dump(radix_tree *tree); +extern void rt_dump_search(radix_tree *tree, uint64 key); +extern void rt_stats(radix_tree *tree); +#endif + +#endif /* RADIXTREE_H */ diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile index c629cbe383..9659eb85d7 100644 --- a/src/test/modules/Makefile +++ b/src/test/modules/Makefile @@ -28,6 +28,7 @@ SUBDIRS = \ test_pg_db_role_setting \ test_pg_dump \ test_predtest \ + test_radixtree \ test_rbtree \ test_regex \ test_rls_hooks \ diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build index 1baa6b558d..232cbdac80 100644 --- a/src/test/modules/meson.build +++ b/src/test/modules/meson.build @@ -24,6 +24,7 @@ subdir('test_parser') subdir('test_pg_db_role_setting') subdir('test_pg_dump') subdir('test_predtest') +subdir('test_radixtree') subdir('test_rbtree') subdir('test_regex') subdir('test_rls_hooks') diff --git a/src/test/modules/test_radixtree/.gitignore b/src/test/modules/test_radixtree/.gitignore new file mode 100644 index 0000000000..5dcb3ff972 --- /dev/null +++ b/src/test/modules/test_radixtree/.gitignore @@ -0,0 +1,4 @@ +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/src/test/modules/test_radixtree/Makefile b/src/test/modules/test_radixtree/Makefile new file mode 100644 index 0000000000..da06b93da3 --- /dev/null +++ b/src/test/modules/test_radixtree/Makefile @@ -0,0 +1,23 @@ +# src/test/modules/test_radixtree/Makefile + +MODULE_big = test_radixtree +OBJS = \ + $(WIN32RES) \ + test_radixtree.o +PGFILEDESC = "test_radixtree - test code for src/backend/lib/radixtree.c" + +EXTENSION = test_radixtree +DATA = test_radixtree--1.0.sql + +REGRESS = test_radixtree + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = src/test/modules/test_radixtree +top_builddir = ../../../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/src/test/modules/test_radixtree/README b/src/test/modules/test_radixtree/README new file mode 100644 index 0000000000..a8b271869a --- /dev/null +++ b/src/test/modules/test_radixtree/README @@ -0,0 +1,7 @@ +test_integerset contains unit tests for testing the integer set implementation +in src/backend/lib/integerset.c. + +The tests verify the correctness of the implementation, but they can also be +used as a micro-benchmark. If you set the 'intset_test_stats' flag in +test_integerset.c, the tests will print extra information about execution time +and memory usage. diff --git a/src/test/modules/test_radixtree/expected/test_radixtree.out b/src/test/modules/test_radixtree/expected/test_radixtree.out new file mode 100644 index 0000000000..ce645cb8b5 --- /dev/null +++ b/src/test/modules/test_radixtree/expected/test_radixtree.out @@ -0,0 +1,36 @@ +CREATE EXTENSION test_radixtree; +-- +-- All the logic is in the test_radixtree() function. It will throw +-- an error if something fails. +-- +SELECT test_radixtree(); +NOTICE: testing basic operations with leaf node 4 +NOTICE: testing basic operations with inner node 4 +NOTICE: testing basic operations with leaf node 32 +NOTICE: testing basic operations with inner node 32 +NOTICE: testing basic operations with leaf node 125 +NOTICE: testing basic operations with inner node 125 +NOTICE: testing basic operations with leaf node 256 +NOTICE: testing basic operations with inner node 256 +NOTICE: testing radix tree node types with shift "0" +NOTICE: testing radix tree node types with shift "8" +NOTICE: testing radix tree node types with shift "16" +NOTICE: testing radix tree node types with shift "24" +NOTICE: testing radix tree node types with shift "32" +NOTICE: testing radix tree node types with shift "40" +NOTICE: testing radix tree node types with shift "48" +NOTICE: testing radix tree node types with shift "56" +NOTICE: testing radix tree with pattern "all ones" +NOTICE: testing radix tree with pattern "alternating bits" +NOTICE: testing radix tree with pattern "clusters of ten" +NOTICE: testing radix tree with pattern "clusters of hundred" +NOTICE: testing radix tree with pattern "one-every-64k" +NOTICE: testing radix tree with pattern "sparse" +NOTICE: testing radix tree with pattern "single values, distance > 2^32" +NOTICE: testing radix tree with pattern "clusters, distance > 2^32" +NOTICE: testing radix tree with pattern "clusters, distance > 2^60" + test_radixtree +---------------- + +(1 row) + diff --git a/src/test/modules/test_radixtree/meson.build b/src/test/modules/test_radixtree/meson.build new file mode 100644 index 0000000000..f96bf159d6 --- /dev/null +++ b/src/test/modules/test_radixtree/meson.build @@ -0,0 +1,34 @@ +# FIXME: prevent install during main install, but not during test :/ + +test_radixtree_sources = files( + 'test_radixtree.c', +) + +if host_system == 'windows' + test_radixtree_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'test_radixtree', + '--FILEDESC', 'test_radixtree - test code for src/backend/lib/radixtree.c',]) +endif + +test_radixtree = shared_module('test_radixtree', + test_radixtree_sources, + kwargs: pg_mod_args, +) +testprep_targets += test_radixtree + +install_data( + 'test_radixtree.control', + 'test_radixtree--1.0.sql', + kwargs: contrib_data_args, +) + +tests += { + 'name': 'test_radixtree', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': [ + 'test_radixtree', + ], + }, +} diff --git a/src/test/modules/test_radixtree/sql/test_radixtree.sql b/src/test/modules/test_radixtree/sql/test_radixtree.sql new file mode 100644 index 0000000000..41ece5e9f5 --- /dev/null +++ b/src/test/modules/test_radixtree/sql/test_radixtree.sql @@ -0,0 +1,7 @@ +CREATE EXTENSION test_radixtree; + +-- +-- All the logic is in the test_radixtree() function. It will throw +-- an error if something fails. +-- +SELECT test_radixtree(); diff --git a/src/test/modules/test_radixtree/test_radixtree--1.0.sql b/src/test/modules/test_radixtree/test_radixtree--1.0.sql new file mode 100644 index 0000000000..074a5a7ea7 --- /dev/null +++ b/src/test/modules/test_radixtree/test_radixtree--1.0.sql @@ -0,0 +1,8 @@ +/* src/test/modules/test_radixtree/test_radixtree--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION test_radixtree" to load this file. \quit + +CREATE FUNCTION test_radixtree() +RETURNS pg_catalog.void STRICT +AS 'MODULE_PATHNAME' LANGUAGE C; diff --git a/src/test/modules/test_radixtree/test_radixtree.c b/src/test/modules/test_radixtree/test_radixtree.c new file mode 100644 index 0000000000..ea993e63df --- /dev/null +++ b/src/test/modules/test_radixtree/test_radixtree.c @@ -0,0 +1,581 @@ +/*-------------------------------------------------------------------------- + * + * test_radixtree.c + * Test radixtree set data structure. + * + * Copyright (c) 2022, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/test/modules/test_radixtree/test_radixtree.c + * + * ------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "common/pg_prng.h" +#include "fmgr.h" +#include "lib/radixtree.h" +#include "miscadmin.h" +#include "nodes/bitmapset.h" +#include "storage/block.h" +#include "storage/itemptr.h" +#include "utils/memutils.h" +#include "utils/timestamp.h" + +#define UINT64_HEX_FORMAT "%" INT64_MODIFIER "X" + +/* + * If you enable this, the "pattern" tests will print information about + * how long populating, probing, and iterating the test set takes, and + * how much memory the test set consumed. That can be used as + * micro-benchmark of various operations and input patterns (you might + * want to increase the number of values used in each of the test, if + * you do that, to reduce noise). + * + * The information is printed to the server's stderr, mostly because + * that's where MemoryContextStats() output goes. + */ +static const bool rt_test_stats = false; + +static int rt_node_kind_fanouts[] = { + 0, + 4, /* RT_NODE_KIND_4 */ + 32, /* RT_NODE_KIND_32 */ + 125, /* RT_NODE_KIND_125 */ + 256 /* RT_NODE_KIND_256 */ +}; +/* + * A struct to define a pattern of integers, for use with the test_pattern() + * function. + */ +typedef struct +{ + char *test_name; /* short name of the test, for humans */ + char *pattern_str; /* a bit pattern */ + uint64 spacing; /* pattern repeats at this interval */ + uint64 num_values; /* number of integers to set in total */ +} test_spec; + +/* Test patterns borrowed from test_integerset.c */ +static const test_spec test_specs[] = { + { + "all ones", "1111111111", + 10, 1000000 + }, + { + "alternating bits", "0101010101", + 10, 1000000 + }, + { + "clusters of ten", "1111111111", + 10000, 1000000 + }, + { + "clusters of hundred", + "1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111", + 10000, 1000000 + }, + { + "one-every-64k", "1", + 65536, 1000000 + }, + { + "sparse", "100000000000000000000000000000001", + 10000000, 1000000 + }, + { + "single values, distance > 2^32", "1", + UINT64CONST(10000000000), 100000 + }, + { + "clusters, distance > 2^32", "10101010", + UINT64CONST(10000000000), 1000000 + }, + { + "clusters, distance > 2^60", "10101010", + UINT64CONST(2000000000000000000), + 23 /* can't be much higher than this, or we + * overflow uint64 */ + } +}; + +PG_MODULE_MAGIC; + +PG_FUNCTION_INFO_V1(test_radixtree); + +static void +test_empty(void) +{ + radix_tree *radixtree; + rt_iter *iter; + uint64 dummy; + uint64 key; + uint64 val; + + radixtree = rt_create(CurrentMemoryContext); + + if (rt_search(radixtree, 0, &dummy)) + elog(ERROR, "rt_search on empty tree returned true"); + + if (rt_search(radixtree, 1, &dummy)) + elog(ERROR, "rt_search on empty tree returned true"); + + if (rt_search(radixtree, PG_UINT64_MAX, &dummy)) + elog(ERROR, "rt_search on empty tree returned true"); + + if (rt_delete(radixtree, 0)) + elog(ERROR, "rt_delete on empty tree returned true"); + + if (rt_num_entries(radixtree) != 0) + elog(ERROR, "rt_num_entries on empty tree return non-zero"); + + iter = rt_begin_iterate(radixtree); + + if (rt_iterate_next(iter, &key, &val)) + elog(ERROR, "rt_itereate_next on empty tree returned true"); + + rt_end_iterate(iter); + + rt_free(radixtree); +} + +static void +test_basic(int children, bool test_inner) +{ + radix_tree *radixtree; + uint64 *keys; + int shift = test_inner ? 8 : 0; + + elog(NOTICE, "testing basic operations with %s node %d", + test_inner ? "inner" : "leaf", children); + + radixtree = rt_create(CurrentMemoryContext); + + /* prepare keys in order like 1, 32, 2, 31, 2, ... */ + keys = palloc(sizeof(uint64) * children); + for (int i = 0; i < children; i++) + { + if (i % 2 == 0) + keys[i] = (uint64) ((i / 2) + 1) << shift; + else + keys[i] = (uint64) (children - (i / 2)) << shift; + } + + /* insert keys */ + for (int i = 0; i < children; i++) + { + if (rt_set(radixtree, keys[i], keys[i])) + elog(ERROR, "new inserted key 0x" UINT64_HEX_FORMAT " is found ", keys[i]); + } + + /* update keys */ + for (int i = 0; i < children; i++) + { + if (!rt_set(radixtree, keys[i], keys[i] + 1)) + elog(ERROR, "could not update key 0x" UINT64_HEX_FORMAT, keys[i]); + } + + /* repeat deleting and inserting keys */ + for (int i = 0; i < children; i++) + { + if (!rt_delete(radixtree, keys[i])) + elog(ERROR, "could not delete key 0x" UINT64_HEX_FORMAT, keys[i]); + if (rt_set(radixtree, keys[i], keys[i])) + elog(ERROR, "new inserted key 0x" UINT64_HEX_FORMAT " is found ", keys[i]); + } + + pfree(keys); + rt_free(radixtree); +} + +/* + * Check if keys from start to end with the shift exist in the tree. + */ +static void +check_search_on_node(radix_tree *radixtree, uint8 shift, int start, int end, + int incr) +{ + for (int i = start; i < end; i++) + { + uint64 key = ((uint64) i << shift); + uint64 val; + + if (!rt_search(radixtree, key, &val)) + elog(ERROR, "key 0x" UINT64_HEX_FORMAT " is not found on node-%d", + key, end); + if (val != key) + elog(ERROR, "rt_search with key 0x" UINT64_HEX_FORMAT " returns 0x" UINT64_HEX_FORMAT ", expected 0x" UINT64_HEX_FORMAT, + key, val, key); + } +} + +static void +test_node_types_insert(radix_tree *radixtree, uint8 shift, bool insert_asc) +{ + uint64 num_entries; + int ninserted = 0; + int start = insert_asc ? 0 : 256; + int incr = insert_asc ? 1 : -1; + int end = insert_asc ? 256 : 0; + int node_kind_idx = 1; + + for (int i = start; i != end; i += incr) + { + uint64 key = ((uint64) i << shift); + bool found; + + found = rt_set(radixtree, key, key); + if (found) + elog(ERROR, "newly inserted key 0x" UINT64_HEX_FORMAT " is found", key); + + /* + * After filling all slots in each node type, check if the values + * are stored properly. + */ + if (ninserted == rt_node_kind_fanouts[node_kind_idx] - 1) + { + int check_start = insert_asc + ? rt_node_kind_fanouts[node_kind_idx - 1] + : rt_node_kind_fanouts[node_kind_idx]; + int check_end = insert_asc + ? rt_node_kind_fanouts[node_kind_idx] + : rt_node_kind_fanouts[node_kind_idx - 1]; + + check_search_on_node(radixtree, shift, check_start, check_end, incr); + node_kind_idx++; + } + + ninserted++; + } + + num_entries = rt_num_entries(radixtree); + + if (num_entries != 256) + elog(ERROR, + "rt_num_entries returned " UINT64_FORMAT ", expected " UINT64_FORMAT, + num_entries, UINT64CONST(256)); +} + +static void +test_node_types_delete(radix_tree *radixtree, uint8 shift) +{ + uint64 num_entries; + + for (int i = 0; i < 256; i++) + { + uint64 key = ((uint64) i << shift); + bool found; + + found = rt_delete(radixtree, key); + + if (!found) + elog(ERROR, "could not delete key 0x" UINT64_HEX_FORMAT, key); + } + + num_entries = rt_num_entries(radixtree); + + /* The tree must be empty */ + if (num_entries != 0) + elog(ERROR, + "rt_num_entries returned " UINT64_FORMAT ", expected " UINT64_FORMAT, + num_entries, UINT64CONST(256)); +} + +/* + * Test for inserting and deleting key-value pairs to each node type at the given shift + * level. + */ +static void +test_node_types(uint8 shift) +{ + radix_tree *radixtree; + + elog(NOTICE, "testing radix tree node types with shift \"%d\"", shift); + + radixtree = rt_create(CurrentMemoryContext); + + /* + * Insert and search entries for every node type at the 'shift' level, + * then delete all entries to make it empty, and insert and search entries + * again. + */ + test_node_types_insert(radixtree, shift, true); + test_node_types_delete(radixtree, shift); + test_node_types_insert(radixtree, shift, false); + + rt_free(radixtree); +} + +/* + * Test with a repeating pattern, defined by the 'spec'. + */ +static void +test_pattern(const test_spec * spec) +{ + radix_tree *radixtree; + rt_iter *iter; + MemoryContext radixtree_ctx; + TimestampTz starttime; + TimestampTz endtime; + uint64 n; + uint64 last_int; + uint64 ndeleted; + uint64 nbefore; + uint64 nafter; + int patternlen; + uint64 *pattern_values; + uint64 pattern_num_values; + + elog(NOTICE, "testing radix tree with pattern \"%s\"", spec->test_name); + if (rt_test_stats) + fprintf(stderr, "-----\ntesting radix tree with pattern \"%s\"\n", spec->test_name); + + /* Pre-process the pattern, creating an array of integers from it. */ + patternlen = strlen(spec->pattern_str); + pattern_values = palloc(patternlen * sizeof(uint64)); + pattern_num_values = 0; + for (int i = 0; i < patternlen; i++) + { + if (spec->pattern_str[i] == '1') + pattern_values[pattern_num_values++] = i; + } + + /* + * Allocate the radix tree. + * + * Allocate it in a separate memory context, so that we can print its + * memory usage easily. + */ + radixtree_ctx = AllocSetContextCreate(CurrentMemoryContext, + "radixtree test", + ALLOCSET_SMALL_SIZES); + MemoryContextSetIdentifier(radixtree_ctx, spec->test_name); + radixtree = rt_create(radixtree_ctx); + + /* + * Add values to the set. + */ + starttime = GetCurrentTimestamp(); + + n = 0; + last_int = 0; + while (n < spec->num_values) + { + uint64 x = 0; + + for (int i = 0; i < pattern_num_values && n < spec->num_values; i++) + { + bool found; + + x = last_int + pattern_values[i]; + + found = rt_set(radixtree, x, x); + + if (found) + elog(ERROR, "newly inserted key 0x" UINT64_HEX_FORMAT " found", x); + + n++; + } + last_int += spec->spacing; + } + + endtime = GetCurrentTimestamp(); + + if (rt_test_stats) + fprintf(stderr, "added " UINT64_FORMAT " values in %d ms\n", + spec->num_values, (int) (endtime - starttime) / 1000); + + /* + * Print stats on the amount of memory used. + * + * We print the usage reported by rt_memory_usage(), as well as the stats + * from the memory context. They should be in the same ballpark, but it's + * hard to automate testing that, so if you're making changes to the + * implementation, just observe that manually. + */ + if (rt_test_stats) + { + uint64 mem_usage; + + /* + * Also print memory usage as reported by rt_memory_usage(). It + * should be in the same ballpark as the usage reported by + * MemoryContextStats(). + */ + mem_usage = rt_memory_usage(radixtree); + fprintf(stderr, "rt_memory_usage() reported " UINT64_FORMAT " (%0.2f bytes / integer)\n", + mem_usage, (double) mem_usage / spec->num_values); + + MemoryContextStats(radixtree_ctx); + } + + /* Check that rt_num_entries works */ + n = rt_num_entries(radixtree); + if (n != spec->num_values) + elog(ERROR, "rt_num_entries returned " UINT64_FORMAT ", expected " UINT64_FORMAT, n, spec->num_values); + + /* + * Test random-access probes with rt_search() + */ + starttime = GetCurrentTimestamp(); + + for (n = 0; n < 100000; n++) + { + bool found; + bool expected; + uint64 x; + uint64 v; + + /* + * Pick next value to probe at random. We limit the probes to the + * last integer that we added to the set, plus an arbitrary constant + * (1000). There's no point in probing the whole 0 - 2^64 range, if + * only a small part of the integer space is used. We would very + * rarely hit values that are actually in the set. + */ + x = pg_prng_uint64_range(&pg_global_prng_state, 0, last_int + 1000); + + /* Do we expect this value to be present in the set? */ + if (x >= last_int) + expected = false; + else + { + uint64 idx = x % spec->spacing; + + if (idx >= patternlen) + expected = false; + else if (spec->pattern_str[idx] == '1') + expected = true; + else + expected = false; + } + + /* Is it present according to rt_search() ? */ + found = rt_search(radixtree, x, &v); + + if (found != expected) + elog(ERROR, "mismatch at 0x" UINT64_HEX_FORMAT ": %d vs %d", x, found, expected); + if (found && (v != x)) + elog(ERROR, "found 0x" UINT64_HEX_FORMAT ", expected 0x" UINT64_HEX_FORMAT, + v, x); + } + endtime = GetCurrentTimestamp(); + if (rt_test_stats) + fprintf(stderr, "probed " UINT64_FORMAT " values in %d ms\n", + n, (int) (endtime - starttime) / 1000); + + /* + * Test iterator + */ + starttime = GetCurrentTimestamp(); + + iter = rt_begin_iterate(radixtree); + n = 0; + last_int = 0; + while (n < spec->num_values) + { + for (int i = 0; i < pattern_num_values && n < spec->num_values; i++) + { + uint64 expected = last_int + pattern_values[i]; + uint64 x; + uint64 val; + + if (!rt_iterate_next(iter, &x, &val)) + break; + + if (x != expected) + elog(ERROR, + "iterate returned wrong key; got 0x" UINT64_HEX_FORMAT ", expected 0x" UINT64_HEX_FORMAT " at %d", + x, expected, i); + if (val != expected) + elog(ERROR, + "iterate returned wrong value; got 0x" UINT64_HEX_FORMAT ", expected 0x" UINT64_HEX_FORMAT " at %d", x, expected, i); + n++; + } + last_int += spec->spacing; + } + endtime = GetCurrentTimestamp(); + if (rt_test_stats) + fprintf(stderr, "iterated " UINT64_FORMAT " values in %d ms\n", + n, (int) (endtime - starttime) / 1000); + + rt_end_iterate(iter); + + if (n < spec->num_values) + elog(ERROR, "iterator stopped short after " UINT64_FORMAT " entries, expected " UINT64_FORMAT, n, spec->num_values); + if (n > spec->num_values) + elog(ERROR, "iterator returned " UINT64_FORMAT " entries, " UINT64_FORMAT " was expected", n, spec->num_values); + + /* + * Test random-access probes with rt_delete() + */ + starttime = GetCurrentTimestamp(); + + nbefore = rt_num_entries(radixtree); + ndeleted = 0; + for (n = 0; n < 1; n++) + { + bool found; + uint64 x; + uint64 v; + + /* + * Pick next value to probe at random. We limit the probes to the + * last integer that we added to the set, plus an arbitrary constant + * (1000). There's no point in probing the whole 0 - 2^64 range, if + * only a small part of the integer space is used. We would very + * rarely hit values that are actually in the set. + */ + x = pg_prng_uint64_range(&pg_global_prng_state, 0, last_int + 1000); + + /* Is it present according to rt_search() ? */ + found = rt_search(radixtree, x, &v); + + if (!found) + continue; + + /* If the key is found, delete it and check again */ + if (!rt_delete(radixtree, x)) + elog(ERROR, "could not delete key 0x" UINT64_HEX_FORMAT, x); + if (rt_search(radixtree, x, &v)) + elog(ERROR, "found deleted key 0x" UINT64_HEX_FORMAT, x); + if (rt_delete(radixtree, x)) + elog(ERROR, "deleted already-deleted key 0x" UINT64_HEX_FORMAT, x); + + ndeleted++; + } + endtime = GetCurrentTimestamp(); + if (rt_test_stats) + fprintf(stderr, "deleted " UINT64_FORMAT " values in %d ms\n", + ndeleted, (int) (endtime - starttime) / 1000); + + nafter = rt_num_entries(radixtree); + + /* Check that rt_num_entries works */ + if ((nbefore - ndeleted) != nafter) + elog(ERROR, "rt_num_entries returned " UINT64_FORMAT ", expected " UINT64_FORMAT "after " UINT64_FORMAT " deletion", + nafter, (nbefore - ndeleted), ndeleted); + + MemoryContextDelete(radixtree_ctx); +} + +Datum +test_radixtree(PG_FUNCTION_ARGS) +{ + test_empty(); + + for (int i = 1; i < lengthof(rt_node_kind_fanouts); i++) + { + test_basic(rt_node_kind_fanouts[i], false); + test_basic(rt_node_kind_fanouts[i], true); + } + + for (int shift = 0; shift <= (64 - 8); shift += 8) + test_node_types(shift); + + /* Test different test patterns, with lots of entries */ + for (int i = 0; i < lengthof(test_specs); i++) + test_pattern(&test_specs[i]); + + PG_RETURN_VOID(); +} diff --git a/src/test/modules/test_radixtree/test_radixtree.control b/src/test/modules/test_radixtree/test_radixtree.control new file mode 100644 index 0000000000..e53f2a3e0c --- /dev/null +++ b/src/test/modules/test_radixtree/test_radixtree.control @@ -0,0 +1,4 @@ +comment = 'Test code for radix tree' +default_version = '1.0' +module_pathname = '$libdir/test_radixtree' +relocatable = true -- 2.39.0 ^ permalink raw reply [nested|flat] 75+ messages in thread
* Re: magical eref alias names @ 2025-07-23 21:35 Tom Lane <[email protected]> 0 siblings, 1 reply; 75+ messages in thread From: Tom Lane @ 2025-07-23 21:35 UTC (permalink / raw) To: Robert Haas <[email protected]>; +Cc: Peter Eisentraut <[email protected]>; pgsql-hackers [ returning to this thread now that v19 is open for business ] Robert Haas <[email protected]> writes: > I rediscovered, or re-encountered, this problem today, which motivated > me to have a closer look at your (Tom's) patch. My feeling is that > it's the right approach. I agree that we could try to keep the current > generated names by extending addRangeTableEntryForSubquery, but I'm > tentatively inclined to think that we shouldn't. That's fine by me. I'm personally content with all of the changes shown in your patchset. > However, I did come across one other mildly interesting case. > expand_single_inheritance_child has this: > ... > What I find curious about this is that we're assigning the parent's > eref to both the child's eref and the child's alias. Maybe there's > something I don't understand here, or maybe it just doesn't matter, > but why wouldn't we assign eref to eref and alias to alias? Or even > alias to alias and generate a new eref? The issue is explained in the previous comment block a few lines up: * Construct an alias clause for the child, which we can also use as eref. * This is important so that EXPLAIN will print the right column aliases * for child-table columns. (Since ruleutils.c doesn't have any easy way * to reassociate parent and child columns, we must get the child column * aliases right to start with. Note that setting childrte->alias forces * ruleutils.c to use these column names, which it otherwise would not.) I think the case this is worried about is that if you have a parent table t(a,b,c), and the query writes say "t as t1(x)", then t's column "a" will be labeled "x" by EXPLAIN and we want the child's column "a" to similarly be labeled "x". So unless we want to start rejiggering ruleutils' already-overly- complex behavior, we have to put something in childrte->alias, even if the parent had no alias. So that's a violation of the principle you were hoping to establish, but as long as it only applies to partitions and inheritance children, I'm not sure it's worth moving heaven and earth to make it different. We could certainly do something a little different than what the code is doing, such as "if the parent does have a user-written alias, use parentrte->alias->aliasname not parentrte->eref->aliasname for the childrte->alias->aliasname". I'm not sure it's worth bothering with that, though. I noticed that the patchset was failing in cfbot because we've since grown another regression test case whose output is affected by 0002. So here's a v3 that incorporates that additional change. I did a little bit of wordsmithing on the commit messages too, but the code changes are identical to v2. regards, tom lane Attachments: [text/x-diff] v3-0001-Don-t-generate-fake-SELECT-or-SELECT-d-subquery-a.patch (10.0K, ../../[email protected]/2-v3-0001-Don-t-generate-fake-SELECT-or-SELECT-d-subquery-a.patch) download | inline diff: From ab46f16a15464e9ba3fae5008ef533e0a6a28303 Mon Sep 17 00:00:00 2001 From: Tom Lane <[email protected]> Date: Wed, 23 Jul 2025 17:04:45 -0400 Subject: [PATCH v3 1/3] Don't generate fake "*SELECT*" or "*SELECT* %d" subquery aliases. rte->alias should point only to a user-written alias, but in these cases that principle was violated. Fixing this causes some regression test output changes: wherever rte->alias previously had a value and is now NULL, rte->eref is now set to a generated name rather than to rte->alias; and the scheme used to generate eref names differs from what we were doing for aliases. The upshot is that instead of "*SELECT*" or "*SELECT* %d", EXPLAIN will now emit "unnamed_subquery" or "unnamed_subquery_%d". But that's a reasonable descriptor, and we were already producing that in yet other cases, so this seems not too objectionable. Author: Tom Lane <[email protected]> Co-authored-by: Robert Haas <[email protected]> Discussion: https://postgr.es/m/CA+TgmoYSYmDA2GvanzPMci084n+mVucv0bJ0HPbs6uhmMN6HMg@mail.gmail.com --- contrib/postgres_fdw/expected/postgres_fdw.out | 8 ++++---- src/backend/executor/functions.c | 2 +- src/backend/parser/analyze.c | 7 ++----- src/test/regress/expected/partition_prune.out | 4 ++-- src/test/regress/expected/rangefuncs.out | 8 ++++---- src/test/regress/expected/union.out | 14 +++++++------- 6 files changed, 20 insertions(+), 23 deletions(-) diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index 4b6e49a5d95..c492ba38c58 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -5098,13 +5098,13 @@ SELECT ft1.c1 FROM ft1 JOIN ft2 on ft1.c1 = ft2.c1 WHERE -- =================================================================== EXPLAIN (verbose, costs off) INSERT INTO ft2 (c1,c2,c3) SELECT c1+1000,c2+100, c3 || c3 FROM ft2 LIMIT 20; - QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Insert on public.ft2 Remote SQL: INSERT INTO "S 1"."T 1"("C 1", c2, c3, c4, c5, c6, c7, c8) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) Batch Size: 1 - -> Subquery Scan on "*SELECT*" - Output: "*SELECT*"."?column?", "*SELECT*"."?column?_1", NULL::integer, "*SELECT*"."?column?_2", NULL::timestamp with time zone, NULL::timestamp without time zone, NULL::character varying(10), 'ft2 '::character(10), NULL::user_enum + -> Subquery Scan on unnamed_subquery + Output: unnamed_subquery."?column?", unnamed_subquery."?column?_1", NULL::integer, unnamed_subquery."?column?_2", NULL::timestamp with time zone, NULL::timestamp without time zone, NULL::character varying(10), 'ft2 '::character(10), NULL::user_enum -> Foreign Scan on public.ft2 ft2_1 Output: (ft2_1.c1 + 1000), (ft2_1.c2 + 100), (ft2_1.c3 || ft2_1.c3) Remote SQL: SELECT "C 1", c2, c3 FROM "S 1"."T 1" LIMIT 20::bigint diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c index 359aafea681..0547fda2101 100644 --- a/src/backend/executor/functions.c +++ b/src/backend/executor/functions.c @@ -2454,7 +2454,7 @@ tlist_coercion_finished: rte = makeNode(RangeTblEntry); rte->rtekind = RTE_SUBQUERY; rte->subquery = parse; - rte->eref = rte->alias = makeAlias("*SELECT*", colnames); + rte->eref = makeAlias("unnamed_subquery", colnames); rte->lateral = false; rte->inh = false; rte->inFromCl = true; diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index 34f7c17f576..b9763ea1714 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -777,7 +777,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt) */ nsitem = addRangeTableEntryForSubquery(pstate, selectQuery, - makeAlias("*SELECT*", NIL), + NULL, false, false); addNSItemToQuery(pstate, nsitem, true, false, false); @@ -2100,7 +2100,6 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt, { /* Process leaf SELECT */ Query *selectQuery; - char selectName[32]; ParseNamespaceItem *nsitem; RangeTblRef *rtr; ListCell *tl; @@ -2156,11 +2155,9 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt, /* * Make the leaf query be a subquery in the top-level rangetable. */ - snprintf(selectName, sizeof(selectName), "*SELECT* %d", - list_length(pstate->p_rtable) + 1); nsitem = addRangeTableEntryForSubquery(pstate, selectQuery, - makeAlias(selectName, NIL), + NULL, false, false); diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out index d1966cd7d82..68ecd951809 100644 --- a/src/test/regress/expected/partition_prune.out +++ b/src/test/regress/expected/partition_prune.out @@ -4763,7 +4763,7 @@ select min(a) over (partition by a order by a) from part_abc where a >= stable_o QUERY PLAN ---------------------------------------------------------------------------------------------- Append - -> Subquery Scan on "*SELECT* 1_1" + -> Subquery Scan on unnamed_subquery_2 -> WindowAgg Window: w1 AS (PARTITION BY part_abc.a ORDER BY part_abc.a) -> Append @@ -4780,7 +4780,7 @@ select min(a) over (partition by a order by a) from part_abc where a >= stable_o -> Index Scan using part_abc_3_2_a_idx on part_abc_3_2 part_abc_4 Index Cond: (a >= (stable_one() + 1)) Filter: (d <= stable_one()) - -> Subquery Scan on "*SELECT* 2" + -> Subquery Scan on unnamed_subquery_1 -> WindowAgg Window: w1 AS (PARTITION BY part_abc_5.a ORDER BY part_abc_5.a) -> Append diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out index c21be83aa4a..30241e22da2 100644 --- a/src/test/regress/expected/rangefuncs.out +++ b/src/test/regress/expected/rangefuncs.out @@ -2130,10 +2130,10 @@ select testrngfunc(); explain (verbose, costs off) select * from testrngfunc(); - QUERY PLAN ----------------------------------------------------------- - Subquery Scan on "*SELECT*" - Output: "*SELECT*"."?column?", "*SELECT*"."?column?_1" + QUERY PLAN +---------------------------------------------------------------------- + Subquery Scan on unnamed_subquery + Output: unnamed_subquery."?column?", unnamed_subquery."?column?_1" -> Unique Output: (1), (2) -> Sort diff --git a/src/test/regress/expected/union.out b/src/test/regress/expected/union.out index 96962817ed4..d3ea433db15 100644 --- a/src/test/regress/expected/union.out +++ b/src/test/regress/expected/union.out @@ -942,7 +942,7 @@ SELECT q1 FROM int8_tbl EXCEPT SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1; ERROR: column "q2" does not exist LINE 1: ... int8_tbl EXCEPT SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1... ^ -DETAIL: There is a column named "q2" in table "*SELECT* 2", but it cannot be referenced from this part of the query. +DETAIL: There is a column named "q2" in table "unnamed_subquery", but it cannot be referenced from this part of the query. -- But this should work: SELECT q1 FROM int8_tbl EXCEPT (((SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1))) ORDER BY 1; q1 @@ -1338,14 +1338,14 @@ where q2 = q2; ---------------------------------------------------------- Unique -> Merge Append - Sort Key: "*SELECT* 1".q1 - -> Subquery Scan on "*SELECT* 1" + Sort Key: unnamed_subquery.q1 + -> Subquery Scan on unnamed_subquery -> Unique -> Sort Sort Key: i81.q1, i81.q2 -> Seq Scan on int8_tbl i81 Filter: (q2 IS NOT NULL) - -> Subquery Scan on "*SELECT* 2" + -> Subquery Scan on unnamed_subquery_1 -> Unique -> Sort Sort Key: i82.q1, i82.q2 @@ -1374,14 +1374,14 @@ where -q1 = q2; -------------------------------------------------------- Unique -> Merge Append - Sort Key: "*SELECT* 1".q1 - -> Subquery Scan on "*SELECT* 1" + Sort Key: unnamed_subquery.q1 + -> Subquery Scan on unnamed_subquery -> Unique -> Sort Sort Key: i81.q1, i81.q2 -> Seq Scan on int8_tbl i81 Filter: ((- q1) = q2) - -> Subquery Scan on "*SELECT* 2" + -> Subquery Scan on unnamed_subquery_1 -> Unique -> Sort Sort Key: i82.q1, i82.q2 -- 2.43.5 [text/x-diff] v3-0002-Don-t-generate-fake-ANY_subquery-aliases-either.patch (4.0K, ../../[email protected]/3-v3-0002-Don-t-generate-fake-ANY_subquery-aliases-either.patch) download | inline diff: From 4bf665212789c0856dd4b166ca9e0359bca3994e Mon Sep 17 00:00:00 2001 From: Robert Haas <[email protected]> Date: Wed, 23 Jul 2025 17:12:34 -0400 Subject: [PATCH v3 2/3] Don't generate fake "ANY_subquery" aliases, either. This is just like the previous commit, but for a different invented alias name. Author: Robert Haas <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/CA+TgmoYSYmDA2GvanzPMci084n+mVucv0bJ0HPbs6uhmMN6HMg@mail.gmail.com --- src/backend/optimizer/plan/subselect.c | 2 +- src/test/regress/expected/memoize.out | 8 ++++---- src/test/regress/expected/subselect.out | 14 +++++++------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c index d71ed958e31..fae18548e07 100644 --- a/src/backend/optimizer/plan/subselect.c +++ b/src/backend/optimizer/plan/subselect.c @@ -1397,7 +1397,7 @@ convert_ANY_sublink_to_join(PlannerInfo *root, SubLink *sublink, */ nsitem = addRangeTableEntryForSubquery(pstate, subselect, - makeAlias("ANY_subquery", NIL), + NULL, use_lateral, false); rte = nsitem->p_rte; diff --git a/src/test/regress/expected/memoize.out b/src/test/regress/expected/memoize.out index 150dc1b44cf..fbcaf113266 100644 --- a/src/test/regress/expected/memoize.out +++ b/src/test/regress/expected/memoize.out @@ -545,15 +545,15 @@ EXPLAIN (COSTS OFF) SELECT * FROM tab_anti t1 WHERE t1.a IN (SELECT a FROM tab_anti t2 WHERE t2.b IN (SELECT t1.b FROM tab_anti t3 WHERE t2.a > 1 OFFSET 0)); - QUERY PLAN -------------------------------------------------- + QUERY PLAN +--------------------------------------------------- Nested Loop Semi Join -> Seq Scan on tab_anti t1 -> Nested Loop Semi Join Join Filter: (t1.a = t2.a) -> Seq Scan on tab_anti t2 - -> Subquery Scan on "ANY_subquery" - Filter: (t2.b = "ANY_subquery".b) + -> Subquery Scan on unnamed_subquery + Filter: (t2.b = unnamed_subquery.b) -> Result One-Time Filter: (t2.a > 1) -> Seq Scan on tab_anti t3 diff --git a/src/test/regress/expected/subselect.out b/src/test/regress/expected/subselect.out index 18fed63e738..54df8fcbba9 100644 --- a/src/test/regress/expected/subselect.out +++ b/src/test/regress/expected/subselect.out @@ -1467,14 +1467,14 @@ select * from int4_tbl o where (f1, f1) in ------------------------------------------------------------------- Nested Loop Semi Join Output: o.f1 - Join Filter: (o.f1 = "ANY_subquery".f1) + Join Filter: (o.f1 = unnamed_subquery.f1) -> Seq Scan on public.int4_tbl o Output: o.f1 -> Materialize - Output: "ANY_subquery".f1, "ANY_subquery".g - -> Subquery Scan on "ANY_subquery" - Output: "ANY_subquery".f1, "ANY_subquery".g - Filter: ("ANY_subquery".f1 = "ANY_subquery".g) + Output: unnamed_subquery.f1, unnamed_subquery.g + -> Subquery Scan on unnamed_subquery + Output: unnamed_subquery.f1, unnamed_subquery.g + Filter: (unnamed_subquery.f1 = unnamed_subquery.g) -> Result Output: i.f1, ((generate_series(1, 50)) / 10) -> ProjectSet @@ -2642,8 +2642,8 @@ ON B.hundred in (SELECT min(c.hundred) FROM tenk2 C WHERE c.odd = b.odd); -> Memoize Cache Key: b.hundred, b.odd Cache Mode: binary - -> Subquery Scan on "ANY_subquery" - Filter: (b.hundred = "ANY_subquery".min) + -> Subquery Scan on unnamed_subquery + Filter: (b.hundred = unnamed_subquery.min) -> Result InitPlan 1 -> Limit -- 2.43.5 [text/x-diff] v3-0003-Don-t-generate-fake-TLOCRN-or-TROCRN-aliases-eith.patch (1.7K, ../../[email protected]/4-v3-0003-Don-t-generate-fake-TLOCRN-or-TROCRN-aliases-eith.patch) download | inline diff: From e92da9d1520e8feb71aa6fd42515c4e34b862535 Mon Sep 17 00:00:00 2001 From: Robert Haas <[email protected]> Date: Wed, 23 Jul 2025 17:16:20 -0400 Subject: [PATCH v3 3/3] Don't generate fake "*TLOCRN*" or "*TROCRN*" aliases, either. This fix actually doesn't change any regression test outputs. Author: Robert Haas <[email protected]> Reviewed-by: Tom Lane <[email protected]> Discussion: https://postgr.es/m/CA+TgmoYSYmDA2GvanzPMci084n+mVucv0bJ0HPbs6uhmMN6HMg@mail.gmail.com --- src/backend/rewrite/rewriteSearchCycle.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/backend/rewrite/rewriteSearchCycle.c b/src/backend/rewrite/rewriteSearchCycle.c index 19b89dee0d0..3b1a9dfc576 100644 --- a/src/backend/rewrite/rewriteSearchCycle.c +++ b/src/backend/rewrite/rewriteSearchCycle.c @@ -282,8 +282,8 @@ rewriteSearchAndCycle(CommonTableExpr *cte) newrte = makeNode(RangeTblEntry); newrte->rtekind = RTE_SUBQUERY; - newrte->alias = makeAlias("*TLOCRN*", cte->ctecolnames); - newrte->eref = newrte->alias; + newrte->alias = NULL; + newrte->eref = makeAlias("*TLOCRN*", cte->ctecolnames); newsubquery = copyObject(rte1->subquery); IncrementVarSublevelsUp((Node *) newsubquery, 1, 1); newrte->subquery = newsubquery; @@ -379,8 +379,8 @@ rewriteSearchAndCycle(CommonTableExpr *cte) ewcl = lappend(ewcl, makeString(cte->cycle_clause->cycle_mark_column)); ewcl = lappend(ewcl, makeString(cte->cycle_clause->cycle_path_column)); } - newrte->alias = makeAlias("*TROCRN*", ewcl); - newrte->eref = newrte->alias; + newrte->alias = NULL; + newrte->eref = makeAlias("*TROCRN*", ewcl); /* * Find the reference to the recursive CTE in the right UNION subquery's -- 2.43.5 ^ permalink raw reply [nested|flat] 75+ messages in thread
* Re: magical eref alias names @ 2025-09-08 17:26 Robert Haas <[email protected]> parent: Tom Lane <[email protected]> 0 siblings, 0 replies; 75+ messages in thread From: Robert Haas @ 2025-09-08 17:26 UTC (permalink / raw) To: Tom Lane <[email protected]>; +Cc: Peter Eisentraut <[email protected]>; pgsql-hackers On Wed, Jul 23, 2025 at 5:35 PM Tom Lane <[email protected]> wrote: > [ returning to this thread now that v19 is open for business ] Thanks for your attention to this, and sorry for the slow response. > I think the case this is worried about is that if you have a parent > table t(a,b,c), and the query writes say "t as t1(x)", then t's column > "a" will be labeled "x" by EXPLAIN and we want the child's column "a" > to similarly be labeled "x". OK, but I don't quite see how that would go wrong. My proposal was to assign eref to eref and alias to alias. If the parent has no alias, then the child would also have no alias, but the eref would match. If the parent does have an alias, then the child would end up with an alias matching the parent, which would presumably take precedence over the eref that would also match the parent. For what we're doing now to be necessary, there must be something in ruleutils.c that either needs the eref and alias of a child to match, or needs the eref of a child to match the alias of the parent, unless I'm missing something. There might well be such a thing, I'm just not sure what it is. > We could certainly do something a little different than what the code > is doing, such as "if the parent does have a user-written alias, use > parentrte->alias->aliasname not parentrte->eref->aliasname for the > childrte->alias->aliasname". I'm not sure it's worth bothering with > that, though. I don't have a clear opinion on this. I don't understand well enough what's being done here. I don't think not doing this is going to cause my development plans any immediate problems, but some of this stuff is quite fiddly and hard to understand. > I noticed that the patchset was failing in cfbot because we've since > grown another regression test case whose output is affected by 0002. > So here's a v3 that incorporates that additional change. I did a > little bit of wordsmithing on the commit messages too, but the code > changes are identical to v2. Thanks. I committed these today, after editing the commit message for 0003 a bit more. -- Robert Haas EDB: http://www.enterprisedb.com ^ permalink raw reply [nested|flat] 75+ messages in thread
end of thread, other threads:[~2025-09-08 17:26 UTC | newest] Thread overview: 75+ messages (download: mbox mbox.gz follow: Atom feed) -- links below jump to the message on this page -- 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2021-03-04 23:45 [PATCH 3/8] Process all scan keys in existing BRIN opclasses Tomas Vondra <[email protected]> 2023-01-09 08:59 Re: [PoC] Improve dead tuple storage for lazy vacuum John Naylor <[email protected]> 2025-07-23 21:35 Re: magical eref alias names Tom Lane <[email protected]> 2025-09-08 17:26 ` Re: magical eref alias names Robert Haas <[email protected]>
This inbox is served by agora; see mirroring instructions for how to clone and mirror all data and code used for this inbox