text: /*
   * 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-2017, 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:
   *
   * 0 - the union of the values in the block range
   * 1 - whether an empty value is present in any tuple in the block range
   * 2 - whether the values in the block range cannot be merged (e.g. an IPv6
   *	   address amidst IPv4 addresses).
   */
  #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];
  }
  