/* * brin_inclusion.c * Implementation of inclusion opclasses for BRIN * * This module provides framework BRIN support functions for the "inclusion" * operator classes. A few SQL-level support functions are also required for * each opclass. * * The "inclusion" BRIN strategy is useful for types that support R-Tree * operations. This implementation is a straight mapping of those operations * to the block-range nature of BRIN, with two exceptions: (a) we explicitly * support "empty" elements: at least with range types, we need to consider * emptiness separately from regular R-Tree strategies; and (b) we need to * consider "unmergeable" elements, that is, a set of elements for whose union * no representation exists. The only case where that happens as of this * writing is the INET type, where IPv6 values cannot be merged with IPv4 * values. * * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION * src/backend/access/brin/brin_inclusion.c */ #include "postgres.h" #include "access/brin_internal.h" #include "access/brin_tuple.h" #include "access/genam.h" #include "access/skey.h" #include "catalog/pg_amop.h" #include "catalog/pg_type.h" #include "utils/builtins.h" #include "utils/datum.h" #include "utils/lsyscache.h" #include "utils/rel.h" #include "utils/syscache.h" /* * Additional SQL level support functions * * Procedure numbers must not use values reserved for BRIN itself; see * brin_internal.h. */ #define INCLUSION_MAX_PROCNUMS 4 /* maximum support procs we need */ #define PROCNUM_MERGE 11 /* required */ #define PROCNUM_MERGEABLE 12 /* optional */ #define PROCNUM_CONTAINS 13 /* optional */ #define PROCNUM_EMPTY 14 /* optional */ /* * Subtract this from procnum to obtain index in InclusionOpaque arrays * (Must be equal to minimum of private procnums). */ #define PROCNUM_BASE 11 /*- * The values stored in the bv_values arrays correspond to: * * INCLUSION_UNION * the union of the values in the block range * INCLUSION_UNMERGEABLE * whether the values in the block range cannot be merged * (e.g. an IPv6 address amidst IPv4 addresses) * INCLUSION_CONTAINS_EMPTY * whether an empty value is present in any tuple * in the block range */ #define INCLUSION_UNION 0 #define INCLUSION_UNMERGEABLE 1 #define INCLUSION_CONTAINS_EMPTY 2 typedef struct InclusionOpaque { FmgrInfo extra_procinfos[INCLUSION_MAX_PROCNUMS]; bool extra_proc_missing[INCLUSION_MAX_PROCNUMS]; Oid cached_subtype; FmgrInfo strategy_procinfos[RTMaxStrategyNumber]; } InclusionOpaque; 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); /* * BRIN inclusion OpcInfo function */ Datum brin_inclusion_opcinfo(PG_FUNCTION_ARGS) { Oid typoid = PG_GETARG_OID(0); BrinOpcInfo *result; TypeCacheEntry *bool_typcache = lookup_type_cache(BOOLOID, 0); /* * All members of opaque are initialized lazily; both procinfo arrays * start out as non-initialized by having fn_oid be InvalidOid, and * "missing" to false, by zeroing here. strategy_procinfos elements can * be invalidated when cached_subtype changes by zeroing fn_oid. * extra_procinfo entries are never invalidated, but if a lookup fails * (which is expected), extra_proc_missing is set to true, indicating not * to look it up again. */ result = palloc0(MAXALIGN(SizeofBrinOpcInfo(3)) + sizeof(InclusionOpaque)); result->oi_nstored = 3; result->oi_opaque = (InclusionOpaque *) MAXALIGN((char *) result + SizeofBrinOpcInfo(3)); /* the union */ result->oi_typcache[INCLUSION_UNION] = lookup_type_cache(typoid, 0); /* includes elements that are not mergeable */ result->oi_typcache[INCLUSION_UNMERGEABLE] = bool_typcache; /* includes the empty element */ result->oi_typcache[INCLUSION_CONTAINS_EMPTY] = bool_typcache; PG_RETURN_POINTER(result); } /* * BRIN inclusion add value function * * Examine the given index tuple (which contains partial status of a certain * page range) by comparing it to the given value that comes from another heap * tuple. If the new value is outside the union specified by the existing * tuple values, update the index tuple and return true. Otherwise, return * false and do not modify in this case. */ Datum brin_inclusion_add_value(PG_FUNCTION_ARGS) { BrinDesc *bdesc = (BrinDesc *) PG_GETARG_POINTER(0); BrinValues *column = (BrinValues *) PG_GETARG_POINTER(1); Datum newval = PG_GETARG_DATUM(2); bool isnull = PG_GETARG_BOOL(3); Oid colloid = PG_GET_COLLATION(); FmgrInfo *finfo; Datum result; bool new = false; AttrNumber attno; Form_pg_attribute attr; /* * If the new value is null, we record that we saw it if it's the first * one; otherwise, there's nothing to do. */ if (isnull) { if (column->bv_hasnulls) PG_RETURN_BOOL(false); column->bv_hasnulls = true; PG_RETURN_BOOL(true); } attno = column->bv_attno; attr = TupleDescAttr(bdesc->bd_tupdesc, attno - 1); /* * If the recorded value is null, copy the new value (which we know to be * not null), and we're almost done. */ if (column->bv_allnulls) { column->bv_values[INCLUSION_UNION] = datumCopy(newval, attr->attbyval, attr->attlen); column->bv_values[INCLUSION_UNMERGEABLE] = BoolGetDatum(false); column->bv_values[INCLUSION_CONTAINS_EMPTY] = BoolGetDatum(false); column->bv_allnulls = false; new = true; } /* * No need for further processing if the block range is marked as * containing unmergeable values. */ if (DatumGetBool(column->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_BOOL(false); /* * If the opclass supports the concept of empty values, test the passed * new value for emptiness; if it returns true, we need to set the * "contains empty" flag in the element (unless already set). */ finfo = inclusion_get_procinfo(bdesc, attno, PROCNUM_EMPTY); if (finfo != NULL && DatumGetBool(FunctionCall1Coll(finfo, colloid, newval))) { if (!DatumGetBool(column->bv_values[INCLUSION_CONTAINS_EMPTY])) { column->bv_values[INCLUSION_CONTAINS_EMPTY] = BoolGetDatum(true); PG_RETURN_BOOL(true); } PG_RETURN_BOOL(false); } if (new) PG_RETURN_BOOL(true); /* Check if the new value is already contained. */ finfo = inclusion_get_procinfo(bdesc, attno, PROCNUM_CONTAINS); if (finfo != NULL && DatumGetBool(FunctionCall2Coll(finfo, colloid, column->bv_values[INCLUSION_UNION], newval))) PG_RETURN_BOOL(false); /* * Check if the new value is mergeable to the existing union. If it is * not, mark the value as containing unmergeable elements and get out. * * Note: at this point we could remove the value from the union, since * it's not going to be used any longer. However, the BRIN framework * doesn't allow for the value not being present. Improve someday. */ finfo = inclusion_get_procinfo(bdesc, attno, PROCNUM_MERGEABLE); if (finfo != NULL && !DatumGetBool(FunctionCall2Coll(finfo, colloid, column->bv_values[INCLUSION_UNION], newval))) { column->bv_values[INCLUSION_UNMERGEABLE] = BoolGetDatum(true); PG_RETURN_BOOL(true); } /* Finally, merge the new value to the existing union. */ finfo = inclusion_get_procinfo(bdesc, attno, PROCNUM_MERGE); Assert(finfo != NULL); result = FunctionCall2Coll(finfo, colloid, column->bv_values[INCLUSION_UNION], newval); if (!attr->attbyval) pfree(DatumGetPointer(column->bv_values[INCLUSION_UNION])); column->bv_values[INCLUSION_UNION] = result; PG_RETURN_BOOL(true); } /* * BRIN inclusion consistent function * * All of the strategies are optional. */ Datum 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); /* Handle IS NULL/IS NOT NULL tests. */ if (key->sk_flags & SK_ISNULL) { if (key->sk_flags & SK_SEARCHNULL) { if (column->bv_allnulls || column->bv_hasnulls) PG_RETURN_BOOL(true); PG_RETURN_BOOL(false); } /* * For IS NOT NULL, we can only skip ranges that are known to have * only nulls. */ if (key->sk_flags & SK_SEARCHNOTNULL) PG_RETURN_BOOL(!column->bv_allnulls); /* * Neither IS NULL nor IS NOT NULL was used; assume all indexable * operators are strict and return false. */ PG_RETURN_BOOL(false); } /* If it is all nulls, it cannot possibly be consistent. */ if (column->bv_allnulls) PG_RETURN_BOOL(false); /* It has to be checked, if it contains elements that are not mergeable. */ 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]; switch (key->sk_strategy) { /* * Placement strategies * * These are implemented by logically negating the result of the * converse placement operator; for this to work, the converse * operator must be part of the opclass. An error will be thrown * by inclusion_get_strategy_procinfo() if the required strategy * is not part of the opclass. * * These all return false if either argument is empty, so there is * no need to check for empty elements. */ case RTLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); PG_RETURN_BOOL(!DatumGetBool(result)); case RTOverLeftStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); PG_RETURN_BOOL(!DatumGetBool(result)); case RTOverRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); PG_RETURN_BOOL(!DatumGetBool(result)); case RTRightStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); PG_RETURN_BOOL(!DatumGetBool(result)); case RTBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); PG_RETURN_BOOL(!DatumGetBool(result)); case RTOverBelowStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAboveStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); PG_RETURN_BOOL(!DatumGetBool(result)); case RTOverAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); PG_RETURN_BOOL(!DatumGetBool(result)); case RTAboveStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverBelowStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); PG_RETURN_BOOL(!DatumGetBool(result)); /* * Overlap and contains strategies * * These strategies are simple enough that we can simply call the * operator and return its result. Empty elements don't change * the result. */ case RTOverlapStrategyNumber: case RTContainsStrategyNumber: case RTOldContainsStrategyNumber: case RTContainsElemStrategyNumber: case RTSubStrategyNumber: case RTSubEqualStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, key->sk_strategy); result = FunctionCall2Coll(finfo, colloid, unionval, query); PG_RETURN_DATUM(result); /* * Contained by strategies * * We cannot just call the original operator for the contained by * strategies because some elements can be contained even though * the union is not; instead we use the overlap operator. * * We check for empty elements separately as they are not merged * to the union but contained by everything. */ case RTContainedByStrategyNumber: case RTOldContainedByStrategyNumber: case RTSuperStrategyNumber: case RTSuperEqualStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) PG_RETURN_BOOL(true); PG_RETURN_DATUM(column->bv_values[INCLUSION_CONTAINS_EMPTY]); /* * Adjacent strategy * * We test for overlap first but to be safe we need to call the * actual adjacent operator also. * * An empty element cannot be adjacent to any other, so there is * no need to check for it. */ case RTAdjacentStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTOverlapStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) PG_RETURN_BOOL(true); finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTAdjacentStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); PG_RETURN_DATUM(result); /* * Basic comparison strategies * * It is straightforward to support the equality strategies with * the contains operator. Generally, inequality strategies do not * make much sense for the types which will be used with the * inclusion BRIN family of opclasses, but is possible to * implement them with logical negation of the left-of and * right-of operators. * * NB: These strategies cannot be used with geometric datatypes * that use comparison of areas! The only exception is the "same" * strategy. * * Empty elements are considered to be less than the others. We * cannot use the empty support function to check the query is an * empty element, because the query can be another data type than * the empty support function argument. So we will return true, * if there is a possibility that empty elements will change the * result. */ case RTLessStrategyNumber: case RTLessEqualStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTRightStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (!DatumGetBool(result)) PG_RETURN_BOOL(true); PG_RETURN_DATUM(column->bv_values[INCLUSION_CONTAINS_EMPTY]); case RTSameStrategyNumber: case RTEqualStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTContainsStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (DatumGetBool(result)) PG_RETURN_BOOL(true); PG_RETURN_DATUM(column->bv_values[INCLUSION_CONTAINS_EMPTY]); case RTGreaterEqualStrategyNumber: finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); if (!DatumGetBool(result)) PG_RETURN_BOOL(true); PG_RETURN_DATUM(column->bv_values[INCLUSION_CONTAINS_EMPTY]); case RTGreaterStrategyNumber: /* no need to check for empty elements */ finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype, RTLeftStrategyNumber); result = FunctionCall2Coll(finfo, colloid, unionval, query); PG_RETURN_BOOL(!DatumGetBool(result)); default: /* shouldn't happen */ elog(ERROR, "invalid strategy number %d", key->sk_strategy); PG_RETURN_BOOL(false); } } /* * BRIN inclusion union function * * Given two BrinValues, update the first of them as a union of the summary * values contained in both. The second one is untouched. */ Datum brin_inclusion_union(PG_FUNCTION_ARGS) { BrinDesc *bdesc = (BrinDesc *) PG_GETARG_POINTER(0); BrinValues *col_a = (BrinValues *) PG_GETARG_POINTER(1); BrinValues *col_b = (BrinValues *) PG_GETARG_POINTER(2); Oid colloid = PG_GET_COLLATION(); AttrNumber attno; Form_pg_attribute attr; FmgrInfo *finfo; Datum result; Assert(col_a->bv_attno == col_b->bv_attno); /* Adjust "hasnulls". */ if (!col_a->bv_hasnulls && col_b->bv_hasnulls) col_a->bv_hasnulls = true; /* If there are no values in B, there's nothing left to do. */ if (col_b->bv_allnulls) PG_RETURN_VOID(); attno = col_a->bv_attno; attr = TupleDescAttr(bdesc->bd_tupdesc, attno - 1); /* * Adjust "allnulls". If A doesn't have values, just copy the values from * B into A, and we're done. We cannot run the operators in this case, * because values in A might contain garbage. Note we already established * that B contains values. */ if (col_a->bv_allnulls) { col_a->bv_allnulls = false; col_a->bv_values[INCLUSION_UNION] = datumCopy(col_b->bv_values[INCLUSION_UNION], attr->attbyval, attr->attlen); col_a->bv_values[INCLUSION_UNMERGEABLE] = col_b->bv_values[INCLUSION_UNMERGEABLE]; col_a->bv_values[INCLUSION_CONTAINS_EMPTY] = col_b->bv_values[INCLUSION_CONTAINS_EMPTY]; PG_RETURN_VOID(); } /* If B includes empty elements, mark A similarly, if needed. */ if (!DatumGetBool(col_a->bv_values[INCLUSION_CONTAINS_EMPTY]) && DatumGetBool(col_b->bv_values[INCLUSION_CONTAINS_EMPTY])) col_a->bv_values[INCLUSION_CONTAINS_EMPTY] = BoolGetDatum(true); /* Check if A includes elements that are not mergeable. */ if (DatumGetBool(col_a->bv_values[INCLUSION_UNMERGEABLE])) PG_RETURN_VOID(); /* If B includes elements that are not mergeable, mark A similarly. */ if (DatumGetBool(col_b->bv_values[INCLUSION_UNMERGEABLE])) { col_a->bv_values[INCLUSION_UNMERGEABLE] = BoolGetDatum(true); PG_RETURN_VOID(); } /* Check if A and B are mergeable; if not, mark A unmergeable. */ finfo = inclusion_get_procinfo(bdesc, attno, PROCNUM_MERGEABLE); if (finfo != NULL && !DatumGetBool(FunctionCall2Coll(finfo, colloid, col_a->bv_values[INCLUSION_UNION], col_b->bv_values[INCLUSION_UNION]))) { col_a->bv_values[INCLUSION_UNMERGEABLE] = BoolGetDatum(true); PG_RETURN_VOID(); } /* Finally, merge B to A. */ finfo = inclusion_get_procinfo(bdesc, attno, PROCNUM_MERGE); Assert(finfo != NULL); result = FunctionCall2Coll(finfo, colloid, col_a->bv_values[INCLUSION_UNION], col_b->bv_values[INCLUSION_UNION]); if (!attr->attbyval) pfree(DatumGetPointer(col_a->bv_values[INCLUSION_UNION])); col_a->bv_values[INCLUSION_UNION] = result; PG_RETURN_VOID(); } /* * Cache and return inclusion opclass support procedure * * Return the procedure corresponding to the given function support number * or null if it is not exists. */ static FmgrInfo * inclusion_get_procinfo(BrinDesc *bdesc, uint16 attno, uint16 procnum) { InclusionOpaque *opaque; uint16 basenum = procnum - PROCNUM_BASE; /* * We cache these in the opaque struct, to avoid repetitive syscache * lookups. */ opaque = (InclusionOpaque *) bdesc->bd_info[attno - 1]->oi_opaque; /* * If we already searched for this proc and didn't find it, don't bother * searching again. */ if (opaque->extra_proc_missing[basenum]) return NULL; if (opaque->extra_procinfos[basenum].fn_oid == InvalidOid) { if (RegProcedureIsValid(index_getprocid(bdesc->bd_index, attno, procnum))) { fmgr_info_copy(&opaque->extra_procinfos[basenum], index_getprocinfo(bdesc->bd_index, attno, procnum), bdesc->bd_context); } else { opaque->extra_proc_missing[basenum] = true; return NULL; } } return &opaque->extra_procinfos[basenum]; } /* * Cache and return the procedure of the given strategy * * Return the procedure corresponding to the given sub-type and strategy * number. The data type of the index will be used as the left hand side of * the operator and the given sub-type will be used as the right hand side. * Throws an error if the pg_amop row does not exist, but that should not * happen with a properly configured opclass. * * It always throws an error when the data type of the opclass is different * from the data type of the column or the expression. That happens when the * column data type has implicit cast to the opclass data type. We don't * bother casting types, because this situation can easily be avoided by * setting storage data type to that of the opclass. The same problem does not * apply to the data type of the right hand side, because the type in the * ScanKey always matches the opclass' one. * * Note: this function mirrors minmax_get_strategy_procinfo; if changes are * made here, see that function too. */ static FmgrInfo * inclusion_get_strategy_procinfo(BrinDesc *bdesc, uint16 attno, Oid subtype, uint16 strategynum) { InclusionOpaque *opaque; Assert(strategynum >= 1 && strategynum <= RTMaxStrategyNumber); opaque = (InclusionOpaque *) bdesc->bd_info[attno - 1]->oi_opaque; /* * We cache the procedures for the last sub-type in the opaque struct, to * avoid repetitive syscache lookups. If the sub-type is changed, * invalidate all the cached entries. */ if (opaque->cached_subtype != subtype) { uint16 i; for (i = 1; i <= RTMaxStrategyNumber; i++) opaque->strategy_procinfos[i - 1].fn_oid = InvalidOid; opaque->cached_subtype = subtype; } if (opaque->strategy_procinfos[strategynum - 1].fn_oid == InvalidOid) { Form_pg_attribute attr; HeapTuple tuple; Oid opfamily, oprid; bool isNull; opfamily = bdesc->bd_index->rd_opfamily[attno - 1]; attr = TupleDescAttr(bdesc->bd_tupdesc, attno - 1); tuple = SearchSysCache4(AMOPSTRATEGY, ObjectIdGetDatum(opfamily), ObjectIdGetDatum(attr->atttypid), ObjectIdGetDatum(subtype), Int16GetDatum(strategynum)); if (!HeapTupleIsValid(tuple)) elog(ERROR, "missing operator %d(%u,%u) in opfamily %u", strategynum, attr->atttypid, subtype, opfamily); oprid = DatumGetObjectId(SysCacheGetAttr(AMOPSTRATEGY, tuple, Anum_pg_amop_amopopr, &isNull)); ReleaseSysCache(tuple); Assert(!isNull && RegProcedureIsValid(oprid)); fmgr_info_cxt(get_opcode(oprid), &opaque->strategy_procinfos[strategynum - 1], bdesc->bd_context); } return &opaque->strategy_procinfos[strategynum - 1]; }