public inbox for [email protected]  
help / color / mirror / Atom feed
[PATCH v5 1/2] Add a new OUTDATED filtering facility for REINDEX command.
5+ messages / 2 participants
[nested] [flat]

* [PATCH v5 1/2] Add a new OUTDATED filtering facility for REINDEX command.
@ 2020-12-03 07:54  Julien Rouhaud <[email protected]>
  0 siblings, 0 replies; 5+ messages in thread

From: Julien Rouhaud @ 2020-12-03 07:54 UTC (permalink / raw)

OUTDATED is added a new unreserved keyword.

When used, REINDEX will only process indexes that have an outdated dependency.
For now, only dependency on collations are supported but we'll likely support
other kind of dependency in the future.

Also add a new pg_index_has_outdated_dependency(regclass) SQL function, so
client code can filter such indexes if needed.  This function will also be used
in a following commit to teach reindexdb to use this new OUTDATED option and
order the tables by the amount of work that will actually be done.

Catversion (should be) bumped.

Author: Julien Rouhaud <[email protected]>
Reviewed-by:
Discussion: https://postgr.es/m/20201203093143.GA64934%40nol
---
 doc/src/sgml/func.sgml                     |  27 ++++--
 doc/src/sgml/ref/reindex.sgml              |  12 +++
 src/backend/catalog/index.c                | 107 ++++++++++++++++++++-
 src/backend/commands/indexcmds.c           |  12 ++-
 src/backend/parser/gram.y                  |   4 +-
 src/backend/utils/cache/relcache.c         |  40 ++++++++
 src/bin/psql/tab-complete.c                |   2 +-
 src/include/catalog/index.h                |   3 +
 src/include/catalog/pg_proc.dat            |   4 +
 src/include/parser/kwlist.h                |   1 +
 src/include/utils/relcache.h               |   1 +
 src/test/regress/expected/create_index.out |  10 ++
 src/test/regress/sql/create_index.sql      |  10 ++
 13 files changed, 221 insertions(+), 12 deletions(-)

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index bf99f82149..2cf6e66234 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -26381,12 +26381,13 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
 
    <para>
     <xref linkend="functions-admin-index-table"/> shows the functions
-    available for index maintenance tasks.  (Note that these maintenance
-    tasks are normally done automatically by autovacuum; use of these
-    functions is only required in special cases.)
-    These functions cannot be executed during recovery.
-    Use of these functions is restricted to superusers and the owner
-    of the given index.
+    available for index maintenance tasks.  (Note that the maintenance
+    tasks performing actions on indexes are normally done automatically by
+    autovacuum; use of these functions is only required in special cases.)
+    The functions performing actions on indexes cannot be executed during
+    recovery.
+    Use of the functions performing actions on indexes is restricted to
+    superusers and the owner of the given index.
    </para>
 
    <table id="functions-admin-index-table">
@@ -26471,6 +26472,20 @@ SELECT pg_size_pretty(sum(pg_relation_size(relid))) AS total_size
         option.
        </para></entry>
       </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_index_has_outdated_dependency</primary>
+        </indexterm>
+        <function>pg_index_has_outdated_dependency</function> ( <parameter>index</parameter> <type>regclass</type> )
+        <returnvalue>boolean</returnvalue>
+       </para>
+       <para>
+        Check if the specified index has any outdated dependency.  For now only
+        dependency on collations are supported.
+       </para></entry>
+      </row>
      </tbody>
     </tgroup>
    </table>
diff --git a/doc/src/sgml/ref/reindex.sgml b/doc/src/sgml/ref/reindex.sgml
index b22d39eba9..2d94d49cde 100644
--- a/doc/src/sgml/ref/reindex.sgml
+++ b/doc/src/sgml/ref/reindex.sgml
@@ -26,6 +26,7 @@ REINDEX [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] { IN
 <phrase>where <replaceable class="parameter">option</replaceable> can be one of:</phrase>
 
     CONCURRENTLY [ <replaceable class="parameter">boolean</replaceable> ]
+    OUTDATED [ <replaceable class="parameter">boolean</replaceable> ]
     TABLESPACE <replaceable class="parameter">new_tablespace</replaceable>
     VERBOSE [ <replaceable class="parameter">boolean</replaceable> ]
 </synopsis>
@@ -188,6 +189,17 @@ REINDEX [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] { IN
     </listitem>
    </varlistentry>
 
+   <varlistentry>
+    <term><literal>OUTDATED</literal></term>
+    <listitem>
+     <para>
+      This option can be used to filter the list of indexes to rebuild and only
+      process indexes that have outdated dependencies.  Fow now, the only
+      handle dependency is for the collation provider version.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry>
     <term><literal>TABLESPACE</literal></term>
     <listitem>
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 4ef61b5efd..571feac5db 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -100,6 +100,12 @@ typedef struct
 	Oid			pendingReindexedIndexes[FLEXIBLE_ARRAY_MEMBER];
 } SerializedReindexState;
 
+typedef struct
+{
+	Oid relid;	/* targetr index oid */
+	bool outdated;	/* depends on at least on deprected collation? */
+} IndexHasOutdatedColl;
+
 /* non-export function prototypes */
 static bool relationHasPrimaryKey(Relation rel);
 static TupleDesc ConstructTupleDescriptor(Relation heapRelation,
@@ -1351,6 +1357,105 @@ index_check_collation_versions(Oid relid)
 	list_free(context.warned_colls);
 }
 
+/*
+ * Detect if an index depends on at least one outdated collation.
+ * This is a callback for visitDependenciesOf().
+ */
+static bool
+do_check_index_has_outdated_collation(const ObjectAddress *otherObject,
+										const char *version,
+										char **new_version,
+										void *data)
+{
+	IndexHasOutdatedColl *context = data;
+	char *current_version;
+
+	/* We only care about dependencies on collations. */
+	if (otherObject->classId != CollationRelationId)
+		return false;
+
+	/* Fast exit if we already found a outdated collation version. */
+	if (context->outdated)
+		return false;
+
+	/* Ask the provider for the current version.  Give up if unsupported. */
+	current_version = get_collation_version_for_oid(otherObject->objectId,
+													false);
+	if (!current_version)
+		return false;
+
+	if (!version || strcmp(version, current_version) != 0)
+		context->outdated = true;
+
+	return false;
+}
+
+Datum
+pg_index_has_outdated_dependency(PG_FUNCTION_ARGS)
+{
+	Oid			indexOid = PG_GETARG_OID(0);
+	Relation	rel;
+	bool		isIndex;
+	bool		res;
+
+	rel = try_relation_open(indexOid, AccessShareLock);
+
+	if (rel == NULL)
+		PG_RETURN_NULL();
+
+	isIndex = rel->rd_rel->relkind == RELKIND_INDEX;
+
+	if (!isIndex)
+	{
+		relation_close(rel, AccessShareLock);
+		PG_RETURN_NULL();
+	}
+
+	res = index_has_outdated_dependency(indexOid);
+
+	relation_close(rel, AccessShareLock);
+
+	PG_RETURN_BOOL(res);
+}
+
+/*
+ * Check whether the given index has a dependency with an outdated
+ * collation version.
+ * Caller must hold a suitable lock and make sure that the given Oid belongs to
+ * an index.
+ */
+bool
+index_has_outdated_collation(Oid indexOid)
+{
+	ObjectAddress object;
+	IndexHasOutdatedColl context;
+
+	object.classId = RelationRelationId;
+	object.objectId = indexOid;
+	object.objectSubId = 0;
+
+	context.relid = indexOid;
+	context.outdated = false;
+
+	visitDependenciesOf(&object, &do_check_index_has_outdated_collation,
+						&context);
+
+	return context.outdated;
+}
+
+/*
+ * Check whether the given index has a dependency with an outdated
+ * refobjversion.
+ * Caller must hold a suitable lock and make sure that the given Oid belongs to
+ * an index.
+ * For now, only dependency on collations are supported.
+ */
+bool
+index_has_outdated_dependency(Oid indexOid)
+{
+	return index_has_outdated_collation(indexOid);
+}
+
 /*
  * Update the version for collations.  A callback for visitDependenciesOf().
  */
@@ -3991,7 +4096,7 @@ reindex_relation(Oid relid, int flags, ReindexParams *params)
 	 * relcache to get this with a sequential scan if ignoring system
 	 * indexes.)
 	 */
-	indexIds = RelationGetIndexList(rel);
+	indexIds = RelationGetIndexListFiltered(rel, params->options);
 
 	if (flags & REINDEX_REL_SUPPRESS_INDEX_USE)
 	{
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 8bc652ecd3..0e1bcad101 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -2484,6 +2484,7 @@ ExecReindex(ParseState *pstate, ReindexStmt *stmt, bool isTopLevel)
 	bool		concurrently = false;
 	bool		verbose = false;
 	char	   *tablespacename = NULL;
+	bool		outdated_filter = false;
 
 	/* Parse option list */
 	foreach(lc, stmt->params)
@@ -2496,6 +2497,8 @@ ExecReindex(ParseState *pstate, ReindexStmt *stmt, bool isTopLevel)
 			concurrently = defGetBoolean(opt);
 		else if (strcmp(opt->defname, "tablespace") == 0)
 			tablespacename = defGetString(opt);
+		else if (strcmp(opt->defname, "outdated") == 0)
+			outdated_filter = defGetBoolean(opt);
 		else
 			ereport(ERROR,
 					(errcode(ERRCODE_SYNTAX_ERROR),
@@ -2510,7 +2513,8 @@ ExecReindex(ParseState *pstate, ReindexStmt *stmt, bool isTopLevel)
 
 	params.options =
 		(verbose ? REINDEXOPT_VERBOSE : 0) |
-		(concurrently ? REINDEXOPT_CONCURRENTLY : 0);
+		(concurrently ? REINDEXOPT_CONCURRENTLY : 0) |
+		(outdated_filter ? REINDEXOPT_OUTDATED : 0);
 
 	/*
 	 * Assign the tablespace OID to move indexes to, with InvalidOid to do
@@ -3307,7 +3311,8 @@ ReindexRelationConcurrently(Oid relationOid, ReindexParams *params)
 									RelationGetRelationName(heapRelation))));
 
 				/* Add all the valid indexes of relation to list */
-				foreach(lc, RelationGetIndexList(heapRelation))
+				foreach(lc, RelationGetIndexListFiltered(heapRelation,
+														 params->options))
 				{
 					Oid			cellOid = lfirst_oid(lc);
 					Relation	indexRelation = index_open(cellOid,
@@ -3359,7 +3364,8 @@ ReindexRelationConcurrently(Oid relationOid, ReindexParams *params)
 
 					MemoryContextSwitchTo(oldcontext);
 
-					foreach(lc2, RelationGetIndexList(toastRelation))
+					foreach(lc2, RelationGetIndexListFiltered(toastRelation,
+															  params->options))
 					{
 						Oid			cellOid = lfirst_oid(lc2);
 						Relation	indexRelation = index_open(cellOid,
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 652be0b96d..a67cfa867c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -674,7 +674,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	NULLS_P NUMERIC
 
 	OBJECT_P OF OFF OFFSET OIDS OLD ON ONLY OPERATOR OPTION OPTIONS OR
-	ORDER ORDINALITY OTHERS OUT_P OUTER_P
+	ORDER ORDINALITY OTHERS OUT_P OUTDATED OUTER_P
 	OVER OVERLAPS OVERLAY OVERRIDING OWNED OWNER
 
 	PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POLICY
@@ -15430,6 +15430,7 @@ unreserved_keyword:
 			| OPTIONS
 			| ORDINALITY
 			| OTHERS
+			| OUTDATED
 			| OVER
 			| OVERRIDING
 			| OWNED
@@ -16002,6 +16003,7 @@ bare_label_keyword:
 			| ORDINALITY
 			| OTHERS
 			| OUT_P
+			| OUTDATED
 			| OUTER_P
 			| OVERLAY
 			| OVERRIDING
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 7ef510cd01..06545ee550 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -4620,6 +4620,46 @@ RelationGetIndexList(Relation relation)
 	return result;
 }
 
+/*
+ * RelationGetIndexListFiltered -- get a filtered list of indexes on this
+ * relation.
+ *
+ * Calls RelationGetIndexList and only keep indexes that have an outdated
+ * dependency.  For now, only collation version dependency is supported.
+ */
+List *
+RelationGetIndexListFiltered(Relation relation, bits32 options)
+{
+	List	   *result,
+			   *full_list;
+	ListCell   *lc;
+
+	full_list = RelationGetIndexList(relation);
+
+	/* Fast exit if no filtering was asked, or if the list if empty. */
+	if (((options & REINDEXOPT_OUTDATED) == 0) || full_list == NIL)
+		return full_list;
+
+	result = NIL;
+	foreach(lc, full_list)
+	{
+		Oid		indexOid = lfirst_oid(lc);
+
+		/*
+		 * Check for outdated collation version dependency.
+		 */
+		if (index_has_outdated_collation(indexOid))
+		{
+			result = lappend_oid(result, indexOid);
+			continue;
+		}
+
+		/* Didn't find any outdated dependency, index will be ignored. */
+	}
+
+	return result;
+}
+
 /*
  * RelationGetStatExtList
  *		get a list of OIDs of statistics objects on this relation
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 9f0208ac49..fac3e0476a 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -3674,7 +3674,7 @@ psql_completion(const char *text, int start, int end)
 		 * one word, so the above test is correct.
 		 */
 		if (ends_with(prev_wd, '(') || ends_with(prev_wd, ','))
-			COMPLETE_WITH("CONCURRENTLY", "TABLESPACE", "VERBOSE");
+			COMPLETE_WITH("CONCURRENTLY", "OUTDATED'", "TABLESPACE", "VERBOSE");
 		else if (TailMatches("TABLESPACE"))
 			COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
 	}
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index e22d506436..298c0c633c 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -42,6 +42,7 @@ typedef struct ReindexParams
 #define REINDEXOPT_REPORT_PROGRESS 0x02 /* report pgstat progress */
 #define REINDEXOPT_MISSING_OK 	0x04	/* skip missing relations */
 #define REINDEXOPT_CONCURRENTLY	0x08	/* concurrent mode */
+#define REINDEXOPT_OUTDATED		0x10/* outdated collation only */
 
 /* state info for validate_index bulkdelete callback */
 typedef struct ValidateIndexState
@@ -137,6 +138,8 @@ extern void FormIndexDatum(IndexInfo *indexInfo,
 						   bool *isnull);
 
 extern void index_check_collation_versions(Oid relid);
+extern bool index_has_outdated_collation(Oid indexOid);
+extern bool index_has_outdated_dependency(Oid indexOid);
 extern void index_update_collation_versions(Oid relid, Oid coll);
 
 extern void index_build(Relation heapRelation,
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3d3974f467..4874d33996 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -949,6 +949,10 @@
   proname => 'pg_indexam_has_property', provolatile => 's',
   prorettype => 'bool', proargtypes => 'oid text',
   prosrc => 'pg_indexam_has_property' },
+{ oid => '8102', descr => 'test property of an index',
+  proname => 'pg_index_has_outdated_dependency', provolatile => 's',
+  prorettype => 'bool', proargtypes => 'regclass',
+  prosrc => 'pg_index_has_outdated_dependency' },
 { oid => '637', descr => 'test property of an index',
   proname => 'pg_index_has_property', provolatile => 's', prorettype => 'bool',
   proargtypes => 'regclass text', prosrc => 'pg_index_has_property' },
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 28083aaac9..e6c725d9a6 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -295,6 +295,7 @@ PG_KEYWORD("order", ORDER, RESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("ordinality", ORDINALITY, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("others", OTHERS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("out", OUT_P, COL_NAME_KEYWORD, BARE_LABEL)
+PG_KEYWORD("outdated", OUTDATED, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("outer", OUTER_P, TYPE_FUNC_NAME_KEYWORD, BARE_LABEL)
 PG_KEYWORD("over", OVER, UNRESERVED_KEYWORD, AS_LABEL)
 PG_KEYWORD("overlaps", OVERLAPS, TYPE_FUNC_NAME_KEYWORD, AS_LABEL)
diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h
index 2fcdf79323..a7a2272abd 100644
--- a/src/include/utils/relcache.h
+++ b/src/include/utils/relcache.h
@@ -45,6 +45,7 @@ extern void RelationClose(Relation relation);
  */
 extern List *RelationGetFKeyList(Relation relation);
 extern List *RelationGetIndexList(Relation relation);
+extern List *RelationGetIndexListFiltered(Relation relation, bits32 options);
 extern List *RelationGetStatExtList(Relation relation);
 extern Oid	RelationGetPrimaryKeyIndex(Relation relation);
 extern Oid	RelationGetReplicaIndex(Relation relation);
diff --git a/src/test/regress/expected/create_index.out b/src/test/regress/expected/create_index.out
index 830fdddf24..5f7c49c650 100644
--- a/src/test/regress/expected/create_index.out
+++ b/src/test/regress/expected/create_index.out
@@ -2018,6 +2018,16 @@ INFO:  index "reindex_verbose_pkey" was reindexed
 \set VERBOSITY default
 DROP TABLE reindex_verbose;
 --
+-- REINDEX (OUTDATED)
+--
+CREATE TABLE reindex_coll(id integer primary key);
+\set VERBOSITY terse \\ -- suppress machine-dependent details
+-- no suitable index should be found
+REINDEX (OUTDATED) TABLE reindex_coll;
+NOTICE:  table "reindex_coll" has no indexes to reindex
+\set VERBOSITY default
+DROP TABLE reindex_coll;
+--
 -- REINDEX CONCURRENTLY
 --
 CREATE TABLE concur_reindex_tab (c1 int);
diff --git a/src/test/regress/sql/create_index.sql b/src/test/regress/sql/create_index.sql
index 8bc76f7c6f..808cacee5d 100644
--- a/src/test/regress/sql/create_index.sql
+++ b/src/test/regress/sql/create_index.sql
@@ -790,6 +790,16 @@ REINDEX (VERBOSE) TABLE reindex_verbose;
 \set VERBOSITY default
 DROP TABLE reindex_verbose;
 
+--
+-- REINDEX (OUTDATED)
+--
+CREATE TABLE reindex_coll(id integer primary key);
+\set VERBOSITY terse \\ -- suppress machine-dependent details
+-- no suitable index should be found
+REINDEX (OUTDATED) TABLE reindex_coll;
+\set VERBOSITY default
+DROP TABLE reindex_coll;
+
 --
 -- REINDEX CONCURRENTLY
 --
-- 
2.30.1


--oao5se3im4osqowp
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
	filename="v5-0002-Add-a-outdated-option-to-reindexdb.patch"



^ permalink  raw  reply  [nested|flat] 5+ messages in thread

* BRIN indexes vs. SK_SEARCHARRAY (and preprocessing scan keys)
@ 2023-02-13 17:01  Tomas Vondra <[email protected]>
  0 siblings, 1 reply; 5+ messages in thread

From: Tomas Vondra @ 2023-02-13 17:01 UTC (permalink / raw)
  To: PostgreSQL Hackers <[email protected]>

Hi,

while experimenting with BRIN indexes after a couple FOSDEM discussions,
I ran into the existing limitation that BRIN indexes don't handle array
scan keys. So BRIN indexes can be used for conditions like

    WHERE a IN (1,2,3,4,5)

but we essentially treat the values as individual scan keys, and for
each one we scan the BRIN index and build/update the bitmap. Which for
large indexes may be fairly expensive - the cost is proportional to the
number of values, so if building the bitmap for 1 value takes 10ms, for
100 values it'll take ~1000ms.

It's not hard to construct cases like this (e.g. when using indexes with
small pages_per_range values) etc. Of course, if the query does a lot of
other expensive stuff, this cost may be insignificant.

I'm not sure how often people do queries with such conditions. But I've
been experimenting with features that'd build such paths, so I took a
stab at a PoC, which can significantly reduce the time needed to build
the bitmaps. And there's a couple more interesting opportunities.


0001 - Support SK_SEARCHARRAY in BRIN minmax
--------------------------------------------
The 0001 part does a "naive" SK_SEARCHARRAY implementation for minmax.
It simply sets amsearcharray=true and then tweaks the consistent
function to handle both the scalar and array scan keys.

This is obviously rather inefficient, because the array is searched
linearly. So yes, we don't walk the index repeatedly, but we have to
compare each range to (almost-)all values.


0002 - Sort the array in brinrescan() and do binsearch
------------------------------------------------------
There's a simple way to optimize the naive approach by sorting the array
and then searching in this array. If the array is sorted, we can search
for the first value >= minvalue, and see if that is consistent (i.e. if
it's <= maxval).

In my experiments this cuts the time needed to build the bitmap for
array to pretty much the same as for a single value.

I think this is similar to the preprocessing of scan keys in b-tree, so
brinrescan() is a natural way to do the sort. The problem however is
where to store the result.

Ideally, we'd store it in BrinOpaque (just like BTScanOpaque in btree),
but the problem is we don't pass that to the consistent functions. Those
only get the ScanKeys and stuff extracted from BrinOpaque.

We might add a parameter to the "consistent" function, but that
conflicts with not wanting to break existing extensions implementing
their own BRIN indexes. We allow the opclasses to define "consistent"
with either 4 or 5 arguments. Adding an argument would mean 5 or 6
arguments, but because of the backwards compatibility we'd need to
support existing opclasses, and 5 is ambiguous :-/

In hindsight, I would probably not chose supporting both 4 and 5
arguments again. It makes it harder for us to maintain the code to make
life easier for extensions, but I'm not aware of any out-of-core BRIN
opclasses anyway. So I'd probably just change the API, it's pretty easy
to update existing extensions.

This patch however does a much simpler thing - it just replaces the
array in the SK_SEARCHARRAY scan key with a sorted one. That works for
for minmax, but not for bloom/inclusion, because those are not based on
sorting. And the ArrayType is not great for minmax either, because it
means we need to deconstruct it again and again, for each range. It'd be
much better to deconstruct the array once.

I'll get back to this ...


0003 - Support SK_SEARCHARRAY in BRIN inclusion
-----------------------------------------------
Trivial modification to support array scan keys, can't benefit from
sorting the array.


0004 - Support SK_SEARCHARRAY in BRIN bloom
-------------------------------------------
Trivial modification to support array scan keys, can't benefit from
sorted array either.

But we might "preprocess" the keys in a different way - bloom needs to
calculate two hashes per key, and at the moment it happens again and
again for each range. So if you have 1M ranges, and SK_SEARCHARRAY query
with 100 values, we'll do 100M calls to PROCNUM_HASH and 200M calls to
hash_uint32_extended(). And our hash functions are pretty expensive,
certainly compared to the fast functions often used for bloom filters.

So the preprocessing might actually calculate the hash functions once,
and then only reuse those in the "consistent" function.

0005 is a dirty PoC illustrating the benefit of caching the hashes.

Unfortunately, this complicates things, because it means:

* The scan key preprocessing is not universal for all BRIN opclasses,
  because some opclasses, i.e. each BRIN opclass might have optional
  BRIN_PROCNUM_PREPROCESS which would preprocess the keys the way the
  opclass would like.

* We can simply replace the array in the scan key the way minmax does
  that with the sorted array, because the data type is not the same
  (hashes are uint64).


When I started to write this e-mail I thought there's pretty much just
one way to move this forward:

1) Add a BRIN_PROCNUM_PREPROCESS to BRIN, doing the preprocessing (if
   not defined, the key is not preprocessed.

2) Store the preprocessed keys in BrinOpaque.

3) Modify the BRIN API to allow passing the preprocessed keys.

As mentioned earlier, I'm not sure how difficult would it be to maintain
backwards compatibility, considering the number of arguments of the
consistent function would be ambiguous.

Maybe the existence of BRIN_PROCNUM_PREPROCESS would be enough to decide
this - if it's decided, no keys are preprocessed (and the opclass would
not support SK_SEARCHARRAY).


But now I realize maybe we can do without adding parameters to the
"consistent" function. We might stash "preprocessed" scankeys into
BrinOpaque, and pass them to the consistent function instead of the
"original" scan keys (at least when the BRIN_PROCNUM_PREPROCESS). In
fact, I see ScanKey even allows AM-specific flags, maybe it'd be useful
to to mark the preprocessed keys.


regards

-- 
Tomas Vondra
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

Attachments:

  [text/x-patch] 0001-Support-SK_SEARCHARRAY-in-BRIN-minmax-20230213.patch (7.7K, ../../[email protected]/2-0001-Support-SK_SEARCHARRAY-in-BRIN-minmax-20230213.patch)
  download | inline diff:
From 3de2f0cbe238c3fbbceca1e3a9ed3c205870a6dd Mon Sep 17 00:00:00 2001
From: Tomas Vondra <[email protected]>
Date: Fri, 10 Feb 2023 16:07:57 +0100
Subject: [PATCH 1/6] Support SK_SEARCHARRAY in BRIN minmax

Set "amsearcharray=true" for BRIN, and extend the minmax opclass to
handle both scalar values and arrays. This allows handling conditions

    ... WHERE a IN (1, 2, 43, 2132, 134)

    ... WHERE a = ANY(ARRAY[34, 45, -1, 234])

    ... WHERE a <= ANY(ARRAY[4938, 282, 2934])

more efficiently - until now we simply built the bitmap for each
scalar value, so we walked the BRIN index many times. Which may be quite
expensive for indexes with many ranges (large tables and/or low
pages_per_range).

There's a couple problems / open questions / TODO items:

- The other opclasses don't handle SK_SEARCHARRAY yet.

- The array is always searched linearly, so this may be costly for large
  arrays (with many elements).

- The array is deconstructed again for each range. We should reuse this,
  somehow.
---
 src/backend/access/brin/brin.c        |   2 +-
 src/backend/access/brin/brin_minmax.c | 177 ++++++++++++++++++++------
 2 files changed, 140 insertions(+), 39 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index de1427a1e0..a600f37c15 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -101,7 +101,7 @@ brinhandler(PG_FUNCTION_ARGS)
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = true;
 	amroutine->amoptionalkey = true;
-	amroutine->amsearcharray = false;
+	amroutine->amsearcharray = true;
 	amroutine->amsearchnulls = true;
 	amroutine->amstorage = true;
 	amroutine->amclusterable = false;
diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c
index 2431591be6..c054d8ff42 100644
--- a/src/backend/access/brin/brin_minmax.c
+++ b/src/backend/access/brin/brin_minmax.c
@@ -16,6 +16,7 @@
 #include "access/stratnum.h"
 #include "catalog/pg_amop.h"
 #include "catalog/pg_type.h"
+#include "utils/array.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
 #include "utils/lsyscache.h"
@@ -157,46 +158,146 @@ brin_minmax_consistent(PG_FUNCTION_ARGS)
 	attno = key->sk_attno;
 	subtype = key->sk_subtype;
 	value = key->sk_argument;
-	switch (key->sk_strategy)
+
+	/*
+	 * For regular (scalar) scan keys, we simply compare the value to the
+	 * range min/max values, and we're done. For SK_SEARCHARRAY keys we
+	 * need to deparse the array and loop through the values.
+	 */
+	if (likely(!(key->sk_flags & SK_SEARCHARRAY)))
 	{
-		case BTLessStrategyNumber:
-		case BTLessEqualStrategyNumber:
-			finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
-												 key->sk_strategy);
-			matches = FunctionCall2Coll(finfo, colloid, column->bv_values[0],
-										value);
-			break;
-		case BTEqualStrategyNumber:
-
-			/*
-			 * In the equality case (WHERE col = someval), we want to return
-			 * the current page range if the minimum value in the range <=
-			 * scan key, and the maximum value >= scan key.
-			 */
-			finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
-												 BTLessEqualStrategyNumber);
-			matches = FunctionCall2Coll(finfo, colloid, column->bv_values[0],
-										value);
-			if (!DatumGetBool(matches))
+		switch (key->sk_strategy)
+		{
+			case BTLessStrategyNumber:
+			case BTLessEqualStrategyNumber:
+				finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
+													 key->sk_strategy);
+				matches = FunctionCall2Coll(finfo, colloid, column->bv_values[0],
+											value);
+				break;
+			case BTEqualStrategyNumber:
+
+				/*
+				 * In the equality case (WHERE col = someval), we want to return
+				 * the current page range if the minimum value in the range <=
+				 * scan key, and the maximum value >= scan key.
+				 */
+				finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
+													 BTLessEqualStrategyNumber);
+				matches = FunctionCall2Coll(finfo, colloid, column->bv_values[0],
+											value);
+				if (!DatumGetBool(matches))
+					break;
+				/* max() >= scankey */
+				finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
+													 BTGreaterEqualStrategyNumber);
+				matches = FunctionCall2Coll(finfo, colloid, column->bv_values[1],
+											value);
+				break;
+			case BTGreaterEqualStrategyNumber:
+			case BTGreaterStrategyNumber:
+				finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
+													 key->sk_strategy);
+				matches = FunctionCall2Coll(finfo, colloid, column->bv_values[1],
+											value);
+				break;
+			default:
+				/* shouldn't happen */
+				elog(ERROR, "invalid strategy number %d", key->sk_strategy);
+				matches = 0;
+				break;
+		}
+	}
+	else
+	{
+		ArrayType  *arrayval;
+		int16		elmlen;
+		bool		elmbyval;
+		char		elmalign;
+		int			num_elems;
+		Datum	   *elem_values;
+		bool	   *elem_nulls;
+
+		arrayval = DatumGetArrayTypeP(key->sk_argument);
+
+		get_typlenbyvalalign(ARR_ELEMTYPE(arrayval),
+							 &elmlen, &elmbyval, &elmalign);
+
+		deconstruct_array(arrayval,
+						  ARR_ELEMTYPE(arrayval),
+						  elmlen, elmbyval, elmalign,
+						  &elem_values, &elem_nulls, &num_elems);
+
+		switch (key->sk_strategy)
+		{
+			case BTLessStrategyNumber:
+			case BTLessEqualStrategyNumber:
+
+				for (int j = 0; j < num_elems; j++)
+				{
+					Datum val = elem_values[j];
+
+					finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
+														 key->sk_strategy);
+					matches = FunctionCall2Coll(finfo, colloid, column->bv_values[0],
+												val);
+
+					if (DatumGetBool(matches))
+						break;
+				}
+				break;
+			case BTEqualStrategyNumber:
+
+				/*
+				 * In the equality case (WHERE col = someval), we want to return
+				 * the current page range if the minimum value in the range <=
+				 * scan key, and the maximum value >= scan key.
+				 *
+				 * XXX For any of the array values.
+				 */
+				for (int j = 0; j < num_elems; j++)
+				{
+					Datum val = elem_values[j];
+
+					finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
+														 BTLessEqualStrategyNumber);
+					matches = FunctionCall2Coll(finfo, colloid, column->bv_values[0],
+												val);
+					if (!DatumGetBool(matches))
+						continue;
+
+					/* max() >= scankey */
+					finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
+														 BTGreaterEqualStrategyNumber);
+					matches = FunctionCall2Coll(finfo, colloid, column->bv_values[1],
+												val);
+
+					if (DatumGetBool(matches))
+						break;
+				}
+				break;
+			case BTGreaterEqualStrategyNumber:
+			case BTGreaterStrategyNumber:
+
+				for (int j = 0; j < num_elems; j++)
+				{
+					Datum val = elem_values[j];
+
+					finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
+														 key->sk_strategy);
+					matches = FunctionCall2Coll(finfo, colloid, column->bv_values[1],
+												val);
+
+					if (DatumGetBool(matches))
+						break;
+				}
+				break;
+			default:
+				/* shouldn't happen */
+				elog(ERROR, "invalid strategy number %d", key->sk_strategy);
+				matches = 0;
 				break;
-			/* max() >= scankey */
-			finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
-												 BTGreaterEqualStrategyNumber);
-			matches = FunctionCall2Coll(finfo, colloid, column->bv_values[1],
-										value);
-			break;
-		case BTGreaterEqualStrategyNumber:
-		case BTGreaterStrategyNumber:
-			finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
-												 key->sk_strategy);
-			matches = FunctionCall2Coll(finfo, colloid, column->bv_values[1],
-										value);
-			break;
-		default:
-			/* shouldn't happen */
-			elog(ERROR, "invalid strategy number %d", key->sk_strategy);
-			matches = 0;
-			break;
+		}
 	}
 
 	PG_RETURN_DATUM(matches);
-- 
2.39.1



  [text/x-patch] 0002-Sort-the-array-in-brinrescan-and-do-binsear-20230213.patch (10.6K, ../../[email protected]/3-0002-Sort-the-array-in-brinrescan-and-do-binsear-20230213.patch)
  download | inline diff:
From b567a8a34ea9b384cd2115700de56ea20b80510f Mon Sep 17 00:00:00 2001
From: Tomas Vondra <[email protected]>
Date: Fri, 10 Feb 2023 22:35:43 +0100
Subject: [PATCH 2/6] Sort the array in brinrescan() and do binsearch

This allows optimizing the check in brin_minmax_consistent.
---
 src/backend/access/brin/brin.c        |  69 ++++++++++
 src/backend/access/brin/brin_minmax.c | 175 +++++++++++++++++++++-----
 2 files changed, 210 insertions(+), 34 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index a600f37c15..40a631d376 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -38,6 +38,7 @@
 #include "utils/datum.h"
 #include "utils/guc.h"
 #include "utils/index_selfuncs.h"
+#include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
 
@@ -721,6 +722,16 @@ bringetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
 	return totalpages * 10;
 }
 
+static int
+compare_array_values(const void *a, const void *b, void *arg)
+{
+	Datum	da = * (Datum *) a;
+	Datum	db = * (Datum *) b;
+	SortSupport	ssup = (SortSupport) arg;
+
+	return ApplySortComparator(da, false, db, false, ssup);
+}
+
 /*
  * Re-initialize state for a BRIN index scan
  */
@@ -736,6 +747,64 @@ brinrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
 	 * here someday, too.
 	 */
 
+	/*
+	 * Presort the array for SK_SEARCHARRAY scan keys.
+	 *
+	 * We simply stash the value back into the ScanKey, because that way it's
+	 * transparent for the opclass. But there's a couple issues with this:
+	 *
+	 * 1) On rescans, we'll preprocess the array again, unnecessarily. And the
+	 * memory is likely retained too, so this may be a memory leak.
+	 *
+	 * 2) It assumes we want to preprocess the keys into the same data type.
+	 * That works for minmax (where we just sort the array), but may not work
+	 * for other opclasses - e.g. for "bloom" we might want pre-compute the
+	 * h1/h2 hashes (two uint64 values) and "inclusion" might try building
+	 * a union of the values, or something like that. And none of this fits
+	 * into an array of the same data type (which may be swapped into ScanKey).
+	 *
+	 * FIXME Needs to handle NULLs correctly.
+	 */
+	for (int i = 0; i < nscankeys; i++)
+	{
+		ScanKey		key = &scankey[i];
+		ArrayType  *arrayval;
+		int16		elmlen;
+		bool		elmbyval;
+		char		elmalign;
+		int			num_elems;
+		Datum	   *elem_values;
+		bool	   *elem_nulls;
+		TypeCacheEntry *type;
+		SortSupportData ssup;
+
+		if (!(key->sk_flags & SK_SEARCHNULL))
+			continue;
+
+		arrayval = DatumGetArrayTypeP(key->sk_argument);
+
+		get_typlenbyvalalign(ARR_ELEMTYPE(arrayval),
+							 &elmlen, &elmbyval, &elmalign);
+
+		deconstruct_array(arrayval,
+						  ARR_ELEMTYPE(arrayval),
+						  elmlen, elmbyval, elmalign,
+						  &elem_values, &elem_nulls, &num_elems);
+
+		type = lookup_type_cache(ARR_ELEMTYPE(arrayval), TYPECACHE_LT_OPR);
+
+		memset(&ssup, 0, sizeof(SortSupportData));
+		PrepareSortSupportFromOrderingOp(type->lt_opr, &ssup);
+
+		qsort_interruptible(elem_values, num_elems, sizeof(Datum),
+							compare_array_values, &ssup);
+
+		arrayval = construct_array_builtin(elem_values, num_elems,
+										   ARR_ELEMTYPE(arrayval));
+
+		key->sk_argument = PointerGetDatum(arrayval);
+	}
+
 	if (scankey && scan->numberOfKeys > 0)
 		memmove(scan->keyData, scankey,
 				scan->numberOfKeys * sizeof(ScanKeyData));
diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c
index c054d8ff42..facc01901a 100644
--- a/src/backend/access/brin/brin_minmax.c
+++ b/src/backend/access/brin/brin_minmax.c
@@ -22,6 +22,7 @@
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
 #include "utils/syscache.h"
+#include "utils/sortsupport.h"
 
 typedef struct MinmaxOpaque
 {
@@ -127,6 +128,63 @@ brin_minmax_add_value(PG_FUNCTION_ARGS)
 	PG_RETURN_BOOL(updated);
 }
 
+
+static int
+compare_array_values(const void *a, const void *b, void *arg)
+{
+	Datum	da = * (Datum *) a;
+	Datum	db = * (Datum *) b;
+	SortSupport	ssup = (SortSupport) arg;
+
+	return ApplySortComparator(da, false, db, false, ssup);
+}
+
+/*
+ * lower_boundary
+ *		Determine lowest index so that (values[index] >= minvalue).
+ *
+ * The array of values is expected to be sorted, so this is the first value
+ * that may fall into the [minvalue, maxvalue] range, as it exceeds minval.
+ * It's not guaranteed, though, as it might exceed maxvalue too.
+ */
+static int
+lower_boundary(Datum *values, int nvalues, Datum minvalue, SortSupport ssup)
+{
+	int		start = 0,
+			end = (nvalues - 1);
+
+	/* everything exceeds minval and might match */
+	if (compare_array_values(&minvalue, &values[start], ssup) <= 0)
+		return 0;
+
+	/* nothing could match */
+	if (compare_array_values(&minvalue, &values[end], ssup) > 0)
+		return nvalues;
+
+	while ((end - start) > 0)
+	{
+		int midpoint;
+		int r;
+
+		midpoint = start + (end - start) / 2;
+
+		r = compare_array_values(&minvalue, &values[midpoint], ssup);
+
+		if (r > 0)
+			start = Max(midpoint, start + 1);
+		else
+			end = midpoint;
+	}
+
+	/* the value should meet the (v >=minvalue) requirement */
+	Assert(compare_array_values(&values[start], &minvalue, ssup) >= 0);
+
+	/* we know start can't be 0, so it's legal to subtract 1 */
+	Assert(compare_array_values(&values[start-1], &minvalue, ssup) < 0);
+
+	return start;
+}
+
 /*
  * 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
@@ -223,6 +281,10 @@ brin_minmax_consistent(PG_FUNCTION_ARGS)
 		get_typlenbyvalalign(ARR_ELEMTYPE(arrayval),
 							 &elmlen, &elmbyval, &elmalign);
 
+		/*
+		 * FIXME We shouldn't deconstruct the array over and over for each page
+		 * range. We should preprocess it once, and then just use it.
+		 */
 		deconstruct_array(arrayval,
 						  ARR_ELEMTYPE(arrayval),
 						  elmlen, elmbyval, elmalign,
@@ -232,19 +294,14 @@ brin_minmax_consistent(PG_FUNCTION_ARGS)
 		{
 			case BTLessStrategyNumber:
 			case BTLessEqualStrategyNumber:
-
-				for (int j = 0; j < num_elems; j++)
-				{
-					Datum val = elem_values[j];
-
-					finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
-														 key->sk_strategy);
-					matches = FunctionCall2Coll(finfo, colloid, column->bv_values[0],
-												val);
-
-					if (DatumGetBool(matches))
-						break;
-				}
+				/*
+				 * Check the last (largest) value in the array - at least this
+				 * value has to exceed the range minval.
+				 */
+				finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
+													 key->sk_strategy);
+				matches = FunctionCall2Coll(finfo, colloid, column->bv_values[0],
+											elem_values[num_elems-1]);
 				break;
 			case BTEqualStrategyNumber:
 
@@ -253,44 +310,94 @@ brin_minmax_consistent(PG_FUNCTION_ARGS)
 				 * the current page range if the minimum value in the range <=
 				 * scan key, and the maximum value >= scan key.
 				 *
-				 * XXX For any of the array values.
+				 * We do this in two phases. We check the array min/max values to see
+				 * if there even can be a matching value, and if yes we do a binary
+				 * search to find the first value that exceeds range minval. And then
+				 * we check if it actually matches the range.
+				 *
+				 * XXX The first phase is probably unnecessary, because lower_bound()
+				 * does pretty much exactly that too.
 				 */
-				for (int j = 0; j < num_elems; j++)
 				{
-					Datum val = elem_values[j];
+					Datum val;
+					SortSupportData ssup;
+					int			lower;
+					TypeCacheEntry *type;
+
+					/* Is the first (smallest) value after the BRIN range? */
+					val = elem_values[0];
 
 					finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
 														 BTLessEqualStrategyNumber);
-					matches = FunctionCall2Coll(finfo, colloid, column->bv_values[0],
-												val);
+					matches = FunctionCall2Coll(finfo, colloid, val, column->bv_values[1]);
+
+					/* minval > max(range values) */
 					if (!DatumGetBool(matches))
-						continue;
+						break;
+
+					/* Is the last (largest) value before the BRIN range? */
+					val = elem_values[num_elems-1];
 
-					/* max() >= scankey */
 					finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
 														 BTGreaterEqualStrategyNumber);
-					matches = FunctionCall2Coll(finfo, colloid, column->bv_values[1],
-												val);
+					matches = FunctionCall2Coll(finfo, colloid, val, column->bv_values[0]);
 
-					if (DatumGetBool(matches))
+					/* maxval < min(range values) */
+					if (!DatumGetBool(matches))
 						break;
-				}
-				break;
-			case BTGreaterEqualStrategyNumber:
-			case BTGreaterStrategyNumber:
 
-				for (int j = 0; j < num_elems; j++)
-				{
-					Datum val = elem_values[j];
+					/*
+					 * OK, there might be some values matching the range. We have
+					 * to search them one by one, or perhaps try binsearch.
+					 */
+					type = lookup_type_cache(ARR_ELEMTYPE(arrayval), TYPECACHE_LT_OPR);
+
+					memset(&ssup, 0, sizeof(SortSupportData));
+					PrepareSortSupportFromOrderingOp(type->lt_opr, &ssup);
 
+					lower = lower_boundary(elem_values, num_elems, column->bv_values[0], &ssup);
+
+					/* no elements can possibly match */
+					if (lower == num_elems)
+					{
+						matches = BoolGetDatum(false);
+						break;
+					}
+
+					/*
+					 * OK, the first element must match the upper boundary too
+					 * (if it does not, no following elements can).
+					 */
+					val = elem_values[lower];
+
+					/*
+					 * In the equality case (WHERE col = someval), we want to return
+					 * the current page range if the minimum value in the range <=
+					 * scan key, and the maximum value >= scan key.
+					 */
 					finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
-														 key->sk_strategy);
-					matches = FunctionCall2Coll(finfo, colloid, column->bv_values[1],
+														 BTLessEqualStrategyNumber);
+					matches = FunctionCall2Coll(finfo, colloid, column->bv_values[0],
 												val);
-
-					if (DatumGetBool(matches))
+					if (!DatumGetBool(matches))
 						break;
+					/* max() >= scankey */
+					finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
+														 BTGreaterEqualStrategyNumber);
+					matches = FunctionCall2Coll(finfo, colloid, column->bv_values[1],
+												val);
+					break;
 				}
+			case BTGreaterEqualStrategyNumber:
+			case BTGreaterStrategyNumber:
+				/*
+				 * Check the first (smallest) value in the array - at least this
+				 * value has to be smaller than the range maxval.
+				 */
+				finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
+													 key->sk_strategy);
+				matches = FunctionCall2Coll(finfo, colloid, column->bv_values[1],
+											elem_values[0]);
 				break;
 			default:
 				/* shouldn't happen */
-- 
2.39.1



  [text/x-patch] 0003-Support-SK_SEARCHARRAY-in-BRIN-minmax-multi-20230213.patch (14.3K, ../../[email protected]/4-0003-Support-SK_SEARCHARRAY-in-BRIN-minmax-multi-20230213.patch)
  download | inline diff:
From a0125596277647b593bc7d1b5df2bbbfe859cbd1 Mon Sep 17 00:00:00 2001
From: Tomas Vondra <[email protected]>
Date: Sat, 11 Feb 2023 19:43:38 +0100
Subject: [PATCH 3/6] Support SK_SEARCHARRAY in BRIN minmax-multi

Similar approach to minmax, but the issues with deconstructing the array
over and over are even more serious.
---
 src/backend/access/brin/brin_minmax_multi.c | 388 ++++++++++++++++----
 1 file changed, 322 insertions(+), 66 deletions(-)

diff --git a/src/backend/access/brin/brin_minmax_multi.c b/src/backend/access/brin/brin_minmax_multi.c
index 0ace6035be..43e9143f2a 100644
--- a/src/backend/access/brin/brin_minmax_multi.c
+++ b/src/backend/access/brin/brin_minmax_multi.c
@@ -2562,6 +2562,63 @@ brin_minmax_multi_add_value(PG_FUNCTION_ARGS)
 	PG_RETURN_BOOL(modified);
 }
 
+
+static int
+compare_array_values(const void *a, const void *b, void *arg)
+{
+	Datum	da = * (Datum *) a;
+	Datum	db = * (Datum *) b;
+	SortSupport	ssup = (SortSupport) arg;
+
+	return ApplySortComparator(da, false, db, false, ssup);
+}
+
+/*
+ * lower_boundary
+ *		Determine lowest index so that (values[index] >= minvalue).
+ *
+ * The array of values is expected to be sorted, so this is the first value
+ * that may fall into the [minvalue, maxvalue] range, as it exceeds minval.
+ * It's not guaranteed, though, as it might exceed maxvalue too.
+ */
+static int
+lower_boundary(Datum *values, int nvalues, Datum minvalue, SortSupport ssup)
+{
+	int		start = 0,
+			end = (nvalues - 1);
+
+	/* everything exceeds minval and might match */
+	if (compare_array_values(&minvalue, &values[start], ssup) <= 0)
+		return 0;
+
+	/* nothing could match */
+	if (compare_array_values(&minvalue, &values[end], ssup) > 0)
+		return nvalues;
+
+	while ((end - start) > 0)
+	{
+		int midpoint;
+		int r;
+
+		midpoint = start + (end - start) / 2;
+
+		r = compare_array_values(&minvalue, &values[midpoint], ssup);
+
+		if (r > 0)
+			start = Max(midpoint, start + 1);
+		else
+			end = midpoint;
+	}
+
+	/* the value should meet the (v >=minvalue) requirement */
+	Assert(compare_array_values(&values[start], &minvalue, ssup) >= 0);
+
+	/* we know start can't be 0, so it's legal to subtract 1 */
+	Assert(compare_array_values(&values[start-1], &minvalue, ssup) < 0);
+
+	return start;
+}
+
 /*
  * 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
@@ -2591,6 +2648,15 @@ brin_minmax_multi_consistent(PG_FUNCTION_ARGS)
 	serialized = (SerializedRanges *) PG_DETOAST_DATUM(column->bv_values[0]);
 	ranges = brin_range_deserialize(serialized->maxvalues, serialized);
 
+	/*
+	 * XXX Would it make sense to have a quick initial check on the whole
+	 * summary? We know most page ranges are not expected to match, and we
+	 * know the ranges/values are sorted so we could check global min/max
+	 * (essentially what regular minmax is doing) and bail if no match is
+	 * possible. That should be cheap and might save a lot on inspecting
+	 * the individual ranges/values.
+	 */
+
 	/* inspect the ranges, and for each one evaluate the scan keys */
 	for (rangeno = 0; rangeno < ranges->nranges; rangeno++)
 	{
@@ -2611,67 +2677,205 @@ brin_minmax_multi_consistent(PG_FUNCTION_ARGS)
 			attno = key->sk_attno;
 			subtype = key->sk_subtype;
 			value = key->sk_argument;
-			switch (key->sk_strategy)
+
+			if (likely(!(key->sk_flags & SK_SEARCHARRAY)))
 			{
-				case BTLessStrategyNumber:
-				case BTLessEqualStrategyNumber:
-					finfo = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
-															   key->sk_strategy);
-					/* first value from the array */
-					matches = FunctionCall2Coll(finfo, colloid, minval, value);
-					break;
+				switch (key->sk_strategy)
+				{
+					case BTLessStrategyNumber:
+					case BTLessEqualStrategyNumber:
+						finfo = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
+																   key->sk_strategy);
+						/* first value from the array */
+						matches = FunctionCall2Coll(finfo, colloid, minval, value);
+						break;
 
-				case BTEqualStrategyNumber:
-					{
-						Datum		compar;
-						FmgrInfo   *cmpFn;
+					case BTEqualStrategyNumber:
+						{
+							Datum		compar;
+							FmgrInfo   *cmpFn;
+
+							/* by default this range does not match */
+							matches = false;
+
+							/*
+							 * Otherwise, need to compare the new value with
+							 * boundaries of all the ranges. First check if it's
+							 * less than the absolute minimum, which is the first
+							 * value in the array.
+							 */
+							cmpFn = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
+																	   BTGreaterStrategyNumber);
+							compar = FunctionCall2Coll(cmpFn, colloid, minval, value);
+
+							/* smaller than the smallest value in this range */
+							if (DatumGetBool(compar))
+								break;
+
+							cmpFn = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
+																	   BTLessStrategyNumber);
+							compar = FunctionCall2Coll(cmpFn, colloid, maxval, value);
+
+							/* larger than the largest value in this range */
+							if (DatumGetBool(compar))
+								break;
+
+							/*
+							 * We haven't managed to eliminate this range, so
+							 * consider it matching.
+							 */
+							matches = true;
 
-						/* by default this range does not match */
-						matches = false;
+							break;
+						}
+					case BTGreaterEqualStrategyNumber:
+					case BTGreaterStrategyNumber:
+						finfo = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
+																   key->sk_strategy);
+						/* last value from the array */
+						matches = FunctionCall2Coll(finfo, colloid, maxval, value);
+						break;
 
-						/*
-						 * Otherwise, need to compare the new value with
-						 * boundaries of all the ranges. First check if it's
-						 * less than the absolute minimum, which is the first
-						 * value in the array.
-						 */
-						cmpFn = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
-																   BTGreaterStrategyNumber);
-						compar = FunctionCall2Coll(cmpFn, colloid, minval, value);
+					default:
+						/* shouldn't happen */
+						elog(ERROR, "invalid strategy number %d", key->sk_strategy);
+						matches = 0;
+						break;
+				}
+			}
+			else
+			{
+				/*
+				 * FIXME This is really wrong, because it deserializes the
+				 * array over and over for each value in the minmax-multi
+				 * summary range.
+				 *
+				 * FIXME In fact, this is even worse than for brin_minmax.c
+				 * because we deconstruct it for every range in the summary
+				 * (so if there are 32 values, that's 16 ranges, and we'll
+				 * deconstruct it again for each of those).
+				 *
+				 * XXX We could deconstruct it once, when we need it for the
+				 * first time. But even better we should do it only once while
+				 * preprocessing the scan keys.
+				 */
+				ArrayType  *arrayval;
+				int16		elmlen;
+				bool		elmbyval;
+				char		elmalign;
+				int			num_elems;
+				Datum	   *elem_values;
+				bool	   *elem_nulls;
 
-						/* smaller than the smallest value in this range */
-						if (DatumGetBool(compar))
-							break;
+				arrayval = DatumGetArrayTypeP(key->sk_argument);
 
-						cmpFn = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
-																   BTLessStrategyNumber);
-						compar = FunctionCall2Coll(cmpFn, colloid, maxval, value);
+				get_typlenbyvalalign(ARR_ELEMTYPE(arrayval),
+									 &elmlen, &elmbyval, &elmalign);
 
-						/* larger than the largest value in this range */
-						if (DatumGetBool(compar))
-							break;
+				deconstruct_array(arrayval,
+								  ARR_ELEMTYPE(arrayval),
+								  elmlen, elmbyval, elmalign,
+								  &elem_values, &elem_nulls, &num_elems);
+
+				switch (key->sk_strategy)
+				{
+					case BTLessStrategyNumber:
+					case BTLessEqualStrategyNumber:
+						finfo = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
+																   key->sk_strategy);
+						/* first value from the array */
+						matches = FunctionCall2Coll(finfo, colloid, minval,
+													elem_values[num_elems-1]);
+						break;
+
+					case BTEqualStrategyNumber:
 
 						/*
-						 * We haven't managed to eliminate this range, so
-						 * consider it matching.
+						 * See brin_minmax.c for description of what this is doing.
 						 */
-						matches = true;
-
+						{
+							Datum val;
+							SortSupportData ssup;
+							int			lower;
+							TypeCacheEntry *type;
+
+							/* Is the first (smallest) value after the BRIN range? */
+							val = elem_values[0];
+
+							finfo = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
+																	   BTLessEqualStrategyNumber);
+							matches = FunctionCall2Coll(finfo, colloid, val, maxval);
+
+							/* minval > max(range values) */
+							if (!DatumGetBool(matches))
+								break;
+
+							/* Is the last (largest) value before the BRIN range? */
+							val = elem_values[num_elems-1];
+
+							finfo = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
+																	   BTGreaterEqualStrategyNumber);
+							matches = FunctionCall2Coll(finfo, colloid, val, minval);
+
+							/* maxval < min(range values) */
+							if (!DatumGetBool(matches))
+								break;
+
+							/*
+							 * OK, there might be some values matching the range. We have
+							 * to search them one by one, or perhaps try binsearch.
+							 */
+							type = lookup_type_cache(ARR_ELEMTYPE(arrayval), TYPECACHE_LT_OPR);
+
+							memset(&ssup, 0, sizeof(SortSupportData));
+							PrepareSortSupportFromOrderingOp(type->lt_opr, &ssup);
+
+							lower = lower_boundary(elem_values, num_elems, minval, &ssup);
+
+							/* no elements can possibly match */
+							if (lower == num_elems)
+							{
+								matches = BoolGetDatum(false);
+								break;
+							}
+
+							/*
+							 * OK, the first element must match the upper boundary too
+							 * (if it does not, no following elements can).
+							 */
+							val = elem_values[lower];
+
+							/*
+							 * In the equality case (WHERE col = someval), we want to return
+							 * the current page range if the minimum value in the range <=
+							 * scan key, and the maximum value >= scan key.
+							 */
+							finfo = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
+																	   BTLessEqualStrategyNumber);
+							matches = FunctionCall2Coll(finfo, colloid, minval, val);
+							if (!DatumGetBool(matches))
+								break;
+							/* max() >= scankey */
+							finfo = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
+																	   BTGreaterEqualStrategyNumber);
+							matches = FunctionCall2Coll(finfo, colloid, maxval, val);
+							break;
+						}
+					case BTGreaterEqualStrategyNumber:
+					case BTGreaterStrategyNumber:
+						finfo = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
+																   key->sk_strategy);
+						/* last value from the array */
+						matches = FunctionCall2Coll(finfo, colloid, maxval,
+													elem_values[0]);
 						break;
-					}
-				case BTGreaterEqualStrategyNumber:
-				case BTGreaterStrategyNumber:
-					finfo = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
-															   key->sk_strategy);
-					/* last value from the array */
-					matches = FunctionCall2Coll(finfo, colloid, maxval, value);
-					break;
 
-				default:
-					/* shouldn't happen */
-					elog(ERROR, "invalid strategy number %d", key->sk_strategy);
-					matches = 0;
-					break;
+					default:
+						/* shouldn't happen */
+						elog(ERROR, "invalid strategy number %d", key->sk_strategy);
+						matches = 0;
+						break;
+				}
 			}
 
 			/* the range has to match all the scan keys */
@@ -2713,28 +2917,80 @@ brin_minmax_multi_consistent(PG_FUNCTION_ARGS)
 			attno = key->sk_attno;
 			subtype = key->sk_subtype;
 			value = key->sk_argument;
-			switch (key->sk_strategy)
+			if (likely(!(key->sk_flags & SK_SEARCHARRAY)))
 			{
-				case BTLessStrategyNumber:
-				case BTLessEqualStrategyNumber:
-				case BTEqualStrategyNumber:
-				case BTGreaterEqualStrategyNumber:
-				case BTGreaterStrategyNumber:
-
-					finfo = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
-															   key->sk_strategy);
-					matches = FunctionCall2Coll(finfo, colloid, val, value);
-					break;
+				switch (key->sk_strategy)
+				{
+					case BTLessStrategyNumber:
+					case BTLessEqualStrategyNumber:
+					case BTEqualStrategyNumber:
+					case BTGreaterEqualStrategyNumber:
+					case BTGreaterStrategyNumber:
+
+						finfo = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
+																   key->sk_strategy);
+						matches = FunctionCall2Coll(finfo, colloid, val, value);
+						break;
 
-				default:
-					/* shouldn't happen */
-					elog(ERROR, "invalid strategy number %d", key->sk_strategy);
-					matches = 0;
-					break;
+					default:
+						/* shouldn't happen */
+						elog(ERROR, "invalid strategy number %d", key->sk_strategy);
+						matches = 0;
+						break;
+				}
+			}
+			else
+			{
+				/*
+				 * FIXME This is really wrong, because it deserializes the
+				 * array over and over for each value in the minmax-multi
+				 * summary.
+				 */
+				ArrayType  *arrayval;
+				int16		elmlen;
+				bool		elmbyval;
+				char		elmalign;
+				int			num_elems;
+				Datum	   *elem_values;
+				bool	   *elem_nulls;
+
+				SortSupportData ssup;
+				int			lower;
+				TypeCacheEntry *type;
+
+				arrayval = DatumGetArrayTypeP(key->sk_argument);
+
+				get_typlenbyvalalign(ARR_ELEMTYPE(arrayval),
+									 &elmlen, &elmbyval, &elmalign);
+
+				deconstruct_array(arrayval,
+								  ARR_ELEMTYPE(arrayval),
+								  elmlen, elmbyval, elmalign,
+								  &elem_values, &elem_nulls, &num_elems);
+
+				/* assume not maches */
+				matches = false;
+
+				/*
+				 * OK, there might be some values matching the range. We have
+				 * to search them one by one, or perhaps try binsearch.
+				 */
+				type = lookup_type_cache(ARR_ELEMTYPE(arrayval), TYPECACHE_LT_OPR);
+
+				memset(&ssup, 0, sizeof(SortSupportData));
+				PrepareSortSupportFromOrderingOp(type->lt_opr, &ssup);
+
+				lower = lower_boundary(elem_values, num_elems, value, &ssup);
+
+				if ((lower < num_elems) &&
+					(compare_array_values(&elem_values[lower], &value, &ssup) == 0))
+				{
+					matches = true;
+				}
 			}
 
 			/* the range has to match all the scan keys */
-			matching &= DatumGetBool(matches);
+			matching &= matches;
 
 			/* once we find a non-matching key, we're done */
 			if (!matching)
-- 
2.39.1



  [text/x-patch] 0004-Support-SK_SEARCHARRAY-in-BRIN-inclusion-20230213.patch (10.3K, ../../[email protected]/5-0004-Support-SK_SEARCHARRAY-in-BRIN-inclusion-20230213.patch)
  download | inline diff:
From f9d5aee070eef54d5898d4ee7862d7900aa59209 Mon Sep 17 00:00:00 2001
From: Tomas Vondra <[email protected]>
Date: Sat, 11 Feb 2023 15:23:25 +0100
Subject: [PATCH 4/6] Support SK_SEARCHARRAY in BRIN inclusion

---
 src/backend/access/brin/brin_inclusion.c | 177 ++++++++++++++++-------
 1 file changed, 123 insertions(+), 54 deletions(-)

diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c
index 248116c149..935452d09d 100644
--- a/src/backend/access/brin/brin_inclusion.c
+++ b/src/backend/access/brin/brin_inclusion.c
@@ -30,6 +30,7 @@
 #include "access/skey.h"
 #include "catalog/pg_amop.h"
 #include "catalog/pg_type.h"
+#include "utils/array.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
 #include "utils/lsyscache.h"
@@ -239,43 +240,20 @@ brin_inclusion_add_value(PG_FUNCTION_ARGS)
 }
 
 /*
- * BRIN inclusion consistent function
- *
- * We're no longer dealing with NULL keys in the consistent function, that is
- * now handled by the AM code. That means we should not get any all-NULL ranges
- * either, because those can't be consistent with regular (not [IS] NULL) keys.
+ * Check consistency of a single scalar value with the BRIN range.
  *
- * All of the strategies are optional.
+ * Called for both scalar scankeys and for each value in SK_SEARCHARRAY.
  */
-Datum
-brin_inclusion_consistent(PG_FUNCTION_ARGS)
+static bool
+brin_inclusion_consistent_value(BrinDesc *bdesc, BrinValues *column,
+								AttrNumber attno,
+								StrategyNumber strategy, Oid subtype,
+								Oid colloid, Datum unionval, Datum query)
 {
-	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;
 
-	/* This opclass uses the old signature with only three arguments. */
-	Assert(PG_NARGS() == 3);
-
-	/* Should not be dealing with all-NULL ranges. */
-	Assert(!column->bv_allnulls);
-
-	/* 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)
+	switch (strategy)
 	{
 			/*
 			 * Placement strategies
@@ -294,49 +272,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
@@ -352,9 +330,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS)
 		case RTSubStrategyNumber:
 		case RTSubEqualStrategyNumber:
 			finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype,
-													key->sk_strategy);
+													strategy);
 			result = FunctionCall2Coll(finfo, colloid, unionval, query);
-			PG_RETURN_DATUM(result);
+			return (DatumGetBool(result));
 
 			/*
 			 * Contained by strategies
@@ -374,9 +352,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 (column->bv_values[INCLUSION_CONTAINS_EMPTY]);
 
 			/*
 			 * Adjacent strategy
@@ -393,12 +371,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
@@ -428,9 +406,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 (column->bv_values[INCLUSION_CONTAINS_EMPTY]);
 
 		case RTSameStrategyNumber:
 		case RTEqualStrategyNumber:
@@ -438,30 +416,121 @@ 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 (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 (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);
+			elog(ERROR, "invalid strategy number %d", strategy);
+			return (false);
+	}
+}
+
+/*
+ * BRIN inclusion consistent function
+ *
+ * We're no longer dealing with NULL keys in the consistent function, that is
+ * now handled by the AM code. That means we should not get any all-NULL ranges
+ * either, because those can't be consistent with regular (not [IS] NULL) keys.
+ *
+ * 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;
+
+	/* This opclass uses the old signature with only three arguments. */
+	Assert(PG_NARGS() == 3);
+
+	/* Should not be dealing with all-NULL ranges. */
+	Assert(!column->bv_allnulls);
+
+	/* 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];
+
+	/*
+	 * For regular (scalar) scan keys, we simply compare the value to the
+	 * range min/max values, and we're done. For SK_SEARCHARRAY keys we
+	 * need to deparse the array and loop through the values.
+	 */
+	if (likely(!(key->sk_flags & SK_SEARCHARRAY)))
+	{
+		bool tmp;
+
+		tmp = brin_inclusion_consistent_value(bdesc, column, attno,
+											  key->sk_strategy,
+											  subtype, colloid,
+											  unionval, query);
+		PG_RETURN_BOOL(tmp);
+	}
+	else
+	{
+		ArrayType  *arrayval;
+		int16		elmlen;
+		bool		elmbyval;
+		char		elmalign;
+		int			num_elems;
+		Datum	   *elem_values;
+		bool	   *elem_nulls;
+		bool		matches = false;
+
+		arrayval = DatumGetArrayTypeP(key->sk_argument);
+
+		get_typlenbyvalalign(ARR_ELEMTYPE(arrayval),
+							 &elmlen, &elmbyval, &elmalign);
+
+		deconstruct_array(arrayval,
+						  ARR_ELEMTYPE(arrayval),
+						  elmlen, elmbyval, elmalign,
+						  &elem_values, &elem_nulls, &num_elems);
+
+		/* have to loop through all elements, having them sorted does not help */
+		for (int i = 0; i < num_elems; i++)
+		{
+			Datum 	query_element = elem_values[i];
+
+			matches = brin_inclusion_consistent_value(bdesc, column, attno,
+													  key->sk_strategy,
+													  subtype, colloid,
+													  unionval, query_element);
+
+			if (matches)
+				break;
+		}
+
+		/* we could get here for empty array, e.g. with "@> '{}'::point[]" */
+		PG_RETURN_BOOL(matches);
 	}
 }
 
-- 
2.39.1



  [text/x-patch] 0005-Support-SK_SEARCHARRAY-in-BRIN-bloom-20230213.patch (3.6K, ../../[email protected]/6-0005-Support-SK_SEARCHARRAY-in-BRIN-bloom-20230213.patch)
  download | inline diff:
From c67e0d995d1cf1c104270974624834940fa5ee16 Mon Sep 17 00:00:00 2001
From: Tomas Vondra <[email protected]>
Date: Sat, 11 Feb 2023 20:50:03 +0100
Subject: [PATCH 5/6] Support SK_SEARCHARRAY in BRIN bloom

---
 src/backend/access/brin/brin_bloom.c | 100 ++++++++++++++++++++++-----
 1 file changed, 81 insertions(+), 19 deletions(-)

diff --git a/src/backend/access/brin/brin_bloom.c b/src/backend/access/brin/brin_bloom.c
index e4953a9d37..c2939ac809 100644
--- a/src/backend/access/brin/brin_bloom.c
+++ b/src/backend/access/brin/brin_bloom.c
@@ -125,6 +125,7 @@
 #include "access/stratnum.h"
 #include "catalog/pg_type.h"
 #include "catalog/pg_amop.h"
+#include "utils/array.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
 #include "utils/lsyscache.h"
@@ -596,26 +597,87 @@ brin_bloom_consistent(PG_FUNCTION_ARGS)
 		attno = key->sk_attno;
 		value = key->sk_argument;
 
-		switch (key->sk_strategy)
+		if (likely(!(key->sk_flags & SK_SEARCHARRAY)))
 		{
-			case BloomEqualStrategyNumber:
-
-				/*
-				 * In the equality case (WHERE col = someval), we want to
-				 * return the current page range if the minimum value in the
-				 * range <= scan key, and the maximum value >= scan key.
-				 */
-				finfo = bloom_get_procinfo(bdesc, attno, PROCNUM_HASH);
-
-				hashValue = DatumGetUInt32(FunctionCall1Coll(finfo, colloid, value));
-				matches &= bloom_contains_value(filter, hashValue);
-
-				break;
-			default:
-				/* shouldn't happen */
-				elog(ERROR, "invalid strategy number %d", key->sk_strategy);
-				matches = 0;
-				break;
+			switch (key->sk_strategy)
+			{
+				case BloomEqualStrategyNumber:
+
+					/*
+					 * In the equality case (WHERE col = someval), we want to
+					 * return the current page range if the minimum value in the
+					 * range <= scan key, and the maximum value >= scan key.
+					 */
+					finfo = bloom_get_procinfo(bdesc, attno, PROCNUM_HASH);
+
+					hashValue = DatumGetUInt32(FunctionCall1Coll(finfo, colloid, value));
+					matches &= bloom_contains_value(filter, hashValue);
+
+					break;
+				default:
+					/* shouldn't happen */
+					elog(ERROR, "invalid strategy number %d", key->sk_strategy);
+					matches = 0;
+					break;
+			}
+		}
+		else
+		{
+			ArrayType  *arrayval;
+			int16		elmlen;
+			bool		elmbyval;
+			char		elmalign;
+			int			num_elems;
+			Datum	   *elem_values;
+			bool	   *elem_nulls;
+
+			arrayval = DatumGetArrayTypeP(key->sk_argument);
+
+			get_typlenbyvalalign(ARR_ELEMTYPE(arrayval),
+								 &elmlen, &elmbyval, &elmalign);
+
+			deconstruct_array(arrayval,
+							  ARR_ELEMTYPE(arrayval),
+							  elmlen, elmbyval, elmalign,
+							  &elem_values, &elem_nulls, &num_elems);
+
+			switch (key->sk_strategy)
+			{
+				case BloomEqualStrategyNumber:
+					{
+						/* assume no match */
+						matches = BoolGetDatum(false);
+
+						/*
+						 * In the equality case (WHERE col = someval), we want to
+						 * return the current page range if the minimum value in the
+						 * range <= scan key, and the maximum value >= scan key.
+						 */
+						finfo = bloom_get_procinfo(bdesc, attno, PROCNUM_HASH);
+
+						for (int i = 0; i < num_elems; i++)
+						{
+							bool	tmp = false;
+							Datum	element = elem_values[i];
+
+							hashValue = DatumGetUInt32(FunctionCall1Coll(finfo, colloid,
+																		 element));
+							tmp = bloom_contains_value(filter, hashValue);
+
+							if (DatumGetBool(tmp))
+							{
+								matches = BoolGetDatum(true);
+								break;
+							}
+						}
+					}
+					break;
+				default:
+					/* shouldn't happen */
+					elog(ERROR, "invalid strategy number %d", key->sk_strategy);
+					matches = 0;
+					break;
+			}
 		}
 
 		if (!matches)
-- 
2.39.1



  [text/x-patch] 0006-PoC-caching-hash-values-in-BRIN-bloom-20230213.patch (3.3K, ../../[email protected]/7-0006-PoC-caching-hash-values-in-BRIN-bloom-20230213.patch)
  download | inline diff:
From d0dead5dedc0f91d7279d351a6432e2592f8d578 Mon Sep 17 00:00:00 2001
From: Tomas Vondra <[email protected]>
Date: Sat, 11 Feb 2023 23:44:21 +0100
Subject: [PATCH 6/6] PoC caching hash values in BRIN bloom

Cache of hash values initialized when processing the first page range.
The cache is not
---
 src/backend/access/brin/brin_bloom.c | 59 ++++++++++++++++++++++++++--
 1 file changed, 55 insertions(+), 4 deletions(-)

diff --git a/src/backend/access/brin/brin_bloom.c b/src/backend/access/brin/brin_bloom.c
index c2939ac809..b57db6be14 100644
--- a/src/backend/access/brin/brin_bloom.c
+++ b/src/backend/access/brin/brin_bloom.c
@@ -129,6 +129,7 @@
 #include "utils/builtins.h"
 #include "utils/datum.h"
 #include "utils/lsyscache.h"
+#include "utils/memutils.h"
 #include "utils/rel.h"
 #include "utils/syscache.h"
 
@@ -260,6 +261,8 @@ typedef struct BloomFilter
 	char		data[FLEXIBLE_ARRAY_MEMBER];
 } BloomFilter;
 
+static uint64 *cache_h1 = NULL;
+static uint64 *cache_h2 = NULL;
 
 /*
  * bloom_init
@@ -405,6 +408,32 @@ bloom_contains_value(BloomFilter *filter, uint32 value)
 	return true;
 }
 
+/*
+ * bloom_contains_value
+ * 		Check if the bloom filter contains a particular value.
+ */
+static bool
+bloom_contains_hashes(BloomFilter *filter, uint64 h1, uint64 h2)
+{
+	int			i;
+
+	/* compute the requested number of hashes */
+	for (i = 0; i < filter->nhashes; i++)
+	{
+		/* h1 + h2 + f(i) */
+		uint32		h = (h1 + i * h2) % filter->nbits;
+		uint32		byte = (h / 8);
+		uint32		bit = (h % 8);
+
+		/* if the bit is not set, the value is not there */
+		if (!(filter->data[byte] & (0x01 << bit)))
+			return false;
+	}
+
+	/* all hashes found in bloom filter */
+	return true;
+}
+
 typedef struct BloomOpaque
 {
 	/*
@@ -440,6 +469,9 @@ brin_bloom_opcinfo(PG_FUNCTION_ARGS)
 		MAXALIGN((char *) result + SizeofBrinOpcInfo(1));
 	result->oi_typcache[0] = lookup_type_cache(PG_BRIN_BLOOM_SUMMARYOID, 0);
 
+	cache_h1 = NULL;
+	cache_h2 = NULL;
+
 	PG_RETURN_POINTER(result);
 }
 
@@ -641,6 +673,28 @@ brin_bloom_consistent(PG_FUNCTION_ARGS)
 							  elmlen, elmbyval, elmalign,
 							  &elem_values, &elem_nulls, &num_elems);
 
+			if (cache_h1 == NULL && cache_h2 == NULL)
+			{
+				MemoryContext oldcxt;
+
+				oldcxt = MemoryContextSwitchTo(bdesc->bd_context);
+				cache_h1 = palloc0(num_elems * sizeof(uint64));
+				cache_h2 = palloc0(num_elems * sizeof(uint64));
+				MemoryContextSwitchTo(oldcxt);
+
+				finfo = bloom_get_procinfo(bdesc, attno, PROCNUM_HASH);
+
+				for (int i = 0; i < num_elems; i++)
+				{
+					Datum	element = elem_values[i];
+
+					hashValue = DatumGetUInt32(FunctionCall1Coll(finfo, colloid, element));
+
+					cache_h1[i] = hash_bytes_uint32_extended(hashValue, BLOOM_SEED_1) % filter->nbits;
+					cache_h2[i] = hash_bytes_uint32_extended(hashValue, BLOOM_SEED_2) % filter->nbits;
+				}
+			}
+
 			switch (key->sk_strategy)
 			{
 				case BloomEqualStrategyNumber:
@@ -658,11 +712,8 @@ brin_bloom_consistent(PG_FUNCTION_ARGS)
 						for (int i = 0; i < num_elems; i++)
 						{
 							bool	tmp = false;
-							Datum	element = elem_values[i];
 
-							hashValue = DatumGetUInt32(FunctionCall1Coll(finfo, colloid,
-																		 element));
-							tmp = bloom_contains_value(filter, hashValue);
+							tmp = bloom_contains_hashes(filter, cache_h1[i], cache_h2[i]);
 
 							if (DatumGetBool(tmp))
 							{
-- 
2.39.1



^ permalink  raw  reply  [nested|flat] 5+ messages in thread

* Re: BRIN indexes vs. SK_SEARCHARRAY (and preprocessing scan keys)
@ 2023-02-16 01:35  Tomas Vondra <[email protected]>
  parent: Tomas Vondra <[email protected]>
  0 siblings, 1 reply; 5+ messages in thread

From: Tomas Vondra @ 2023-02-16 01:35 UTC (permalink / raw)
  To: PostgreSQL Hackers <[email protected]>

Hi,

Attached is a patch series adopting the idea of scan key preprocessing
in brinrescan(), and producing scan keys. It turns out to work pretty
nicely, and it allows different opclasses doing different things:

- minmax / minmax-multi: sort the array values (leave scalars alone)
- inclusion: no preprocessing
- bloom: precalculate hash values

The _consistent functions are modified to leverage the preprocessed
keys. I wonder if it should check the existence of the (optional)
procedure, and fallback to the non-optimized search if not defined.

That would allow opclasses (e.g. from extensions) to keep using the
built-in consistent function without tweaking the definition to also
have the preprocess function. But that seems like a rather minor issue,
especially because the number of external opclasses is tiny and updating
the definition to also reference the preprocess function is trivial. I
don't think it's worth the extra code complexity.

0001 and 0002 are minor code cleanup in the opclasses introduced in PG
13. There's a couple places assigning boolean values to Datum variables,
and misleading comments.

0003 is a minor refactoring making the Bloom filter size calculation
easier to reuse.

0004 introduces the optional "preprocess" opclass procedure, and calls
it for keys from brinrescan().

0005-0008 add the preprocess procedure to the various BRIN types, and
adjust the consistent procedures accordingly.


Attached is a python script I used to measure this. It builds a table
with 10M rows, with sequential but slightly randomized (value may move
within the 1% of table), and minmax/bloom indexes. The table has ~500MB,
the indexes are using pages_per_range=1 (tiny, but simulates large table
with regular page ranges).

And then the script queries the table with different number of random
values in the "IN (...)" clause, and measures query duration (in ms).

The results look like this:

                           int            text
  index   values   master  patched    master  patched     int   text
  ------------------------------------------------------------------
  minmax       1        7        7        27       25    100%    92%
              10       66       15       277       70     23%    25%
              20      132       16       558       85     12%    15%
              50      331       21      1398      102      7%     7%
             100      663       29      2787      118      4%     4%
             500     3312       81     13964      198      2%     1%
  ------------------------------------------------------------------
  bloom        1       30       27        23       18     92%    77%
              10      302      208       231       35     69%    15%
              20      585      381       463       54     65%    12%
              50     1299      761      1159      111     59%    10%
             100     2194     1099      2312      204     50%     9%
             500     6850     1228     11559      918     18%     8%
  ------------------------------------------------------------------

With minmax, consider for example queries with 20 values, which used to
take ~130ms, but with the patch this drops to 16ms (~23%). And the
improvement is even more significant for larger number of values. For
text data the results are pretty comparable.

With bloom indexes, the improvements are proportional to how expensive
the hash function is (for the data type). For int the hash is fairly
cheap, so the improvement is rather moderate (but visible). For text,
the improvements are way more significant - for 10 values the duration
is reduced by a whopping 85%.


regards

-- 
Tomas Vondra
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

Attachments:

  [text/x-patch] 0001-BRIN-bloom-cleanup-20230215.patch (1.9K, ../../[email protected]/2-0001-BRIN-bloom-cleanup-20230215.patch)
  download | inline diff:
From 9c2726e9a227dad2ed11d246bea321c37e38c5c1 Mon Sep 17 00:00:00 2001
From: Tomas Vondra <[email protected]>
Date: Wed, 15 Feb 2023 12:39:23 +0100
Subject: [PATCH 1/9] BRIN bloom cleanup

Minor cleanup of the BRIN bloom code - use the proper data type for the
boolean variable, and correct comment copied from minmax.
---
 src/backend/access/brin/brin_bloom.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/src/backend/access/brin/brin_bloom.c b/src/backend/access/brin/brin_bloom.c
index e4953a9d37..6c716924ff 100644
--- a/src/backend/access/brin/brin_bloom.c
+++ b/src/backend/access/brin/brin_bloom.c
@@ -574,7 +574,7 @@ brin_bloom_consistent(PG_FUNCTION_ARGS)
 	Oid			colloid = PG_GET_COLLATION();
 	AttrNumber	attno;
 	Datum		value;
-	Datum		matches;
+	bool		matches;
 	FmgrInfo   *finfo;
 	uint32		hashValue;
 	BloomFilter *filter;
@@ -584,6 +584,7 @@ brin_bloom_consistent(PG_FUNCTION_ARGS)
 
 	Assert(filter);
 
+	/* assume all scan keys match */
 	matches = true;
 
 	for (keyno = 0; keyno < nkeys; keyno++)
@@ -601,9 +602,8 @@ brin_bloom_consistent(PG_FUNCTION_ARGS)
 			case BloomEqualStrategyNumber:
 
 				/*
-				 * In the equality case (WHERE col = someval), we want to
-				 * return the current page range if the minimum value in the
-				 * range <= scan key, and the maximum value >= scan key.
+				 * We want to return the current page range if the bloom filter
+				 * seems to contain the value.
 				 */
 				finfo = bloom_get_procinfo(bdesc, attno, PROCNUM_HASH);
 
@@ -614,7 +614,7 @@ brin_bloom_consistent(PG_FUNCTION_ARGS)
 			default:
 				/* shouldn't happen */
 				elog(ERROR, "invalid strategy number %d", key->sk_strategy);
-				matches = 0;
+				matches = false;
 				break;
 		}
 
@@ -622,7 +622,7 @@ brin_bloom_consistent(PG_FUNCTION_ARGS)
 			break;
 	}
 
-	PG_RETURN_DATUM(matches);
+	PG_RETURN_BOOL(matches);
 }
 
 /*
-- 
2.39.1



  [text/x-patch] 0002-BRIN-minmax-multi-cleanup-20230215.patch (2.2K, ../../[email protected]/3-0002-BRIN-minmax-multi-cleanup-20230215.patch)
  download | inline diff:
From b57514ed44566295bb8388db200b7d1638dda002 Mon Sep 17 00:00:00 2001
From: Tomas Vondra <[email protected]>
Date: Wed, 15 Feb 2023 12:53:47 +0100
Subject: [PATCH 2/9] BRIN minmax-multi cleanup

When assigning to a Datum variable, use BoolGetDatum() consistently.
Simnplify the code by using PG_RETURN_BOOL().
---
 src/backend/access/brin/brin_minmax_multi.c | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/src/backend/access/brin/brin_minmax_multi.c b/src/backend/access/brin/brin_minmax_multi.c
index 0ace6035be..859e0022fb 100644
--- a/src/backend/access/brin/brin_minmax_multi.c
+++ b/src/backend/access/brin/brin_minmax_multi.c
@@ -2627,7 +2627,7 @@ brin_minmax_multi_consistent(PG_FUNCTION_ARGS)
 						FmgrInfo   *cmpFn;
 
 						/* by default this range does not match */
-						matches = false;
+						matches = BoolGetDatum(false);
 
 						/*
 						 * Otherwise, need to compare the new value with
@@ -2655,7 +2655,7 @@ brin_minmax_multi_consistent(PG_FUNCTION_ARGS)
 						 * We haven't managed to eliminate this range, so
 						 * consider it matching.
 						 */
-						matches = true;
+						matches = BoolGetDatum(true);
 
 						break;
 					}
@@ -2670,7 +2670,7 @@ brin_minmax_multi_consistent(PG_FUNCTION_ARGS)
 				default:
 					/* shouldn't happen */
 					elog(ERROR, "invalid strategy number %d", key->sk_strategy);
-					matches = 0;
+					matches = BoolGetDatum(false);
 					break;
 			}
 
@@ -2686,7 +2686,7 @@ brin_minmax_multi_consistent(PG_FUNCTION_ARGS)
 		 * have we found a range matching all scan keys? if yes, we're done
 		 */
 		if (matching)
-			PG_RETURN_DATUM(BoolGetDatum(true));
+			PG_RETURN_BOOL(true);
 	}
 
 	/*
@@ -2729,7 +2729,7 @@ brin_minmax_multi_consistent(PG_FUNCTION_ARGS)
 				default:
 					/* shouldn't happen */
 					elog(ERROR, "invalid strategy number %d", key->sk_strategy);
-					matches = 0;
+					matches = BoolGetDatum(false);
 					break;
 			}
 
@@ -2743,10 +2743,10 @@ brin_minmax_multi_consistent(PG_FUNCTION_ARGS)
 
 		/* have we found a range matching all scan keys? if yes, we're done */
 		if (matching)
-			PG_RETURN_DATUM(BoolGetDatum(true));
+			PG_RETURN_BOOL(true);
 	}
 
-	PG_RETURN_DATUM(BoolGetDatum(false));
+	PG_RETURN_BOOL(false);
 }
 
 /*
-- 
2.39.1



  [text/x-patch] 0003-Introduce-bloom_filter_size-20230215.patch (3.5K, ../../[email protected]/4-0003-Introduce-bloom_filter_size-20230215.patch)
  download | inline diff:
From 5c48f948486a5b4da5e888ab57451818ebb36eef Mon Sep 17 00:00:00 2001
From: Tomas Vondra <[email protected]>
Date: Tue, 14 Feb 2023 20:28:08 +0100
Subject: [PATCH 3/9] Introduce bloom_filter_size

Wrap calculation of Bloom filter parameters (from ndistinct and false
positive rate) into a function. We'll need to do this calculation in
other places, and this makes it more consistent.
---
 src/backend/access/brin/brin_bloom.c | 63 +++++++++++++++++++++-------
 1 file changed, 47 insertions(+), 16 deletions(-)

diff --git a/src/backend/access/brin/brin_bloom.c b/src/backend/access/brin/brin_bloom.c
index 6c716924ff..4ff80aeb0c 100644
--- a/src/backend/access/brin/brin_bloom.c
+++ b/src/backend/access/brin/brin_bloom.c
@@ -259,6 +259,48 @@ typedef struct BloomFilter
 	char		data[FLEXIBLE_ARRAY_MEMBER];
 } BloomFilter;
 
+/*
+ * bloom_filter_size
+ *		Calculate Bloom filter parameters (nbits, nbytes, nhashes).
+ *
+ * Given expected number of distinct values and desired false positive rate,
+ * calculates the optimal parameters of the Bloom filter.
+ *
+ * The resulting parameters are returned through nbytesp (number of bytes),
+ * nbitsp (number of bits) and nhashesp (number of hash functions). If a
+ * pointer is NULL, the parameter is not returned.
+ */
+static void
+bloom_filter_size(int ndistinct, double false_positive_rate,
+				  int *nbytesp, int *nbitsp, int *nhashesp)
+{
+	double	k;
+	int		nbits,
+			nbytes;
+
+	/* sizing bloom filter: -(n * ln(p)) / (ln(2))^2 */
+	nbits = ceil(-(ndistinct * log(false_positive_rate)) / pow(log(2.0), 2));
+
+	/* round m to whole bytes */
+	nbytes = ((nbits + 7) / 8);
+	nbits = nbytes * 8;
+
+	/*
+	 * round(log(2.0) * m / ndistinct), but assume round() may not be
+	 * available on Windows
+	 */
+	k = log(2.0) * nbits / ndistinct;
+	k = (k - floor(k) >= 0.5) ? ceil(k) : floor(k);
+
+	if (nbytesp)
+		*nbytesp = nbytes;
+
+	if (nbitsp)
+		*nbitsp = nbits;
+
+	if (nhashesp)
+		*nhashesp = (int) k;
+}
 
 /*
  * bloom_init
@@ -275,19 +317,15 @@ bloom_init(int ndistinct, double false_positive_rate)
 
 	int			nbits;			/* size of filter / number of bits */
 	int			nbytes;			/* size of filter / number of bytes */
-
-	double		k;				/* number of hash functions */
+	int			nhashes;		/* number of hash functions */
 
 	Assert(ndistinct > 0);
 	Assert((false_positive_rate >= BLOOM_MIN_FALSE_POSITIVE_RATE) &&
 		   (false_positive_rate < BLOOM_MAX_FALSE_POSITIVE_RATE));
 
-	/* sizing bloom filter: -(n * ln(p)) / (ln(2))^2 */
-	nbits = ceil(-(ndistinct * log(false_positive_rate)) / pow(log(2.0), 2));
-
-	/* round m to whole bytes */
-	nbytes = ((nbits + 7) / 8);
-	nbits = nbytes * 8;
+	/* calculate bloom filter size / parameters */
+	bloom_filter_size(ndistinct, false_positive_rate,
+					  &nbytes, &nbits, &nhashes);
 
 	/*
 	 * Reject filters that are obviously too large to store on a page.
@@ -310,13 +348,6 @@ bloom_init(int ndistinct, double false_positive_rate)
 		elog(ERROR, "the bloom filter is too large (%d > %zu)", nbytes,
 			 BloomMaxFilterSize);
 
-	/*
-	 * round(log(2.0) * m / ndistinct), but assume round() may not be
-	 * available on Windows
-	 */
-	k = log(2.0) * nbits / ndistinct;
-	k = (k - floor(k) >= 0.5) ? ceil(k) : floor(k);
-
 	/*
 	 * We allocate the whole filter. Most of it is going to be 0 bits, so the
 	 * varlena is easy to compress.
@@ -326,7 +357,7 @@ bloom_init(int ndistinct, double false_positive_rate)
 	filter = (BloomFilter *) palloc0(len);
 
 	filter->flags = 0;
-	filter->nhashes = (int) k;
+	filter->nhashes = nhashes;
 	filter->nbits = nbits;
 
 	SET_VARSIZE(filter, len);
-- 
2.39.1



  [text/x-patch] 0004-Introduce-BRIN_PROCNUM_PREPROCESS-procedure-20230215.patch (4.8K, ../../[email protected]/5-0004-Introduce-BRIN_PROCNUM_PREPROCESS-procedure-20230215.patch)
  download | inline diff:
From 48593363cdca9358efb3784cb8e3b33952764842 Mon Sep 17 00:00:00 2001
From: Tomas Vondra <[email protected]>
Date: Tue, 14 Feb 2023 20:29:33 +0100
Subject: [PATCH 4/9] Introduce BRIN_PROCNUM_PREPROCESS procedure

Allow BRIN opclasses to define an optional procedure to preprocess scan
keys, and call it from brinrescan(). This allows the opclass to modify
the keys in various ways - sort arrays, calculate hashes, ...

Note: The procedure is optional, so existing opclasses don't need to add
it. But if it uses the existing BRIN_PROCNUM_CONSISTENT function, it'll
get broken. If we want to make this backwards-compatible, we might check
if BRIN_PROCNUM_PREPROCESS exist from BRIN_PROCNUM_CONSISTENT, and
adjust behavior based on that.
---
 src/backend/access/brin/brin.c     | 72 ++++++++++++++++++++++++++----
 src/include/access/brin_internal.h |  1 +
 2 files changed, 65 insertions(+), 8 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index 85ae795949..ef3d64daf6 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -66,6 +66,12 @@ typedef struct BrinOpaque
 	BlockNumber bo_pagesPerRange;
 	BrinRevmap *bo_rmAccess;
 	BrinDesc   *bo_bdesc;
+
+	/* preprocessed scan keys */
+	int			bo_numScanKeys;		/* number of (preprocessed) scan keys */
+	ScanKey	   *bo_scanKeys;		/* modified copy of scan->keyData */
+	MemoryContext bo_scanKeysCxt;	/* scan-lifespan context for key data */
+
 } BrinOpaque;
 
 #define BRIN_ALL_BLOCKRANGES	InvalidBlockNumber
@@ -334,6 +340,11 @@ brinbeginscan(Relation r, int nkeys, int norderbys)
 	opaque->bo_rmAccess = brinRevmapInitialize(r, &opaque->bo_pagesPerRange,
 											   scan->xs_snapshot);
 	opaque->bo_bdesc = brin_build_desc(r);
+
+	opaque->bo_numScanKeys = 0;
+	opaque->bo_scanKeys = NULL;
+	opaque->bo_scanKeysCxt = NULL;
+
 	scan->opaque = opaque;
 
 	return scan;
@@ -456,7 +467,7 @@ bringetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
 	/* Preprocess the scan keys - split them into per-attribute arrays. */
 	for (int keyno = 0; keyno < scan->numberOfKeys; keyno++)
 	{
-		ScanKey		key = &scan->keyData[keyno];
+		ScanKey		key = opaque->bo_scanKeys[keyno];
 		AttrNumber	keyattno = key->sk_attno;
 
 		/*
@@ -735,17 +746,62 @@ void
 brinrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
 		   ScanKey orderbys, int norderbys)
 {
-	/*
-	 * Other index AMs preprocess the scan keys at this point, or sometime
-	 * early during the scan; this lets them optimize by removing redundant
-	 * keys, or doing early returns when they are impossible to satisfy; see
-	 * _bt_preprocess_keys for an example.  Something like that could be added
-	 * here someday, too.
-	 */
+	BrinOpaque *bo = (BrinOpaque *) scan->opaque;
+	Relation	idxRel = scan->indexRelation;
+	MemoryContext	oldcxt;
 
 	if (scankey && scan->numberOfKeys > 0)
 		memmove(scan->keyData, scankey,
 				scan->numberOfKeys * sizeof(ScanKeyData));
+
+	/*
+	 * Use the BRIN_PROCNUM_PREPROCESS procedure (if defined) to preprocess
+	 * the scan keys. The procedure may do anything, as long as the result
+	 * looks like a ScanKey. If there's no procedure, we keep the original
+	 * scan key.
+	 *
+	 * FIXME Probably need fixes to handle NULLs correctly.
+	 */
+	if (bo->bo_scanKeysCxt == NULL)
+		bo->bo_scanKeysCxt = AllocSetContextCreate(CurrentMemoryContext,
+												   "BRIN scan keys context",
+												   ALLOCSET_SMALL_SIZES);
+	else
+		MemoryContextReset(bo->bo_scanKeysCxt);
+
+	oldcxt = MemoryContextSwitchTo(bo->bo_scanKeysCxt);
+
+	bo->bo_scanKeys = palloc0(sizeof(ScanKey) * nscankeys);
+
+	for (int i = 0; i < nscankeys; i++)
+	{
+		FmgrInfo   *finfo;
+		ScanKey		key = &scan->keyData[i];
+		Oid			procid;
+		Datum		ret;
+
+		/* fetch key preprocess support procedure if specified */
+		procid = index_getprocid(idxRel, key->sk_attno,
+								 BRIN_PROCNUM_PREPROCESS);
+
+		/* not specified, just point to the original key */
+		if (!OidIsValid(procid))
+		{
+			bo->bo_scanKeys[i] = key;
+			continue;
+		}
+
+		finfo = index_getprocinfo(idxRel, key->sk_attno,
+								  BRIN_PROCNUM_PREPROCESS);
+
+		ret = FunctionCall2(finfo,
+							PointerGetDatum(bo->bo_bdesc),
+							PointerGetDatum(key));
+
+		bo->bo_scanKeys[i] = (ScanKey) DatumGetPointer(ret);
+	}
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
diff --git a/src/include/access/brin_internal.h b/src/include/access/brin_internal.h
index 97ddc925b2..d6a51f2bc4 100644
--- a/src/include/access/brin_internal.h
+++ b/src/include/access/brin_internal.h
@@ -73,6 +73,7 @@ typedef struct BrinDesc
 #define BRIN_PROCNUM_UNION			4
 #define BRIN_MANDATORY_NPROCS		4
 #define BRIN_PROCNUM_OPTIONS 		5	/* optional */
+#define BRIN_PROCNUM_PREPROCESS		6	/* optional */
 /* procedure numbers up to 10 are reserved for BRIN future expansion */
 #define BRIN_FIRST_OPTIONAL_PROCNUM 11
 #define BRIN_LAST_OPTIONAL_PROCNUM	15
-- 
2.39.1



  [text/x-patch] 0005-Support-SK_SEARCHARRAY-in-BRIN-minmax-20230215.patch (27.7K, ../../[email protected]/6-0005-Support-SK_SEARCHARRAY-in-BRIN-minmax-20230215.patch)
  download | inline diff:
From 455932eb8e4d05e9f05350ea1a137e93bf786ae9 Mon Sep 17 00:00:00 2001
From: Tomas Vondra <[email protected]>
Date: Fri, 10 Feb 2023 16:07:57 +0100
Subject: [PATCH 5/9] Support SK_SEARCHARRAY in BRIN minmax

Set "amsearcharray=true" for BRIN, and extend the minmax opclass to
handle both scalar values and arrays. This allows handling conditions

    ... WHERE a IN (1, 2, 43, 2132, 134)

    ... WHERE a = ANY(ARRAY[34, 45, -1, 234])

    ... WHERE a <= ANY(ARRAY[4938, 282, 2934])

more efficiently - until now we simply built the bitmap for each
scalar value, so we walked the BRIN index many times. Which may be quite
expensive for indexes with many ranges (large tables and/or low
pages_per_range).

There's a couple problems / open questions / TODO items:

- The other opclasses don't handle SK_SEARCHARRAY yet.

- The array is always searched linearly, so this may be costly for large
  arrays (with many elements).

- The array is deconstructed again for each range. We should reuse this,
  somehow.
---
 src/backend/access/brin/brin.c          |   3 +-
 src/backend/access/brin/brin_minmax.c   | 346 +++++++++++++++++++++---
 src/backend/access/brin/brin_validate.c |   4 +
 src/include/catalog/pg_amproc.dat       |  64 +++++
 src/include/catalog/pg_proc.dat         |   3 +
 src/test/regress/expected/amutils.out   |   2 +-
 6 files changed, 382 insertions(+), 40 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index ef3d64daf6..a232275c14 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -38,6 +38,7 @@
 #include "utils/datum.h"
 #include "utils/guc.h"
 #include "utils/index_selfuncs.h"
+#include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
 
@@ -107,7 +108,7 @@ brinhandler(PG_FUNCTION_ARGS)
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = true;
 	amroutine->amoptionalkey = true;
-	amroutine->amsearcharray = false;
+	amroutine->amsearcharray = true;
 	amroutine->amsearchnulls = true;
 	amroutine->amstorage = true;
 	amroutine->amclusterable = false;
diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c
index 2431591be6..359bb39f96 100644
--- a/src/backend/access/brin/brin_minmax.c
+++ b/src/backend/access/brin/brin_minmax.c
@@ -16,11 +16,21 @@
 #include "access/stratnum.h"
 #include "catalog/pg_amop.h"
 #include "catalog/pg_type.h"
+#include "utils/array.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
 #include "utils/syscache.h"
+#include "utils/sortsupport.h"
+
+/*
+ * We use some private sk_flags bits in preprocessed scan keys.  We're allowed
+ * to use bits 16-31 (see skey.h).  The uppermost bits are copied from the
+ * index's indoption[] array entry for the index attribute.
+ */
+#define SK_BRIN_SORTED	0x00010000	/* deconstructed and sorted array */
+
 
 typedef struct MinmaxOpaque
 {
@@ -126,6 +136,133 @@ brin_minmax_add_value(PG_FUNCTION_ARGS)
 	PG_RETURN_BOOL(updated);
 }
 
+
+static int
+compare_array_values(const void *a, const void *b, void *arg)
+{
+	Datum	da = * (Datum *) a;
+	Datum	db = * (Datum *) b;
+	SortSupport	ssup = (SortSupport) arg;
+
+	return ApplySortComparator(da, false, db, false, ssup);
+}
+
+/*
+ * lower_boundary
+ *		Determine lowest index so that (values[index] >= minvalue).
+ *
+ * The array of values is expected to be sorted, so this is the first value
+ * that may fall into the [minvalue, maxvalue] range, as it exceeds minval.
+ * It's not guaranteed, though, as it might exceed maxvalue too.
+ */
+static int
+lower_boundary(Datum *values, int nvalues, Datum minvalue, SortSupport ssup)
+{
+	int		start = 0,
+			end = (nvalues - 1);
+
+	/* everything exceeds minval and might match */
+	if (compare_array_values(&minvalue, &values[start], ssup) <= 0)
+		return 0;
+
+	/* nothing could match */
+	if (compare_array_values(&minvalue, &values[end], ssup) > 0)
+		return nvalues;
+
+	while ((end - start) > 0)
+	{
+		int midpoint;
+		int r;
+
+		midpoint = start + (end - start) / 2;
+
+		r = compare_array_values(&minvalue, &values[midpoint], ssup);
+
+		if (r > 0)
+			start = Max(midpoint, start + 1);
+		else
+			end = midpoint;
+	}
+
+	/* the value should meet the (v >=minvalue) requirement */
+	Assert(compare_array_values(&values[start], &minvalue, ssup) >= 0);
+
+	/* we know start can't be 0, so it's legal to subtract 1 */
+	Assert(compare_array_values(&values[start-1], &minvalue, ssup) < 0);
+
+	return start;
+}
+
+typedef struct ScanKeyArray {
+	Oid		typeid;
+	int		nelements;
+	Datum  *elements;
+} ScanKeyArray;
+
+Datum
+brin_minmax_preprocess(PG_FUNCTION_ARGS)
+{
+	// BrinDesc   *bdesc = (BrinDesc *) PG_GETARG_POINTER(0);
+	ScanKey		key = (ScanKey) PG_GETARG_POINTER(1);
+	ScanKey		newkey;
+	ScanKeyArray *scanarray;
+
+	ArrayType  *arrayval;
+	int16		elmlen;
+	bool		elmbyval;
+	char		elmalign;
+	int			num_elems;
+	Datum	   *elem_values;
+	bool	   *elem_nulls;
+	TypeCacheEntry *type;
+	SortSupportData ssup;
+
+	/* ignore scalar keys */
+	if (!(key->sk_flags & SK_SEARCHARRAY))
+		PG_RETURN_POINTER(key);
+
+	arrayval = DatumGetArrayTypeP(key->sk_argument);
+
+	get_typlenbyvalalign(ARR_ELEMTYPE(arrayval),
+						 &elmlen, &elmbyval, &elmalign);
+
+	deconstruct_array(arrayval,
+					  ARR_ELEMTYPE(arrayval),
+					  elmlen, elmbyval, elmalign,
+					  &elem_values, &elem_nulls, &num_elems);
+
+	type = lookup_type_cache(ARR_ELEMTYPE(arrayval), TYPECACHE_LT_OPR);
+
+	memset(&ssup, 0, sizeof(SortSupportData));
+
+	ssup.ssup_collation = key->sk_collation;
+	ssup.ssup_cxt = CurrentMemoryContext;
+
+	PrepareSortSupportFromOrderingOp(type->lt_opr, &ssup);
+
+	qsort_interruptible(elem_values, num_elems, sizeof(Datum),
+						compare_array_values, &ssup);
+
+	scanarray = palloc0(sizeof(ScanKeyArray));
+	scanarray->typeid = ARR_ELEMTYPE(arrayval);
+	scanarray->nelements = num_elems;
+	scanarray->elements = elem_values;
+
+	newkey = palloc0(sizeof(ScanKeyData));
+
+	ScanKeyEntryInitializeWithInfo(newkey,
+								   (key->sk_flags | SK_BRIN_SORTED),
+								   key->sk_attno,
+								   key->sk_strategy,
+								   key->sk_subtype,
+								   key->sk_collation,
+								   &key->sk_func,
+								   PointerGetDatum(scanarray));
+
+	PG_RETURN_POINTER(newkey);
+}
+
+
 /*
  * 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
@@ -157,46 +294,179 @@ brin_minmax_consistent(PG_FUNCTION_ARGS)
 	attno = key->sk_attno;
 	subtype = key->sk_subtype;
 	value = key->sk_argument;
-	switch (key->sk_strategy)
+
+	/*
+	 * For regular (scalar) scan keys, we simply compare the value to the
+	 * range min/max values, and we're done. For preprocessed SK_SEARCHARRAY
+	 * keys we need to loop through the deparsed values.
+	 */
+	if (likely(!(key->sk_flags & SK_BRIN_SORTED)))
+	{
+		switch (key->sk_strategy)
+		{
+			case BTLessStrategyNumber:
+			case BTLessEqualStrategyNumber:
+				finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
+													 key->sk_strategy);
+				matches = FunctionCall2Coll(finfo, colloid, column->bv_values[0],
+											value);
+				break;
+			case BTEqualStrategyNumber:
+
+				/*
+				 * In the equality case (WHERE col = someval), we want to return
+				 * the current page range if the minimum value in the range <=
+				 * scan key, and the maximum value >= scan key.
+				 */
+				finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
+													 BTLessEqualStrategyNumber);
+				matches = FunctionCall2Coll(finfo, colloid, column->bv_values[0],
+											value);
+				if (!DatumGetBool(matches))
+					break;
+				/* max() >= scankey */
+				finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
+													 BTGreaterEqualStrategyNumber);
+				matches = FunctionCall2Coll(finfo, colloid, column->bv_values[1],
+											value);
+				break;
+			case BTGreaterEqualStrategyNumber:
+			case BTGreaterStrategyNumber:
+				finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
+													 key->sk_strategy);
+				matches = FunctionCall2Coll(finfo, colloid, column->bv_values[1],
+											value);
+				break;
+			default:
+				/* shouldn't happen */
+				elog(ERROR, "invalid strategy number %d", key->sk_strategy);
+				matches = 0;
+				break;
+		}
+	}
+	else
 	{
-		case BTLessStrategyNumber:
-		case BTLessEqualStrategyNumber:
-			finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
-												 key->sk_strategy);
-			matches = FunctionCall2Coll(finfo, colloid, column->bv_values[0],
-										value);
-			break;
-		case BTEqualStrategyNumber:
-
-			/*
-			 * In the equality case (WHERE col = someval), we want to return
-			 * the current page range if the minimum value in the range <=
-			 * scan key, and the maximum value >= scan key.
-			 */
-			finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
-												 BTLessEqualStrategyNumber);
-			matches = FunctionCall2Coll(finfo, colloid, column->bv_values[0],
-										value);
-			if (!DatumGetBool(matches))
+		ScanKeyArray *array = (ScanKeyArray *) value;
+
+		switch (key->sk_strategy)
+		{
+			case BTLessStrategyNumber:
+			case BTLessEqualStrategyNumber:
+				/*
+				 * Check the last (largest) value in the array - at least this
+				 * value has to exceed the range minval.
+				 */
+				finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
+													 key->sk_strategy);
+				matches = FunctionCall2Coll(finfo, colloid, column->bv_values[0],
+											array->elements[array->nelements-1]);
+				break;
+			case BTEqualStrategyNumber:
+
+				/*
+				 * In the equality case (WHERE col = someval), we want to return
+				 * the current page range if the minimum value in the range <=
+				 * scan key, and the maximum value >= scan key.
+				 *
+				 * We do this in two phases. We check the array min/max values to see
+				 * if there even can be a matching value, and if yes we do a binary
+				 * search to find the first value that exceeds range minval. And then
+				 * we check if it actually matches the range.
+				 *
+				 * XXX The first phase is probably unnecessary, because lower_bound()
+				 * does pretty much exactly that too.
+				 */
+				{
+					Datum val;
+					SortSupportData ssup;
+					int			lower;
+					TypeCacheEntry *type;
+
+					/* Is the first (smallest) value after the BRIN range? */
+					val = array->elements[0];
+
+					finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
+														 BTLessEqualStrategyNumber);
+					matches = FunctionCall2Coll(finfo, colloid, val, column->bv_values[1]);
+
+					/* minval > max(range values) */
+					if (!DatumGetBool(matches))
+						break;
+
+					/* Is the last (largest) value before the BRIN range? */
+					val = array->elements[array->nelements-1];
+
+					finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
+														 BTGreaterEqualStrategyNumber);
+					matches = FunctionCall2Coll(finfo, colloid, val, column->bv_values[0]);
+
+					/* maxval < min(range values) */
+					if (!DatumGetBool(matches))
+						break;
+
+					/*
+					 * OK, there might be some values matching the range. We have
+					 * to search them one by one, or perhaps try binsearch.
+					 */
+					type = lookup_type_cache(array->typeid, TYPECACHE_LT_OPR);
+
+					memset(&ssup, 0, sizeof(SortSupportData));
+
+					ssup.ssup_collation = key->sk_collation;
+					ssup.ssup_cxt = CurrentMemoryContext;
+
+					PrepareSortSupportFromOrderingOp(type->lt_opr, &ssup);
+
+					lower = lower_boundary(array->elements, array->nelements, column->bv_values[0], &ssup);
+
+					/* no elements can possibly match */
+					if (lower == array->nelements)
+					{
+						matches = BoolGetDatum(false);
+						break;
+					}
+
+					/*
+					 * OK, the first element must match the upper boundary too
+					 * (if it does not, no following elements can).
+					 */
+					val = array->elements[lower];
+
+					/*
+					 * In the equality case (WHERE col = someval), we want to return
+					 * the current page range if the minimum value in the range <=
+					 * scan key, and the maximum value >= scan key.
+					 */
+					finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
+														 BTLessEqualStrategyNumber);
+					matches = FunctionCall2Coll(finfo, colloid, column->bv_values[0],
+												val);
+					if (!DatumGetBool(matches))
+						break;
+					/* max() >= scankey */
+					finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
+														 BTGreaterEqualStrategyNumber);
+					matches = FunctionCall2Coll(finfo, colloid, column->bv_values[1],
+												val);
+					break;
+				}
+			case BTGreaterEqualStrategyNumber:
+			case BTGreaterStrategyNumber:
+				/*
+				 * Check the first (smallest) value in the array - at least this
+				 * value has to be smaller than the range maxval.
+				 */
+				finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
+													 key->sk_strategy);
+				matches = FunctionCall2Coll(finfo, colloid, column->bv_values[1],
+											array->elements[0]);
+				break;
+			default:
+				/* shouldn't happen */
+				elog(ERROR, "invalid strategy number %d", key->sk_strategy);
+				matches = 0;
 				break;
-			/* max() >= scankey */
-			finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
-												 BTGreaterEqualStrategyNumber);
-			matches = FunctionCall2Coll(finfo, colloid, column->bv_values[1],
-										value);
-			break;
-		case BTGreaterEqualStrategyNumber:
-		case BTGreaterStrategyNumber:
-			finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
-												 key->sk_strategy);
-			matches = FunctionCall2Coll(finfo, colloid, column->bv_values[1],
-										value);
-			break;
-		default:
-			/* shouldn't happen */
-			elog(ERROR, "invalid strategy number %d", key->sk_strategy);
-			matches = 0;
-			break;
+		}
 	}
 
 	PG_RETURN_DATUM(matches);
diff --git a/src/backend/access/brin/brin_validate.c b/src/backend/access/brin/brin_validate.c
index c8edfb3759..0889e24bc0 100644
--- a/src/backend/access/brin/brin_validate.c
+++ b/src/backend/access/brin/brin_validate.c
@@ -108,6 +108,10 @@ brinvalidate(Oid opclassoid)
 			case BRIN_PROCNUM_OPTIONS:
 				ok = check_amoptsproc_signature(procform->amproc);
 				break;
+			case BRIN_PROCNUM_PREPROCESS:
+				ok = check_amproc_signature(procform->amproc, INTERNALOID, true,
+											2, 2, INTERNALOID, INTERNALOID);
+				break;
 			default:
 				/* Complain if it's not a valid optional proc number */
 				if (procform->amprocnum < BRIN_FIRST_OPTIONAL_PROCNUM ||
diff --git a/src/include/catalog/pg_amproc.dat b/src/include/catalog/pg_amproc.dat
index 5b950129de..166681c31e 100644
--- a/src/include/catalog/pg_amproc.dat
+++ b/src/include/catalog/pg_amproc.dat
@@ -804,6 +804,8 @@
   amproc => 'brin_minmax_consistent' },
 { amprocfamily => 'brin/bytea_minmax_ops', amproclefttype => 'bytea',
   amprocrighttype => 'bytea', amprocnum => '4', amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/bytea_minmax_ops', amproclefttype => 'bytea',
+  amprocrighttype => 'bytea', amprocnum => '6', amproc => 'brin_minmax_preprocess' },
 
 # bloom bytea
 { amprocfamily => 'brin/bytea_bloom_ops', amproclefttype => 'bytea',
@@ -835,6 +837,8 @@
   amproc => 'brin_minmax_consistent' },
 { amprocfamily => 'brin/char_minmax_ops', amproclefttype => 'char',
   amprocrighttype => 'char', amprocnum => '4', amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/char_minmax_ops', amproclefttype => 'char',
+  amprocrighttype => 'char', amprocnum => '6', amproc => 'brin_minmax_preprocess' },
 
 # bloom "char"
 { amprocfamily => 'brin/char_bloom_ops', amproclefttype => 'char',
@@ -864,6 +868,8 @@
   amproc => 'brin_minmax_consistent' },
 { amprocfamily => 'brin/name_minmax_ops', amproclefttype => 'name',
   amprocrighttype => 'name', amprocnum => '4', amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/name_minmax_ops', amproclefttype => 'name',
+  amprocrighttype => 'name', amprocnum => '6', amproc => 'brin_minmax_preprocess' },
 
 # bloom name
 { amprocfamily => 'brin/name_bloom_ops', amproclefttype => 'name',
@@ -893,6 +899,8 @@
   amproc => 'brin_minmax_consistent' },
 { amprocfamily => 'brin/integer_minmax_ops', amproclefttype => 'int8',
   amprocrighttype => 'int8', amprocnum => '4', amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/integer_minmax_ops', amproclefttype => 'int8',
+  amprocrighttype => 'int8', amprocnum => '6', amproc => 'brin_minmax_preprocess' },
 
 { amprocfamily => 'brin/integer_minmax_ops', amproclefttype => 'int2',
   amprocrighttype => 'int2', amprocnum => '1',
@@ -905,6 +913,8 @@
   amproc => 'brin_minmax_consistent' },
 { amprocfamily => 'brin/integer_minmax_ops', amproclefttype => 'int2',
   amprocrighttype => 'int2', amprocnum => '4', amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/integer_minmax_ops', amproclefttype => 'int2',
+  amprocrighttype => 'int2', amprocnum => '6', amproc => 'brin_minmax_preprocess' },
 
 { amprocfamily => 'brin/integer_minmax_ops', amproclefttype => 'int4',
   amprocrighttype => 'int4', amprocnum => '1',
@@ -917,6 +927,8 @@
   amproc => 'brin_minmax_consistent' },
 { amprocfamily => 'brin/integer_minmax_ops', amproclefttype => 'int4',
   amprocrighttype => 'int4', amprocnum => '4', amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/integer_minmax_ops', amproclefttype => 'int4',
+  amprocrighttype => 'int4', amprocnum => '6', amproc => 'brin_minmax_preprocess' },
 
 # minmax multi integer: int2, int4, int8
 { amprocfamily => 'brin/integer_minmax_multi_ops', amproclefttype => 'int2',
@@ -1034,6 +1046,8 @@
   amproc => 'brin_minmax_consistent' },
 { amprocfamily => 'brin/text_minmax_ops', amproclefttype => 'text',
   amprocrighttype => 'text', amprocnum => '4', amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/text_minmax_ops', amproclefttype => 'text',
+  amprocrighttype => 'text', amprocnum => '6', amproc => 'brin_minmax_preprocess' },
 
 # bloom text
 { amprocfamily => 'brin/text_bloom_ops', amproclefttype => 'text',
@@ -1062,6 +1076,8 @@
   amproc => 'brin_minmax_consistent' },
 { amprocfamily => 'brin/oid_minmax_ops', amproclefttype => 'oid',
   amprocrighttype => 'oid', amprocnum => '4', amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/oid_minmax_ops', amproclefttype => 'oid',
+  amprocrighttype => 'oid', amprocnum => '6', amproc => 'brin_minmax_preprocess' },
 
 # minmax multi oid
 { amprocfamily => 'brin/oid_minmax_multi_ops', amproclefttype => 'oid',
@@ -1110,6 +1126,8 @@
   amproc => 'brin_minmax_consistent' },
 { amprocfamily => 'brin/tid_minmax_ops', amproclefttype => 'tid',
   amprocrighttype => 'tid', amprocnum => '4', amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/tid_minmax_ops', amproclefttype => 'tid',
+  amprocrighttype => 'tid', amprocnum => '6', amproc => 'brin_minmax_preprocess' },
 
 # bloom tid
 { amprocfamily => 'brin/tid_bloom_ops', amproclefttype => 'tid',
@@ -1160,6 +1178,9 @@
 { amprocfamily => 'brin/float_minmax_ops', amproclefttype => 'float4',
   amprocrighttype => 'float4', amprocnum => '4',
   amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/float_minmax_ops', amproclefttype => 'float4',
+  amprocrighttype => 'float4', amprocnum => '6',
+  amproc => 'brin_minmax_preprocess' },
 
 { amprocfamily => 'brin/float_minmax_ops', amproclefttype => 'float8',
   amprocrighttype => 'float8', amprocnum => '1',
@@ -1173,6 +1194,9 @@
 { amprocfamily => 'brin/float_minmax_ops', amproclefttype => 'float8',
   amprocrighttype => 'float8', amprocnum => '4',
   amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/float_minmax_ops', amproclefttype => 'float8',
+  amprocrighttype => 'float8', amprocnum => '6',
+  amproc => 'brin_minmax_preprocess' },
 
 # minmax multi float
 { amprocfamily => 'brin/float_minmax_multi_ops', amproclefttype => 'float4',
@@ -1261,6 +1285,9 @@
 { amprocfamily => 'brin/macaddr_minmax_ops', amproclefttype => 'macaddr',
   amprocrighttype => 'macaddr', amprocnum => '4',
   amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/macaddr_minmax_ops', amproclefttype => 'macaddr',
+  amprocrighttype => 'macaddr', amprocnum => '6',
+  amproc => 'brin_minmax_preprocess' },
 
 # minmax multi macaddr
 { amprocfamily => 'brin/macaddr_minmax_multi_ops', amproclefttype => 'macaddr',
@@ -1314,6 +1341,9 @@
 { amprocfamily => 'brin/macaddr8_minmax_ops', amproclefttype => 'macaddr8',
   amprocrighttype => 'macaddr8', amprocnum => '4',
   amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/macaddr8_minmax_ops', amproclefttype => 'macaddr8',
+  amprocrighttype => 'macaddr8', amprocnum => '6',
+  amproc => 'brin_minmax_preprocess' },
 
 # minmax multi macaddr8
 { amprocfamily => 'brin/macaddr8_minmax_multi_ops',
@@ -1366,6 +1396,8 @@
   amproc => 'brin_minmax_consistent' },
 { amprocfamily => 'brin/network_minmax_ops', amproclefttype => 'inet',
   amprocrighttype => 'inet', amprocnum => '4', amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/network_minmax_ops', amproclefttype => 'inet',
+  amprocrighttype => 'inet', amprocnum => '6', amproc => 'brin_minmax_preprocess' },
 
 # minmax multi inet
 { amprocfamily => 'brin/network_minmax_multi_ops', amproclefttype => 'inet',
@@ -1436,6 +1468,9 @@
 { amprocfamily => 'brin/bpchar_minmax_ops', amproclefttype => 'bpchar',
   amprocrighttype => 'bpchar', amprocnum => '4',
   amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/bpchar_minmax_ops', amproclefttype => 'bpchar',
+  amprocrighttype => 'bpchar', amprocnum => '6',
+  amproc => 'brin_minmax_preprocess' },
 
 # bloom character
 { amprocfamily => 'brin/bpchar_bloom_ops', amproclefttype => 'bpchar',
@@ -1467,6 +1502,8 @@
   amproc => 'brin_minmax_consistent' },
 { amprocfamily => 'brin/time_minmax_ops', amproclefttype => 'time',
   amprocrighttype => 'time', amprocnum => '4', amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/time_minmax_ops', amproclefttype => 'time',
+  amprocrighttype => 'time', amprocnum => '6', amproc => 'brin_minmax_preprocess' },
 
 # minmax multi time without time zone
 { amprocfamily => 'brin/time_minmax_multi_ops', amproclefttype => 'time',
@@ -1517,6 +1554,9 @@
 { amprocfamily => 'brin/datetime_minmax_ops', amproclefttype => 'timestamp',
   amprocrighttype => 'timestamp', amprocnum => '4',
   amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/datetime_minmax_ops', amproclefttype => 'timestamp',
+  amprocrighttype => 'timestamp', amprocnum => '6',
+  amproc => 'brin_minmax_preprocess' },
 
 { amprocfamily => 'brin/datetime_minmax_ops', amproclefttype => 'timestamptz',
   amprocrighttype => 'timestamptz', amprocnum => '1',
@@ -1530,6 +1570,9 @@
 { amprocfamily => 'brin/datetime_minmax_ops', amproclefttype => 'timestamptz',
   amprocrighttype => 'timestamptz', amprocnum => '4',
   amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/datetime_minmax_ops', amproclefttype => 'timestamptz',
+  amprocrighttype => 'timestamptz', amprocnum => '6',
+  amproc => 'brin_minmax_preprocess' },
 
 { amprocfamily => 'brin/datetime_minmax_ops', amproclefttype => 'date',
   amprocrighttype => 'date', amprocnum => '1',
@@ -1542,6 +1585,8 @@
   amproc => 'brin_minmax_consistent' },
 { amprocfamily => 'brin/datetime_minmax_ops', amproclefttype => 'date',
   amprocrighttype => 'date', amprocnum => '4', amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/datetime_minmax_ops', amproclefttype => 'date',
+  amprocrighttype => 'date', amprocnum => '6', amproc => 'brin_minmax_preprocess' },
 
 # minmax multi datetime (date, timestamp, timestamptz)
 { amprocfamily => 'brin/datetime_minmax_multi_ops',
@@ -1668,6 +1713,9 @@
 { amprocfamily => 'brin/interval_minmax_ops', amproclefttype => 'interval',
   amprocrighttype => 'interval', amprocnum => '4',
   amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/interval_minmax_ops', amproclefttype => 'interval',
+  amprocrighttype => 'interval', amprocnum => '6',
+  amproc => 'brin_minmax_preprocess' },
 
 # minmax multi interval
 { amprocfamily => 'brin/interval_minmax_multi_ops',
@@ -1721,6 +1769,9 @@
 { amprocfamily => 'brin/timetz_minmax_ops', amproclefttype => 'timetz',
   amprocrighttype => 'timetz', amprocnum => '4',
   amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/timetz_minmax_ops', amproclefttype => 'timetz',
+  amprocrighttype => 'timetz', amprocnum => '6',
+  amproc => 'brin_minmax_preprocess' },
 
 # minmax multi time with time zone
 { amprocfamily => 'brin/timetz_minmax_multi_ops', amproclefttype => 'timetz',
@@ -1771,6 +1822,8 @@
   amproc => 'brin_minmax_consistent' },
 { amprocfamily => 'brin/bit_minmax_ops', amproclefttype => 'bit',
   amprocrighttype => 'bit', amprocnum => '4', amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/bit_minmax_ops', amproclefttype => 'bit',
+  amprocrighttype => 'bit', amprocnum => '6', amproc => 'brin_minmax_preprocess' },
 
 # minmax bit varying
 { amprocfamily => 'brin/varbit_minmax_ops', amproclefttype => 'varbit',
@@ -1785,6 +1838,9 @@
 { amprocfamily => 'brin/varbit_minmax_ops', amproclefttype => 'varbit',
   amprocrighttype => 'varbit', amprocnum => '4',
   amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/varbit_minmax_ops', amproclefttype => 'varbit',
+  amprocrighttype => 'varbit', amprocnum => '6',
+  amproc => 'brin_minmax_preprocess' },
 
 # minmax numeric
 { amprocfamily => 'brin/numeric_minmax_ops', amproclefttype => 'numeric',
@@ -1799,6 +1855,9 @@
 { amprocfamily => 'brin/numeric_minmax_ops', amproclefttype => 'numeric',
   amprocrighttype => 'numeric', amprocnum => '4',
   amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/numeric_minmax_ops', amproclefttype => 'numeric',
+  amprocrighttype => 'numeric', amprocnum => '6',
+  amproc => 'brin_minmax_preprocess' },
 
 # minmax multi numeric
 { amprocfamily => 'brin/numeric_minmax_multi_ops', amproclefttype => 'numeric',
@@ -1851,6 +1910,8 @@
   amproc => 'brin_minmax_consistent' },
 { amprocfamily => 'brin/uuid_minmax_ops', amproclefttype => 'uuid',
   amprocrighttype => 'uuid', amprocnum => '4', amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/uuid_minmax_ops', amproclefttype => 'uuid',
+  amprocrighttype => 'uuid', amprocnum => '6', amproc => 'brin_minmax_preprocess' },
 
 # minmax multi uuid
 { amprocfamily => 'brin/uuid_minmax_multi_ops', amproclefttype => 'uuid',
@@ -1924,6 +1985,9 @@
 { amprocfamily => 'brin/pg_lsn_minmax_ops', amproclefttype => 'pg_lsn',
   amprocrighttype => 'pg_lsn', amprocnum => '4',
   amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/pg_lsn_minmax_ops', amproclefttype => 'pg_lsn',
+  amprocrighttype => 'pg_lsn', amprocnum => '6',
+  amproc => 'brin_minmax_preprocess' },
 
 # minmax multi pg_lsn
 { amprocfamily => 'brin/pg_lsn_minmax_multi_ops', amproclefttype => 'pg_lsn',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 66b73c3900..6638552bd0 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8496,6 +8496,9 @@
 { oid => '3386', descr => 'BRIN minmax support',
   proname => 'brin_minmax_union', prorettype => 'bool',
   proargtypes => 'internal internal internal', prosrc => 'brin_minmax_union' },
+{ oid => '9327', descr => 'BRIN minmax support',
+  proname => 'brin_minmax_preprocess', prorettype => 'internal',
+  proargtypes => 'internal internal', prosrc => 'brin_minmax_preprocess' },
 
 # BRIN minmax multi
 { oid => '4616', descr => 'BRIN multi minmax support',
diff --git a/src/test/regress/expected/amutils.out b/src/test/regress/expected/amutils.out
index 7ab6113c61..f3e1fbd2ae 100644
--- a/src/test/regress/expected/amutils.out
+++ b/src/test/regress/expected/amutils.out
@@ -102,7 +102,7 @@ select prop,
  orderable          | t     | f    | f    | f            | f           | f   | f
  distance_orderable | f     | f    | t    | f            | t           | f   | f
  returnable         | t     | f    | f    | t            | t           | f   | f
- search_array       | t     | f    | f    | f            | f           | f   | f
+ search_array       | t     | f    | f    | f            | f           | f   | t
  search_nulls       | t     | f    | t    | t            | t           | f   | t
  bogus              |       |      |      |              |             |     | 
 (10 rows)
-- 
2.39.1



  [text/x-patch] 0006-Support-SK_SEARCHARRAY-in-BRIN-minmax-multi-20230215.patch (26.9K, ../../[email protected]/7-0006-Support-SK_SEARCHARRAY-in-BRIN-minmax-multi-20230215.patch)
  download | inline diff:
From 7bbafab1085dea175ef7e54ac8513c633fe77461 Mon Sep 17 00:00:00 2001
From: Tomas Vondra <[email protected]>
Date: Sat, 11 Feb 2023 19:43:38 +0100
Subject: [PATCH 6/9] Support SK_SEARCHARRAY in BRIN minmax-multi

Similar approach to minmax, but the issues with deconstructing the array
over and over are even more serious.
---
 src/backend/access/brin/brin_minmax_multi.c | 439 +++++++++++++++++---
 src/include/catalog/pg_amproc.dat           |  57 +++
 src/include/catalog/pg_proc.dat             |   4 +
 3 files changed, 434 insertions(+), 66 deletions(-)

diff --git a/src/backend/access/brin/brin_minmax_multi.c b/src/backend/access/brin/brin_minmax_multi.c
index 859e0022fb..b70116d33b 100644
--- a/src/backend/access/brin/brin_minmax_multi.c
+++ b/src/backend/access/brin/brin_minmax_multi.c
@@ -109,6 +109,14 @@
 #define		MINMAX_BUFFER_MAX				8192
 #define		MINMAX_BUFFER_LOAD_FACTOR		0.5
 
+/*
+ * We use some private sk_flags bits in preprocessed scan keys.  We're allowed
+ * to use bits 16-31 (see skey.h).  The uppermost bits are copied from the
+ * index's indoption[] array entry for the index attribute.
+ */
+#define SK_BRIN_SORTED	0x00010000	/* deconstructed and sorted array */
+
+
 typedef struct MinmaxMultiOpaque
 {
 	FmgrInfo	extra_procinfos[MINMAX_MAX_PROCNUMS];
@@ -2562,6 +2570,132 @@ brin_minmax_multi_add_value(PG_FUNCTION_ARGS)
 	PG_RETURN_BOOL(modified);
 }
 
+
+static int
+compare_array_values(const void *a, const void *b, void *arg)
+{
+	Datum	da = * (Datum *) a;
+	Datum	db = * (Datum *) b;
+	SortSupport	ssup = (SortSupport) arg;
+
+	return ApplySortComparator(da, false, db, false, ssup);
+}
+
+/*
+ * lower_boundary
+ *		Determine lowest index so that (values[index] >= minvalue).
+ *
+ * The array of values is expected to be sorted, so this is the first value
+ * that may fall into the [minvalue, maxvalue] range, as it exceeds minval.
+ * It's not guaranteed, though, as it might exceed maxvalue too.
+ */
+static int
+lower_boundary(Datum *values, int nvalues, Datum minvalue, SortSupport ssup)
+{
+	int		start = 0,
+			end = (nvalues - 1);
+
+	/* everything exceeds minval and might match */
+	if (compare_array_values(&minvalue, &values[start], ssup) <= 0)
+		return 0;
+
+	/* nothing could match */
+	if (compare_array_values(&minvalue, &values[end], ssup) > 0)
+		return nvalues;
+
+	while ((end - start) > 0)
+	{
+		int midpoint;
+		int r;
+
+		midpoint = start + (end - start) / 2;
+
+		r = compare_array_values(&minvalue, &values[midpoint], ssup);
+
+		if (r > 0)
+			start = Max(midpoint, start + 1);
+		else
+			end = midpoint;
+	}
+
+	/* the value should meet the (v >=minvalue) requirement */
+	Assert(compare_array_values(&values[start], &minvalue, ssup) >= 0);
+
+	/* we know start can't be 0, so it's legal to subtract 1 */
+	Assert(compare_array_values(&values[start-1], &minvalue, ssup) < 0);
+
+	return start;
+}
+
+typedef struct ScanKeyArray {
+	Oid		typeid;
+	int		nelements;
+	Datum  *elements;
+} ScanKeyArray;
+
+Datum
+brin_minmax_multi_preprocess(PG_FUNCTION_ARGS)
+{
+	// BrinDesc   *bdesc = (BrinDesc *) PG_GETARG_POINTER(0);
+	ScanKey		key = (ScanKey) PG_GETARG_POINTER(1);
+	ScanKey		newkey;
+	ScanKeyArray *scanarray;
+
+	ArrayType  *arrayval;
+	int16		elmlen;
+	bool		elmbyval;
+	char		elmalign;
+	int			num_elems;
+	Datum	   *elem_values;
+	bool	   *elem_nulls;
+	TypeCacheEntry *type;
+	SortSupportData ssup;
+
+	/* ignore scalar keys */
+	if (!(key->sk_flags & SK_SEARCHARRAY))
+		PG_RETURN_POINTER(key);
+
+	arrayval = DatumGetArrayTypeP(key->sk_argument);
+
+	get_typlenbyvalalign(ARR_ELEMTYPE(arrayval),
+						 &elmlen, &elmbyval, &elmalign);
+
+	deconstruct_array(arrayval,
+					  ARR_ELEMTYPE(arrayval),
+					  elmlen, elmbyval, elmalign,
+					  &elem_values, &elem_nulls, &num_elems);
+
+	type = lookup_type_cache(ARR_ELEMTYPE(arrayval), TYPECACHE_LT_OPR);
+
+	memset(&ssup, 0, sizeof(SortSupportData));
+
+	ssup.ssup_collation = key->sk_collation;
+	ssup.ssup_cxt = CurrentMemoryContext;
+
+	PrepareSortSupportFromOrderingOp(type->lt_opr, &ssup);
+
+	qsort_interruptible(elem_values, num_elems, sizeof(Datum),
+						compare_array_values, &ssup);
+
+	scanarray = palloc0(sizeof(ScanKeyArray));
+	scanarray->typeid = ARR_ELEMTYPE(arrayval);
+	scanarray->nelements = num_elems;
+	scanarray->elements = elem_values;
+
+	newkey = palloc0(sizeof(ScanKeyData));
+
+	ScanKeyEntryInitializeWithInfo(newkey,
+								   (key->sk_flags | SK_BRIN_SORTED),
+								   key->sk_attno,
+								   key->sk_strategy,
+								   key->sk_subtype,
+								   key->sk_collation,
+								   &key->sk_func,
+								   PointerGetDatum(scanarray));
+
+	PG_RETURN_POINTER(newkey);
+}
+
 /*
  * 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
@@ -2591,6 +2725,15 @@ brin_minmax_multi_consistent(PG_FUNCTION_ARGS)
 	serialized = (SerializedRanges *) PG_DETOAST_DATUM(column->bv_values[0]);
 	ranges = brin_range_deserialize(serialized->maxvalues, serialized);
 
+	/*
+	 * XXX Would it make sense to have a quick initial check on the whole
+	 * summary? We know most page ranges are not expected to match, and we
+	 * know the ranges/values are sorted so we could check global min/max
+	 * (essentially what regular minmax is doing) and bail if no match is
+	 * possible. That should be cheap and might save a lot on inspecting
+	 * the individual ranges/values.
+	 */
+
 	/* inspect the ranges, and for each one evaluate the scan keys */
 	for (rangeno = 0; rangeno < ranges->nranges; rangeno++)
 	{
@@ -2611,67 +2754,179 @@ brin_minmax_multi_consistent(PG_FUNCTION_ARGS)
 			attno = key->sk_attno;
 			subtype = key->sk_subtype;
 			value = key->sk_argument;
-			switch (key->sk_strategy)
-			{
-				case BTLessStrategyNumber:
-				case BTLessEqualStrategyNumber:
-					finfo = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
-															   key->sk_strategy);
-					/* first value from the array */
-					matches = FunctionCall2Coll(finfo, colloid, minval, value);
-					break;
 
-				case BTEqualStrategyNumber:
-					{
-						Datum		compar;
-						FmgrInfo   *cmpFn;
+			if (likely(!(key->sk_flags & SK_BRIN_SORTED)))
+			{
+				switch (key->sk_strategy)
+				{
+					case BTLessStrategyNumber:
+					case BTLessEqualStrategyNumber:
+						finfo = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
+																   key->sk_strategy);
+						/* first value from the array */
+						matches = FunctionCall2Coll(finfo, colloid, minval, value);
+						break;
 
-						/* by default this range does not match */
-						matches = BoolGetDatum(false);
+					case BTEqualStrategyNumber:
+						{
+							Datum		compar;
+							FmgrInfo   *cmpFn;
+
+							/* by default this range does not match */
+							matches = BoolGetDatum(false);
+
+							/*
+							 * Otherwise, need to compare the new value with
+							 * boundaries of all the ranges. First check if it's
+							 * less than the absolute minimum, which is the first
+							 * value in the array.
+							 */
+							cmpFn = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
+																	   BTGreaterStrategyNumber);
+							compar = FunctionCall2Coll(cmpFn, colloid, minval, value);
+
+							/* smaller than the smallest value in this range */
+							if (DatumGetBool(compar))
+								break;
+
+							cmpFn = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
+																	   BTLessStrategyNumber);
+							compar = FunctionCall2Coll(cmpFn, colloid, maxval, value);
+
+							/* larger than the largest value in this range */
+							if (DatumGetBool(compar))
+								break;
+
+							/*
+							 * We haven't managed to eliminate this range, so
+							 * consider it matching.
+							 */
+							matches = BoolGetDatum(true);
 
-						/*
-						 * Otherwise, need to compare the new value with
-						 * boundaries of all the ranges. First check if it's
-						 * less than the absolute minimum, which is the first
-						 * value in the array.
-						 */
-						cmpFn = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
-																   BTGreaterStrategyNumber);
-						compar = FunctionCall2Coll(cmpFn, colloid, minval, value);
-
-						/* smaller than the smallest value in this range */
-						if (DatumGetBool(compar))
 							break;
+						}
+					case BTGreaterEqualStrategyNumber:
+					case BTGreaterStrategyNumber:
+						finfo = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
+																   key->sk_strategy);
+						/* last value from the array */
+						matches = FunctionCall2Coll(finfo, colloid, maxval, value);
+						break;
 
-						cmpFn = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
-																   BTLessStrategyNumber);
-						compar = FunctionCall2Coll(cmpFn, colloid, maxval, value);
+					default:
+						/* shouldn't happen */
+						elog(ERROR, "invalid strategy number %d", key->sk_strategy);
+						matches = BoolGetDatum(false);
+						break;
+				}
+			}
+			else
+			{
+				ScanKeyArray *array = (ScanKeyArray *) value;
 
-						/* larger than the largest value in this range */
-						if (DatumGetBool(compar))
-							break;
+				switch (key->sk_strategy)
+				{
+					case BTLessStrategyNumber:
+					case BTLessEqualStrategyNumber:
+						finfo = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
+																   key->sk_strategy);
+						/* first value from the array */
+						matches = FunctionCall2Coll(finfo, colloid, minval,
+													array->elements[array->nelements-1]);
+						break;
+
+					case BTEqualStrategyNumber:
 
 						/*
-						 * We haven't managed to eliminate this range, so
-						 * consider it matching.
+						 * See brin_minmax.c for description of what this is doing.
 						 */
-						matches = BoolGetDatum(true);
-
+						{
+							Datum val;
+							SortSupportData ssup;
+							int			lower;
+							TypeCacheEntry *type;
+
+							/* Is the first (smallest) value after the BRIN range? */
+							val = array->elements[0];
+
+							finfo = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
+																	   BTLessEqualStrategyNumber);
+							matches = FunctionCall2Coll(finfo, colloid, val, maxval);
+
+							/* minval > max(range values) */
+							if (!DatumGetBool(matches))
+								break;
+
+							/* Is the last (largest) value before the BRIN range? */
+							val = array->elements[array->nelements-1];
+
+							finfo = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
+																	   BTGreaterEqualStrategyNumber);
+							matches = FunctionCall2Coll(finfo, colloid, val, minval);
+
+							/* maxval < min(range values) */
+							if (!DatumGetBool(matches))
+								break;
+
+							/*
+							 * OK, there might be some values matching the range. We have
+							 * to search them one by one, or perhaps try binsearch.
+							 */
+							type = lookup_type_cache(array->typeid, TYPECACHE_LT_OPR);
+
+							memset(&ssup, 0, sizeof(SortSupportData));
+
+							ssup.ssup_collation = key->sk_collation;
+							ssup.ssup_cxt = CurrentMemoryContext;
+
+							PrepareSortSupportFromOrderingOp(type->lt_opr, &ssup);
+
+							lower = lower_boundary(array->elements, array->nelements, minval, &ssup);
+
+							/* no elements can possibly match */
+							if (lower == array->nelements)
+							{
+								matches = BoolGetDatum(false);
+								break;
+							}
+
+							/*
+							 * OK, the first element must match the upper boundary too
+							 * (if it does not, no following elements can).
+							 */
+							val = array->elements[lower];
+
+							/*
+							 * In the equality case (WHERE col = someval), we want to return
+							 * the current page range if the minimum value in the range <=
+							 * scan key, and the maximum value >= scan key.
+							 */
+							finfo = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
+																	   BTLessEqualStrategyNumber);
+							matches = FunctionCall2Coll(finfo, colloid, minval, val);
+							if (!DatumGetBool(matches))
+								break;
+							/* max() >= scankey */
+							finfo = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
+																	   BTGreaterEqualStrategyNumber);
+							matches = FunctionCall2Coll(finfo, colloid, maxval, val);
+							break;
+						}
+					case BTGreaterEqualStrategyNumber:
+					case BTGreaterStrategyNumber:
+						finfo = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
+																   key->sk_strategy);
+						/* last value from the array */
+						matches = FunctionCall2Coll(finfo, colloid, maxval,
+													array->elements[0]);
 						break;
-					}
-				case BTGreaterEqualStrategyNumber:
-				case BTGreaterStrategyNumber:
-					finfo = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
-															   key->sk_strategy);
-					/* last value from the array */
-					matches = FunctionCall2Coll(finfo, colloid, maxval, value);
-					break;
 
-				default:
-					/* shouldn't happen */
-					elog(ERROR, "invalid strategy number %d", key->sk_strategy);
-					matches = BoolGetDatum(false);
-					break;
+					default:
+						/* shouldn't happen */
+						elog(ERROR, "invalid strategy number %d", key->sk_strategy);
+						matches = BoolGetDatum(false);
+						break;
+				}
 			}
 
 			/* the range has to match all the scan keys */
@@ -2713,24 +2968,76 @@ brin_minmax_multi_consistent(PG_FUNCTION_ARGS)
 			attno = key->sk_attno;
 			subtype = key->sk_subtype;
 			value = key->sk_argument;
-			switch (key->sk_strategy)
+			if (likely(!(key->sk_flags & SK_SEARCHARRAY)))
 			{
-				case BTLessStrategyNumber:
-				case BTLessEqualStrategyNumber:
-				case BTEqualStrategyNumber:
-				case BTGreaterEqualStrategyNumber:
-				case BTGreaterStrategyNumber:
-
-					finfo = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
-															   key->sk_strategy);
-					matches = FunctionCall2Coll(finfo, colloid, val, value);
-					break;
+				switch (key->sk_strategy)
+				{
+					case BTLessStrategyNumber:
+					case BTLessEqualStrategyNumber:
+					case BTEqualStrategyNumber:
+					case BTGreaterEqualStrategyNumber:
+					case BTGreaterStrategyNumber:
+
+						finfo = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
+																   key->sk_strategy);
+						matches = FunctionCall2Coll(finfo, colloid, val, value);
+						break;
 
-				default:
-					/* shouldn't happen */
-					elog(ERROR, "invalid strategy number %d", key->sk_strategy);
-					matches = BoolGetDatum(false);
-					break;
+					default:
+						/* shouldn't happen */
+						elog(ERROR, "invalid strategy number %d", key->sk_strategy);
+						matches = BoolGetDatum(false);
+						break;
+				}
+			}
+			else
+			{
+				/*
+				 * FIXME This is really wrong, because it deserializes the
+				 * array over and over for each value in the minmax-multi
+				 * summary.
+				 */
+				ArrayType  *arrayval;
+				int16		elmlen;
+				bool		elmbyval;
+				char		elmalign;
+				int			num_elems;
+				Datum	   *elem_values;
+				bool	   *elem_nulls;
+
+				SortSupportData ssup;
+				int			lower;
+				TypeCacheEntry *type;
+
+				arrayval = DatumGetArrayTypeP(key->sk_argument);
+
+				get_typlenbyvalalign(ARR_ELEMTYPE(arrayval),
+									 &elmlen, &elmbyval, &elmalign);
+
+				deconstruct_array(arrayval,
+								  ARR_ELEMTYPE(arrayval),
+								  elmlen, elmbyval, elmalign,
+								  &elem_values, &elem_nulls, &num_elems);
+
+				/* assume not maches */
+				matches = BoolGetDatum(false);
+
+				/*
+				 * OK, there might be some values matching the range. We have
+				 * to search them one by one, or perhaps try binsearch.
+				 */
+				type = lookup_type_cache(ARR_ELEMTYPE(arrayval), TYPECACHE_LT_OPR);
+
+				memset(&ssup, 0, sizeof(SortSupportData));
+				PrepareSortSupportFromOrderingOp(type->lt_opr, &ssup);
+
+				lower = lower_boundary(elem_values, num_elems, value, &ssup);
+
+				if ((lower < num_elems) &&
+					(compare_array_values(&elem_values[lower], &value, &ssup) == 0))
+				{
+					matches = BoolGetDatum(true);
+				}
 			}
 
 			/* the range has to match all the scan keys */
diff --git a/src/include/catalog/pg_amproc.dat b/src/include/catalog/pg_amproc.dat
index 166681c31e..4f17f0d58c 100644
--- a/src/include/catalog/pg_amproc.dat
+++ b/src/include/catalog/pg_amproc.dat
@@ -946,6 +946,9 @@
 { amprocfamily => 'brin/integer_minmax_multi_ops', amproclefttype => 'int2',
   amprocrighttype => 'int2', amprocnum => '5',
   amproc => 'brin_minmax_multi_options' },
+{ amprocfamily => 'brin/integer_minmax_multi_ops', amproclefttype => 'int2',
+  amprocrighttype => 'int2', amprocnum => '6',
+  amproc => 'brin_minmax_multi_preprocess' },
 { amprocfamily => 'brin/integer_minmax_multi_ops', amproclefttype => 'int2',
   amprocrighttype => 'int2', amprocnum => '11',
   amproc => 'brin_minmax_multi_distance_int2' },
@@ -965,6 +968,9 @@
 { amprocfamily => 'brin/integer_minmax_multi_ops', amproclefttype => 'int4',
   amprocrighttype => 'int4', amprocnum => '5',
   amproc => 'brin_minmax_multi_options' },
+{ amprocfamily => 'brin/integer_minmax_multi_ops', amproclefttype => 'int4',
+  amprocrighttype => 'int4', amprocnum => '6',
+  amproc => 'brin_minmax_multi_preprocess' },
 { amprocfamily => 'brin/integer_minmax_multi_ops', amproclefttype => 'int4',
   amprocrighttype => 'int4', amprocnum => '11',
   amproc => 'brin_minmax_multi_distance_int4' },
@@ -984,6 +990,9 @@
 { amprocfamily => 'brin/integer_minmax_multi_ops', amproclefttype => 'int8',
   amprocrighttype => 'int8', amprocnum => '5',
   amproc => 'brin_minmax_multi_options' },
+{ amprocfamily => 'brin/integer_minmax_multi_ops', amproclefttype => 'int8',
+  amprocrighttype => 'int8', amprocnum => '6',
+  amproc => 'brin_minmax_multi_preprocess' },
 { amprocfamily => 'brin/integer_minmax_multi_ops', amproclefttype => 'int8',
   amprocrighttype => 'int8', amprocnum => '11',
   amproc => 'brin_minmax_multi_distance_int8' },
@@ -1095,6 +1104,9 @@
 { amprocfamily => 'brin/oid_minmax_multi_ops', amproclefttype => 'oid',
   amprocrighttype => 'oid', amprocnum => '5',
   amproc => 'brin_minmax_multi_options' },
+{ amprocfamily => 'brin/oid_minmax_multi_ops', amproclefttype => 'oid',
+  amprocrighttype => 'oid', amprocnum => '6',
+  amproc => 'brin_minmax_multi_preprocess' },
 { amprocfamily => 'brin/oid_minmax_multi_ops', amproclefttype => 'oid',
   amprocrighttype => 'oid', amprocnum => '11',
   amproc => 'brin_minmax_multi_distance_int4' },
@@ -1161,6 +1173,9 @@
 { amprocfamily => 'brin/tid_minmax_multi_ops', amproclefttype => 'tid',
   amprocrighttype => 'tid', amprocnum => '5',
   amproc => 'brin_minmax_multi_options' },
+{ amprocfamily => 'brin/tid_minmax_multi_ops', amproclefttype => 'tid',
+  amprocrighttype => 'tid', amprocnum => '6',
+  amproc => 'brin_minmax_multi_preprocess' },
 { amprocfamily => 'brin/tid_minmax_multi_ops', amproclefttype => 'tid',
   amprocrighttype => 'tid', amprocnum => '11',
   amproc => 'brin_minmax_multi_distance_tid' },
@@ -1214,6 +1229,9 @@
 { amprocfamily => 'brin/float_minmax_multi_ops', amproclefttype => 'float4',
   amprocrighttype => 'float4', amprocnum => '5',
   amproc => 'brin_minmax_multi_options' },
+{ amprocfamily => 'brin/float_minmax_multi_ops', amproclefttype => 'float4',
+  amprocrighttype => 'float4', amprocnum => '6',
+  amproc => 'brin_minmax_multi_preprocess' },
 { amprocfamily => 'brin/float_minmax_multi_ops', amproclefttype => 'float4',
   amprocrighttype => 'float4', amprocnum => '11',
   amproc => 'brin_minmax_multi_distance_float4' },
@@ -1233,6 +1251,9 @@
 { amprocfamily => 'brin/float_minmax_multi_ops', amproclefttype => 'float8',
   amprocrighttype => 'float8', amprocnum => '5',
   amproc => 'brin_minmax_multi_options' },
+{ amprocfamily => 'brin/float_minmax_multi_ops', amproclefttype => 'float8',
+  amprocrighttype => 'float8', amprocnum => '6',
+  amproc => 'brin_minmax_multi_preprocess' },
 { amprocfamily => 'brin/float_minmax_multi_ops', amproclefttype => 'float8',
   amprocrighttype => 'float8', amprocnum => '11',
   amproc => 'brin_minmax_multi_distance_float8' },
@@ -1305,6 +1326,9 @@
 { amprocfamily => 'brin/macaddr_minmax_multi_ops', amproclefttype => 'macaddr',
   amprocrighttype => 'macaddr', amprocnum => '5',
   amproc => 'brin_minmax_multi_options' },
+{ amprocfamily => 'brin/macaddr_minmax_multi_ops', amproclefttype => 'macaddr',
+  amprocrighttype => 'macaddr', amprocnum => '6',
+  amproc => 'brin_minmax_multi_preprocess' },
 { amprocfamily => 'brin/macaddr_minmax_multi_ops', amproclefttype => 'macaddr',
   amprocrighttype => 'macaddr', amprocnum => '11',
   amproc => 'brin_minmax_multi_distance_macaddr' },
@@ -1361,6 +1385,9 @@
 { amprocfamily => 'brin/macaddr8_minmax_multi_ops',
   amproclefttype => 'macaddr8', amprocrighttype => 'macaddr8', amprocnum => '5',
   amproc => 'brin_minmax_multi_options' },
+{ amprocfamily => 'brin/macaddr8_minmax_multi_ops',
+  amproclefttype => 'macaddr8', amprocrighttype => 'macaddr8', amprocnum => '6',
+  amproc => 'brin_minmax_multi_preprocess' },
 { amprocfamily => 'brin/macaddr8_minmax_multi_ops',
   amproclefttype => 'macaddr8', amprocrighttype => 'macaddr8',
   amprocnum => '11', amproc => 'brin_minmax_multi_distance_macaddr8' },
@@ -1415,6 +1442,9 @@
 { amprocfamily => 'brin/network_minmax_multi_ops', amproclefttype => 'inet',
   amprocrighttype => 'inet', amprocnum => '5',
   amproc => 'brin_minmax_multi_options' },
+{ amprocfamily => 'brin/network_minmax_multi_ops', amproclefttype => 'inet',
+  amprocrighttype => 'inet', amprocnum => '6',
+  amproc => 'brin_minmax_multi_preprocess' },
 { amprocfamily => 'brin/network_minmax_multi_ops', amproclefttype => 'inet',
   amprocrighttype => 'inet', amprocnum => '11',
   amproc => 'brin_minmax_multi_distance_inet' },
@@ -1521,6 +1551,9 @@
 { amprocfamily => 'brin/time_minmax_multi_ops', amproclefttype => 'time',
   amprocrighttype => 'time', amprocnum => '5',
   amproc => 'brin_minmax_multi_options' },
+{ amprocfamily => 'brin/time_minmax_multi_ops', amproclefttype => 'time',
+  amprocrighttype => 'time', amprocnum => '6',
+  amproc => 'brin_minmax_multi_preprocess' },
 { amprocfamily => 'brin/time_minmax_multi_ops', amproclefttype => 'time',
   amprocrighttype => 'time', amprocnum => '11',
   amproc => 'brin_minmax_multi_distance_time' },
@@ -1604,6 +1637,9 @@
 { amprocfamily => 'brin/datetime_minmax_multi_ops',
   amproclefttype => 'timestamp', amprocrighttype => 'timestamp',
   amprocnum => '5', amproc => 'brin_minmax_multi_options' },
+{ amprocfamily => 'brin/datetime_minmax_multi_ops',
+  amproclefttype => 'timestamp', amprocrighttype => 'timestamp',
+  amprocnum => '6', amproc => 'brin_minmax_multi_preprocess' },
 { amprocfamily => 'brin/datetime_minmax_multi_ops',
   amproclefttype => 'timestamp', amprocrighttype => 'timestamp',
   amprocnum => '11', amproc => 'brin_minmax_multi_distance_timestamp' },
@@ -1623,6 +1659,9 @@
 { amprocfamily => 'brin/datetime_minmax_multi_ops',
   amproclefttype => 'timestamptz', amprocrighttype => 'timestamptz',
   amprocnum => '5', amproc => 'brin_minmax_multi_options' },
+{ amprocfamily => 'brin/datetime_minmax_multi_ops',
+  amproclefttype => 'timestamptz', amprocrighttype => 'timestamptz',
+  amprocnum => '6', amproc => 'brin_minmax_multi_preprocess' },
 { amprocfamily => 'brin/datetime_minmax_multi_ops',
   amproclefttype => 'timestamptz', amprocrighttype => 'timestamptz',
   amprocnum => '11', amproc => 'brin_minmax_multi_distance_timestamp' },
@@ -1642,6 +1681,9 @@
 { amprocfamily => 'brin/datetime_minmax_multi_ops', amproclefttype => 'date',
   amprocrighttype => 'date', amprocnum => '5',
   amproc => 'brin_minmax_multi_options' },
+{ amprocfamily => 'brin/datetime_minmax_multi_ops', amproclefttype => 'date',
+  amprocrighttype => 'date', amprocnum => '6',
+  amproc => 'brin_minmax_multi_preprocess' },
 { amprocfamily => 'brin/datetime_minmax_multi_ops', amproclefttype => 'date',
   amprocrighttype => 'date', amprocnum => '11',
   amproc => 'brin_minmax_multi_distance_date' },
@@ -1733,6 +1775,9 @@
 { amprocfamily => 'brin/interval_minmax_multi_ops',
   amproclefttype => 'interval', amprocrighttype => 'interval', amprocnum => '5',
   amproc => 'brin_minmax_multi_options' },
+{ amprocfamily => 'brin/interval_minmax_multi_ops',
+  amproclefttype => 'interval', amprocrighttype => 'interval', amprocnum => '6',
+  amproc => 'brin_minmax_multi_preprocess' },
 { amprocfamily => 'brin/interval_minmax_multi_ops',
   amproclefttype => 'interval', amprocrighttype => 'interval',
   amprocnum => '11', amproc => 'brin_minmax_multi_distance_interval' },
@@ -1789,6 +1834,9 @@
 { amprocfamily => 'brin/timetz_minmax_multi_ops', amproclefttype => 'timetz',
   amprocrighttype => 'timetz', amprocnum => '5',
   amproc => 'brin_minmax_multi_options' },
+{ amprocfamily => 'brin/timetz_minmax_multi_ops', amproclefttype => 'timetz',
+  amprocrighttype => 'timetz', amprocnum => '6',
+  amproc => 'brin_minmax_multi_preprocess' },
 { amprocfamily => 'brin/timetz_minmax_multi_ops', amproclefttype => 'timetz',
   amprocrighttype => 'timetz', amprocnum => '11',
   amproc => 'brin_minmax_multi_distance_timetz' },
@@ -1875,6 +1923,9 @@
 { amprocfamily => 'brin/numeric_minmax_multi_ops', amproclefttype => 'numeric',
   amprocrighttype => 'numeric', amprocnum => '5',
   amproc => 'brin_minmax_multi_options' },
+{ amprocfamily => 'brin/numeric_minmax_multi_ops', amproclefttype => 'numeric',
+  amprocrighttype => 'numeric', amprocnum => '6',
+  amproc => 'brin_minmax_multi_preprocess' },
 { amprocfamily => 'brin/numeric_minmax_multi_ops', amproclefttype => 'numeric',
   amprocrighttype => 'numeric', amprocnum => '11',
   amproc => 'brin_minmax_multi_distance_numeric' },
@@ -1929,6 +1980,9 @@
 { amprocfamily => 'brin/uuid_minmax_multi_ops', amproclefttype => 'uuid',
   amprocrighttype => 'uuid', amprocnum => '5',
   amproc => 'brin_minmax_multi_options' },
+{ amprocfamily => 'brin/uuid_minmax_multi_ops', amproclefttype => 'uuid',
+  amprocrighttype => 'uuid', amprocnum => '6',
+  amproc => 'brin_minmax_multi_preprocess' },
 { amprocfamily => 'brin/uuid_minmax_multi_ops', amproclefttype => 'uuid',
   amprocrighttype => 'uuid', amprocnum => '11',
   amproc => 'brin_minmax_multi_distance_uuid' },
@@ -2005,6 +2059,9 @@
 { amprocfamily => 'brin/pg_lsn_minmax_multi_ops', amproclefttype => 'pg_lsn',
   amprocrighttype => 'pg_lsn', amprocnum => '5',
   amproc => 'brin_minmax_multi_options' },
+{ amprocfamily => 'brin/pg_lsn_minmax_multi_ops', amproclefttype => 'pg_lsn',
+  amprocrighttype => 'pg_lsn', amprocnum => '6',
+  amproc => 'brin_minmax_multi_preprocess' },
 { amprocfamily => 'brin/pg_lsn_minmax_multi_ops', amproclefttype => 'pg_lsn',
   amprocrighttype => 'pg_lsn', amprocnum => '11',
   amproc => 'brin_minmax_multi_distance_pg_lsn' },
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 6638552bd0..4e8d666864 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8520,6 +8520,10 @@
   proname => 'brin_minmax_multi_options', proisstrict => 'f',
   prorettype => 'void', proargtypes => 'internal',
   prosrc => 'brin_minmax_multi_options' },
+{ oid => '9326', descr => 'BRIN multi minmax support',
+  proname => 'brin_minmax_multi_preprocess', proisstrict => 'f',
+  prorettype => 'internal', proargtypes => 'internal internal',
+  prosrc => 'brin_minmax_multi_preprocess' },
 
 { oid => '4621', descr => 'BRIN multi minmax int2 distance',
   proname => 'brin_minmax_multi_distance_int2', prorettype => 'float8',
-- 
2.39.1



  [text/x-patch] 0007-Support-SK_SEARCHARRAY-in-BRIN-inclusion-20230215.patch (14.2K, ../../[email protected]/8-0007-Support-SK_SEARCHARRAY-in-BRIN-inclusion-20230215.patch)
  download | inline diff:
From 6b5190a3d564ea75f144bc74160e2443f21760ae Mon Sep 17 00:00:00 2001
From: Tomas Vondra <[email protected]>
Date: Sat, 11 Feb 2023 15:23:25 +0100
Subject: [PATCH 7/9] Support SK_SEARCHARRAY in BRIN inclusion

---
 src/backend/access/brin/brin_inclusion.c | 221 +++++++++++++++++------
 src/include/catalog/pg_amproc.dat        |   9 +
 src/include/catalog/pg_proc.dat          |   4 +
 3 files changed, 180 insertions(+), 54 deletions(-)

diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c
index 248116c149..0f409ef527 100644
--- a/src/backend/access/brin/brin_inclusion.c
+++ b/src/backend/access/brin/brin_inclusion.c
@@ -30,6 +30,7 @@
 #include "access/skey.h"
 #include "catalog/pg_amop.h"
 #include "catalog/pg_type.h"
+#include "utils/array.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
 #include "utils/lsyscache.h"
@@ -72,6 +73,13 @@
 #define INCLUSION_UNMERGEABLE		1
 #define INCLUSION_CONTAINS_EMPTY	2
 
+/*
+ * We use some private sk_flags bits in preprocessed scan keys.  We're allowed
+ * to use bits 16-31 (see skey.h).  The uppermost bits are copied from the
+ * index's indoption[] array entry for the index attribute.
+ */
+#define SK_BRIN_ARRAY	0x00010000	/* deconstructed array */
+
 
 typedef struct InclusionOpaque
 {
@@ -238,44 +246,74 @@ brin_inclusion_add_value(PG_FUNCTION_ARGS)
 	PG_RETURN_BOOL(true);
 }
 
+typedef struct ScanKeyArray {
+	int		nelements;
+	Datum  *elements;
+} ScanKeyArray;
+
+Datum
+brin_inclusion_preprocess(PG_FUNCTION_ARGS)
+{
+	// BrinDesc   *bdesc = (BrinDesc *) PG_GETARG_POINTER(0);
+	ScanKey		key = (ScanKey) PG_GETARG_POINTER(1);
+	ScanKey		newkey;
+	ScanKeyArray *scanarray;
+
+	ArrayType  *arrayval;
+	int16		elmlen;
+	bool		elmbyval;
+	char		elmalign;
+	int			num_elems;
+	Datum	   *elem_values;
+	bool	   *elem_nulls;
+
+	/* ignore scalar keys */
+	if (!(key->sk_flags & SK_SEARCHARRAY))
+		PG_RETURN_POINTER(key);
+
+	arrayval = DatumGetArrayTypeP(key->sk_argument);
+
+	get_typlenbyvalalign(ARR_ELEMTYPE(arrayval),
+						 &elmlen, &elmbyval, &elmalign);
+
+	deconstruct_array(arrayval,
+					  ARR_ELEMTYPE(arrayval),
+					  elmlen, elmbyval, elmalign,
+					  &elem_values, &elem_nulls, &num_elems);
+
+	scanarray = palloc0(sizeof(ScanKeyArray));
+	scanarray->nelements = num_elems;
+	scanarray->elements = elem_values;
+
+	newkey = palloc0(sizeof(ScanKeyData));
+
+	ScanKeyEntryInitializeWithInfo(newkey,
+								   (key->sk_flags | SK_BRIN_ARRAY),
+								   key->sk_attno,
+								   key->sk_strategy,
+								   key->sk_subtype,
+								   key->sk_collation,
+								   &key->sk_func,
+								   PointerGetDatum(scanarray));
+
+	PG_RETURN_POINTER(newkey);
+}
+
 /*
- * BRIN inclusion consistent function
+ * Check consistency of a single scalar value with the BRIN range.
  *
- * We're no longer dealing with NULL keys in the consistent function, that is
- * now handled by the AM code. That means we should not get any all-NULL ranges
- * either, because those can't be consistent with regular (not [IS] NULL) keys.
- *
- * All of the strategies are optional.
+ * Called for both scalar scankeys and for each value in SK_SEARCHARRAY.
  */
-Datum
-brin_inclusion_consistent(PG_FUNCTION_ARGS)
+static bool
+brin_inclusion_consistent_value(BrinDesc *bdesc, BrinValues *column,
+								AttrNumber attno,
+								StrategyNumber strategy, Oid subtype,
+								Oid colloid, Datum unionval, Datum query)
 {
-	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;
 
-	/* This opclass uses the old signature with only three arguments. */
-	Assert(PG_NARGS() == 3);
-
-	/* Should not be dealing with all-NULL ranges. */
-	Assert(!column->bv_allnulls);
-
-	/* 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)
+	switch (strategy)
 	{
 			/*
 			 * Placement strategies
@@ -294,49 +332,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
@@ -352,9 +390,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS)
 		case RTSubStrategyNumber:
 		case RTSubEqualStrategyNumber:
 			finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype,
-													key->sk_strategy);
+													strategy);
 			result = FunctionCall2Coll(finfo, colloid, unionval, query);
-			PG_RETURN_DATUM(result);
+			return (DatumGetBool(result));
 
 			/*
 			 * Contained by strategies
@@ -374,9 +412,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 (column->bv_values[INCLUSION_CONTAINS_EMPTY]);
 
 			/*
 			 * Adjacent strategy
@@ -393,12 +431,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
@@ -428,9 +466,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 (column->bv_values[INCLUSION_CONTAINS_EMPTY]);
 
 		case RTSameStrategyNumber:
 		case RTEqualStrategyNumber:
@@ -438,30 +476,105 @@ 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 (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 (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);
+			elog(ERROR, "invalid strategy number %d", strategy);
+			return (false);
+	}
+}
+
+/*
+ * BRIN inclusion consistent function
+ *
+ * We're no longer dealing with NULL keys in the consistent function, that is
+ * now handled by the AM code. That means we should not get any all-NULL ranges
+ * either, because those can't be consistent with regular (not [IS] NULL) keys.
+ *
+ * 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;
+
+	/* This opclass uses the old signature with only three arguments. */
+	Assert(PG_NARGS() == 3);
+
+	/* Should not be dealing with all-NULL ranges. */
+	Assert(!column->bv_allnulls);
+
+	/* 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];
+
+	/*
+	 * For regular (scalar) scan keys, we simply compare the value to the
+	 * range min/max values, and we're done. For SK_SEARCHARRAY keys we
+	 * need to deparse the array and loop through the values.
+	 */
+	if (likely(!(key->sk_flags & SK_SEARCHARRAY)))
+	{
+		bool tmp;
+
+		tmp = brin_inclusion_consistent_value(bdesc, column, attno,
+											  key->sk_strategy,
+											  subtype, colloid,
+											  unionval, query);
+		PG_RETURN_BOOL(tmp);
+	}
+	else
+	{
+		ScanKeyArray *array = (ScanKeyArray *) query;
+		bool		matches = false;
+
+		/* have to loop through all elements, having them sorted does not help */
+		for (int i = 0; i < array->nelements; i++)
+		{
+			Datum 	query_element = array->elements[i];
+
+			matches = brin_inclusion_consistent_value(bdesc, column, attno,
+													  key->sk_strategy,
+													  subtype, colloid,
+													  unionval, query_element);
+
+			if (matches)
+				break;
+		}
+
+		/* we could get here for empty array, e.g. with "@> '{}'::point[]" */
+		PG_RETURN_BOOL(matches);
 	}
 }
 
diff --git a/src/include/catalog/pg_amproc.dat b/src/include/catalog/pg_amproc.dat
index 4f17f0d58c..ed5b21e7f9 100644
--- a/src/include/catalog/pg_amproc.dat
+++ b/src/include/catalog/pg_amproc.dat
@@ -1478,6 +1478,9 @@
 { amprocfamily => 'brin/network_inclusion_ops', amproclefttype => 'inet',
   amprocrighttype => 'inet', amprocnum => '4',
   amproc => 'brin_inclusion_union' },
+{ amprocfamily => 'brin/network_inclusion_ops', amproclefttype => 'inet',
+  amprocrighttype => 'inet', amprocnum => '6',
+  amproc => 'brin_inclusion_preprocess' },
 { amprocfamily => 'brin/network_inclusion_ops', amproclefttype => 'inet',
   amprocrighttype => 'inet', amprocnum => '11', amproc => 'inet_merge' },
 { amprocfamily => 'brin/network_inclusion_ops', amproclefttype => 'inet',
@@ -2016,6 +2019,9 @@
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
   amprocrighttype => 'anyrange', amprocnum => '4',
   amproc => 'brin_inclusion_union' },
+{ amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
+  amprocrighttype => 'anyrange', amprocnum => '6',
+  amproc => 'brin_inclusion_preprocess' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
   amprocrighttype => 'anyrange', amprocnum => '11',
   amproc => 'range_merge(anyrange,anyrange)' },
@@ -2097,6 +2103,9 @@
 { amprocfamily => 'brin/box_inclusion_ops', amproclefttype => 'box',
   amprocrighttype => 'box', amprocnum => '4',
   amproc => 'brin_inclusion_union' },
+{ amprocfamily => 'brin/box_inclusion_ops', amproclefttype => 'box',
+  amprocrighttype => 'box', amprocnum => '6',
+  amproc => 'brin_inclusion_preprocess' },
 { amprocfamily => 'brin/box_inclusion_ops', amproclefttype => 'box',
   amprocrighttype => 'box', amprocnum => '11', amproc => 'bound_box' },
 { amprocfamily => 'brin/box_inclusion_ops', amproclefttype => 'box',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4e8d666864..753c41d5cd 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8610,6 +8610,10 @@
   proname => 'brin_inclusion_union', prorettype => 'bool',
   proargtypes => 'internal internal internal',
   prosrc => 'brin_inclusion_union' },
+{ oid => '9324', descr => 'BRIN inclusion support',
+  proname => 'brin_inclusion_preprocess', proisstrict => 'f',
+  prorettype => 'internal', proargtypes => 'internal internal',
+  prosrc => 'brin_inclusion_preprocess' },
 
 # BRIN bloom
 { oid => '4591', descr => 'BRIN bloom support',
-- 
2.39.1



  [text/x-patch] 0008-Support-SK_SEARCHARRAY-in-BRIN-bloom-20230215.patch (20.0K, ../../[email protected]/9-0008-Support-SK_SEARCHARRAY-in-BRIN-bloom-20230215.patch)
  download | inline diff:
From 2cf5d136f6a80ca2cfc703c54566fe636b41ca28 Mon Sep 17 00:00:00 2001
From: Tomas Vondra <[email protected]>
Date: Sat, 11 Feb 2023 20:50:03 +0100
Subject: [PATCH 8/9] Support SK_SEARCHARRAY in BRIN bloom

---
 src/backend/access/brin/brin_bloom.c | 157 ++++++++++++++++++++++-----
 src/include/catalog/pg_amproc.dat    |  60 ++++++++++
 src/include/catalog/pg_proc.dat      |   3 +
 3 files changed, 194 insertions(+), 26 deletions(-)

diff --git a/src/backend/access/brin/brin_bloom.c b/src/backend/access/brin/brin_bloom.c
index 4ff80aeb0c..48b9847bb2 100644
--- a/src/backend/access/brin/brin_bloom.c
+++ b/src/backend/access/brin/brin_bloom.c
@@ -125,9 +125,11 @@
 #include "access/stratnum.h"
 #include "catalog/pg_type.h"
 #include "catalog/pg_amop.h"
+#include "utils/array.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
 #include "utils/lsyscache.h"
+#include "utils/memutils.h"
 #include "utils/rel.h"
 #include "utils/syscache.h"
 
@@ -151,6 +153,13 @@
  */
 #define		PROCNUM_BASE			11
 
+/*
+ * We use some private sk_flags bits in preprocessed scan keys.  We're allowed
+ * to use bits 16-31 (see skey.h).  The uppermost bits are copied from the
+ * index's indoption[] array entry for the index attribute.
+ */
+#define SK_BRIN_HASHES	0x00010000	/* deconstructed array, calculated hashes */
+
 /*
  * Storage type for BRIN's reloptions.
  */
@@ -402,21 +411,14 @@ bloom_add_value(BloomFilter *filter, uint32 value, bool *updated)
 	return filter;
 }
 
-
 /*
  * bloom_contains_value
  * 		Check if the bloom filter contains a particular value.
  */
 static bool
-bloom_contains_value(BloomFilter *filter, uint32 value)
+bloom_contains_hashes(BloomFilter *filter, uint64 h1, uint64 h2)
 {
 	int			i;
-	uint64		h1,
-				h2;
-
-	/* calculate the two hashes */
-	h1 = hash_bytes_uint32_extended(value, BLOOM_SEED_1) % filter->nbits;
-	h2 = hash_bytes_uint32_extended(value, BLOOM_SEED_2) % filter->nbits;
 
 	/* compute the requested number of hashes */
 	for (i = 0; i < filter->nhashes; i++)
@@ -590,6 +592,99 @@ brin_bloom_add_value(PG_FUNCTION_ARGS)
 	PG_RETURN_BOOL(updated);
 }
 
+typedef struct HashCache {
+	int		nelements;
+	uint64 *h1;
+	uint64 *h2;
+} HashCache;
+
+Datum
+brin_bloom_preprocess(PG_FUNCTION_ARGS)
+{
+	BrinDesc   *bdesc = (BrinDesc *) PG_GETARG_POINTER(0);
+	ScanKey		key = (ScanKey) PG_GETARG_POINTER(1);
+	BloomOptions *opts = (BloomOptions *) PG_GET_OPCLASS_OPTIONS();
+	ScanKey		newkey;
+	HashCache  *cache = palloc0(sizeof(HashCache));
+
+	int			nbits;
+	FmgrInfo   *finfo;
+	uint32		hashValue;
+
+	/* we'll need to calculate hashes, so get the proc */
+	finfo = bloom_get_procinfo(bdesc, key->sk_attno, PROCNUM_HASH);
+
+	/*
+	 * We don't have a filter from any range yet, so we just re-calculate
+	 * the size (number of bits) just like bloom_init.
+	 */
+	bloom_filter_size(brin_bloom_get_ndistinct(bdesc, opts),
+					  BloomGetFalsePositiveRate(opts),
+					  NULL, &nbits, NULL);
+
+	/* precalculate the hash even for simple scan keys */
+	if (!(key->sk_flags & SK_SEARCHARRAY))
+	{
+		Datum value = key->sk_argument;
+
+		cache->nelements = 1;
+		cache->h1 = (uint64 *) palloc0(sizeof(uint64));
+		cache->h2 = (uint64 *) palloc0(sizeof(uint64));
+
+		hashValue = DatumGetUInt32(FunctionCall1Coll(finfo, key->sk_collation, value));
+
+		cache->h1[0] = hash_bytes_uint32_extended(hashValue, BLOOM_SEED_1) % nbits;
+		cache->h2[0] = hash_bytes_uint32_extended(hashValue, BLOOM_SEED_2) % nbits;
+	}
+	else
+	{
+		ArrayType  *arrayval;
+		int16		elmlen;
+		bool		elmbyval;
+		char		elmalign;
+		int			num_elems;
+		Datum	   *elem_values;
+		bool	   *elem_nulls;
+
+		arrayval = DatumGetArrayTypeP(key->sk_argument);
+
+		get_typlenbyvalalign(ARR_ELEMTYPE(arrayval),
+							 &elmlen, &elmbyval, &elmalign);
+
+		deconstruct_array(arrayval,
+						  ARR_ELEMTYPE(arrayval),
+						  elmlen, elmbyval, elmalign,
+						  &elem_values, &elem_nulls, &num_elems);
+
+		cache->nelements = num_elems;
+		cache->h1 = (uint64 *) palloc0(sizeof(uint64) * num_elems);
+		cache->h2 = (uint64 *) palloc0(sizeof(uint64) * num_elems);
+
+		for (int i = 0; i < num_elems; i++)
+		{
+			Datum	element = elem_values[i];
+
+			hashValue = DatumGetUInt32(FunctionCall1Coll(finfo, key->sk_collation, element));
+
+			cache->h1[i] = hash_bytes_uint32_extended(hashValue, BLOOM_SEED_1) % nbits;
+			cache->h2[i] = hash_bytes_uint32_extended(hashValue, BLOOM_SEED_2) % nbits;
+		}
+	}
+
+	newkey = palloc0(sizeof(ScanKeyData));
+
+	ScanKeyEntryInitializeWithInfo(newkey,
+								   (key->sk_flags | SK_BRIN_HASHES),
+								   key->sk_attno,
+								   key->sk_strategy,
+								   key->sk_subtype,
+								   key->sk_collation,
+								   &key->sk_func,
+								   PointerGetDatum(cache));
+
+	PG_RETURN_POINTER(newkey);
+}
+
 /*
  * Given an index tuple corresponding to a certain page range and a scan key,
  * return whether the scan key is consistent with the index tuple's bloom
@@ -598,16 +693,10 @@ brin_bloom_add_value(PG_FUNCTION_ARGS)
 Datum
 brin_bloom_consistent(PG_FUNCTION_ARGS)
 {
-	BrinDesc   *bdesc = (BrinDesc *) PG_GETARG_POINTER(0);
 	BrinValues *column = (BrinValues *) PG_GETARG_POINTER(1);
 	ScanKey    *keys = (ScanKey *) PG_GETARG_POINTER(2);
 	int			nkeys = PG_GETARG_INT32(3);
-	Oid			colloid = PG_GET_COLLATION();
-	AttrNumber	attno;
-	Datum		value;
 	bool		matches;
-	FmgrInfo   *finfo;
-	uint32		hashValue;
 	BloomFilter *filter;
 	int			keyno;
 
@@ -621,26 +710,42 @@ brin_bloom_consistent(PG_FUNCTION_ARGS)
 	for (keyno = 0; keyno < nkeys; keyno++)
 	{
 		ScanKey		key = keys[keyno];
+		HashCache  *cache = (HashCache *) key->sk_argument;
 
 		/* NULL keys are handled and filtered-out in bringetbitmap */
 		Assert(!(key->sk_flags & SK_ISNULL));
 
-		attno = key->sk_attno;
-		value = key->sk_argument;
+		/*
+		 * Keys should be preprocessed into a hash cache (even a single
+		 * value scan keys, not just SK_SEARCHARRAY ones).
+		 */
+		Assert(key->sk_flags & SK_BRIN_HASHES);
 
 		switch (key->sk_strategy)
 		{
 			case BloomEqualStrategyNumber:
-
-				/*
-				 * We want to return the current page range if the bloom filter
-				 * seems to contain the value.
-				 */
-				finfo = bloom_get_procinfo(bdesc, attno, PROCNUM_HASH);
-
-				hashValue = DatumGetUInt32(FunctionCall1Coll(finfo, colloid, value));
-				matches &= bloom_contains_value(filter, hashValue);
-
+				{
+					/* assume no match */
+					matches = false;
+
+					/*
+					 * We want to return the current page range if the bloom filter
+					 * seems to contain any of the values (or a single value).
+					 */
+					for (int i = 0; i < cache->nelements; i++)
+					{
+						bool	tmp = false;
+
+						tmp = bloom_contains_hashes(filter, cache->h1[i], cache->h2[i]);
+
+						/* if we found a matching value, we have a match */
+						if (DatumGetBool(tmp))
+						{
+							matches = BoolGetDatum(true);
+							break;
+						}
+					}
+				}
 				break;
 			default:
 				/* shouldn't happen */
diff --git a/src/include/catalog/pg_amproc.dat b/src/include/catalog/pg_amproc.dat
index ed5b21e7f9..d951fcd1a0 100644
--- a/src/include/catalog/pg_amproc.dat
+++ b/src/include/catalog/pg_amproc.dat
@@ -822,6 +822,9 @@
 { amprocfamily => 'brin/bytea_bloom_ops', amproclefttype => 'bytea',
   amprocrighttype => 'bytea', amprocnum => '5',
   amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/bytea_bloom_ops', amproclefttype => 'bytea',
+  amprocrighttype => 'bytea', amprocnum => '6',
+  amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/bytea_bloom_ops', amproclefttype => 'bytea',
   amprocrighttype => 'bytea', amprocnum => '11', amproc => 'hashvarlena' },
 
@@ -853,6 +856,8 @@
   amprocrighttype => 'char', amprocnum => '4', amproc => 'brin_bloom_union' },
 { amprocfamily => 'brin/char_bloom_ops', amproclefttype => 'char',
   amprocrighttype => 'char', amprocnum => '5', amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/char_bloom_ops', amproclefttype => 'char',
+  amprocrighttype => 'char', amprocnum => '6', amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/char_bloom_ops', amproclefttype => 'char',
   amprocrighttype => 'char', amprocnum => '11', amproc => 'hashchar' },
 
@@ -884,6 +889,8 @@
   amprocrighttype => 'name', amprocnum => '4', amproc => 'brin_bloom_union' },
 { amprocfamily => 'brin/name_bloom_ops', amproclefttype => 'name',
   amprocrighttype => 'name', amprocnum => '5', amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/name_bloom_ops', amproclefttype => 'name',
+  amprocrighttype => 'name', amprocnum => '6', amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/name_bloom_ops', amproclefttype => 'name',
   amprocrighttype => 'name', amprocnum => '11', amproc => 'hashname' },
 
@@ -1010,6 +1017,8 @@
   amprocrighttype => 'int8', amprocnum => '4', amproc => 'brin_bloom_union' },
 { amprocfamily => 'brin/integer_bloom_ops', amproclefttype => 'int8',
   amprocrighttype => 'int8', amprocnum => '5', amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/integer_bloom_ops', amproclefttype => 'int8',
+  amprocrighttype => 'int8', amprocnum => '6', amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/integer_bloom_ops', amproclefttype => 'int8',
   amprocrighttype => 'int8', amprocnum => '11', amproc => 'hashint8' },
 
@@ -1025,6 +1034,8 @@
   amprocrighttype => 'int2', amprocnum => '4', amproc => 'brin_bloom_union' },
 { amprocfamily => 'brin/integer_bloom_ops', amproclefttype => 'int2',
   amprocrighttype => 'int2', amprocnum => '5', amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/integer_bloom_ops', amproclefttype => 'int2',
+  amprocrighttype => 'int2', amprocnum => '6', amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/integer_bloom_ops', amproclefttype => 'int2',
   amprocrighttype => 'int2', amprocnum => '11', amproc => 'hashint2' },
 
@@ -1040,6 +1051,8 @@
   amprocrighttype => 'int4', amprocnum => '4', amproc => 'brin_bloom_union' },
 { amprocfamily => 'brin/integer_bloom_ops', amproclefttype => 'int4',
   amprocrighttype => 'int4', amprocnum => '5', amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/integer_bloom_ops', amproclefttype => 'int4',
+  amprocrighttype => 'int4', amprocnum => '6', amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/integer_bloom_ops', amproclefttype => 'int4',
   amprocrighttype => 'int4', amprocnum => '11', amproc => 'hashint4' },
 
@@ -1071,6 +1084,8 @@
   amprocrighttype => 'text', amprocnum => '4', amproc => 'brin_bloom_union' },
 { amprocfamily => 'brin/text_bloom_ops', amproclefttype => 'text',
   amprocrighttype => 'text', amprocnum => '5', amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/text_bloom_ops', amproclefttype => 'text',
+  amprocrighttype => 'text', amprocnum => '6', amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/text_bloom_ops', amproclefttype => 'text',
   amprocrighttype => 'text', amprocnum => '11', amproc => 'hashtext' },
 
@@ -1124,6 +1139,8 @@
   amprocrighttype => 'oid', amprocnum => '4', amproc => 'brin_bloom_union' },
 { amprocfamily => 'brin/oid_bloom_ops', amproclefttype => 'oid',
   amprocrighttype => 'oid', amprocnum => '5', amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/oid_bloom_ops', amproclefttype => 'oid',
+  amprocrighttype => 'oid', amprocnum => '6', amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/oid_bloom_ops', amproclefttype => 'oid',
   amprocrighttype => 'oid', amprocnum => '11', amproc => 'hashoid' },
 
@@ -1154,6 +1171,8 @@
   amprocrighttype => 'tid', amprocnum => '4', amproc => 'brin_bloom_union' },
 { amprocfamily => 'brin/tid_bloom_ops', amproclefttype => 'tid',
   amprocrighttype => 'tid', amprocnum => '5', amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/tid_bloom_ops', amproclefttype => 'tid',
+  amprocrighttype => 'tid', amprocnum => '6', amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/tid_bloom_ops', amproclefttype => 'tid',
   amprocrighttype => 'tid', amprocnum => '11', amproc => 'hashtid' },
 
@@ -1273,6 +1292,9 @@
 { amprocfamily => 'brin/float_bloom_ops', amproclefttype => 'float4',
   amprocrighttype => 'float4', amprocnum => '5',
   amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/float_bloom_ops', amproclefttype => 'float4',
+  amprocrighttype => 'float4', amprocnum => '6',
+  amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/float_bloom_ops', amproclefttype => 'float4',
   amprocrighttype => 'float4', amprocnum => '11', amproc => 'hashfloat4' },
 
@@ -1290,6 +1312,9 @@
 { amprocfamily => 'brin/float_bloom_ops', amproclefttype => 'float8',
   amprocrighttype => 'float8', amprocnum => '5',
   amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/float_bloom_ops', amproclefttype => 'float8',
+  amprocrighttype => 'float8', amprocnum => '6',
+  amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/float_bloom_ops', amproclefttype => 'float8',
   amprocrighttype => 'float8', amprocnum => '11', amproc => 'hashfloat8' },
 
@@ -1349,6 +1374,9 @@
 { amprocfamily => 'brin/macaddr_bloom_ops', amproclefttype => 'macaddr',
   amprocrighttype => 'macaddr', amprocnum => '5',
   amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/macaddr_bloom_ops', amproclefttype => 'macaddr',
+  amprocrighttype => 'macaddr', amprocnum => '6',
+  amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/macaddr_bloom_ops', amproclefttype => 'macaddr',
   amprocrighttype => 'macaddr', amprocnum => '11', amproc => 'hashmacaddr' },
 
@@ -1408,6 +1436,9 @@
 { amprocfamily => 'brin/macaddr8_bloom_ops', amproclefttype => 'macaddr8',
   amprocrighttype => 'macaddr8', amprocnum => '5',
   amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/macaddr8_bloom_ops', amproclefttype => 'macaddr8',
+  amprocrighttype => 'macaddr8', amprocnum => '6',
+  amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/macaddr8_bloom_ops', amproclefttype => 'macaddr8',
   amprocrighttype => 'macaddr8', amprocnum => '11', amproc => 'hashmacaddr8' },
 
@@ -1462,6 +1493,8 @@
   amprocrighttype => 'inet', amprocnum => '4', amproc => 'brin_bloom_union' },
 { amprocfamily => 'brin/network_bloom_ops', amproclefttype => 'inet',
   amprocrighttype => 'inet', amprocnum => '5', amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/network_bloom_ops', amproclefttype => 'inet',
+  amprocrighttype => 'inet', amprocnum => '6', amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/network_bloom_ops', amproclefttype => 'inet',
   amprocrighttype => 'inet', amprocnum => '11', amproc => 'hashinet' },
 
@@ -1520,6 +1553,9 @@
 { amprocfamily => 'brin/bpchar_bloom_ops', amproclefttype => 'bpchar',
   amprocrighttype => 'bpchar', amprocnum => '5',
   amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/bpchar_bloom_ops', amproclefttype => 'bpchar',
+  amprocrighttype => 'bpchar', amprocnum => '6',
+  amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/bpchar_bloom_ops', amproclefttype => 'bpchar',
   amprocrighttype => 'bpchar', amprocnum => '11', amproc => 'hashbpchar' },
 
@@ -1574,6 +1610,8 @@
   amprocrighttype => 'time', amprocnum => '4', amproc => 'brin_bloom_union' },
 { amprocfamily => 'brin/time_bloom_ops', amproclefttype => 'time',
   amprocrighttype => 'time', amprocnum => '5', amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/time_bloom_ops', amproclefttype => 'time',
+  amprocrighttype => 'time', amprocnum => '6', amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/time_bloom_ops', amproclefttype => 'time',
   amprocrighttype => 'time', amprocnum => '11', amproc => 'time_hash' },
 
@@ -1707,6 +1745,9 @@
 { amprocfamily => 'brin/datetime_bloom_ops', amproclefttype => 'timestamp',
   amprocrighttype => 'timestamp', amprocnum => '5',
   amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/datetime_bloom_ops', amproclefttype => 'timestamp',
+  amprocrighttype => 'timestamp', amprocnum => '6',
+  amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/datetime_bloom_ops', amproclefttype => 'timestamp',
   amprocrighttype => 'timestamp', amprocnum => '11',
   amproc => 'timestamp_hash' },
@@ -1726,6 +1767,9 @@
 { amprocfamily => 'brin/datetime_bloom_ops', amproclefttype => 'timestamptz',
   amprocrighttype => 'timestamptz', amprocnum => '5',
   amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/datetime_bloom_ops', amproclefttype => 'timestamptz',
+  amprocrighttype => 'timestamptz', amprocnum => '6',
+  amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/datetime_bloom_ops', amproclefttype => 'timestamptz',
   amprocrighttype => 'timestamptz', amprocnum => '11',
   amproc => 'timestamp_hash' },
@@ -1742,6 +1786,8 @@
   amprocrighttype => 'date', amprocnum => '4', amproc => 'brin_bloom_union' },
 { amprocfamily => 'brin/datetime_bloom_ops', amproclefttype => 'date',
   amprocrighttype => 'date', amprocnum => '5', amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/datetime_bloom_ops', amproclefttype => 'date',
+  amprocrighttype => 'date', amprocnum => '6', amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/datetime_bloom_ops', amproclefttype => 'date',
   amprocrighttype => 'date', amprocnum => '11', amproc => 'hashint4' },
 
@@ -1801,6 +1847,9 @@
 { amprocfamily => 'brin/interval_bloom_ops', amproclefttype => 'interval',
   amprocrighttype => 'interval', amprocnum => '5',
   amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/interval_bloom_ops', amproclefttype => 'interval',
+  amprocrighttype => 'interval', amprocnum => '6',
+  amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/interval_bloom_ops', amproclefttype => 'interval',
   amprocrighttype => 'interval', amprocnum => '11', amproc => 'interval_hash' },
 
@@ -1859,6 +1908,9 @@
 { amprocfamily => 'brin/timetz_bloom_ops', amproclefttype => 'timetz',
   amprocrighttype => 'timetz', amprocnum => '5',
   amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/timetz_bloom_ops', amproclefttype => 'timetz',
+  amprocrighttype => 'timetz', amprocnum => '6',
+  amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/timetz_bloom_ops', amproclefttype => 'timetz',
   amprocrighttype => 'timetz', amprocnum => '11', amproc => 'timetz_hash' },
 
@@ -1949,6 +2001,9 @@
 { amprocfamily => 'brin/numeric_bloom_ops', amproclefttype => 'numeric',
   amprocrighttype => 'numeric', amprocnum => '5',
   amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/numeric_bloom_ops', amproclefttype => 'numeric',
+  amprocrighttype => 'numeric', amprocnum => '6',
+  amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/numeric_bloom_ops', amproclefttype => 'numeric',
   amprocrighttype => 'numeric', amprocnum => '11', amproc => 'hash_numeric' },
 
@@ -2003,6 +2058,8 @@
   amprocrighttype => 'uuid', amprocnum => '4', amproc => 'brin_bloom_union' },
 { amprocfamily => 'brin/uuid_bloom_ops', amproclefttype => 'uuid',
   amprocrighttype => 'uuid', amprocnum => '5', amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/uuid_bloom_ops', amproclefttype => 'uuid',
+  amprocrighttype => 'uuid', amprocnum => '6', amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/uuid_bloom_ops', amproclefttype => 'uuid',
   amprocrighttype => 'uuid', amprocnum => '11', amproc => 'uuid_hash' },
 
@@ -2087,6 +2144,9 @@
 { amprocfamily => 'brin/pg_lsn_bloom_ops', amproclefttype => 'pg_lsn',
   amprocrighttype => 'pg_lsn', amprocnum => '5',
   amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/pg_lsn_bloom_ops', amproclefttype => 'pg_lsn',
+  amprocrighttype => 'pg_lsn', amprocnum => '6',
+  amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/pg_lsn_bloom_ops', amproclefttype => 'pg_lsn',
   amprocrighttype => 'pg_lsn', amprocnum => '11', amproc => 'pg_lsn_hash' },
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 753c41d5cd..4325229c9d 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8633,6 +8633,9 @@
 { oid => '4595', descr => 'BRIN bloom support',
   proname => 'brin_bloom_options', proisstrict => 'f', prorettype => 'void',
   proargtypes => 'internal', prosrc => 'brin_bloom_options' },
+{ oid => '9325', descr => 'BRIN bloom support',
+  proname => 'brin_bloom_preprocess', proisstrict => 'f', prorettype => 'internal',
+  proargtypes => 'internal internal', prosrc => 'brin_bloom_preprocess' },
 
 # userlock replacements
 { oid => '2880', descr => 'obtain exclusive advisory lock',
-- 
2.39.1



  [text/x-python] brin.py (2.3K, ../../[email protected]/10-brin.py)
  download | inline:
import psycopg2
import sys
import time

conn = psycopg2.connect('host=localhost dbname=test')

conn.set_session(autocommit=True)

nrows = 10000000

for t in ['int', 'text']:

	for o in ['minmax', 'bloom']:

		for d in [int(nrows*5/100), int(nrows/100)]:

			if t == 'int':
				cur = conn.cursor()
				cur.execute('drop table if exists t')
				cur.execute('create table t (a int)')
				cur.execute('insert into t select * from (select i from generate_series(1,%d) s(i)) foo order by (i + %d * random() / 100.0)' % (nrows, d))

				if o == 'minmax':
					cur.execute('create index on t using brin (a int4_minmax_ops) with (pages_per_range=1)')
				else:
					cur.execute('create index on t using brin (a int4_bloom_ops(n_distinct_per_range=200)) with (pages_per_range=1)')

				cur.execute('vacuum analyze t')
				cur.close()
			else:
				cur = conn.cursor()
				cur.execute('drop table if exists t')
				cur.execute('create table t (a text)')
				cur.execute('insert into t select a from (select row_number() over (order by a) as i, a from (select md5(i::text) a from generate_series(1,%d) s(i) order by 1) foo) bar order by (i + %d * random() / 100.0)' % (nrows, d))

				if o == 'minmax':
					cur.execute('create index on t using brin (a text_minmax_ops) with (pages_per_range=1)')
				else:
					cur.execute('create index on t using brin (a text_bloom_ops(n_distinct_per_range=250)) with (pages_per_range=1)')

				cur.execute('vacuum analyze t')
				cur.close()

			cnt = sum([1, 10, 20, 50, 100, 500]) * 10

			cur = conn.cursor()
			cur.execute('select a from t order by random() limit %d' % (cnt,))
			values = [str(v[0]) for v in cur.fetchall()]
			cur.close()

			# 10 runs
			for r in range(0,10):

				# number of values in the lists
				for v in [1, 10, 20, 50, 100, 500]:

					vals = values[:v]
					values = values[v:]

					if len(vals) != v:
						print ('incorrect length', len(vals))
						sys.exit(1)

					if t == 'text':
						vals = "'" + "','".join(vals) + "'"
					else:
						vals = ','.join(vals)

					cur = conn.cursor()

					cur.execute('set enable_seqscan = off')
					cur.execute('set max_parallel_workers_per_gather = 0')

					query = 'explain (analyze, timing off) select * from t where a in (%s)' % (vals,)

					s = time.time()
					cur.execute(query)
					e = time.time()

					cur.close()

					print(t, o, r, d, v, round(1000 * (e-s),3))

					sys.stdout.flush()

^ permalink  raw  reply  [nested|flat] 5+ messages in thread

* Re: BRIN indexes vs. SK_SEARCHARRAY (and preprocessing scan keys)
@ 2023-02-17 02:50  Tomas Vondra <[email protected]>
  parent: Tomas Vondra <[email protected]>
  0 siblings, 1 reply; 5+ messages in thread

From: Tomas Vondra @ 2023-02-17 02:50 UTC (permalink / raw)
  To: PostgreSQL Hackers <[email protected]>

cfbot identified a couple issues in the pathes:

1) not handling NULLs correctly (or rather at all). There was a FIXME,
so I took this as a sign it's time to finally address that.

2) minmax-multi did not fully adopt the preprocessed values in the
second part of the _consistent function

The patches also add a bunch of regression tests to improve coverage.


While adding those, I ran into an interesting behavior with BRIN bloom
indexes. If you have such index on a bigint column, then this won't use
the index:

  SELECT * FROM t WHERE b = 82;

unless you cast the constant to bigint like this:

  SELECT * FROM t WHERE b = 82::bigint;

I vaguely remember dealing with this while working on the bloom indexes,
and concluding this is OK. But what's interesting is that with multiple
values in the IN clause it works and this will use the index:

  SELECT * FROM t WHERE b IN (82, 83);

That's a bit surprising.


regards

-- 
Tomas Vondra
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

Attachments:

  [text/x-patch] 0001-BRIN-bloom-cleanup-20230217.patch (1.9K, ../../[email protected]/2-0001-BRIN-bloom-cleanup-20230217.patch)
  download | inline diff:
From 87cf7111f86c6bcd847fe7d001d1c27f582dcf0e Mon Sep 17 00:00:00 2001
From: Tomas Vondra <[email protected]>
Date: Wed, 15 Feb 2023 12:39:23 +0100
Subject: [PATCH 1/9] BRIN bloom cleanup

Minor cleanup of the BRIN bloom code - use the proper data type for the
boolean variable, and correct comment copied from minmax.
---
 src/backend/access/brin/brin_bloom.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/src/backend/access/brin/brin_bloom.c b/src/backend/access/brin/brin_bloom.c
index e4953a9d37b..6c716924fff 100644
--- a/src/backend/access/brin/brin_bloom.c
+++ b/src/backend/access/brin/brin_bloom.c
@@ -574,7 +574,7 @@ brin_bloom_consistent(PG_FUNCTION_ARGS)
 	Oid			colloid = PG_GET_COLLATION();
 	AttrNumber	attno;
 	Datum		value;
-	Datum		matches;
+	bool		matches;
 	FmgrInfo   *finfo;
 	uint32		hashValue;
 	BloomFilter *filter;
@@ -584,6 +584,7 @@ brin_bloom_consistent(PG_FUNCTION_ARGS)
 
 	Assert(filter);
 
+	/* assume all scan keys match */
 	matches = true;
 
 	for (keyno = 0; keyno < nkeys; keyno++)
@@ -601,9 +602,8 @@ brin_bloom_consistent(PG_FUNCTION_ARGS)
 			case BloomEqualStrategyNumber:
 
 				/*
-				 * In the equality case (WHERE col = someval), we want to
-				 * return the current page range if the minimum value in the
-				 * range <= scan key, and the maximum value >= scan key.
+				 * We want to return the current page range if the bloom filter
+				 * seems to contain the value.
 				 */
 				finfo = bloom_get_procinfo(bdesc, attno, PROCNUM_HASH);
 
@@ -614,7 +614,7 @@ brin_bloom_consistent(PG_FUNCTION_ARGS)
 			default:
 				/* shouldn't happen */
 				elog(ERROR, "invalid strategy number %d", key->sk_strategy);
-				matches = 0;
+				matches = false;
 				break;
 		}
 
@@ -622,7 +622,7 @@ brin_bloom_consistent(PG_FUNCTION_ARGS)
 			break;
 	}
 
-	PG_RETURN_DATUM(matches);
+	PG_RETURN_BOOL(matches);
 }
 
 /*
-- 
2.39.1



  [text/x-patch] 0002-BRIN-minmax-multi-cleanup-20230217.patch (2.2K, ../../[email protected]/3-0002-BRIN-minmax-multi-cleanup-20230217.patch)
  download | inline diff:
From 35c9a76d78b3f32dbed52527206fd76a791772f6 Mon Sep 17 00:00:00 2001
From: Tomas Vondra <[email protected]>
Date: Wed, 15 Feb 2023 12:53:47 +0100
Subject: [PATCH 2/9] BRIN minmax-multi cleanup

When assigning to a Datum variable, use BoolGetDatum() consistently.
Simnplify the code by using PG_RETURN_BOOL().
---
 src/backend/access/brin/brin_minmax_multi.c | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/src/backend/access/brin/brin_minmax_multi.c b/src/backend/access/brin/brin_minmax_multi.c
index 0ace6035beb..859e0022fb4 100644
--- a/src/backend/access/brin/brin_minmax_multi.c
+++ b/src/backend/access/brin/brin_minmax_multi.c
@@ -2627,7 +2627,7 @@ brin_minmax_multi_consistent(PG_FUNCTION_ARGS)
 						FmgrInfo   *cmpFn;
 
 						/* by default this range does not match */
-						matches = false;
+						matches = BoolGetDatum(false);
 
 						/*
 						 * Otherwise, need to compare the new value with
@@ -2655,7 +2655,7 @@ brin_minmax_multi_consistent(PG_FUNCTION_ARGS)
 						 * We haven't managed to eliminate this range, so
 						 * consider it matching.
 						 */
-						matches = true;
+						matches = BoolGetDatum(true);
 
 						break;
 					}
@@ -2670,7 +2670,7 @@ brin_minmax_multi_consistent(PG_FUNCTION_ARGS)
 				default:
 					/* shouldn't happen */
 					elog(ERROR, "invalid strategy number %d", key->sk_strategy);
-					matches = 0;
+					matches = BoolGetDatum(false);
 					break;
 			}
 
@@ -2686,7 +2686,7 @@ brin_minmax_multi_consistent(PG_FUNCTION_ARGS)
 		 * have we found a range matching all scan keys? if yes, we're done
 		 */
 		if (matching)
-			PG_RETURN_DATUM(BoolGetDatum(true));
+			PG_RETURN_BOOL(true);
 	}
 
 	/*
@@ -2729,7 +2729,7 @@ brin_minmax_multi_consistent(PG_FUNCTION_ARGS)
 				default:
 					/* shouldn't happen */
 					elog(ERROR, "invalid strategy number %d", key->sk_strategy);
-					matches = 0;
+					matches = BoolGetDatum(false);
 					break;
 			}
 
@@ -2743,10 +2743,10 @@ brin_minmax_multi_consistent(PG_FUNCTION_ARGS)
 
 		/* have we found a range matching all scan keys? if yes, we're done */
 		if (matching)
-			PG_RETURN_DATUM(BoolGetDatum(true));
+			PG_RETURN_BOOL(true);
 	}
 
-	PG_RETURN_DATUM(BoolGetDatum(false));
+	PG_RETURN_BOOL(false);
 }
 
 /*
-- 
2.39.1



  [text/x-patch] 0003-Introduce-bloom_filter_size-20230217.patch (3.5K, ../../[email protected]/4-0003-Introduce-bloom_filter_size-20230217.patch)
  download | inline diff:
From 6750b0c6ba872dd29cb72a72a20a9b4ef2831c92 Mon Sep 17 00:00:00 2001
From: Tomas Vondra <[email protected]>
Date: Tue, 14 Feb 2023 20:28:08 +0100
Subject: [PATCH 3/9] Introduce bloom_filter_size

Wrap calculation of Bloom filter parameters (from ndistinct and false
positive rate) into a function. We'll need to do this calculation in
other places, and this makes it more consistent.
---
 src/backend/access/brin/brin_bloom.c | 63 +++++++++++++++++++++-------
 1 file changed, 47 insertions(+), 16 deletions(-)

diff --git a/src/backend/access/brin/brin_bloom.c b/src/backend/access/brin/brin_bloom.c
index 6c716924fff..4ff80aeb0cc 100644
--- a/src/backend/access/brin/brin_bloom.c
+++ b/src/backend/access/brin/brin_bloom.c
@@ -259,6 +259,48 @@ typedef struct BloomFilter
 	char		data[FLEXIBLE_ARRAY_MEMBER];
 } BloomFilter;
 
+/*
+ * bloom_filter_size
+ *		Calculate Bloom filter parameters (nbits, nbytes, nhashes).
+ *
+ * Given expected number of distinct values and desired false positive rate,
+ * calculates the optimal parameters of the Bloom filter.
+ *
+ * The resulting parameters are returned through nbytesp (number of bytes),
+ * nbitsp (number of bits) and nhashesp (number of hash functions). If a
+ * pointer is NULL, the parameter is not returned.
+ */
+static void
+bloom_filter_size(int ndistinct, double false_positive_rate,
+				  int *nbytesp, int *nbitsp, int *nhashesp)
+{
+	double	k;
+	int		nbits,
+			nbytes;
+
+	/* sizing bloom filter: -(n * ln(p)) / (ln(2))^2 */
+	nbits = ceil(-(ndistinct * log(false_positive_rate)) / pow(log(2.0), 2));
+
+	/* round m to whole bytes */
+	nbytes = ((nbits + 7) / 8);
+	nbits = nbytes * 8;
+
+	/*
+	 * round(log(2.0) * m / ndistinct), but assume round() may not be
+	 * available on Windows
+	 */
+	k = log(2.0) * nbits / ndistinct;
+	k = (k - floor(k) >= 0.5) ? ceil(k) : floor(k);
+
+	if (nbytesp)
+		*nbytesp = nbytes;
+
+	if (nbitsp)
+		*nbitsp = nbits;
+
+	if (nhashesp)
+		*nhashesp = (int) k;
+}
 
 /*
  * bloom_init
@@ -275,19 +317,15 @@ bloom_init(int ndistinct, double false_positive_rate)
 
 	int			nbits;			/* size of filter / number of bits */
 	int			nbytes;			/* size of filter / number of bytes */
-
-	double		k;				/* number of hash functions */
+	int			nhashes;		/* number of hash functions */
 
 	Assert(ndistinct > 0);
 	Assert((false_positive_rate >= BLOOM_MIN_FALSE_POSITIVE_RATE) &&
 		   (false_positive_rate < BLOOM_MAX_FALSE_POSITIVE_RATE));
 
-	/* sizing bloom filter: -(n * ln(p)) / (ln(2))^2 */
-	nbits = ceil(-(ndistinct * log(false_positive_rate)) / pow(log(2.0), 2));
-
-	/* round m to whole bytes */
-	nbytes = ((nbits + 7) / 8);
-	nbits = nbytes * 8;
+	/* calculate bloom filter size / parameters */
+	bloom_filter_size(ndistinct, false_positive_rate,
+					  &nbytes, &nbits, &nhashes);
 
 	/*
 	 * Reject filters that are obviously too large to store on a page.
@@ -310,13 +348,6 @@ bloom_init(int ndistinct, double false_positive_rate)
 		elog(ERROR, "the bloom filter is too large (%d > %zu)", nbytes,
 			 BloomMaxFilterSize);
 
-	/*
-	 * round(log(2.0) * m / ndistinct), but assume round() may not be
-	 * available on Windows
-	 */
-	k = log(2.0) * nbits / ndistinct;
-	k = (k - floor(k) >= 0.5) ? ceil(k) : floor(k);
-
 	/*
 	 * We allocate the whole filter. Most of it is going to be 0 bits, so the
 	 * varlena is easy to compress.
@@ -326,7 +357,7 @@ bloom_init(int ndistinct, double false_positive_rate)
 	filter = (BloomFilter *) palloc0(len);
 
 	filter->flags = 0;
-	filter->nhashes = (int) k;
+	filter->nhashes = nhashes;
 	filter->nbits = nbits;
 
 	SET_VARSIZE(filter, len);
-- 
2.39.1



  [text/x-patch] 0004-Add-minmax-multi-inequality-tests-20230217.patch (16.8K, ../../[email protected]/5-0004-Add-minmax-multi-inequality-tests-20230217.patch)
  download | inline diff:
From 9371feb7462bf565399cdbf844849a41be135803 Mon Sep 17 00:00:00 2001
From: Tomas Vondra <[email protected]>
Date: Fri, 17 Feb 2023 02:25:52 +0100
Subject: [PATCH 4/9] Add minmax-multi inequality tests

Add tests exercising the other scan key strategies, to improve test
coverage.
---
 src/test/regress/expected/brin_multi.out | 363 +++++++++++++++++++++++
 src/test/regress/sql/brin_multi.sql      | 136 +++++++++
 2 files changed, 499 insertions(+)

diff --git a/src/test/regress/expected/brin_multi.out b/src/test/regress/expected/brin_multi.out
index f3309f433f8..98e1cdaa361 100644
--- a/src/test/regress/expected/brin_multi.out
+++ b/src/test/regress/expected/brin_multi.out
@@ -460,3 +460,366 @@ EXPLAIN (COSTS OFF) SELECT * FROM brin_test_multi WHERE b = 1;
    Filter: (b = 1)
 (2 rows)
 
+-- do some inequality tests
+CREATE TABLE brin_test_multi_1 (a INT, b BIGINT) WITH (fillfactor=10);
+INSERT INTO brin_test_multi_1
+SELECT i/5 + mod(911 * i + 483, 25),
+       i/10 + mod(751 * i + 221, 41)
+  FROM generate_series(1,1000) s(i);
+CREATE INDEX brin_test_multi_1_idx_1 ON brin_test_multi_1 USING brin (a int4_minmax_multi_ops) WITH (pages_per_range=5);
+CREATE INDEX brin_test_multi_1_idx_2 ON brin_test_multi_1 USING brin (b int8_minmax_multi_ops) WITH (pages_per_range=5);
+SET enable_seqscan=off;
+-- int: less than
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a < 37;
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_test_multi_1
+         Recheck Cond: (a < 37)
+         ->  Bitmap Index Scan on brin_test_multi_1_idx_1
+               Index Cond: (a < 37)
+(5 rows)
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a < 37;
+ count 
+-------
+   124
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a < 113;
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_test_multi_1
+         Recheck Cond: (a < 113)
+         ->  Bitmap Index Scan on brin_test_multi_1_idx_1
+               Index Cond: (a < 113)
+(5 rows)
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a < 113;
+ count 
+-------
+   504
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a <= 177;
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_test_multi_1
+         Recheck Cond: (a <= 177)
+         ->  Bitmap Index Scan on brin_test_multi_1_idx_1
+               Index Cond: (a <= 177)
+(5 rows)
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a <= 177;
+ count 
+-------
+   829
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a <= 25;
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_test_multi_1
+         Recheck Cond: (a <= 25)
+         ->  Bitmap Index Scan on brin_test_multi_1_idx_1
+               Index Cond: (a <= 25)
+(5 rows)
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a <= 25;
+ count 
+-------
+    69
+(1 row)
+
+-- int: greater than
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a > 120;
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_test_multi_1
+         Recheck Cond: (a > 120)
+         ->  Bitmap Index Scan on brin_test_multi_1_idx_1
+               Index Cond: (a > 120)
+(5 rows)
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a > 120;
+ count 
+-------
+   456
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a >= 180;
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_test_multi_1
+         Recheck Cond: (a >= 180)
+         ->  Bitmap Index Scan on brin_test_multi_1_idx_1
+               Index Cond: (a >= 180)
+(5 rows)
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a >= 180;
+ count 
+-------
+   161
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a > 71;
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_test_multi_1
+         Recheck Cond: (a > 71)
+         ->  Bitmap Index Scan on brin_test_multi_1_idx_1
+               Index Cond: (a > 71)
+(5 rows)
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a > 71;
+ count 
+-------
+   701
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a >= 63;
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_test_multi_1
+         Recheck Cond: (a >= 63)
+         ->  Bitmap Index Scan on brin_test_multi_1_idx_1
+               Index Cond: (a >= 63)
+(5 rows)
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a >= 63;
+ count 
+-------
+   746
+(1 row)
+
+-- bigint: less than
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b < 73;
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_test_multi_1
+         Recheck Cond: (b < 73)
+         ->  Bitmap Index Scan on brin_test_multi_1_idx_2
+               Index Cond: (b < 73)
+(5 rows)
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b < 73;
+ count 
+-------
+   529
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b <= 47;
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_test_multi_1
+         Recheck Cond: (b <= 47)
+         ->  Bitmap Index Scan on brin_test_multi_1_idx_2
+               Index Cond: (b <= 47)
+(5 rows)
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b <= 47;
+ count 
+-------
+   279
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b < 199;
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_test_multi_1
+         Recheck Cond: (b < 199)
+         ->  Bitmap Index Scan on brin_test_multi_1_idx_2
+               Index Cond: (b < 199)
+(5 rows)
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b < 199;
+ count 
+-------
+  1000
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b <= 150;
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_test_multi_1
+         Recheck Cond: (b <= 150)
+         ->  Bitmap Index Scan on brin_test_multi_1_idx_2
+               Index Cond: (b <= 150)
+(5 rows)
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b <= 150;
+ count 
+-------
+  1000
+(1 row)
+
+-- bigint: greater than
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b > 93;
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_test_multi_1
+         Recheck Cond: (b > 93)
+         ->  Bitmap Index Scan on brin_test_multi_1_idx_2
+               Index Cond: (b > 93)
+(5 rows)
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b > 93;
+ count 
+-------
+   261
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b > 37;
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_test_multi_1
+         Recheck Cond: (b > 37)
+         ->  Bitmap Index Scan on brin_test_multi_1_idx_2
+               Index Cond: (b > 37)
+(5 rows)
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b > 37;
+ count 
+-------
+   821
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b >= 215;
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_test_multi_1
+         Recheck Cond: (b >= 215)
+         ->  Bitmap Index Scan on brin_test_multi_1_idx_2
+               Index Cond: (b >= 215)
+(5 rows)
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b >= 215;
+ count 
+-------
+     0
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b > 201;
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_test_multi_1
+         Recheck Cond: (b > 201)
+         ->  Bitmap Index Scan on brin_test_multi_1_idx_2
+               Index Cond: (b > 201)
+(5 rows)
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b > 201;
+ count 
+-------
+     0
+(1 row)
+
+DROP TABLE brin_test_multi_1;
+RESET enable_seqscan;
+-- do some inequality tests for varlena data types
+CREATE TABLE brin_test_multi_2 (a UUID) WITH (fillfactor=10);
+INSERT INTO brin_test_multi_2
+SELECT v::uuid FROM (SELECT row_number() OVER (ORDER BY v) c, v FROM (SELECT md5((i/13)::text) AS v FROM generate_series(1,1000) s(i)) foo) bar ORDER BY c + 25 * random();
+CREATE INDEX brin_test_multi_2_idx ON brin_test_multi_2 USING brin (a uuid_minmax_multi_ops) WITH (pages_per_range=5);
+SET enable_seqscan=off;
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_2 WHERE a < '33e75ff0-9dd6-01bb-e69f-351039152189';
+                                  QUERY PLAN                                  
+------------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_test_multi_2
+         Recheck Cond: (a < '33e75ff0-9dd6-01bb-e69f-351039152189'::uuid)
+         ->  Bitmap Index Scan on brin_test_multi_2_idx
+               Index Cond: (a < '33e75ff0-9dd6-01bb-e69f-351039152189'::uuid)
+(5 rows)
+
+SELECT COUNT(*) FROM brin_test_multi_2 WHERE a < '33e75ff0-9dd6-01bb-e69f-351039152189';
+ count 
+-------
+   195
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_2 WHERE a > '33e75ff0-9dd6-01bb-e69f-351039152189';
+                                  QUERY PLAN                                  
+------------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_test_multi_2
+         Recheck Cond: (a > '33e75ff0-9dd6-01bb-e69f-351039152189'::uuid)
+         ->  Bitmap Index Scan on brin_test_multi_2_idx
+               Index Cond: (a > '33e75ff0-9dd6-01bb-e69f-351039152189'::uuid)
+(5 rows)
+
+SELECT COUNT(*) FROM brin_test_multi_2 WHERE a > '33e75ff0-9dd6-01bb-e69f-351039152189';
+ count 
+-------
+   792
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_2 WHERE a <= 'f457c545-a9de-d88f-18ec-ee47145a72c0';
+                                  QUERY PLAN                                   
+-------------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_test_multi_2
+         Recheck Cond: (a <= 'f457c545-a9de-d88f-18ec-ee47145a72c0'::uuid)
+         ->  Bitmap Index Scan on brin_test_multi_2_idx
+               Index Cond: (a <= 'f457c545-a9de-d88f-18ec-ee47145a72c0'::uuid)
+(5 rows)
+
+SELECT COUNT(*) FROM brin_test_multi_2 WHERE a <= 'f457c545-a9de-d88f-18ec-ee47145a72c0';
+ count 
+-------
+   961
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_2 WHERE a >= 'c51ce410-c124-a10e-0db5-e4b97fc2af39';
+                                  QUERY PLAN                                   
+-------------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_test_multi_2
+         Recheck Cond: (a >= 'c51ce410-c124-a10e-0db5-e4b97fc2af39'::uuid)
+         ->  Bitmap Index Scan on brin_test_multi_2_idx
+               Index Cond: (a >= 'c51ce410-c124-a10e-0db5-e4b97fc2af39'::uuid)
+(5 rows)
+
+SELECT COUNT(*) FROM brin_test_multi_2 WHERE a >= 'c51ce410-c124-a10e-0db5-e4b97fc2af39';
+ count 
+-------
+   272
+(1 row)
+
+DROP TABLE brin_test_multi_2;
+RESET enable_seqscan;
diff --git a/src/test/regress/sql/brin_multi.sql b/src/test/regress/sql/brin_multi.sql
index 2189b6ccf42..a59e182bc25 100644
--- a/src/test/regress/sql/brin_multi.sql
+++ b/src/test/regress/sql/brin_multi.sql
@@ -414,3 +414,139 @@ VACUUM ANALYZE brin_test_multi;
 EXPLAIN (COSTS OFF) SELECT * FROM brin_test_multi WHERE a = 1;
 -- Ensure brin index is not used when values are not correlated
 EXPLAIN (COSTS OFF) SELECT * FROM brin_test_multi WHERE b = 1;
+
+
+-- do some inequality tests
+CREATE TABLE brin_test_multi_1 (a INT, b BIGINT) WITH (fillfactor=10);
+INSERT INTO brin_test_multi_1
+SELECT i/5 + mod(911 * i + 483, 25),
+       i/10 + mod(751 * i + 221, 41)
+  FROM generate_series(1,1000) s(i);
+
+CREATE INDEX brin_test_multi_1_idx_1 ON brin_test_multi_1 USING brin (a int4_minmax_multi_ops) WITH (pages_per_range=5);
+CREATE INDEX brin_test_multi_1_idx_2 ON brin_test_multi_1 USING brin (b int8_minmax_multi_ops) WITH (pages_per_range=5);
+
+SET enable_seqscan=off;
+
+-- int: less than
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a < 37;
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a < 37;
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a < 113;
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a < 113;
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a <= 177;
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a <= 177;
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a <= 25;
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a <= 25;
+
+-- int: greater than
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a > 120;
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a > 120;
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a >= 180;
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a >= 180;
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a > 71;
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a > 71;
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a >= 63;
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a >= 63;
+
+-- bigint: less than
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b < 73;
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b < 73;
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b <= 47;
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b <= 47;
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b < 199;
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b < 199;
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b <= 150;
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b <= 150;
+
+-- bigint: greater than
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b > 93;
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b > 93;
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b > 37;
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b > 37;
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b >= 215;
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b >= 215;
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b > 201;
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b > 201;
+
+DROP TABLE brin_test_multi_1;
+RESET enable_seqscan;
+
+
+-- do some inequality tests for varlena data types
+CREATE TABLE brin_test_multi_2 (a UUID) WITH (fillfactor=10);
+INSERT INTO brin_test_multi_2
+SELECT v::uuid FROM (SELECT row_number() OVER (ORDER BY v) c, v FROM (SELECT md5((i/13)::text) AS v FROM generate_series(1,1000) s(i)) foo) bar ORDER BY c + 25 * random();
+
+CREATE INDEX brin_test_multi_2_idx ON brin_test_multi_2 USING brin (a uuid_minmax_multi_ops) WITH (pages_per_range=5);
+
+SET enable_seqscan=off;
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_2 WHERE a < '33e75ff0-9dd6-01bb-e69f-351039152189';
+
+SELECT COUNT(*) FROM brin_test_multi_2 WHERE a < '33e75ff0-9dd6-01bb-e69f-351039152189';
+
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_2 WHERE a > '33e75ff0-9dd6-01bb-e69f-351039152189';
+
+SELECT COUNT(*) FROM brin_test_multi_2 WHERE a > '33e75ff0-9dd6-01bb-e69f-351039152189';
+
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_2 WHERE a <= 'f457c545-a9de-d88f-18ec-ee47145a72c0';
+
+SELECT COUNT(*) FROM brin_test_multi_2 WHERE a <= 'f457c545-a9de-d88f-18ec-ee47145a72c0';
+
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_2 WHERE a >= 'c51ce410-c124-a10e-0db5-e4b97fc2af39';
+
+SELECT COUNT(*) FROM brin_test_multi_2 WHERE a >= 'c51ce410-c124-a10e-0db5-e4b97fc2af39';
+
+DROP TABLE brin_test_multi_2;
+RESET enable_seqscan;
-- 
2.39.1



  [text/x-patch] 0005-Introduce-BRIN_PROCNUM_PREPROCESS-procedure-20230217.patch (4.8K, ../../[email protected]/6-0005-Introduce-BRIN_PROCNUM_PREPROCESS-procedure-20230217.patch)
  download | inline diff:
From 22b906ddff0ea4f58a35772f160a0c99d671a01a Mon Sep 17 00:00:00 2001
From: Tomas Vondra <[email protected]>
Date: Tue, 14 Feb 2023 20:29:33 +0100
Subject: [PATCH 5/9] Introduce BRIN_PROCNUM_PREPROCESS procedure

Allow BRIN opclasses to define an optional procedure to preprocess scan
keys, and call it from brinrescan(). This allows the opclass to modify
the keys in various ways - sort arrays, calculate hashes, ...

Note: The procedure is optional, so existing opclasses don't need to add
it. But if it uses the existing BRIN_PROCNUM_CONSISTENT function, it'll
get broken. If we want to make this backwards-compatible, we might check
if BRIN_PROCNUM_PREPROCESS exist from BRIN_PROCNUM_CONSISTENT, and
adjust behavior based on that.
---
 src/backend/access/brin/brin.c     | 72 ++++++++++++++++++++++++++----
 src/include/access/brin_internal.h |  1 +
 2 files changed, 65 insertions(+), 8 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index de1427a1e0e..a68ecf722c5 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -66,6 +66,12 @@ typedef struct BrinOpaque
 	BlockNumber bo_pagesPerRange;
 	BrinRevmap *bo_rmAccess;
 	BrinDesc   *bo_bdesc;
+
+	/* preprocessed scan keys */
+	int			bo_numScanKeys;		/* number of (preprocessed) scan keys */
+	ScanKey	   *bo_scanKeys;		/* modified copy of scan->keyData */
+	MemoryContext bo_scanKeysCxt;	/* scan-lifespan context for key data */
+
 } BrinOpaque;
 
 #define BRIN_ALL_BLOCKRANGES	InvalidBlockNumber
@@ -334,6 +340,11 @@ brinbeginscan(Relation r, int nkeys, int norderbys)
 	opaque->bo_rmAccess = brinRevmapInitialize(r, &opaque->bo_pagesPerRange,
 											   scan->xs_snapshot);
 	opaque->bo_bdesc = brin_build_desc(r);
+
+	opaque->bo_numScanKeys = 0;
+	opaque->bo_scanKeys = NULL;
+	opaque->bo_scanKeysCxt = NULL;
+
 	scan->opaque = opaque;
 
 	return scan;
@@ -456,7 +467,7 @@ bringetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
 	/* Preprocess the scan keys - split them into per-attribute arrays. */
 	for (int keyno = 0; keyno < scan->numberOfKeys; keyno++)
 	{
-		ScanKey		key = &scan->keyData[keyno];
+		ScanKey		key = opaque->bo_scanKeys[keyno];
 		AttrNumber	keyattno = key->sk_attno;
 
 		/*
@@ -728,17 +739,62 @@ void
 brinrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
 		   ScanKey orderbys, int norderbys)
 {
-	/*
-	 * Other index AMs preprocess the scan keys at this point, or sometime
-	 * early during the scan; this lets them optimize by removing redundant
-	 * keys, or doing early returns when they are impossible to satisfy; see
-	 * _bt_preprocess_keys for an example.  Something like that could be added
-	 * here someday, too.
-	 */
+	BrinOpaque *bo = (BrinOpaque *) scan->opaque;
+	Relation	idxRel = scan->indexRelation;
+	MemoryContext	oldcxt;
 
 	if (scankey && scan->numberOfKeys > 0)
 		memmove(scan->keyData, scankey,
 				scan->numberOfKeys * sizeof(ScanKeyData));
+
+	/*
+	 * Use the BRIN_PROCNUM_PREPROCESS procedure (if defined) to preprocess
+	 * the scan keys. The procedure may do anything, as long as the result
+	 * looks like a ScanKey. If there's no procedure, we keep the original
+	 * scan key.
+	 *
+	 * FIXME Probably need fixes to handle NULLs correctly.
+	 */
+	if (bo->bo_scanKeysCxt == NULL)
+		bo->bo_scanKeysCxt = AllocSetContextCreate(CurrentMemoryContext,
+												   "BRIN scan keys context",
+												   ALLOCSET_SMALL_SIZES);
+	else
+		MemoryContextReset(bo->bo_scanKeysCxt);
+
+	oldcxt = MemoryContextSwitchTo(bo->bo_scanKeysCxt);
+
+	bo->bo_scanKeys = palloc0(sizeof(ScanKey) * nscankeys);
+
+	for (int i = 0; i < nscankeys; i++)
+	{
+		FmgrInfo   *finfo;
+		ScanKey		key = &scan->keyData[i];
+		Oid			procid;
+		Datum		ret;
+
+		/* fetch key preprocess support procedure if specified */
+		procid = index_getprocid(idxRel, key->sk_attno,
+								 BRIN_PROCNUM_PREPROCESS);
+
+		/* not specified, just point to the original key */
+		if (!OidIsValid(procid))
+		{
+			bo->bo_scanKeys[i] = key;
+			continue;
+		}
+
+		finfo = index_getprocinfo(idxRel, key->sk_attno,
+								  BRIN_PROCNUM_PREPROCESS);
+
+		ret = FunctionCall2(finfo,
+							PointerGetDatum(bo->bo_bdesc),
+							PointerGetDatum(key));
+
+		bo->bo_scanKeys[i] = (ScanKey) DatumGetPointer(ret);
+	}
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
diff --git a/src/include/access/brin_internal.h b/src/include/access/brin_internal.h
index 97ddc925b27..d6a51f2bc49 100644
--- a/src/include/access/brin_internal.h
+++ b/src/include/access/brin_internal.h
@@ -73,6 +73,7 @@ typedef struct BrinDesc
 #define BRIN_PROCNUM_UNION			4
 #define BRIN_MANDATORY_NPROCS		4
 #define BRIN_PROCNUM_OPTIONS 		5	/* optional */
+#define BRIN_PROCNUM_PREPROCESS		6	/* optional */
 /* procedure numbers up to 10 are reserved for BRIN future expansion */
 #define BRIN_FIRST_OPTIONAL_PROCNUM 11
 #define BRIN_LAST_OPTIONAL_PROCNUM	15
-- 
2.39.1



  [text/x-patch] 0006-Support-SK_SEARCHARRAY-in-BRIN-minmax-20230217.patch (69.5K, ../../[email protected]/7-0006-Support-SK_SEARCHARRAY-in-BRIN-minmax-20230217.patch)
  download | inline diff:
From a63e47e78babdb7e16813480a7bcc6a85efb6768 Mon Sep 17 00:00:00 2001
From: Tomas Vondra <[email protected]>
Date: Fri, 10 Feb 2023 16:07:57 +0100
Subject: [PATCH 6/9] Support SK_SEARCHARRAY in BRIN minmax

Set "amsearcharray=true" for BRIN, and extend the minmax opclass to
handle both scalar values and arrays. This allows handling conditions

    ... WHERE a IN (1, 2, 43, 2132, 134)

    ... WHERE a = ANY(ARRAY[34, 45, -1, 234])

    ... WHERE a <= ANY(ARRAY[4938, 282, 2934])

more efficiently - until now we simply built the bitmap for each
scalar value, so we walked the BRIN index many times. Which may be quite
expensive for indexes with many ranges (large tables and/or low
pages_per_range).

There's a couple problems / open questions / TODO items:

- The other opclasses don't handle SK_SEARCHARRAY yet.

- The array is always searched linearly, so this may be costly for large
  arrays (with many elements).

- The array is deconstructed again for each range. We should reuse this,
  somehow.
---
 src/backend/access/brin/brin.c          |   3 +-
 src/backend/access/brin/brin_minmax.c   | 375 +++++++++--
 src/backend/access/brin/brin_validate.c |   4 +
 src/include/catalog/pg_amproc.dat       |  64 ++
 src/include/catalog/pg_proc.dat         |   3 +
 src/test/regress/expected/amutils.out   |   2 +-
 src/test/regress/expected/brin.out      | 858 ++++++++++++++++++++++++
 src/test/regress/sql/brin.sql           | 283 ++++++++
 8 files changed, 1552 insertions(+), 40 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index a68ecf722c5..4b761d28d9d 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -38,6 +38,7 @@
 #include "utils/datum.h"
 #include "utils/guc.h"
 #include "utils/index_selfuncs.h"
+#include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
 
@@ -107,7 +108,7 @@ brinhandler(PG_FUNCTION_ARGS)
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = true;
 	amroutine->amoptionalkey = true;
-	amroutine->amsearcharray = false;
+	amroutine->amsearcharray = true;
 	amroutine->amsearchnulls = true;
 	amroutine->amstorage = true;
 	amroutine->amclusterable = false;
diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c
index 2431591be65..d77474fa1de 100644
--- a/src/backend/access/brin/brin_minmax.c
+++ b/src/backend/access/brin/brin_minmax.c
@@ -16,11 +16,21 @@
 #include "access/stratnum.h"
 #include "catalog/pg_amop.h"
 #include "catalog/pg_type.h"
+#include "utils/array.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
 #include "utils/syscache.h"
+#include "utils/sortsupport.h"
+
+/*
+ * We use some private sk_flags bits in preprocessed scan keys.  We're allowed
+ * to use bits 16-31 (see skey.h).  The uppermost bits are copied from the
+ * index's indoption[] array entry for the index attribute.
+ */
+#define SK_BRIN_SORTED	0x00010000	/* deconstructed and sorted array */
+
 
 typedef struct MinmaxOpaque
 {
@@ -126,6 +136,158 @@ brin_minmax_add_value(PG_FUNCTION_ARGS)
 	PG_RETURN_BOOL(updated);
 }
 
+
+static int
+compare_array_values(const void *a, const void *b, void *arg)
+{
+	Datum	da = * (Datum *) a;
+	Datum	db = * (Datum *) b;
+	SortSupport	ssup = (SortSupport) arg;
+
+	return ApplySortComparator(da, false, db, false, ssup);
+}
+
+/*
+ * lower_boundary
+ *		Determine lowest index so that (values[index] >= minvalue).
+ *
+ * The array of values is expected to be sorted, so this is the first value
+ * that may fall into the [minvalue, maxvalue] range, as it exceeds minval.
+ * It's not guaranteed, though, as it might exceed maxvalue too.
+ */
+static int
+lower_boundary(Datum *values, int nvalues, Datum minvalue, SortSupport ssup)
+{
+	int		start = 0,
+			end = (nvalues - 1);
+
+	/* everything exceeds minval and might match */
+	if (compare_array_values(&minvalue, &values[start], ssup) <= 0)
+		return 0;
+
+	/* nothing could match */
+	if (compare_array_values(&minvalue, &values[end], ssup) > 0)
+		return nvalues;
+
+	while ((end - start) > 0)
+	{
+		int midpoint;
+		int r;
+
+		midpoint = start + (end - start) / 2;
+
+		r = compare_array_values(&minvalue, &values[midpoint], ssup);
+
+		if (r > 0)
+			start = Max(midpoint, start + 1);
+		else
+			end = midpoint;
+	}
+
+	/* the value should meet the (v >=minvalue) requirement */
+	Assert(compare_array_values(&values[start], &minvalue, ssup) >= 0);
+
+	/* we know start can't be 0, so it's legal to subtract 1 */
+	Assert(compare_array_values(&values[start-1], &minvalue, ssup) < 0);
+
+	return start;
+}
+
+typedef struct ScanKeyArray {
+	Oid		typeid;
+	int		nelements;
+	Datum  *elements;
+} ScanKeyArray;
+
+Datum
+brin_minmax_preprocess(PG_FUNCTION_ARGS)
+{
+	// BrinDesc   *bdesc = (BrinDesc *) PG_GETARG_POINTER(0);
+	ScanKey		key = (ScanKey) PG_GETARG_POINTER(1);
+	ScanKey		newkey;
+	ScanKeyArray *scanarray;
+
+	ArrayType  *arrayval;
+	int16		elmlen;
+	bool		elmbyval;
+	char		elmalign;
+	int			num_elems;
+	Datum	   *elem_values;
+	bool	   *elem_nulls;
+	TypeCacheEntry *type;
+	SortSupportData ssup;
+
+	/* number of non-null elements in the array */
+	int			num_nonnulls;
+
+	/*
+	 * ignore scalar keys
+	 *
+	 * XXX Maybe we should preprocess scalar keys too. It'd make the consistent
+	 * function simpler by removing the branching.
+	 */
+	if (!(key->sk_flags & SK_SEARCHARRAY))
+		PG_RETURN_POINTER(key);
+
+	arrayval = DatumGetArrayTypeP(key->sk_argument);
+
+	get_typlenbyvalalign(ARR_ELEMTYPE(arrayval),
+						 &elmlen, &elmbyval, &elmalign);
+
+	deconstruct_array(arrayval,
+					  ARR_ELEMTYPE(arrayval),
+					  elmlen, elmbyval, elmalign,
+					  &elem_values, &elem_nulls, &num_elems);
+
+	/* eliminate NULL elements */
+	num_nonnulls = 0;
+	for (int i = 0; i < num_elems; i++)
+	{
+		/* skip NULL elements */
+		if (elem_nulls[i])
+			continue;
+
+		/* if needed, move the non-NULL ones */
+		if (num_nonnulls != i)
+			elem_values[num_nonnulls] = elem_values[i];
+
+		num_nonnulls++;
+	}
+
+	num_elems = num_nonnulls;
+
+	type = lookup_type_cache(ARR_ELEMTYPE(arrayval), TYPECACHE_LT_OPR);
+
+	memset(&ssup, 0, sizeof(SortSupportData));
+
+	ssup.ssup_collation = key->sk_collation;
+	ssup.ssup_cxt = CurrentMemoryContext;
+
+	PrepareSortSupportFromOrderingOp(type->lt_opr, &ssup);
+
+	qsort_interruptible(elem_values, num_elems, sizeof(Datum),
+						compare_array_values, &ssup);
+
+	scanarray = palloc0(sizeof(ScanKeyArray));
+	scanarray->typeid = ARR_ELEMTYPE(arrayval);
+	scanarray->nelements = num_elems;
+	scanarray->elements = elem_values;
+
+	newkey = palloc0(sizeof(ScanKeyData));
+
+	ScanKeyEntryInitializeWithInfo(newkey,
+								   (key->sk_flags | SK_BRIN_SORTED),
+								   key->sk_attno,
+								   key->sk_strategy,
+								   key->sk_subtype,
+								   key->sk_collation,
+								   &key->sk_func,
+								   PointerGetDatum(scanarray));
+
+	PG_RETURN_POINTER(newkey);
+}
+
+
 /*
  * 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
@@ -157,46 +319,183 @@ brin_minmax_consistent(PG_FUNCTION_ARGS)
 	attno = key->sk_attno;
 	subtype = key->sk_subtype;
 	value = key->sk_argument;
-	switch (key->sk_strategy)
+
+	/*
+	 * For regular (scalar) scan keys, we simply compare the value to the
+	 * range min/max values, and we're done. For preprocessed SK_SEARCHARRAY
+	 * keys we need to loop through the deparsed values.
+	 */
+	if (likely(!(key->sk_flags & SK_BRIN_SORTED)))
 	{
-		case BTLessStrategyNumber:
-		case BTLessEqualStrategyNumber:
-			finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
-												 key->sk_strategy);
-			matches = FunctionCall2Coll(finfo, colloid, column->bv_values[0],
-										value);
-			break;
-		case BTEqualStrategyNumber:
-
-			/*
-			 * In the equality case (WHERE col = someval), we want to return
-			 * the current page range if the minimum value in the range <=
-			 * scan key, and the maximum value >= scan key.
-			 */
-			finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
-												 BTLessEqualStrategyNumber);
-			matches = FunctionCall2Coll(finfo, colloid, column->bv_values[0],
-										value);
-			if (!DatumGetBool(matches))
+		switch (key->sk_strategy)
+		{
+			case BTLessStrategyNumber:
+			case BTLessEqualStrategyNumber:
+				finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
+													 key->sk_strategy);
+				matches = FunctionCall2Coll(finfo, colloid, column->bv_values[0],
+											value);
+				break;
+			case BTEqualStrategyNumber:
+
+				/*
+				 * In the equality case (WHERE col = someval), we want to return
+				 * the current page range if the minimum value in the range <=
+				 * scan key, and the maximum value >= scan key.
+				 */
+				finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
+													 BTLessEqualStrategyNumber);
+				matches = FunctionCall2Coll(finfo, colloid, column->bv_values[0],
+											value);
+				if (!DatumGetBool(matches))
+					break;
+				/* max() >= scankey */
+				finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
+													 BTGreaterEqualStrategyNumber);
+				matches = FunctionCall2Coll(finfo, colloid, column->bv_values[1],
+											value);
+				break;
+			case BTGreaterEqualStrategyNumber:
+			case BTGreaterStrategyNumber:
+				finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
+													 key->sk_strategy);
+				matches = FunctionCall2Coll(finfo, colloid, column->bv_values[1],
+											value);
+				break;
+			default:
+				/* shouldn't happen */
+				elog(ERROR, "invalid strategy number %d", key->sk_strategy);
+				matches = 0;
+				break;
+		}
+	}
+	else
+	{
+		ScanKeyArray *array = (ScanKeyArray *) value;
+
+		/* can happen if the IN list contained just NULLs */
+		if (array->nelements == 0)
+			PG_RETURN_BOOL(false);
+
+		switch (key->sk_strategy)
+		{
+			case BTLessStrategyNumber:
+			case BTLessEqualStrategyNumber:
+				/*
+				 * Check the last (largest) value in the array - at least this
+				 * value has to exceed the range minval.
+				 */
+				finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
+													 key->sk_strategy);
+				matches = FunctionCall2Coll(finfo, colloid, column->bv_values[0],
+											array->elements[array->nelements-1]);
+				break;
+			case BTEqualStrategyNumber:
+
+				/*
+				 * In the equality case (WHERE col = someval), we want to return
+				 * the current page range if the minimum value in the range <=
+				 * scan key, and the maximum value >= scan key.
+				 *
+				 * We do this in two phases. We check the array min/max values to see
+				 * if there even can be a matching value, and if yes we do a binary
+				 * search to find the first value that exceeds range minval. And then
+				 * we check if it actually matches the range.
+				 *
+				 * XXX The first phase is probably unnecessary, because lower_bound()
+				 * does pretty much exactly that too.
+				 */
+				{
+					Datum val;
+					SortSupportData ssup;
+					int			lower;
+					TypeCacheEntry *type;
+
+					/* Is the first (smallest) value after the BRIN range? */
+					val = array->elements[0];
+
+					finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
+														 BTLessEqualStrategyNumber);
+					matches = FunctionCall2Coll(finfo, colloid, val, column->bv_values[1]);
+
+					/* minval > max(range values) */
+					if (!DatumGetBool(matches))
+						break;
+
+					/* Is the last (largest) value before the BRIN range? */
+					val = array->elements[array->nelements-1];
+
+					finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
+														 BTGreaterEqualStrategyNumber);
+					matches = FunctionCall2Coll(finfo, colloid, val, column->bv_values[0]);
+
+					/* maxval < min(range values) */
+					if (!DatumGetBool(matches))
+						break;
+
+					/*
+					 * OK, there might be some values matching the range. We have
+					 * to search them one by one, or perhaps try binsearch.
+					 */
+					type = lookup_type_cache(array->typeid, TYPECACHE_LT_OPR);
+
+					memset(&ssup, 0, sizeof(SortSupportData));
+
+					ssup.ssup_collation = key->sk_collation;
+					ssup.ssup_cxt = CurrentMemoryContext;
+
+					PrepareSortSupportFromOrderingOp(type->lt_opr, &ssup);
+
+					lower = lower_boundary(array->elements, array->nelements, column->bv_values[0], &ssup);
+
+					/* no elements can possibly match */
+					if (lower == array->nelements)
+					{
+						matches = BoolGetDatum(false);
+						break;
+					}
+
+					/*
+					 * OK, the first element must match the upper boundary too
+					 * (if it does not, no following elements can).
+					 */
+					val = array->elements[lower];
+
+					/*
+					 * In the equality case (WHERE col = someval), we want to return
+					 * the current page range if the minimum value in the range <=
+					 * scan key, and the maximum value >= scan key.
+					 */
+					finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
+														 BTLessEqualStrategyNumber);
+					matches = FunctionCall2Coll(finfo, colloid, column->bv_values[0],
+												val);
+					if (!DatumGetBool(matches))
+						break;
+					/* max() >= scankey */
+					finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
+														 BTGreaterEqualStrategyNumber);
+					matches = FunctionCall2Coll(finfo, colloid, column->bv_values[1],
+												val);
+					break;
+				}
+			case BTGreaterEqualStrategyNumber:
+			case BTGreaterStrategyNumber:
+				/*
+				 * Check the first (smallest) value in the array - at least this
+				 * value has to be smaller than the range maxval.
+				 */
+				finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
+													 key->sk_strategy);
+				matches = FunctionCall2Coll(finfo, colloid, column->bv_values[1],
+											array->elements[0]);
+				break;
+			default:
+				/* shouldn't happen */
+				elog(ERROR, "invalid strategy number %d", key->sk_strategy);
+				matches = 0;
 				break;
-			/* max() >= scankey */
-			finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
-												 BTGreaterEqualStrategyNumber);
-			matches = FunctionCall2Coll(finfo, colloid, column->bv_values[1],
-										value);
-			break;
-		case BTGreaterEqualStrategyNumber:
-		case BTGreaterStrategyNumber:
-			finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
-												 key->sk_strategy);
-			matches = FunctionCall2Coll(finfo, colloid, column->bv_values[1],
-										value);
-			break;
-		default:
-			/* shouldn't happen */
-			elog(ERROR, "invalid strategy number %d", key->sk_strategy);
-			matches = 0;
-			break;
+		}
 	}
 
 	PG_RETURN_DATUM(matches);
diff --git a/src/backend/access/brin/brin_validate.c b/src/backend/access/brin/brin_validate.c
index c8edfb37591..0889e24bc01 100644
--- a/src/backend/access/brin/brin_validate.c
+++ b/src/backend/access/brin/brin_validate.c
@@ -108,6 +108,10 @@ brinvalidate(Oid opclassoid)
 			case BRIN_PROCNUM_OPTIONS:
 				ok = check_amoptsproc_signature(procform->amproc);
 				break;
+			case BRIN_PROCNUM_PREPROCESS:
+				ok = check_amproc_signature(procform->amproc, INTERNALOID, true,
+											2, 2, INTERNALOID, INTERNALOID);
+				break;
 			default:
 				/* Complain if it's not a valid optional proc number */
 				if (procform->amprocnum < BRIN_FIRST_OPTIONAL_PROCNUM ||
diff --git a/src/include/catalog/pg_amproc.dat b/src/include/catalog/pg_amproc.dat
index 5b950129de0..166681c31ef 100644
--- a/src/include/catalog/pg_amproc.dat
+++ b/src/include/catalog/pg_amproc.dat
@@ -804,6 +804,8 @@
   amproc => 'brin_minmax_consistent' },
 { amprocfamily => 'brin/bytea_minmax_ops', amproclefttype => 'bytea',
   amprocrighttype => 'bytea', amprocnum => '4', amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/bytea_minmax_ops', amproclefttype => 'bytea',
+  amprocrighttype => 'bytea', amprocnum => '6', amproc => 'brin_minmax_preprocess' },
 
 # bloom bytea
 { amprocfamily => 'brin/bytea_bloom_ops', amproclefttype => 'bytea',
@@ -835,6 +837,8 @@
   amproc => 'brin_minmax_consistent' },
 { amprocfamily => 'brin/char_minmax_ops', amproclefttype => 'char',
   amprocrighttype => 'char', amprocnum => '4', amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/char_minmax_ops', amproclefttype => 'char',
+  amprocrighttype => 'char', amprocnum => '6', amproc => 'brin_minmax_preprocess' },
 
 # bloom "char"
 { amprocfamily => 'brin/char_bloom_ops', amproclefttype => 'char',
@@ -864,6 +868,8 @@
   amproc => 'brin_minmax_consistent' },
 { amprocfamily => 'brin/name_minmax_ops', amproclefttype => 'name',
   amprocrighttype => 'name', amprocnum => '4', amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/name_minmax_ops', amproclefttype => 'name',
+  amprocrighttype => 'name', amprocnum => '6', amproc => 'brin_minmax_preprocess' },
 
 # bloom name
 { amprocfamily => 'brin/name_bloom_ops', amproclefttype => 'name',
@@ -893,6 +899,8 @@
   amproc => 'brin_minmax_consistent' },
 { amprocfamily => 'brin/integer_minmax_ops', amproclefttype => 'int8',
   amprocrighttype => 'int8', amprocnum => '4', amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/integer_minmax_ops', amproclefttype => 'int8',
+  amprocrighttype => 'int8', amprocnum => '6', amproc => 'brin_minmax_preprocess' },
 
 { amprocfamily => 'brin/integer_minmax_ops', amproclefttype => 'int2',
   amprocrighttype => 'int2', amprocnum => '1',
@@ -905,6 +913,8 @@
   amproc => 'brin_minmax_consistent' },
 { amprocfamily => 'brin/integer_minmax_ops', amproclefttype => 'int2',
   amprocrighttype => 'int2', amprocnum => '4', amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/integer_minmax_ops', amproclefttype => 'int2',
+  amprocrighttype => 'int2', amprocnum => '6', amproc => 'brin_minmax_preprocess' },
 
 { amprocfamily => 'brin/integer_minmax_ops', amproclefttype => 'int4',
   amprocrighttype => 'int4', amprocnum => '1',
@@ -917,6 +927,8 @@
   amproc => 'brin_minmax_consistent' },
 { amprocfamily => 'brin/integer_minmax_ops', amproclefttype => 'int4',
   amprocrighttype => 'int4', amprocnum => '4', amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/integer_minmax_ops', amproclefttype => 'int4',
+  amprocrighttype => 'int4', amprocnum => '6', amproc => 'brin_minmax_preprocess' },
 
 # minmax multi integer: int2, int4, int8
 { amprocfamily => 'brin/integer_minmax_multi_ops', amproclefttype => 'int2',
@@ -1034,6 +1046,8 @@
   amproc => 'brin_minmax_consistent' },
 { amprocfamily => 'brin/text_minmax_ops', amproclefttype => 'text',
   amprocrighttype => 'text', amprocnum => '4', amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/text_minmax_ops', amproclefttype => 'text',
+  amprocrighttype => 'text', amprocnum => '6', amproc => 'brin_minmax_preprocess' },
 
 # bloom text
 { amprocfamily => 'brin/text_bloom_ops', amproclefttype => 'text',
@@ -1062,6 +1076,8 @@
   amproc => 'brin_minmax_consistent' },
 { amprocfamily => 'brin/oid_minmax_ops', amproclefttype => 'oid',
   amprocrighttype => 'oid', amprocnum => '4', amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/oid_minmax_ops', amproclefttype => 'oid',
+  amprocrighttype => 'oid', amprocnum => '6', amproc => 'brin_minmax_preprocess' },
 
 # minmax multi oid
 { amprocfamily => 'brin/oid_minmax_multi_ops', amproclefttype => 'oid',
@@ -1110,6 +1126,8 @@
   amproc => 'brin_minmax_consistent' },
 { amprocfamily => 'brin/tid_minmax_ops', amproclefttype => 'tid',
   amprocrighttype => 'tid', amprocnum => '4', amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/tid_minmax_ops', amproclefttype => 'tid',
+  amprocrighttype => 'tid', amprocnum => '6', amproc => 'brin_minmax_preprocess' },
 
 # bloom tid
 { amprocfamily => 'brin/tid_bloom_ops', amproclefttype => 'tid',
@@ -1160,6 +1178,9 @@
 { amprocfamily => 'brin/float_minmax_ops', amproclefttype => 'float4',
   amprocrighttype => 'float4', amprocnum => '4',
   amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/float_minmax_ops', amproclefttype => 'float4',
+  amprocrighttype => 'float4', amprocnum => '6',
+  amproc => 'brin_minmax_preprocess' },
 
 { amprocfamily => 'brin/float_minmax_ops', amproclefttype => 'float8',
   amprocrighttype => 'float8', amprocnum => '1',
@@ -1173,6 +1194,9 @@
 { amprocfamily => 'brin/float_minmax_ops', amproclefttype => 'float8',
   amprocrighttype => 'float8', amprocnum => '4',
   amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/float_minmax_ops', amproclefttype => 'float8',
+  amprocrighttype => 'float8', amprocnum => '6',
+  amproc => 'brin_minmax_preprocess' },
 
 # minmax multi float
 { amprocfamily => 'brin/float_minmax_multi_ops', amproclefttype => 'float4',
@@ -1261,6 +1285,9 @@
 { amprocfamily => 'brin/macaddr_minmax_ops', amproclefttype => 'macaddr',
   amprocrighttype => 'macaddr', amprocnum => '4',
   amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/macaddr_minmax_ops', amproclefttype => 'macaddr',
+  amprocrighttype => 'macaddr', amprocnum => '6',
+  amproc => 'brin_minmax_preprocess' },
 
 # minmax multi macaddr
 { amprocfamily => 'brin/macaddr_minmax_multi_ops', amproclefttype => 'macaddr',
@@ -1314,6 +1341,9 @@
 { amprocfamily => 'brin/macaddr8_minmax_ops', amproclefttype => 'macaddr8',
   amprocrighttype => 'macaddr8', amprocnum => '4',
   amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/macaddr8_minmax_ops', amproclefttype => 'macaddr8',
+  amprocrighttype => 'macaddr8', amprocnum => '6',
+  amproc => 'brin_minmax_preprocess' },
 
 # minmax multi macaddr8
 { amprocfamily => 'brin/macaddr8_minmax_multi_ops',
@@ -1366,6 +1396,8 @@
   amproc => 'brin_minmax_consistent' },
 { amprocfamily => 'brin/network_minmax_ops', amproclefttype => 'inet',
   amprocrighttype => 'inet', amprocnum => '4', amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/network_minmax_ops', amproclefttype => 'inet',
+  amprocrighttype => 'inet', amprocnum => '6', amproc => 'brin_minmax_preprocess' },
 
 # minmax multi inet
 { amprocfamily => 'brin/network_minmax_multi_ops', amproclefttype => 'inet',
@@ -1436,6 +1468,9 @@
 { amprocfamily => 'brin/bpchar_minmax_ops', amproclefttype => 'bpchar',
   amprocrighttype => 'bpchar', amprocnum => '4',
   amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/bpchar_minmax_ops', amproclefttype => 'bpchar',
+  amprocrighttype => 'bpchar', amprocnum => '6',
+  amproc => 'brin_minmax_preprocess' },
 
 # bloom character
 { amprocfamily => 'brin/bpchar_bloom_ops', amproclefttype => 'bpchar',
@@ -1467,6 +1502,8 @@
   amproc => 'brin_minmax_consistent' },
 { amprocfamily => 'brin/time_minmax_ops', amproclefttype => 'time',
   amprocrighttype => 'time', amprocnum => '4', amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/time_minmax_ops', amproclefttype => 'time',
+  amprocrighttype => 'time', amprocnum => '6', amproc => 'brin_minmax_preprocess' },
 
 # minmax multi time without time zone
 { amprocfamily => 'brin/time_minmax_multi_ops', amproclefttype => 'time',
@@ -1517,6 +1554,9 @@
 { amprocfamily => 'brin/datetime_minmax_ops', amproclefttype => 'timestamp',
   amprocrighttype => 'timestamp', amprocnum => '4',
   amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/datetime_minmax_ops', amproclefttype => 'timestamp',
+  amprocrighttype => 'timestamp', amprocnum => '6',
+  amproc => 'brin_minmax_preprocess' },
 
 { amprocfamily => 'brin/datetime_minmax_ops', amproclefttype => 'timestamptz',
   amprocrighttype => 'timestamptz', amprocnum => '1',
@@ -1530,6 +1570,9 @@
 { amprocfamily => 'brin/datetime_minmax_ops', amproclefttype => 'timestamptz',
   amprocrighttype => 'timestamptz', amprocnum => '4',
   amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/datetime_minmax_ops', amproclefttype => 'timestamptz',
+  amprocrighttype => 'timestamptz', amprocnum => '6',
+  amproc => 'brin_minmax_preprocess' },
 
 { amprocfamily => 'brin/datetime_minmax_ops', amproclefttype => 'date',
   amprocrighttype => 'date', amprocnum => '1',
@@ -1542,6 +1585,8 @@
   amproc => 'brin_minmax_consistent' },
 { amprocfamily => 'brin/datetime_minmax_ops', amproclefttype => 'date',
   amprocrighttype => 'date', amprocnum => '4', amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/datetime_minmax_ops', amproclefttype => 'date',
+  amprocrighttype => 'date', amprocnum => '6', amproc => 'brin_minmax_preprocess' },
 
 # minmax multi datetime (date, timestamp, timestamptz)
 { amprocfamily => 'brin/datetime_minmax_multi_ops',
@@ -1668,6 +1713,9 @@
 { amprocfamily => 'brin/interval_minmax_ops', amproclefttype => 'interval',
   amprocrighttype => 'interval', amprocnum => '4',
   amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/interval_minmax_ops', amproclefttype => 'interval',
+  amprocrighttype => 'interval', amprocnum => '6',
+  amproc => 'brin_minmax_preprocess' },
 
 # minmax multi interval
 { amprocfamily => 'brin/interval_minmax_multi_ops',
@@ -1721,6 +1769,9 @@
 { amprocfamily => 'brin/timetz_minmax_ops', amproclefttype => 'timetz',
   amprocrighttype => 'timetz', amprocnum => '4',
   amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/timetz_minmax_ops', amproclefttype => 'timetz',
+  amprocrighttype => 'timetz', amprocnum => '6',
+  amproc => 'brin_minmax_preprocess' },
 
 # minmax multi time with time zone
 { amprocfamily => 'brin/timetz_minmax_multi_ops', amproclefttype => 'timetz',
@@ -1771,6 +1822,8 @@
   amproc => 'brin_minmax_consistent' },
 { amprocfamily => 'brin/bit_minmax_ops', amproclefttype => 'bit',
   amprocrighttype => 'bit', amprocnum => '4', amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/bit_minmax_ops', amproclefttype => 'bit',
+  amprocrighttype => 'bit', amprocnum => '6', amproc => 'brin_minmax_preprocess' },
 
 # minmax bit varying
 { amprocfamily => 'brin/varbit_minmax_ops', amproclefttype => 'varbit',
@@ -1785,6 +1838,9 @@
 { amprocfamily => 'brin/varbit_minmax_ops', amproclefttype => 'varbit',
   amprocrighttype => 'varbit', amprocnum => '4',
   amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/varbit_minmax_ops', amproclefttype => 'varbit',
+  amprocrighttype => 'varbit', amprocnum => '6',
+  amproc => 'brin_minmax_preprocess' },
 
 # minmax numeric
 { amprocfamily => 'brin/numeric_minmax_ops', amproclefttype => 'numeric',
@@ -1799,6 +1855,9 @@
 { amprocfamily => 'brin/numeric_minmax_ops', amproclefttype => 'numeric',
   amprocrighttype => 'numeric', amprocnum => '4',
   amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/numeric_minmax_ops', amproclefttype => 'numeric',
+  amprocrighttype => 'numeric', amprocnum => '6',
+  amproc => 'brin_minmax_preprocess' },
 
 # minmax multi numeric
 { amprocfamily => 'brin/numeric_minmax_multi_ops', amproclefttype => 'numeric',
@@ -1851,6 +1910,8 @@
   amproc => 'brin_minmax_consistent' },
 { amprocfamily => 'brin/uuid_minmax_ops', amproclefttype => 'uuid',
   amprocrighttype => 'uuid', amprocnum => '4', amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/uuid_minmax_ops', amproclefttype => 'uuid',
+  amprocrighttype => 'uuid', amprocnum => '6', amproc => 'brin_minmax_preprocess' },
 
 # minmax multi uuid
 { amprocfamily => 'brin/uuid_minmax_multi_ops', amproclefttype => 'uuid',
@@ -1924,6 +1985,9 @@
 { amprocfamily => 'brin/pg_lsn_minmax_ops', amproclefttype => 'pg_lsn',
   amprocrighttype => 'pg_lsn', amprocnum => '4',
   amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/pg_lsn_minmax_ops', amproclefttype => 'pg_lsn',
+  amprocrighttype => 'pg_lsn', amprocnum => '6',
+  amproc => 'brin_minmax_preprocess' },
 
 # minmax multi pg_lsn
 { amprocfamily => 'brin/pg_lsn_minmax_multi_ops', amproclefttype => 'pg_lsn',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 66b73c3900d..6638552bd05 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8496,6 +8496,9 @@
 { oid => '3386', descr => 'BRIN minmax support',
   proname => 'brin_minmax_union', prorettype => 'bool',
   proargtypes => 'internal internal internal', prosrc => 'brin_minmax_union' },
+{ oid => '9327', descr => 'BRIN minmax support',
+  proname => 'brin_minmax_preprocess', prorettype => 'internal',
+  proargtypes => 'internal internal', prosrc => 'brin_minmax_preprocess' },
 
 # BRIN minmax multi
 { oid => '4616', descr => 'BRIN multi minmax support',
diff --git a/src/test/regress/expected/amutils.out b/src/test/regress/expected/amutils.out
index 7ab6113c619..f3e1fbd2ae3 100644
--- a/src/test/regress/expected/amutils.out
+++ b/src/test/regress/expected/amutils.out
@@ -102,7 +102,7 @@ select prop,
  orderable          | t     | f    | f    | f            | f           | f   | f
  distance_orderable | f     | f    | t    | f            | t           | f   | f
  returnable         | t     | f    | f    | t            | t           | f   | f
- search_array       | t     | f    | f    | f            | f           | f   | f
+ search_array       | t     | f    | f    | f            | f           | f   | t
  search_nulls       | t     | f    | t    | t            | t           | f   | t
  bogus              |       |      |      |              |             |     | 
 (10 rows)
diff --git a/src/test/regress/expected/brin.out b/src/test/regress/expected/brin.out
index 73fa38396e4..93d7314e599 100644
--- a/src/test/regress/expected/brin.out
+++ b/src/test/regress/expected/brin.out
@@ -572,3 +572,861 @@ CREATE UNLOGGED TABLE brintest_unlogged (n numrange);
 CREATE INDEX brinidx_unlogged ON brintest_unlogged USING brin (n);
 INSERT INTO brintest_unlogged VALUES (numrange(0, 2^1000::numeric));
 DROP TABLE brintest_unlogged;
+-- do some tests on IN clauses for simple data types
+CREATE TABLE brin_in_test_1 (a INT, b BIGINT) WITH (fillfactor=10);
+INSERT INTO brin_in_test_1
+SELECT i/5 + mod(991 * i + 617, 20),
+       i/10 + mod(853 * i + 491, 30)
+  FROM generate_series(1,1000) s(i);
+CREATE INDEX brin_in_test_1_idx_1 ON brin_in_test_1 USING brin (a int4_minmax_ops) WITH (pages_per_range=1);
+CREATE INDEX brin_in_test_1_idx_2 ON brin_in_test_1 USING brin (b int8_minmax_ops) WITH (pages_per_range=1);
+SET enable_seqscan=off;
+-- int: equalities
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a IN (113);
+                      QUERY PLAN                       
+-------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (a = 113)
+         ->  Bitmap Index Scan on brin_in_test_1_idx_1
+               Index Cond: (a = 113)
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a IN (113);
+ count 
+-------
+     8
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a IN (113, NULL);
+                          QUERY PLAN                           
+---------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (a = ANY ('{113,NULL}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_1
+               Index Cond: (a = ANY ('{113,NULL}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a IN (113, NULL);
+ count 
+-------
+     8
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a IN (NULL, NULL);
+                           QUERY PLAN                           
+----------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (a = ANY ('{NULL,NULL}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_1
+               Index Cond: (a = ANY ('{NULL,NULL}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a IN (NULL, NULL);
+ count 
+-------
+     0
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a IN (113, 177);
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (a = ANY ('{113,177}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_1
+               Index Cond: (a = ANY ('{113,177}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a IN (113, 177);
+ count 
+-------
+    16
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a IN (113, 177, NULL);
+                            QUERY PLAN                             
+-------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (a = ANY ('{113,177,NULL}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_1
+               Index Cond: (a = ANY ('{113,177,NULL}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a IN (113, 177, NULL);
+ count 
+-------
+    16
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a IN (113, 177, 25);
+                           QUERY PLAN                            
+-----------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (a = ANY ('{113,177,25}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_1
+               Index Cond: (a = ANY ('{113,177,25}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a IN (113, 177, 25);
+ count 
+-------
+    24
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a IN (NULL, 113, 177, 25);
+                              QUERY PLAN                              
+----------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (a = ANY ('{NULL,113,177,25}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_1
+               Index Cond: (a = ANY ('{NULL,113,177,25}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a IN (NULL, 113, 177, 25);
+ count 
+-------
+    24
+(1 row)
+
+-- int: less than
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a < ANY (ARRAY[30]);
+                       QUERY PLAN                        
+---------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (a < ANY ('{30}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_1
+               Index Cond: (a < ANY ('{30}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a < ANY (ARRAY[30]);
+ count 
+-------
+   103
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a < ANY (ARRAY[20, NULL]);
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (a < ANY ('{20,NULL}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_1
+               Index Cond: (a < ANY ('{20,NULL}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a < ANY (ARRAY[20, NULL]);
+ count 
+-------
+    51
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a < ANY (ARRAY[NULL, NULL]::int[]);
+                           QUERY PLAN                           
+----------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (a < ANY ('{NULL,NULL}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_1
+               Index Cond: (a < ANY ('{NULL,NULL}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a < ANY (ARRAY[NULL, NULL]::int[]);
+ count 
+-------
+     0
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a < ANY (ARRAY[35, 29]);
+                         QUERY PLAN                         
+------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (a < ANY ('{35,29}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_1
+               Index Cond: (a < ANY ('{35,29}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a < ANY (ARRAY[35, 29]);
+ count 
+-------
+   127
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a <= ANY (ARRAY[45, 60, NULL]);
+                            QUERY PLAN                            
+------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (a <= ANY ('{45,60,NULL}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_1
+               Index Cond: (a <= ANY ('{45,60,NULL}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a <= ANY (ARRAY[45, 60, NULL]);
+ count 
+-------
+   255
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a < ANY (ARRAY[41, 37, 55]);
+                          QUERY PLAN                           
+---------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (a < ANY ('{41,37,55}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_1
+               Index Cond: (a < ANY ('{41,37,55}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a < ANY (ARRAY[41, 37, 55]);
+ count 
+-------
+   227
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a <= ANY (ARRAY[NULL, 60, 43, 94]);
+                             QUERY PLAN                              
+---------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (a <= ANY ('{NULL,60,43,94}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_1
+               Index Cond: (a <= ANY ('{NULL,60,43,94}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a <= ANY (ARRAY[NULL, 60, 43, 94]);
+ count 
+-------
+   427
+(1 row)
+
+-- int: greater than
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a > ANY (ARRAY[200]);
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (a > ANY ('{200}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_1
+               Index Cond: (a > ANY ('{200}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a > ANY (ARRAY[200]);
+ count 
+-------
+    45
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a > ANY (ARRAY[177, NULL]);
+                          QUERY PLAN                           
+---------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (a > ANY ('{177,NULL}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_1
+               Index Cond: (a > ANY ('{177,NULL}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a > ANY (ARRAY[177, NULL]);
+ count 
+-------
+   157
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a > ANY (ARRAY[NULL, NULL]::int[]);
+                           QUERY PLAN                           
+----------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (a > ANY ('{NULL,NULL}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_1
+               Index Cond: (a > ANY ('{NULL,NULL}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a > ANY (ARRAY[NULL, NULL]::int[]);
+ count 
+-------
+     0
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a > ANY (ARRAY[153, 140]);
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (a > ANY ('{153,140}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_1
+               Index Cond: (a > ANY ('{153,140}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a > ANY (ARRAY[153, 140]);
+ count 
+-------
+   345
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a >= ANY (ARRAY[173, 191, NULL]);
+                             QUERY PLAN                             
+--------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (a >= ANY ('{173,191,NULL}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_1
+               Index Cond: (a >= ANY ('{173,191,NULL}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a >= ANY (ARRAY[173, 191, NULL]);
+ count 
+-------
+   185
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a > ANY (ARRAY[120, 184, 164]);
+                            QUERY PLAN                            
+------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (a > ANY ('{120,184,164}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_1
+               Index Cond: (a > ANY ('{120,184,164}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a > ANY (ARRAY[120, 184, 164]);
+ count 
+-------
+   445
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a >= ANY (ARRAY[NULL, 130, 181, 169]);
+                               QUERY PLAN                               
+------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (a >= ANY ('{NULL,130,181,169}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_1
+               Index Cond: (a >= ANY ('{NULL,130,181,169}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a >= ANY (ARRAY[NULL, 130, 181, 169]);
+ count 
+-------
+   397
+(1 row)
+
+-- bigint: eqalities
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b IN (82);
+                      QUERY PLAN                       
+-------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (b = 82)
+         ->  Bitmap Index Scan on brin_in_test_1_idx_2
+               Index Cond: (b = 82)
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b IN (82);
+ count 
+-------
+    10
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b IN (82, NULL);
+                         QUERY PLAN                          
+-------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (b = ANY ('{82,NULL}'::bigint[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_2
+               Index Cond: (b = ANY ('{82,NULL}'::bigint[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b IN (82, NULL);
+ count 
+-------
+    10
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b IN (NULL, NULL);
+                          QUERY PLAN                           
+---------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (b = ANY ('{NULL,NULL}'::bigint[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_2
+               Index Cond: (b = ANY ('{NULL,NULL}'::bigint[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b IN (NULL, NULL);
+ count 
+-------
+     0
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b IN (82, 41);
+                        QUERY PLAN                         
+-----------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (b = ANY ('{82,41}'::bigint[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_2
+               Index Cond: (b = ANY ('{82,41}'::bigint[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b IN (82, 41);
+ count 
+-------
+    20
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b IN (82, 41, NULL);
+                           QUERY PLAN                           
+----------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (b = ANY ('{82,41,NULL}'::bigint[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_2
+               Index Cond: (b = ANY ('{82,41,NULL}'::bigint[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b IN (82, 41, NULL);
+ count 
+-------
+    20
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b IN (82, 41, 15);
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (b = ANY ('{82,41,15}'::bigint[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_2
+               Index Cond: (b = ANY ('{82,41,15}'::bigint[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b IN (82, 41, 15);
+ count 
+-------
+    25
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b IN (NULL, 82, 41, 15);
+                            QUERY PLAN                             
+-------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (b = ANY ('{NULL,82,41,15}'::bigint[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_2
+               Index Cond: (b = ANY ('{NULL,82,41,15}'::bigint[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b IN (NULL, 82, 41, 15);
+ count 
+-------
+    25
+(1 row)
+
+-- bigint: less than
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b < ANY (ARRAY[31]);
+                       QUERY PLAN                        
+---------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (b < ANY ('{31}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_2
+               Index Cond: (b < ANY ('{31}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b < ANY (ARRAY[31]);
+ count 
+-------
+   164
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b < ANY (ARRAY[55, NULL]);
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (b < ANY ('{55,NULL}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_2
+               Index Cond: (b < ANY ('{55,NULL}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b < ANY (ARRAY[55, NULL]);
+ count 
+-------
+   404
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b < ANY (ARRAY[NULL, NULL]::bigint[]);
+                          QUERY PLAN                           
+---------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (b < ANY ('{NULL,NULL}'::bigint[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_2
+               Index Cond: (b < ANY ('{NULL,NULL}'::bigint[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b < ANY (ARRAY[NULL, NULL]::bigint[]);
+ count 
+-------
+     0
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b <= ANY (ARRAY[73, 51]);
+                         QUERY PLAN                          
+-------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (b <= ANY ('{73,51}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_2
+               Index Cond: (b <= ANY ('{73,51}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b <= ANY (ARRAY[73, 51]);
+ count 
+-------
+   594
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b < ANY (ARRAY[69, 87, NULL]);
+                           QUERY PLAN                            
+-----------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (b < ANY ('{69,87,NULL}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_2
+               Index Cond: (b < ANY ('{69,87,NULL}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b < ANY (ARRAY[69, 87, NULL]);
+ count 
+-------
+   724
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b <= ANY (ARRAY[82, 91, 35]);
+                           QUERY PLAN                           
+----------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (b <= ANY ('{82,91,35}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_2
+               Index Cond: (b <= ANY ('{82,91,35}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b <= ANY (ARRAY[82, 91, 35]);
+ count 
+-------
+   774
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b < ANY (ARRAY[NULL, 63, 21, 85]);
+                             QUERY PLAN                             
+--------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (b < ANY ('{NULL,63,21,85}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_2
+               Index Cond: (b < ANY ('{NULL,63,21,85}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b < ANY (ARRAY[NULL, 63, 21, 85]);
+ count 
+-------
+   704
+(1 row)
+
+-- bigint: greater than
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b > ANY (ARRAY[94]);
+                       QUERY PLAN                        
+---------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (b > ANY ('{94}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_2
+               Index Cond: (b > ANY ('{94}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b > ANY (ARRAY[94]);
+ count 
+-------
+   196
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b > ANY (ARRAY[80, NULL]);
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (b > ANY ('{80,NULL}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_2
+               Index Cond: (b > ANY ('{80,NULL}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b > ANY (ARRAY[80, NULL]);
+ count 
+-------
+   336
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b > ANY (ARRAY[NULL, NULL]::bigint[]);
+                          QUERY PLAN                           
+---------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (b > ANY ('{NULL,NULL}'::bigint[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_2
+               Index Cond: (b > ANY ('{NULL,NULL}'::bigint[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b > ANY (ARRAY[NULL, NULL]::bigint[]);
+ count 
+-------
+     0
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b > ANY (ARRAY[199, 107]);
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (b > ANY ('{199,107}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_2
+               Index Cond: (b > ANY ('{199,107}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b > ANY (ARRAY[199, 107]);
+ count 
+-------
+    78
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b >= ANY (ARRAY[182, 101, NULL]);
+                             QUERY PLAN                             
+--------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (b >= ANY ('{182,101,NULL}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_2
+               Index Cond: (b >= ANY ('{182,101,NULL}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b >= ANY (ARRAY[182, 101, NULL]);
+ count 
+-------
+   137
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b > ANY (ARRAY[300, 106, 251]);
+                            QUERY PLAN                            
+------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (b > ANY ('{300,106,251}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_2
+               Index Cond: (b > ANY ('{300,106,251}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b > ANY (ARRAY[300, 106, 251]);
+ count 
+-------
+    86
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b > ANY (ARRAY[NULL, 182, 101, 155]);
+                              QUERY PLAN                               
+-----------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (b > ANY ('{NULL,182,101,155}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_2
+               Index Cond: (b > ANY ('{NULL,182,101,155}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b > ANY (ARRAY[NULL, 182, 101, 155]);
+ count 
+-------
+   127
+(1 row)
+
+DROP TABLE brin_in_test_1;
+RESET enable_seqscan;
+-- do some tests on IN clauses for varlena data types
+CREATE TABLE brin_in_test_2 (a TEXT) WITH (fillfactor=10);
+INSERT INTO brin_in_test_2
+SELECT v FROM (SELECT row_number() OVER (ORDER BY v) c, v FROM (SELECT md5((i/13)::text) AS v FROM generate_series(1,1000) s(i)) foo) bar ORDER BY c + 25 * random();
+CREATE INDEX brin_in_test_2_idx ON brin_in_test_2 USING brin (a text_minmax_ops) WITH (pages_per_range=1);
+SET enable_seqscan=off;
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189');
+                                QUERY PLAN                                
+--------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_2
+         Recheck Cond: (a = '33e75ff09dd601bbe69f351039152189'::text)
+         ->  Bitmap Index Scan on brin_in_test_2_idx
+               Index Cond: (a = '33e75ff09dd601bbe69f351039152189'::text)
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189');
+ count 
+-------
+    13
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', NULL);
+                                       QUERY PLAN                                        
+-----------------------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_2
+         Recheck Cond: (a = ANY ('{33e75ff09dd601bbe69f351039152189,NULL}'::text[]))
+         ->  Bitmap Index Scan on brin_in_test_2_idx
+               Index Cond: (a = ANY ('{33e75ff09dd601bbe69f351039152189,NULL}'::text[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', NULL);
+ count 
+-------
+    13
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_2 WHERE a IN (NULL, NULL);
+                         QUERY PLAN                          
+-------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_2
+         Recheck Cond: (a = ANY ('{NULL,NULL}'::text[]))
+         ->  Bitmap Index Scan on brin_in_test_2_idx
+               Index Cond: (a = ANY ('{NULL,NULL}'::text[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_2 WHERE a IN (NULL, NULL);
+ count 
+-------
+     0
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0');
+                                                     QUERY PLAN                                                      
+---------------------------------------------------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_2
+         Recheck Cond: (a = ANY ('{33e75ff09dd601bbe69f351039152189,f457c545a9ded88f18ecee47145a72c0}'::text[]))
+         ->  Bitmap Index Scan on brin_in_test_2_idx
+               Index Cond: (a = ANY ('{33e75ff09dd601bbe69f351039152189,f457c545a9ded88f18ecee47145a72c0}'::text[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0');
+ count 
+-------
+    26
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0', NULL);
+                                                        QUERY PLAN                                                        
+--------------------------------------------------------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_2
+         Recheck Cond: (a = ANY ('{33e75ff09dd601bbe69f351039152189,f457c545a9ded88f18ecee47145a72c0,NULL}'::text[]))
+         ->  Bitmap Index Scan on brin_in_test_2_idx
+               Index Cond: (a = ANY ('{33e75ff09dd601bbe69f351039152189,f457c545a9ded88f18ecee47145a72c0,NULL}'::text[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0', NULL);
+ count 
+-------
+    26
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0', 'c51ce410c124a10e0db5e4b97fc2af39');
+                                                                      QUERY PLAN                                                                      
+------------------------------------------------------------------------------------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_2
+         Recheck Cond: (a = ANY ('{33e75ff09dd601bbe69f351039152189,f457c545a9ded88f18ecee47145a72c0,c51ce410c124a10e0db5e4b97fc2af39}'::text[]))
+         ->  Bitmap Index Scan on brin_in_test_2_idx
+               Index Cond: (a = ANY ('{33e75ff09dd601bbe69f351039152189,f457c545a9ded88f18ecee47145a72c0,c51ce410c124a10e0db5e4b97fc2af39}'::text[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0', 'c51ce410c124a10e0db5e4b97fc2af39');
+ count 
+-------
+    39
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_2 WHERE a IN (NULL, '33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0', 'c51ce410c124a10e0db5e4b97fc2af39');
+                                                                        QUERY PLAN                                                                         
+-----------------------------------------------------------------------------------------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_2
+         Recheck Cond: (a = ANY ('{NULL,33e75ff09dd601bbe69f351039152189,f457c545a9ded88f18ecee47145a72c0,c51ce410c124a10e0db5e4b97fc2af39}'::text[]))
+         ->  Bitmap Index Scan on brin_in_test_2_idx
+               Index Cond: (a = ANY ('{NULL,33e75ff09dd601bbe69f351039152189,f457c545a9ded88f18ecee47145a72c0,c51ce410c124a10e0db5e4b97fc2af39}'::text[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_2 WHERE a IN (NULL, '33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0', 'c51ce410c124a10e0db5e4b97fc2af39');
+ count 
+-------
+    39
+(1 row)
+
+DROP TABLE brin_in_test_2;
+RESET enable_seqscan;
diff --git a/src/test/regress/sql/brin.sql b/src/test/regress/sql/brin.sql
index e68e9e18df5..95da027047e 100644
--- a/src/test/regress/sql/brin.sql
+++ b/src/test/regress/sql/brin.sql
@@ -515,3 +515,286 @@ CREATE UNLOGGED TABLE brintest_unlogged (n numrange);
 CREATE INDEX brinidx_unlogged ON brintest_unlogged USING brin (n);
 INSERT INTO brintest_unlogged VALUES (numrange(0, 2^1000::numeric));
 DROP TABLE brintest_unlogged;
+
+-- do some tests on IN clauses for simple data types
+CREATE TABLE brin_in_test_1 (a INT, b BIGINT) WITH (fillfactor=10);
+INSERT INTO brin_in_test_1
+SELECT i/5 + mod(991 * i + 617, 20),
+       i/10 + mod(853 * i + 491, 30)
+  FROM generate_series(1,1000) s(i);
+
+CREATE INDEX brin_in_test_1_idx_1 ON brin_in_test_1 USING brin (a int4_minmax_ops) WITH (pages_per_range=1);
+CREATE INDEX brin_in_test_1_idx_2 ON brin_in_test_1 USING brin (b int8_minmax_ops) WITH (pages_per_range=1);
+
+SET enable_seqscan=off;
+
+-- int: equalities
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a IN (113);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a IN (113);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a IN (113, NULL);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a IN (113, NULL);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a IN (NULL, NULL);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a IN (NULL, NULL);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a IN (113, 177);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a IN (113, 177);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a IN (113, 177, NULL);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a IN (113, 177, NULL);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a IN (113, 177, 25);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a IN (113, 177, 25);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a IN (NULL, 113, 177, 25);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a IN (NULL, 113, 177, 25);
+
+-- int: less than
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a < ANY (ARRAY[30]);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a < ANY (ARRAY[30]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a < ANY (ARRAY[20, NULL]);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a < ANY (ARRAY[20, NULL]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a < ANY (ARRAY[NULL, NULL]::int[]);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a < ANY (ARRAY[NULL, NULL]::int[]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a < ANY (ARRAY[35, 29]);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a < ANY (ARRAY[35, 29]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a <= ANY (ARRAY[45, 60, NULL]);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a <= ANY (ARRAY[45, 60, NULL]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a < ANY (ARRAY[41, 37, 55]);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a < ANY (ARRAY[41, 37, 55]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a <= ANY (ARRAY[NULL, 60, 43, 94]);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a <= ANY (ARRAY[NULL, 60, 43, 94]);
+
+
+-- int: greater than
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a > ANY (ARRAY[200]);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a > ANY (ARRAY[200]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a > ANY (ARRAY[177, NULL]);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a > ANY (ARRAY[177, NULL]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a > ANY (ARRAY[NULL, NULL]::int[]);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a > ANY (ARRAY[NULL, NULL]::int[]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a > ANY (ARRAY[153, 140]);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a > ANY (ARRAY[153, 140]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a >= ANY (ARRAY[173, 191, NULL]);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a >= ANY (ARRAY[173, 191, NULL]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a > ANY (ARRAY[120, 184, 164]);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a > ANY (ARRAY[120, 184, 164]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a >= ANY (ARRAY[NULL, 130, 181, 169]);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a >= ANY (ARRAY[NULL, 130, 181, 169]);
+
+
+-- bigint: eqalities
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b IN (82);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b IN (82);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b IN (82, NULL);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b IN (82, NULL);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b IN (NULL, NULL);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b IN (NULL, NULL);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b IN (82, 41);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b IN (82, 41);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b IN (82, 41, NULL);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b IN (82, 41, NULL);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b IN (82, 41, 15);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b IN (82, 41, 15);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b IN (NULL, 82, 41, 15);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b IN (NULL, 82, 41, 15);
+
+
+-- bigint: less than
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b < ANY (ARRAY[31]);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b < ANY (ARRAY[31]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b < ANY (ARRAY[55, NULL]);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b < ANY (ARRAY[55, NULL]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b < ANY (ARRAY[NULL, NULL]::bigint[]);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b < ANY (ARRAY[NULL, NULL]::bigint[]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b <= ANY (ARRAY[73, 51]);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b <= ANY (ARRAY[73, 51]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b < ANY (ARRAY[69, 87, NULL]);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b < ANY (ARRAY[69, 87, NULL]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b <= ANY (ARRAY[82, 91, 35]);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b <= ANY (ARRAY[82, 91, 35]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b < ANY (ARRAY[NULL, 63, 21, 85]);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b < ANY (ARRAY[NULL, 63, 21, 85]);
+
+
+-- bigint: greater than
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b > ANY (ARRAY[94]);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b > ANY (ARRAY[94]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b > ANY (ARRAY[80, NULL]);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b > ANY (ARRAY[80, NULL]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b > ANY (ARRAY[NULL, NULL]::bigint[]);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b > ANY (ARRAY[NULL, NULL]::bigint[]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b > ANY (ARRAY[199, 107]);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b > ANY (ARRAY[199, 107]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b >= ANY (ARRAY[182, 101, NULL]);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b >= ANY (ARRAY[182, 101, NULL]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b > ANY (ARRAY[300, 106, 251]);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b > ANY (ARRAY[300, 106, 251]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b > ANY (ARRAY[NULL, 182, 101, 155]);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b > ANY (ARRAY[NULL, 182, 101, 155]);
+
+
+DROP TABLE brin_in_test_1;
+RESET enable_seqscan;
+
+-- do some tests on IN clauses for varlena data types
+CREATE TABLE brin_in_test_2 (a TEXT) WITH (fillfactor=10);
+INSERT INTO brin_in_test_2
+SELECT v FROM (SELECT row_number() OVER (ORDER BY v) c, v FROM (SELECT md5((i/13)::text) AS v FROM generate_series(1,1000) s(i)) foo) bar ORDER BY c + 25 * random();
+
+CREATE INDEX brin_in_test_2_idx ON brin_in_test_2 USING brin (a text_minmax_ops) WITH (pages_per_range=1);
+
+SET enable_seqscan=off;
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189');
+
+SELECT COUNT(*) FROM brin_in_test_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189');
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', NULL);
+
+SELECT COUNT(*) FROM brin_in_test_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', NULL);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_2 WHERE a IN (NULL, NULL);
+
+SELECT COUNT(*) FROM brin_in_test_2 WHERE a IN (NULL, NULL);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0');
+
+SELECT COUNT(*) FROM brin_in_test_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0');
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0', NULL);
+
+SELECT COUNT(*) FROM brin_in_test_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0', NULL);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0', 'c51ce410c124a10e0db5e4b97fc2af39');
+
+SELECT COUNT(*) FROM brin_in_test_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0', 'c51ce410c124a10e0db5e4b97fc2af39');
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_2 WHERE a IN (NULL, '33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0', 'c51ce410c124a10e0db5e4b97fc2af39');
+
+SELECT COUNT(*) FROM brin_in_test_2 WHERE a IN (NULL, '33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0', 'c51ce410c124a10e0db5e4b97fc2af39');
+
+DROP TABLE brin_in_test_2;
+RESET enable_seqscan;
-- 
2.39.1



  [text/x-patch] 0007-Support-SK_SEARCHARRAY-in-BRIN-minmax-multi-20230217.patch (75.1K, ../../[email protected]/8-0007-Support-SK_SEARCHARRAY-in-BRIN-minmax-multi-20230217.patch)
  download | inline diff:
From 2371aef4c60ad4ddb1f683985cb9c9177e2afe88 Mon Sep 17 00:00:00 2001
From: Tomas Vondra <[email protected]>
Date: Fri, 17 Feb 2023 02:45:14 +0100
Subject: [PATCH 7/9] Support SK_SEARCHARRAY in BRIN minmax-multi

Similar approach to minmax, but the issues with deconstructing the array
over and over are even more serious.
---
 src/backend/access/brin/brin_minmax_multi.c | 502 +++++++++--
 src/include/catalog/pg_amproc.dat           |  57 ++
 src/include/catalog/pg_proc.dat             |   4 +
 src/test/regress/expected/brin_multi.out    | 926 ++++++++++++++++++++
 src/test/regress/sql/brin_multi.sql         | 301 +++++++
 5 files changed, 1725 insertions(+), 65 deletions(-)

diff --git a/src/backend/access/brin/brin_minmax_multi.c b/src/backend/access/brin/brin_minmax_multi.c
index 859e0022fb4..dd22b3e3c02 100644
--- a/src/backend/access/brin/brin_minmax_multi.c
+++ b/src/backend/access/brin/brin_minmax_multi.c
@@ -109,6 +109,14 @@
 #define		MINMAX_BUFFER_MAX				8192
 #define		MINMAX_BUFFER_LOAD_FACTOR		0.5
 
+/*
+ * We use some private sk_flags bits in preprocessed scan keys.  We're allowed
+ * to use bits 16-31 (see skey.h).  The uppermost bits are copied from the
+ * index's indoption[] array entry for the index attribute.
+ */
+#define SK_BRIN_SORTED	0x00010000	/* deconstructed and sorted array */
+
+
 typedef struct MinmaxMultiOpaque
 {
 	FmgrInfo	extra_procinfos[MINMAX_MAX_PROCNUMS];
@@ -2562,6 +2570,157 @@ brin_minmax_multi_add_value(PG_FUNCTION_ARGS)
 	PG_RETURN_BOOL(modified);
 }
 
+
+static int
+compare_array_values(const void *a, const void *b, void *arg)
+{
+	Datum	da = * (Datum *) a;
+	Datum	db = * (Datum *) b;
+	SortSupport	ssup = (SortSupport) arg;
+
+	return ApplySortComparator(da, false, db, false, ssup);
+}
+
+/*
+ * lower_boundary
+ *		Determine lowest index so that (values[index] >= minvalue).
+ *
+ * The array of values is expected to be sorted, so this is the first value
+ * that may fall into the [minvalue, maxvalue] range, as it exceeds minval.
+ * It's not guaranteed, though, as it might exceed maxvalue too.
+ */
+static int
+lower_boundary(Datum *values, int nvalues, Datum minvalue, SortSupport ssup)
+{
+	int		start = 0,
+			end = (nvalues - 1);
+
+	/* everything exceeds minval and might match */
+	if (compare_array_values(&minvalue, &values[start], ssup) <= 0)
+		return 0;
+
+	/* nothing could match */
+	if (compare_array_values(&minvalue, &values[end], ssup) > 0)
+		return nvalues;
+
+	while ((end - start) > 0)
+	{
+		int midpoint;
+		int r;
+
+		midpoint = start + (end - start) / 2;
+
+		r = compare_array_values(&minvalue, &values[midpoint], ssup);
+
+		if (r > 0)
+			start = Max(midpoint, start + 1);
+		else
+			end = midpoint;
+	}
+
+	/* the value should meet the (v >=minvalue) requirement */
+	Assert(compare_array_values(&values[start], &minvalue, ssup) >= 0);
+
+	/* we know start can't be 0, so it's legal to subtract 1 */
+	Assert(compare_array_values(&values[start-1], &minvalue, ssup) < 0);
+
+	return start;
+}
+
+typedef struct ScanKeyArray {
+	Oid		typeid;
+	int		nelements;
+	Datum  *elements;
+} ScanKeyArray;
+
+Datum
+brin_minmax_multi_preprocess(PG_FUNCTION_ARGS)
+{
+	// BrinDesc   *bdesc = (BrinDesc *) PG_GETARG_POINTER(0);
+	ScanKey		key = (ScanKey) PG_GETARG_POINTER(1);
+	ScanKey		newkey;
+	ScanKeyArray *scanarray;
+
+	ArrayType  *arrayval;
+	int16		elmlen;
+	bool		elmbyval;
+	char		elmalign;
+	int			num_elems;
+	Datum	   *elem_values;
+	bool	   *elem_nulls;
+	TypeCacheEntry *type;
+	SortSupportData ssup;
+
+	/* number of non-null elements in the array */
+	int			num_nonnulls;
+
+	/*
+	 * ignore scalar keys
+	 *
+	 * XXX Maybe we should preprocess scalar keys too. It'd make the consistent
+	 * function simpler by removing the branching.
+	 */
+	if (!(key->sk_flags & SK_SEARCHARRAY))
+		PG_RETURN_POINTER(key);
+
+	arrayval = DatumGetArrayTypeP(key->sk_argument);
+
+	get_typlenbyvalalign(ARR_ELEMTYPE(arrayval),
+						 &elmlen, &elmbyval, &elmalign);
+
+	deconstruct_array(arrayval,
+					  ARR_ELEMTYPE(arrayval),
+					  elmlen, elmbyval, elmalign,
+					  &elem_values, &elem_nulls, &num_elems);
+
+	/* eliminate NULL elements */
+	num_nonnulls = 0;
+	for (int i = 0; i < num_elems; i++)
+	{
+		/* skip NULL elements */
+		if (elem_nulls[i])
+			continue;
+
+		/* if needed, move the non-NULL ones */
+		if (num_nonnulls != i)
+			elem_values[num_nonnulls] = elem_values[i];
+
+		num_nonnulls++;
+	}
+
+	num_elems = num_nonnulls;
+
+	type = lookup_type_cache(ARR_ELEMTYPE(arrayval), TYPECACHE_LT_OPR);
+
+	memset(&ssup, 0, sizeof(SortSupportData));
+
+	ssup.ssup_collation = key->sk_collation;
+	ssup.ssup_cxt = CurrentMemoryContext;
+
+	PrepareSortSupportFromOrderingOp(type->lt_opr, &ssup);
+
+	qsort_interruptible(elem_values, num_elems, sizeof(Datum),
+						compare_array_values, &ssup);
+
+	scanarray = palloc0(sizeof(ScanKeyArray));
+	scanarray->typeid = ARR_ELEMTYPE(arrayval);
+	scanarray->nelements = num_elems;
+	scanarray->elements = elem_values;
+
+	newkey = palloc0(sizeof(ScanKeyData));
+
+	ScanKeyEntryInitializeWithInfo(newkey,
+								   (key->sk_flags | SK_BRIN_SORTED),
+								   key->sk_attno,
+								   key->sk_strategy,
+								   key->sk_subtype,
+								   key->sk_collation,
+								   &key->sk_func,
+								   PointerGetDatum(scanarray));
+
+	PG_RETURN_POINTER(newkey);
+}
+
 /*
  * 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
@@ -2591,6 +2750,15 @@ brin_minmax_multi_consistent(PG_FUNCTION_ARGS)
 	serialized = (SerializedRanges *) PG_DETOAST_DATUM(column->bv_values[0]);
 	ranges = brin_range_deserialize(serialized->maxvalues, serialized);
 
+	/*
+	 * XXX Would it make sense to have a quick initial check on the whole
+	 * summary? We know most page ranges are not expected to match, and we
+	 * know the ranges/values are sorted so we could check global min/max
+	 * (essentially what regular minmax is doing) and bail if no match is
+	 * possible. That should be cheap and might save a lot on inspecting
+	 * the individual ranges/values.
+	 */
+
 	/* inspect the ranges, and for each one evaluate the scan keys */
 	for (rangeno = 0; rangeno < ranges->nranges; rangeno++)
 	{
@@ -2611,67 +2779,183 @@ brin_minmax_multi_consistent(PG_FUNCTION_ARGS)
 			attno = key->sk_attno;
 			subtype = key->sk_subtype;
 			value = key->sk_argument;
-			switch (key->sk_strategy)
+
+			if (likely(!(key->sk_flags & SK_BRIN_SORTED)))
 			{
-				case BTLessStrategyNumber:
-				case BTLessEqualStrategyNumber:
-					finfo = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
-															   key->sk_strategy);
-					/* first value from the array */
-					matches = FunctionCall2Coll(finfo, colloid, minval, value);
-					break;
+				switch (key->sk_strategy)
+				{
+					case BTLessStrategyNumber:
+					case BTLessEqualStrategyNumber:
+						finfo = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
+																   key->sk_strategy);
+						/* first value from the array */
+						matches = FunctionCall2Coll(finfo, colloid, minval, value);
+						break;
 
-				case BTEqualStrategyNumber:
-					{
-						Datum		compar;
-						FmgrInfo   *cmpFn;
+					case BTEqualStrategyNumber:
+						{
+							Datum		compar;
+							FmgrInfo   *cmpFn;
+
+							/* by default this range does not match */
+							matches = BoolGetDatum(false);
+
+							/*
+							 * Otherwise, need to compare the new value with
+							 * boundaries of all the ranges. First check if it's
+							 * less than the absolute minimum, which is the first
+							 * value in the array.
+							 */
+							cmpFn = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
+																	   BTGreaterStrategyNumber);
+							compar = FunctionCall2Coll(cmpFn, colloid, minval, value);
+
+							/* smaller than the smallest value in this range */
+							if (DatumGetBool(compar))
+								break;
+
+							cmpFn = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
+																	   BTLessStrategyNumber);
+							compar = FunctionCall2Coll(cmpFn, colloid, maxval, value);
+
+							/* larger than the largest value in this range */
+							if (DatumGetBool(compar))
+								break;
+
+							/*
+							 * We haven't managed to eliminate this range, so
+							 * consider it matching.
+							 */
+							matches = BoolGetDatum(true);
 
-						/* by default this range does not match */
-						matches = BoolGetDatum(false);
+							break;
+						}
+					case BTGreaterEqualStrategyNumber:
+					case BTGreaterStrategyNumber:
+						finfo = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
+																   key->sk_strategy);
+						/* last value from the array */
+						matches = FunctionCall2Coll(finfo, colloid, maxval, value);
+						break;
 
-						/*
-						 * Otherwise, need to compare the new value with
-						 * boundaries of all the ranges. First check if it's
-						 * less than the absolute minimum, which is the first
-						 * value in the array.
-						 */
-						cmpFn = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
-																   BTGreaterStrategyNumber);
-						compar = FunctionCall2Coll(cmpFn, colloid, minval, value);
+					default:
+						/* shouldn't happen */
+						elog(ERROR, "invalid strategy number %d", key->sk_strategy);
+						matches = BoolGetDatum(false);
+						break;
+				}
+			}
+			else
+			{
+				ScanKeyArray *array = (ScanKeyArray *) value;
 
-						/* smaller than the smallest value in this range */
-						if (DatumGetBool(compar))
-							break;
+				/* can happen if the IN list contained just NULLs */
+				if (array->nelements == 0)
+					PG_RETURN_BOOL(false);
 
-						cmpFn = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
-																   BTLessStrategyNumber);
-						compar = FunctionCall2Coll(cmpFn, colloid, maxval, value);
+				switch (key->sk_strategy)
+				{
+					case BTLessStrategyNumber:
+					case BTLessEqualStrategyNumber:
+						finfo = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
+																   key->sk_strategy);
+						/* first value from the array */
+						matches = FunctionCall2Coll(finfo, colloid, minval,
+													array->elements[array->nelements-1]);
+						break;
 
-						/* larger than the largest value in this range */
-						if (DatumGetBool(compar))
-							break;
+					case BTEqualStrategyNumber:
 
 						/*
-						 * We haven't managed to eliminate this range, so
-						 * consider it matching.
+						 * See brin_minmax.c for description of what this is doing.
 						 */
-						matches = BoolGetDatum(true);
-
+						{
+							Datum val;
+							SortSupportData ssup;
+							int			lower;
+							TypeCacheEntry *type;
+
+							/* Is the first (smallest) value after the BRIN range? */
+							val = array->elements[0];
+
+							finfo = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
+																	   BTLessEqualStrategyNumber);
+							matches = FunctionCall2Coll(finfo, colloid, val, maxval);
+
+							/* minval > max(range values) */
+							if (!DatumGetBool(matches))
+								break;
+
+							/* Is the last (largest) value before the BRIN range? */
+							val = array->elements[array->nelements-1];
+
+							finfo = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
+																	   BTGreaterEqualStrategyNumber);
+							matches = FunctionCall2Coll(finfo, colloid, val, minval);
+
+							/* maxval < min(range values) */
+							if (!DatumGetBool(matches))
+								break;
+
+							/*
+							 * OK, there might be some values matching the range. We have
+							 * to search them one by one, or perhaps try binsearch.
+							 */
+							type = lookup_type_cache(array->typeid, TYPECACHE_LT_OPR);
+
+							memset(&ssup, 0, sizeof(SortSupportData));
+
+							ssup.ssup_collation = key->sk_collation;
+							ssup.ssup_cxt = CurrentMemoryContext;
+
+							PrepareSortSupportFromOrderingOp(type->lt_opr, &ssup);
+
+							lower = lower_boundary(array->elements, array->nelements, minval, &ssup);
+
+							/* no elements can possibly match */
+							if (lower == array->nelements)
+							{
+								matches = BoolGetDatum(false);
+								break;
+							}
+
+							/*
+							 * OK, the first element must match the upper boundary too
+							 * (if it does not, no following elements can).
+							 */
+							val = array->elements[lower];
+
+							/*
+							 * In the equality case (WHERE col = someval), we want to return
+							 * the current page range if the minimum value in the range <=
+							 * scan key, and the maximum value >= scan key.
+							 */
+							finfo = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
+																	   BTLessEqualStrategyNumber);
+							matches = FunctionCall2Coll(finfo, colloid, minval, val);
+							if (!DatumGetBool(matches))
+								break;
+							/* max() >= scankey */
+							finfo = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
+																	   BTGreaterEqualStrategyNumber);
+							matches = FunctionCall2Coll(finfo, colloid, maxval, val);
+							break;
+						}
+					case BTGreaterEqualStrategyNumber:
+					case BTGreaterStrategyNumber:
+						finfo = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
+																   key->sk_strategy);
+						/* last value from the array */
+						matches = FunctionCall2Coll(finfo, colloid, maxval,
+													array->elements[0]);
 						break;
-					}
-				case BTGreaterEqualStrategyNumber:
-				case BTGreaterStrategyNumber:
-					finfo = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
-															   key->sk_strategy);
-					/* last value from the array */
-					matches = FunctionCall2Coll(finfo, colloid, maxval, value);
-					break;
 
-				default:
-					/* shouldn't happen */
-					elog(ERROR, "invalid strategy number %d", key->sk_strategy);
-					matches = BoolGetDatum(false);
-					break;
+					default:
+						/* shouldn't happen */
+						elog(ERROR, "invalid strategy number %d", key->sk_strategy);
+						matches = BoolGetDatum(false);
+						break;
+				}
 			}
 
 			/* the range has to match all the scan keys */
@@ -2713,24 +2997,112 @@ brin_minmax_multi_consistent(PG_FUNCTION_ARGS)
 			attno = key->sk_attno;
 			subtype = key->sk_subtype;
 			value = key->sk_argument;
-			switch (key->sk_strategy)
+			if (likely(!(key->sk_flags & SK_SEARCHARRAY)))
 			{
-				case BTLessStrategyNumber:
-				case BTLessEqualStrategyNumber:
-				case BTEqualStrategyNumber:
-				case BTGreaterEqualStrategyNumber:
-				case BTGreaterStrategyNumber:
-
-					finfo = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
-															   key->sk_strategy);
-					matches = FunctionCall2Coll(finfo, colloid, val, value);
-					break;
+				switch (key->sk_strategy)
+				{
+					case BTLessStrategyNumber:
+					case BTLessEqualStrategyNumber:
+					case BTEqualStrategyNumber:
+					case BTGreaterEqualStrategyNumber:
+					case BTGreaterStrategyNumber:
+
+						finfo = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
+																   key->sk_strategy);
+						matches = FunctionCall2Coll(finfo, colloid, val, value);
+						break;
 
-				default:
-					/* shouldn't happen */
-					elog(ERROR, "invalid strategy number %d", key->sk_strategy);
-					matches = BoolGetDatum(false);
-					break;
+					default:
+						/* shouldn't happen */
+						elog(ERROR, "invalid strategy number %d", key->sk_strategy);
+						matches = BoolGetDatum(false);
+						break;
+				}
+			}
+			else
+			{
+				ScanKeyArray *array = (ScanKeyArray *) value;
+
+				/* can happen if the IN list contained just NULLs */
+				if (array->nelements == 0)
+					PG_RETURN_BOOL(false);
+
+				/*
+				 * XXX We should be able to be smarter for the scalar values, as
+				 * we keep them sorted too. So we should be able to quickly check
+				 * if any of the values can match the sorted key values.
+				 */
+				switch (key->sk_strategy)
+				{
+					case BTLessStrategyNumber:
+					case BTLessEqualStrategyNumber:
+						finfo = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
+																   key->sk_strategy);
+						/* first value from the array */
+						matches = FunctionCall2Coll(finfo, colloid, val,
+													array->elements[array->nelements-1]);
+						break;
+
+					case BTEqualStrategyNumber:
+
+						/*
+						 * See brin_minmax.c for description of what this is doing.
+						 */
+						{
+							SortSupportData ssup;
+							int			lower;
+							TypeCacheEntry *type;
+
+							/*
+							 * OK, there might be some values matching the range. We have
+							 * to search them one by one, or perhaps try binsearch.
+							 */
+							type = lookup_type_cache(array->typeid, TYPECACHE_LT_OPR);
+
+							memset(&ssup, 0, sizeof(SortSupportData));
+
+							ssup.ssup_collation = key->sk_collation;
+							ssup.ssup_cxt = CurrentMemoryContext;
+
+							PrepareSortSupportFromOrderingOp(type->lt_opr, &ssup);
+
+							lower = lower_boundary(array->elements, array->nelements, val, &ssup);
+
+							/* no elements can possibly match */
+							if (lower == array->nelements)
+							{
+								matches = BoolGetDatum(false);
+								break;
+							}
+
+							/*
+							 * OK, check the first element must match the upper boundary too
+							 * (if it does not, no following elements can).
+							 *
+							 * In the equality case (WHERE col = someval), we want to return
+							 * the current page range if the minimum value in the range <=
+							 * scan key, and the maximum value >= scan key.
+							 */
+							finfo = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
+																	   BTEqualStrategyNumber);
+							matches = FunctionCall2Coll(finfo, colloid, val, array->elements[lower]);
+							break;
+						}
+					case BTGreaterEqualStrategyNumber:
+					case BTGreaterStrategyNumber:
+						finfo = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
+																   key->sk_strategy);
+						/* last value from the array */
+						matches = FunctionCall2Coll(finfo, colloid, val,
+													array->elements[0]);
+						break;
+
+					default:
+						/* shouldn't happen */
+						elog(ERROR, "invalid strategy number %d", key->sk_strategy);
+						matches = BoolGetDatum(false);
+						break;
+				}
 			}
 
 			/* the range has to match all the scan keys */
diff --git a/src/include/catalog/pg_amproc.dat b/src/include/catalog/pg_amproc.dat
index 166681c31ef..4f17f0d58c1 100644
--- a/src/include/catalog/pg_amproc.dat
+++ b/src/include/catalog/pg_amproc.dat
@@ -946,6 +946,9 @@
 { amprocfamily => 'brin/integer_minmax_multi_ops', amproclefttype => 'int2',
   amprocrighttype => 'int2', amprocnum => '5',
   amproc => 'brin_minmax_multi_options' },
+{ amprocfamily => 'brin/integer_minmax_multi_ops', amproclefttype => 'int2',
+  amprocrighttype => 'int2', amprocnum => '6',
+  amproc => 'brin_minmax_multi_preprocess' },
 { amprocfamily => 'brin/integer_minmax_multi_ops', amproclefttype => 'int2',
   amprocrighttype => 'int2', amprocnum => '11',
   amproc => 'brin_minmax_multi_distance_int2' },
@@ -965,6 +968,9 @@
 { amprocfamily => 'brin/integer_minmax_multi_ops', amproclefttype => 'int4',
   amprocrighttype => 'int4', amprocnum => '5',
   amproc => 'brin_minmax_multi_options' },
+{ amprocfamily => 'brin/integer_minmax_multi_ops', amproclefttype => 'int4',
+  amprocrighttype => 'int4', amprocnum => '6',
+  amproc => 'brin_minmax_multi_preprocess' },
 { amprocfamily => 'brin/integer_minmax_multi_ops', amproclefttype => 'int4',
   amprocrighttype => 'int4', amprocnum => '11',
   amproc => 'brin_minmax_multi_distance_int4' },
@@ -984,6 +990,9 @@
 { amprocfamily => 'brin/integer_minmax_multi_ops', amproclefttype => 'int8',
   amprocrighttype => 'int8', amprocnum => '5',
   amproc => 'brin_minmax_multi_options' },
+{ amprocfamily => 'brin/integer_minmax_multi_ops', amproclefttype => 'int8',
+  amprocrighttype => 'int8', amprocnum => '6',
+  amproc => 'brin_minmax_multi_preprocess' },
 { amprocfamily => 'brin/integer_minmax_multi_ops', amproclefttype => 'int8',
   amprocrighttype => 'int8', amprocnum => '11',
   amproc => 'brin_minmax_multi_distance_int8' },
@@ -1095,6 +1104,9 @@
 { amprocfamily => 'brin/oid_minmax_multi_ops', amproclefttype => 'oid',
   amprocrighttype => 'oid', amprocnum => '5',
   amproc => 'brin_minmax_multi_options' },
+{ amprocfamily => 'brin/oid_minmax_multi_ops', amproclefttype => 'oid',
+  amprocrighttype => 'oid', amprocnum => '6',
+  amproc => 'brin_minmax_multi_preprocess' },
 { amprocfamily => 'brin/oid_minmax_multi_ops', amproclefttype => 'oid',
   amprocrighttype => 'oid', amprocnum => '11',
   amproc => 'brin_minmax_multi_distance_int4' },
@@ -1161,6 +1173,9 @@
 { amprocfamily => 'brin/tid_minmax_multi_ops', amproclefttype => 'tid',
   amprocrighttype => 'tid', amprocnum => '5',
   amproc => 'brin_minmax_multi_options' },
+{ amprocfamily => 'brin/tid_minmax_multi_ops', amproclefttype => 'tid',
+  amprocrighttype => 'tid', amprocnum => '6',
+  amproc => 'brin_minmax_multi_preprocess' },
 { amprocfamily => 'brin/tid_minmax_multi_ops', amproclefttype => 'tid',
   amprocrighttype => 'tid', amprocnum => '11',
   amproc => 'brin_minmax_multi_distance_tid' },
@@ -1214,6 +1229,9 @@
 { amprocfamily => 'brin/float_minmax_multi_ops', amproclefttype => 'float4',
   amprocrighttype => 'float4', amprocnum => '5',
   amproc => 'brin_minmax_multi_options' },
+{ amprocfamily => 'brin/float_minmax_multi_ops', amproclefttype => 'float4',
+  amprocrighttype => 'float4', amprocnum => '6',
+  amproc => 'brin_minmax_multi_preprocess' },
 { amprocfamily => 'brin/float_minmax_multi_ops', amproclefttype => 'float4',
   amprocrighttype => 'float4', amprocnum => '11',
   amproc => 'brin_minmax_multi_distance_float4' },
@@ -1233,6 +1251,9 @@
 { amprocfamily => 'brin/float_minmax_multi_ops', amproclefttype => 'float8',
   amprocrighttype => 'float8', amprocnum => '5',
   amproc => 'brin_minmax_multi_options' },
+{ amprocfamily => 'brin/float_minmax_multi_ops', amproclefttype => 'float8',
+  amprocrighttype => 'float8', amprocnum => '6',
+  amproc => 'brin_minmax_multi_preprocess' },
 { amprocfamily => 'brin/float_minmax_multi_ops', amproclefttype => 'float8',
   amprocrighttype => 'float8', amprocnum => '11',
   amproc => 'brin_minmax_multi_distance_float8' },
@@ -1305,6 +1326,9 @@
 { amprocfamily => 'brin/macaddr_minmax_multi_ops', amproclefttype => 'macaddr',
   amprocrighttype => 'macaddr', amprocnum => '5',
   amproc => 'brin_minmax_multi_options' },
+{ amprocfamily => 'brin/macaddr_minmax_multi_ops', amproclefttype => 'macaddr',
+  amprocrighttype => 'macaddr', amprocnum => '6',
+  amproc => 'brin_minmax_multi_preprocess' },
 { amprocfamily => 'brin/macaddr_minmax_multi_ops', amproclefttype => 'macaddr',
   amprocrighttype => 'macaddr', amprocnum => '11',
   amproc => 'brin_minmax_multi_distance_macaddr' },
@@ -1361,6 +1385,9 @@
 { amprocfamily => 'brin/macaddr8_minmax_multi_ops',
   amproclefttype => 'macaddr8', amprocrighttype => 'macaddr8', amprocnum => '5',
   amproc => 'brin_minmax_multi_options' },
+{ amprocfamily => 'brin/macaddr8_minmax_multi_ops',
+  amproclefttype => 'macaddr8', amprocrighttype => 'macaddr8', amprocnum => '6',
+  amproc => 'brin_minmax_multi_preprocess' },
 { amprocfamily => 'brin/macaddr8_minmax_multi_ops',
   amproclefttype => 'macaddr8', amprocrighttype => 'macaddr8',
   amprocnum => '11', amproc => 'brin_minmax_multi_distance_macaddr8' },
@@ -1415,6 +1442,9 @@
 { amprocfamily => 'brin/network_minmax_multi_ops', amproclefttype => 'inet',
   amprocrighttype => 'inet', amprocnum => '5',
   amproc => 'brin_minmax_multi_options' },
+{ amprocfamily => 'brin/network_minmax_multi_ops', amproclefttype => 'inet',
+  amprocrighttype => 'inet', amprocnum => '6',
+  amproc => 'brin_minmax_multi_preprocess' },
 { amprocfamily => 'brin/network_minmax_multi_ops', amproclefttype => 'inet',
   amprocrighttype => 'inet', amprocnum => '11',
   amproc => 'brin_minmax_multi_distance_inet' },
@@ -1521,6 +1551,9 @@
 { amprocfamily => 'brin/time_minmax_multi_ops', amproclefttype => 'time',
   amprocrighttype => 'time', amprocnum => '5',
   amproc => 'brin_minmax_multi_options' },
+{ amprocfamily => 'brin/time_minmax_multi_ops', amproclefttype => 'time',
+  amprocrighttype => 'time', amprocnum => '6',
+  amproc => 'brin_minmax_multi_preprocess' },
 { amprocfamily => 'brin/time_minmax_multi_ops', amproclefttype => 'time',
   amprocrighttype => 'time', amprocnum => '11',
   amproc => 'brin_minmax_multi_distance_time' },
@@ -1604,6 +1637,9 @@
 { amprocfamily => 'brin/datetime_minmax_multi_ops',
   amproclefttype => 'timestamp', amprocrighttype => 'timestamp',
   amprocnum => '5', amproc => 'brin_minmax_multi_options' },
+{ amprocfamily => 'brin/datetime_minmax_multi_ops',
+  amproclefttype => 'timestamp', amprocrighttype => 'timestamp',
+  amprocnum => '6', amproc => 'brin_minmax_multi_preprocess' },
 { amprocfamily => 'brin/datetime_minmax_multi_ops',
   amproclefttype => 'timestamp', amprocrighttype => 'timestamp',
   amprocnum => '11', amproc => 'brin_minmax_multi_distance_timestamp' },
@@ -1623,6 +1659,9 @@
 { amprocfamily => 'brin/datetime_minmax_multi_ops',
   amproclefttype => 'timestamptz', amprocrighttype => 'timestamptz',
   amprocnum => '5', amproc => 'brin_minmax_multi_options' },
+{ amprocfamily => 'brin/datetime_minmax_multi_ops',
+  amproclefttype => 'timestamptz', amprocrighttype => 'timestamptz',
+  amprocnum => '6', amproc => 'brin_minmax_multi_preprocess' },
 { amprocfamily => 'brin/datetime_minmax_multi_ops',
   amproclefttype => 'timestamptz', amprocrighttype => 'timestamptz',
   amprocnum => '11', amproc => 'brin_minmax_multi_distance_timestamp' },
@@ -1642,6 +1681,9 @@
 { amprocfamily => 'brin/datetime_minmax_multi_ops', amproclefttype => 'date',
   amprocrighttype => 'date', amprocnum => '5',
   amproc => 'brin_minmax_multi_options' },
+{ amprocfamily => 'brin/datetime_minmax_multi_ops', amproclefttype => 'date',
+  amprocrighttype => 'date', amprocnum => '6',
+  amproc => 'brin_minmax_multi_preprocess' },
 { amprocfamily => 'brin/datetime_minmax_multi_ops', amproclefttype => 'date',
   amprocrighttype => 'date', amprocnum => '11',
   amproc => 'brin_minmax_multi_distance_date' },
@@ -1733,6 +1775,9 @@
 { amprocfamily => 'brin/interval_minmax_multi_ops',
   amproclefttype => 'interval', amprocrighttype => 'interval', amprocnum => '5',
   amproc => 'brin_minmax_multi_options' },
+{ amprocfamily => 'brin/interval_minmax_multi_ops',
+  amproclefttype => 'interval', amprocrighttype => 'interval', amprocnum => '6',
+  amproc => 'brin_minmax_multi_preprocess' },
 { amprocfamily => 'brin/interval_minmax_multi_ops',
   amproclefttype => 'interval', amprocrighttype => 'interval',
   amprocnum => '11', amproc => 'brin_minmax_multi_distance_interval' },
@@ -1789,6 +1834,9 @@
 { amprocfamily => 'brin/timetz_minmax_multi_ops', amproclefttype => 'timetz',
   amprocrighttype => 'timetz', amprocnum => '5',
   amproc => 'brin_minmax_multi_options' },
+{ amprocfamily => 'brin/timetz_minmax_multi_ops', amproclefttype => 'timetz',
+  amprocrighttype => 'timetz', amprocnum => '6',
+  amproc => 'brin_minmax_multi_preprocess' },
 { amprocfamily => 'brin/timetz_minmax_multi_ops', amproclefttype => 'timetz',
   amprocrighttype => 'timetz', amprocnum => '11',
   amproc => 'brin_minmax_multi_distance_timetz' },
@@ -1875,6 +1923,9 @@
 { amprocfamily => 'brin/numeric_minmax_multi_ops', amproclefttype => 'numeric',
   amprocrighttype => 'numeric', amprocnum => '5',
   amproc => 'brin_minmax_multi_options' },
+{ amprocfamily => 'brin/numeric_minmax_multi_ops', amproclefttype => 'numeric',
+  amprocrighttype => 'numeric', amprocnum => '6',
+  amproc => 'brin_minmax_multi_preprocess' },
 { amprocfamily => 'brin/numeric_minmax_multi_ops', amproclefttype => 'numeric',
   amprocrighttype => 'numeric', amprocnum => '11',
   amproc => 'brin_minmax_multi_distance_numeric' },
@@ -1929,6 +1980,9 @@
 { amprocfamily => 'brin/uuid_minmax_multi_ops', amproclefttype => 'uuid',
   amprocrighttype => 'uuid', amprocnum => '5',
   amproc => 'brin_minmax_multi_options' },
+{ amprocfamily => 'brin/uuid_minmax_multi_ops', amproclefttype => 'uuid',
+  amprocrighttype => 'uuid', amprocnum => '6',
+  amproc => 'brin_minmax_multi_preprocess' },
 { amprocfamily => 'brin/uuid_minmax_multi_ops', amproclefttype => 'uuid',
   amprocrighttype => 'uuid', amprocnum => '11',
   amproc => 'brin_minmax_multi_distance_uuid' },
@@ -2005,6 +2059,9 @@
 { amprocfamily => 'brin/pg_lsn_minmax_multi_ops', amproclefttype => 'pg_lsn',
   amprocrighttype => 'pg_lsn', amprocnum => '5',
   amproc => 'brin_minmax_multi_options' },
+{ amprocfamily => 'brin/pg_lsn_minmax_multi_ops', amproclefttype => 'pg_lsn',
+  amprocrighttype => 'pg_lsn', amprocnum => '6',
+  amproc => 'brin_minmax_multi_preprocess' },
 { amprocfamily => 'brin/pg_lsn_minmax_multi_ops', amproclefttype => 'pg_lsn',
   amprocrighttype => 'pg_lsn', amprocnum => '11',
   amproc => 'brin_minmax_multi_distance_pg_lsn' },
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 6638552bd05..4e8d6668648 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8520,6 +8520,10 @@
   proname => 'brin_minmax_multi_options', proisstrict => 'f',
   prorettype => 'void', proargtypes => 'internal',
   prosrc => 'brin_minmax_multi_options' },
+{ oid => '9326', descr => 'BRIN multi minmax support',
+  proname => 'brin_minmax_multi_preprocess', proisstrict => 'f',
+  prorettype => 'internal', proargtypes => 'internal internal',
+  prosrc => 'brin_minmax_multi_preprocess' },
 
 { oid => '4621', descr => 'BRIN multi minmax int2 distance',
   proname => 'brin_minmax_multi_distance_int2', prorettype => 'float8',
diff --git a/src/test/regress/expected/brin_multi.out b/src/test/regress/expected/brin_multi.out
index 98e1cdaa361..15355b699d7 100644
--- a/src/test/regress/expected/brin_multi.out
+++ b/src/test/regress/expected/brin_multi.out
@@ -823,3 +823,929 @@ SELECT COUNT(*) FROM brin_test_multi_2 WHERE a >= 'c51ce410-c124-a10e-0db5-e4b97
 
 DROP TABLE brin_test_multi_2;
 RESET enable_seqscan;
+-- do some tests on IN clauses for simple data types
+CREATE TABLE brin_in_test_multi_1 (a INT, b BIGINT) WITH (fillfactor=10);
+INSERT INTO brin_in_test_multi_1
+SELECT i/5 + mod(991 * i + 617, 20),
+       i/10 + mod(853 * i + 491, 30)
+  FROM generate_series(1,1000) s(i);
+CREATE INDEX brin_in_test_multi_1_idx_1 ON brin_in_test_multi_1 USING brin (a int4_minmax_multi_ops) WITH (pages_per_range=1);
+CREATE INDEX brin_in_test_multi_1_idx_2 ON brin_in_test_multi_1 USING brin (b int8_minmax_multi_ops) WITH (pages_per_range=1);
+SET enable_seqscan=off;
+-- int: equality
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (113);
+                         QUERY PLAN                          
+-------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (a = 113)
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_1
+               Index Cond: (a = 113)
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (113);
+ count 
+-------
+     8
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (113, NULL);
+                          QUERY PLAN                           
+---------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (a = ANY ('{113,NULL}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_1
+               Index Cond: (a = ANY ('{113,NULL}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (113, NULL);
+ count 
+-------
+     8
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (NULL, NULL);
+                           QUERY PLAN                           
+----------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (a = ANY ('{NULL,NULL}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_1
+               Index Cond: (a = ANY ('{NULL,NULL}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (NULL, NULL);
+ count 
+-------
+     0
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (113, 177);
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (a = ANY ('{113,177}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_1
+               Index Cond: (a = ANY ('{113,177}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (113, 177);
+ count 
+-------
+    16
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (-113, -177);
+                           QUERY PLAN                           
+----------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (a = ANY ('{-113,-177}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_1
+               Index Cond: (a = ANY ('{-113,-177}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (-113, -177);
+ count 
+-------
+     0
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (313, 377);
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (a = ANY ('{313,377}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_1
+               Index Cond: (a = ANY ('{313,377}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (313, 377);
+ count 
+-------
+     0
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (113, 177, NULL);
+                            QUERY PLAN                             
+-------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (a = ANY ('{113,177,NULL}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_1
+               Index Cond: (a = ANY ('{113,177,NULL}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (113, 177, NULL);
+ count 
+-------
+    16
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (113, 177, 25);
+                           QUERY PLAN                            
+-----------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (a = ANY ('{113,177,25}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_1
+               Index Cond: (a = ANY ('{113,177,25}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (113, 177, 25);
+ count 
+-------
+    24
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (NULL, 113, 177, 25);
+                              QUERY PLAN                              
+----------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (a = ANY ('{NULL,113,177,25}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_1
+               Index Cond: (a = ANY ('{NULL,113,177,25}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (NULL, 113, 177, 25);
+ count 
+-------
+    24
+(1 row)
+
+-- int: less than
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a < ANY(ARRAY[113]);
+                         QUERY PLAN                          
+-------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (a < ANY ('{113}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_1
+               Index Cond: (a < ANY ('{113}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a < ANY(ARRAY[113]);
+ count 
+-------
+   515
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a < ANY(ARRAY[113, NULL]);
+                          QUERY PLAN                           
+---------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (a < ANY ('{113,NULL}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_1
+               Index Cond: (a < ANY ('{113,NULL}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a < ANY(ARRAY[113, NULL]);
+ count 
+-------
+   515
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a < ANY(ARRAY[NULL, NULL]::int[]);
+                           QUERY PLAN                           
+----------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (a < ANY ('{NULL,NULL}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_1
+               Index Cond: (a < ANY ('{NULL,NULL}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a < ANY(ARRAY[NULL, NULL]::int[]);
+ count 
+-------
+     0
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a <= ANY(ARRAY[113, 177]);
+                          QUERY PLAN                           
+---------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (a <= ANY ('{113,177}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_1
+               Index Cond: (a <= ANY ('{113,177}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a <= ANY(ARRAY[113, 177]);
+ count 
+-------
+   843
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a < ANY(ARRAY[113, 177, NULL]);
+                            QUERY PLAN                             
+-------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (a < ANY ('{113,177,NULL}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_1
+               Index Cond: (a < ANY ('{113,177,NULL}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a < ANY(ARRAY[113, 177, NULL]);
+ count 
+-------
+   835
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a <= ANY(ARRAY[113, 177, 25]);
+                            QUERY PLAN                            
+------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (a <= ANY ('{113,177,25}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_1
+               Index Cond: (a <= ANY ('{113,177,25}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a <= ANY(ARRAY[113, 177, 25]);
+ count 
+-------
+   843
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a < ANY(ARRAY[NULL, 113, 177, 25]);
+                              QUERY PLAN                              
+----------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (a < ANY ('{NULL,113,177,25}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_1
+               Index Cond: (a < ANY ('{NULL,113,177,25}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a < ANY(ARRAY[NULL, 113, 177, 25]);
+ count 
+-------
+   835
+(1 row)
+
+-- int: greater than
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a > ANY(ARRAY[113]);
+                         QUERY PLAN                          
+-------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (a > ANY ('{113}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_1
+               Index Cond: (a > ANY ('{113}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a > ANY(ARRAY[113]);
+ count 
+-------
+   477
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a > ANY(ARRAY[113, NULL]);
+                          QUERY PLAN                           
+---------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (a > ANY ('{113,NULL}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_1
+               Index Cond: (a > ANY ('{113,NULL}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a > ANY(ARRAY[113, NULL]);
+ count 
+-------
+   477
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a > ANY(ARRAY[NULL, NULL]::int[]);
+                           QUERY PLAN                           
+----------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (a > ANY ('{NULL,NULL}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_1
+               Index Cond: (a > ANY ('{NULL,NULL}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a > ANY(ARRAY[NULL, NULL]::int[]);
+ count 
+-------
+     0
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a >= ANY(ARRAY[113, 177]);
+                          QUERY PLAN                           
+---------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (a >= ANY ('{113,177}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_1
+               Index Cond: (a >= ANY ('{113,177}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a >= ANY(ARRAY[113, 177]);
+ count 
+-------
+   485
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a > ANY(ARRAY[113, 177, NULL]);
+                            QUERY PLAN                             
+-------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (a > ANY ('{113,177,NULL}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_1
+               Index Cond: (a > ANY ('{113,177,NULL}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a > ANY(ARRAY[113, 177, NULL]);
+ count 
+-------
+   477
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a > ANY(ARRAY[113, 177, 25]);
+                           QUERY PLAN                            
+-----------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (a > ANY ('{113,177,25}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_1
+               Index Cond: (a > ANY ('{113,177,25}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a > ANY(ARRAY[113, 177, 25]);
+ count 
+-------
+   917
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a >= ANY(ARRAY[NULL, 113, 177, 25]);
+                              QUERY PLAN                               
+-----------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (a >= ANY ('{NULL,113,177,25}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_1
+               Index Cond: (a >= ANY ('{NULL,113,177,25}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a >= ANY(ARRAY[NULL, 113, 177, 25]);
+ count 
+-------
+   925
+(1 row)
+
+-- bigint: equality
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (82);
+                         QUERY PLAN                          
+-------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (b = 82)
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_2
+               Index Cond: (b = 82)
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (82);
+ count 
+-------
+    10
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (82, NULL);
+                         QUERY PLAN                          
+-------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (b = ANY ('{82,NULL}'::bigint[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_2
+               Index Cond: (b = ANY ('{82,NULL}'::bigint[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (82, NULL);
+ count 
+-------
+    10
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (NULL, NULL);
+                          QUERY PLAN                           
+---------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (b = ANY ('{NULL,NULL}'::bigint[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_2
+               Index Cond: (b = ANY ('{NULL,NULL}'::bigint[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (NULL, NULL);
+ count 
+-------
+     0
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (82, 41);
+                         QUERY PLAN                          
+-------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (b = ANY ('{82,41}'::bigint[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_2
+               Index Cond: (b = ANY ('{82,41}'::bigint[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (82, 41);
+ count 
+-------
+    20
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (-82, -141);
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (b = ANY ('{-82,-141}'::bigint[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_2
+               Index Cond: (b = ANY ('{-82,-141}'::bigint[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (-82, -141);
+ count 
+-------
+     0
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (382, 441);
+                         QUERY PLAN                          
+-------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (b = ANY ('{382,441}'::bigint[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_2
+               Index Cond: (b = ANY ('{382,441}'::bigint[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (382, 441);
+ count 
+-------
+     0
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (82, 41, NULL);
+                           QUERY PLAN                           
+----------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (b = ANY ('{82,41,NULL}'::bigint[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_2
+               Index Cond: (b = ANY ('{82,41,NULL}'::bigint[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (82, 41, NULL);
+ count 
+-------
+    20
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (82, 41, 15);
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (b = ANY ('{82,41,15}'::bigint[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_2
+               Index Cond: (b = ANY ('{82,41,15}'::bigint[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (82, 41, 15);
+ count 
+-------
+    25
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (NULL, 82, 41, 15);
+                            QUERY PLAN                             
+-------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (b = ANY ('{NULL,82,41,15}'::bigint[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_2
+               Index Cond: (b = ANY ('{NULL,82,41,15}'::bigint[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (NULL, 82, 41, 15);
+ count 
+-------
+    25
+(1 row)
+
+-- bigint: less than
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b < ANY(ARRAY[82]);
+                         QUERY PLAN                          
+-------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (b < ANY ('{82}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_2
+               Index Cond: (b < ANY ('{82}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b < ANY(ARRAY[82]);
+ count 
+-------
+   674
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b < ANY(ARRAY[82, NULL]);
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (b < ANY ('{82,NULL}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_2
+               Index Cond: (b < ANY ('{82,NULL}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b < ANY(ARRAY[82, NULL]);
+ count 
+-------
+   674
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b < ANY(ARRAY[NULL, NULL]::bigint[]);
+                          QUERY PLAN                           
+---------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (b < ANY ('{NULL,NULL}'::bigint[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_2
+               Index Cond: (b < ANY ('{NULL,NULL}'::bigint[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b < ANY(ARRAY[NULL, NULL]::bigint[]);
+ count 
+-------
+     0
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b <= ANY(ARRAY[82, 41]);
+                         QUERY PLAN                          
+-------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (b <= ANY ('{82,41}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_2
+               Index Cond: (b <= ANY ('{82,41}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b <= ANY(ARRAY[82, 41]);
+ count 
+-------
+   684
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b < ANY(ARRAY[82, 41, NULL]);
+                           QUERY PLAN                            
+-----------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (b < ANY ('{82,41,NULL}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_2
+               Index Cond: (b < ANY ('{82,41,NULL}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b < ANY(ARRAY[82, 41, NULL]);
+ count 
+-------
+   674
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b <= ANY(ARRAY[82, 41, 15]);
+                           QUERY PLAN                           
+----------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (b <= ANY ('{82,41,15}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_2
+               Index Cond: (b <= ANY ('{82,41,15}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b <= ANY(ARRAY[82, 41, 15]);
+ count 
+-------
+   684
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b < ANY(ARRAY[NULL, 82, 41, 15]);
+                             QUERY PLAN                             
+--------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (b < ANY ('{NULL,82,41,15}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_2
+               Index Cond: (b < ANY ('{NULL,82,41,15}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b < ANY(ARRAY[NULL, 82, 41, 15]);
+ count 
+-------
+   674
+(1 row)
+
+-- bigint: greater than
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b > ANY(ARRAY[82]);
+                         QUERY PLAN                          
+-------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (b > ANY ('{82}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_2
+               Index Cond: (b > ANY ('{82}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b > ANY(ARRAY[82]);
+ count 
+-------
+   316
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b > ANY(ARRAY[82, NULL]);
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (b > ANY ('{82,NULL}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_2
+               Index Cond: (b > ANY ('{82,NULL}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b > ANY(ARRAY[82, NULL]);
+ count 
+-------
+   316
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b > ANY(ARRAY[NULL, NULL]::bigint[]);
+                          QUERY PLAN                           
+---------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (b > ANY ('{NULL,NULL}'::bigint[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_2
+               Index Cond: (b > ANY ('{NULL,NULL}'::bigint[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b > ANY(ARRAY[NULL, NULL]::bigint[]);
+ count 
+-------
+     0
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b > ANY(ARRAY[82, 41]);
+                         QUERY PLAN                          
+-------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (b > ANY ('{82,41}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_2
+               Index Cond: (b > ANY ('{82,41}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b > ANY(ARRAY[82, 41]);
+ count 
+-------
+   726
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b >= ANY(ARRAY[82, 41, NULL]);
+                            QUERY PLAN                            
+------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (b >= ANY ('{82,41,NULL}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_2
+               Index Cond: (b >= ANY ('{82,41,NULL}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b >= ANY(ARRAY[82, 41, NULL]);
+ count 
+-------
+   736
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b >= ANY(ARRAY[82, 41, 15]);
+                           QUERY PLAN                           
+----------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (b >= ANY ('{82,41,15}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_2
+               Index Cond: (b >= ANY ('{82,41,15}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b >= ANY(ARRAY[82, 41, 15]);
+ count 
+-------
+   961
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b > ANY(ARRAY[NULL, 82, 41, 15]);
+                             QUERY PLAN                             
+--------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (b > ANY ('{NULL,82,41,15}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_2
+               Index Cond: (b > ANY ('{NULL,82,41,15}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b > ANY(ARRAY[NULL, 82, 41, 15]);
+ count 
+-------
+   956
+(1 row)
+
+DROP TABLE brin_in_test_multi_1;
+RESET enable_seqscan;
+-- do some tests on IN clauses for varlena data types
+CREATE TABLE brin_in_test_multi_2 (a UUID) WITH (fillfactor=10);
+INSERT INTO brin_in_test_multi_2
+SELECT v::uuid FROM (SELECT row_number() OVER (ORDER BY v) c, v FROM (SELECT md5((i/13)::text) AS v FROM generate_series(1,1000) s(i)) foo) bar ORDER BY c + 25 * random();
+CREATE INDEX brin_in_test_multi_2_idx ON brin_in_test_multi_2 USING brin (a uuid_minmax_multi_ops) WITH (pages_per_range=1);
+SET enable_seqscan=off;
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_2 WHERE a IN ('33e75ff0-9dd6-01bb-e69f-351039152189');
+                                  QUERY PLAN                                  
+------------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_2
+         Recheck Cond: (a = '33e75ff0-9dd6-01bb-e69f-351039152189'::uuid)
+         ->  Bitmap Index Scan on brin_in_test_multi_2_idx
+               Index Cond: (a = '33e75ff0-9dd6-01bb-e69f-351039152189'::uuid)
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_2 WHERE a IN ('33e75ff0-9dd6-01bb-e69f-351039152189');
+ count 
+-------
+    13
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_2 WHERE a IN ('33e75ff0-9dd6-01bb-e69f-351039152189', NULL);
+                                         QUERY PLAN                                          
+---------------------------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_2
+         Recheck Cond: (a = ANY ('{33e75ff0-9dd6-01bb-e69f-351039152189,NULL}'::uuid[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_2_idx
+               Index Cond: (a = ANY ('{33e75ff0-9dd6-01bb-e69f-351039152189,NULL}'::uuid[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_2 WHERE a IN ('33e75ff0-9dd6-01bb-e69f-351039152189', NULL);
+ count 
+-------
+    13
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_2 WHERE a IN (NULL, NULL);
+                         QUERY PLAN                          
+-------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_2
+         Recheck Cond: (a = ANY ('{NULL,NULL}'::uuid[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_2_idx
+               Index Cond: (a = ANY ('{NULL,NULL}'::uuid[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_2 WHERE a IN (NULL, NULL);
+ count 
+-------
+     0
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_2 WHERE a IN ('33e75ff0-9dd6-01bb-e69f-351039152189', 'f457c545-a9de-d88f-18ec-ee47145a72c0');
+                                                         QUERY PLAN                                                          
+-----------------------------------------------------------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_2
+         Recheck Cond: (a = ANY ('{33e75ff0-9dd6-01bb-e69f-351039152189,f457c545-a9de-d88f-18ec-ee47145a72c0}'::uuid[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_2_idx
+               Index Cond: (a = ANY ('{33e75ff0-9dd6-01bb-e69f-351039152189,f457c545-a9de-d88f-18ec-ee47145a72c0}'::uuid[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_2 WHERE a IN ('33e75ff0-9dd6-01bb-e69f-351039152189', 'f457c545-a9de-d88f-18ec-ee47145a72c0');
+ count 
+-------
+    26
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_2 WHERE a IN ('33e75ff0-9dd6-01bb-e69f-351039152189', 'f457c545-a9de-d88f-18ec-ee47145a72c0', NULL);
+                                                            QUERY PLAN                                                            
+----------------------------------------------------------------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_2
+         Recheck Cond: (a = ANY ('{33e75ff0-9dd6-01bb-e69f-351039152189,f457c545-a9de-d88f-18ec-ee47145a72c0,NULL}'::uuid[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_2_idx
+               Index Cond: (a = ANY ('{33e75ff0-9dd6-01bb-e69f-351039152189,f457c545-a9de-d88f-18ec-ee47145a72c0,NULL}'::uuid[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_2 WHERE a IN ('33e75ff0-9dd6-01bb-e69f-351039152189', 'f457c545-a9de-d88f-18ec-ee47145a72c0', NULL);
+ count 
+-------
+    26
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_2 WHERE a IN ('33e75ff0-9dd6-01bb-e69f-351039152189', 'f457c545-a9de-d88f-18ec-ee47145a72c0', 'c51ce410-c124-a10e-0db5-e4b97fc2af39');
+                                                                            QUERY PLAN                                                                            
+------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_2
+         Recheck Cond: (a = ANY ('{33e75ff0-9dd6-01bb-e69f-351039152189,f457c545-a9de-d88f-18ec-ee47145a72c0,c51ce410-c124-a10e-0db5-e4b97fc2af39}'::uuid[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_2_idx
+               Index Cond: (a = ANY ('{33e75ff0-9dd6-01bb-e69f-351039152189,f457c545-a9de-d88f-18ec-ee47145a72c0,c51ce410-c124-a10e-0db5-e4b97fc2af39}'::uuid[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_2 WHERE a IN ('33e75ff0-9dd6-01bb-e69f-351039152189', 'f457c545-a9de-d88f-18ec-ee47145a72c0', 'c51ce410-c124-a10e-0db5-e4b97fc2af39');
+ count 
+-------
+    39
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_2 WHERE a IN (NULL, '33e75ff0-9dd6-01bb-e69f-351039152189', 'f457c545-a9de-d88f-18ec-ee47145a72c0', 'c51ce410-c124-a10e-0db5-e4b97fc2af39');
+                                                                              QUERY PLAN                                                                               
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_2
+         Recheck Cond: (a = ANY ('{NULL,33e75ff0-9dd6-01bb-e69f-351039152189,f457c545-a9de-d88f-18ec-ee47145a72c0,c51ce410-c124-a10e-0db5-e4b97fc2af39}'::uuid[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_2_idx
+               Index Cond: (a = ANY ('{NULL,33e75ff0-9dd6-01bb-e69f-351039152189,f457c545-a9de-d88f-18ec-ee47145a72c0,c51ce410-c124-a10e-0db5-e4b97fc2af39}'::uuid[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_2 WHERE a IN (NULL, '33e75ff0-9dd6-01bb-e69f-351039152189', 'f457c545-a9de-d88f-18ec-ee47145a72c0', 'c51ce410-c124-a10e-0db5-e4b97fc2af39');
+ count 
+-------
+    39
+(1 row)
+
+DROP TABLE brin_in_test_multi_2;
+RESET enable_seqscan;
diff --git a/src/test/regress/sql/brin_multi.sql b/src/test/regress/sql/brin_multi.sql
index a59e182bc25..fa1d5f7dfef 100644
--- a/src/test/regress/sql/brin_multi.sql
+++ b/src/test/regress/sql/brin_multi.sql
@@ -550,3 +550,304 @@ SELECT COUNT(*) FROM brin_test_multi_2 WHERE a >= 'c51ce410-c124-a10e-0db5-e4b97
 
 DROP TABLE brin_test_multi_2;
 RESET enable_seqscan;
+
+
+-- do some tests on IN clauses for simple data types
+CREATE TABLE brin_in_test_multi_1 (a INT, b BIGINT) WITH (fillfactor=10);
+INSERT INTO brin_in_test_multi_1
+SELECT i/5 + mod(991 * i + 617, 20),
+       i/10 + mod(853 * i + 491, 30)
+  FROM generate_series(1,1000) s(i);
+
+CREATE INDEX brin_in_test_multi_1_idx_1 ON brin_in_test_multi_1 USING brin (a int4_minmax_multi_ops) WITH (pages_per_range=1);
+CREATE INDEX brin_in_test_multi_1_idx_2 ON brin_in_test_multi_1 USING brin (b int8_minmax_multi_ops) WITH (pages_per_range=1);
+
+SET enable_seqscan=off;
+
+-- int: equality
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (113);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (113);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (113, NULL);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (113, NULL);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (NULL, NULL);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (NULL, NULL);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (113, 177);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (113, 177);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (-113, -177);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (-113, -177);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (313, 377);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (313, 377);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (113, 177, NULL);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (113, 177, NULL);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (113, 177, 25);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (113, 177, 25);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (NULL, 113, 177, 25);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (NULL, 113, 177, 25);
+
+-- int: less than
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a < ANY(ARRAY[113]);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a < ANY(ARRAY[113]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a < ANY(ARRAY[113, NULL]);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a < ANY(ARRAY[113, NULL]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a < ANY(ARRAY[NULL, NULL]::int[]);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a < ANY(ARRAY[NULL, NULL]::int[]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a <= ANY(ARRAY[113, 177]);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a <= ANY(ARRAY[113, 177]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a < ANY(ARRAY[113, 177, NULL]);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a < ANY(ARRAY[113, 177, NULL]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a <= ANY(ARRAY[113, 177, 25]);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a <= ANY(ARRAY[113, 177, 25]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a < ANY(ARRAY[NULL, 113, 177, 25]);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a < ANY(ARRAY[NULL, 113, 177, 25]);
+
+-- int: greater than
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a > ANY(ARRAY[113]);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a > ANY(ARRAY[113]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a > ANY(ARRAY[113, NULL]);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a > ANY(ARRAY[113, NULL]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a > ANY(ARRAY[NULL, NULL]::int[]);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a > ANY(ARRAY[NULL, NULL]::int[]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a >= ANY(ARRAY[113, 177]);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a >= ANY(ARRAY[113, 177]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a > ANY(ARRAY[113, 177, NULL]);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a > ANY(ARRAY[113, 177, NULL]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a > ANY(ARRAY[113, 177, 25]);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a > ANY(ARRAY[113, 177, 25]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a >= ANY(ARRAY[NULL, 113, 177, 25]);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a >= ANY(ARRAY[NULL, 113, 177, 25]);
+
+-- bigint: equality
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (82);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (82);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (82, NULL);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (82, NULL);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (NULL, NULL);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (NULL, NULL);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (82, 41);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (82, 41);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (-82, -141);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (-82, -141);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (382, 441);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (382, 441);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (82, 41, NULL);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (82, 41, NULL);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (82, 41, 15);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (82, 41, 15);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (NULL, 82, 41, 15);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (NULL, 82, 41, 15);
+
+-- bigint: less than
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b < ANY(ARRAY[82]);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b < ANY(ARRAY[82]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b < ANY(ARRAY[82, NULL]);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b < ANY(ARRAY[82, NULL]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b < ANY(ARRAY[NULL, NULL]::bigint[]);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b < ANY(ARRAY[NULL, NULL]::bigint[]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b <= ANY(ARRAY[82, 41]);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b <= ANY(ARRAY[82, 41]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b < ANY(ARRAY[82, 41, NULL]);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b < ANY(ARRAY[82, 41, NULL]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b <= ANY(ARRAY[82, 41, 15]);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b <= ANY(ARRAY[82, 41, 15]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b < ANY(ARRAY[NULL, 82, 41, 15]);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b < ANY(ARRAY[NULL, 82, 41, 15]);
+
+-- bigint: greater than
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b > ANY(ARRAY[82]);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b > ANY(ARRAY[82]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b > ANY(ARRAY[82, NULL]);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b > ANY(ARRAY[82, NULL]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b > ANY(ARRAY[NULL, NULL]::bigint[]);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b > ANY(ARRAY[NULL, NULL]::bigint[]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b > ANY(ARRAY[82, 41]);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b > ANY(ARRAY[82, 41]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b >= ANY(ARRAY[82, 41, NULL]);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b >= ANY(ARRAY[82, 41, NULL]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b >= ANY(ARRAY[82, 41, 15]);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b >= ANY(ARRAY[82, 41, 15]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b > ANY(ARRAY[NULL, 82, 41, 15]);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b > ANY(ARRAY[NULL, 82, 41, 15]);
+
+
+DROP TABLE brin_in_test_multi_1;
+RESET enable_seqscan;
+
+
+-- do some tests on IN clauses for varlena data types
+CREATE TABLE brin_in_test_multi_2 (a UUID) WITH (fillfactor=10);
+INSERT INTO brin_in_test_multi_2
+SELECT v::uuid FROM (SELECT row_number() OVER (ORDER BY v) c, v FROM (SELECT md5((i/13)::text) AS v FROM generate_series(1,1000) s(i)) foo) bar ORDER BY c + 25 * random();
+
+CREATE INDEX brin_in_test_multi_2_idx ON brin_in_test_multi_2 USING brin (a uuid_minmax_multi_ops) WITH (pages_per_range=1);
+
+SET enable_seqscan=off;
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_2 WHERE a IN ('33e75ff0-9dd6-01bb-e69f-351039152189');
+
+SELECT COUNT(*) FROM brin_in_test_multi_2 WHERE a IN ('33e75ff0-9dd6-01bb-e69f-351039152189');
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_2 WHERE a IN ('33e75ff0-9dd6-01bb-e69f-351039152189', NULL);
+
+SELECT COUNT(*) FROM brin_in_test_multi_2 WHERE a IN ('33e75ff0-9dd6-01bb-e69f-351039152189', NULL);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_2 WHERE a IN (NULL, NULL);
+
+SELECT COUNT(*) FROM brin_in_test_multi_2 WHERE a IN (NULL, NULL);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_2 WHERE a IN ('33e75ff0-9dd6-01bb-e69f-351039152189', 'f457c545-a9de-d88f-18ec-ee47145a72c0');
+
+SELECT COUNT(*) FROM brin_in_test_multi_2 WHERE a IN ('33e75ff0-9dd6-01bb-e69f-351039152189', 'f457c545-a9de-d88f-18ec-ee47145a72c0');
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_2 WHERE a IN ('33e75ff0-9dd6-01bb-e69f-351039152189', 'f457c545-a9de-d88f-18ec-ee47145a72c0', NULL);
+
+SELECT COUNT(*) FROM brin_in_test_multi_2 WHERE a IN ('33e75ff0-9dd6-01bb-e69f-351039152189', 'f457c545-a9de-d88f-18ec-ee47145a72c0', NULL);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_2 WHERE a IN ('33e75ff0-9dd6-01bb-e69f-351039152189', 'f457c545-a9de-d88f-18ec-ee47145a72c0', 'c51ce410-c124-a10e-0db5-e4b97fc2af39');
+
+SELECT COUNT(*) FROM brin_in_test_multi_2 WHERE a IN ('33e75ff0-9dd6-01bb-e69f-351039152189', 'f457c545-a9de-d88f-18ec-ee47145a72c0', 'c51ce410-c124-a10e-0db5-e4b97fc2af39');
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_2 WHERE a IN (NULL, '33e75ff0-9dd6-01bb-e69f-351039152189', 'f457c545-a9de-d88f-18ec-ee47145a72c0', 'c51ce410-c124-a10e-0db5-e4b97fc2af39');
+
+SELECT COUNT(*) FROM brin_in_test_multi_2 WHERE a IN (NULL, '33e75ff0-9dd6-01bb-e69f-351039152189', 'f457c545-a9de-d88f-18ec-ee47145a72c0', 'c51ce410-c124-a10e-0db5-e4b97fc2af39');
+
+DROP TABLE brin_in_test_multi_2;
+RESET enable_seqscan;
-- 
2.39.1



  [text/x-patch] 0008-Support-SK_SEARCHARRAY-in-BRIN-inclusion-20230217.patch (22.8K, ../../[email protected]/9-0008-Support-SK_SEARCHARRAY-in-BRIN-inclusion-20230217.patch)
  download | inline diff:
From cecf684b47836ce4d3c0a485bbfd7f4cc1ff1962 Mon Sep 17 00:00:00 2001
From: Tomas Vondra <[email protected]>
Date: Sat, 11 Feb 2023 15:23:25 +0100
Subject: [PATCH 8/9] Support SK_SEARCHARRAY in BRIN inclusion

---
 src/backend/access/brin/brin_inclusion.c | 246 ++++++++++++++++++-----
 src/include/catalog/pg_amproc.dat        |   9 +
 src/include/catalog/pg_proc.dat          |   4 +
 src/test/regress/expected/brin.out       | 132 ++++++++++++
 src/test/regress/sql/brin.sql            |  53 +++++
 5 files changed, 390 insertions(+), 54 deletions(-)

diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c
index 248116c1494..84858430950 100644
--- a/src/backend/access/brin/brin_inclusion.c
+++ b/src/backend/access/brin/brin_inclusion.c
@@ -30,6 +30,7 @@
 #include "access/skey.h"
 #include "catalog/pg_amop.h"
 #include "catalog/pg_type.h"
+#include "utils/array.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
 #include "utils/lsyscache.h"
@@ -72,6 +73,13 @@
 #define INCLUSION_UNMERGEABLE		1
 #define INCLUSION_CONTAINS_EMPTY	2
 
+/*
+ * We use some private sk_flags bits in preprocessed scan keys.  We're allowed
+ * to use bits 16-31 (see skey.h).  The uppermost bits are copied from the
+ * index's indoption[] array entry for the index attribute.
+ */
+#define SK_BRIN_ARRAY	0x00010000	/* deconstructed array */
+
 
 typedef struct InclusionOpaque
 {
@@ -238,44 +246,94 @@ brin_inclusion_add_value(PG_FUNCTION_ARGS)
 	PG_RETURN_BOOL(true);
 }
 
+typedef struct ScanKeyArray {
+	int		nelements;
+	Datum  *elements;
+} ScanKeyArray;
+
+Datum
+brin_inclusion_preprocess(PG_FUNCTION_ARGS)
+{
+	// BrinDesc   *bdesc = (BrinDesc *) PG_GETARG_POINTER(0);
+	ScanKey		key = (ScanKey) PG_GETARG_POINTER(1);
+	ScanKey		newkey;
+	ScanKeyArray *scanarray;
+
+	ArrayType  *arrayval;
+	int16		elmlen;
+	bool		elmbyval;
+	char		elmalign;
+	int			num_elems;
+	Datum	   *elem_values;
+	bool	   *elem_nulls;
+
+	/* number of non-null elements in the array */
+	int			num_nonnulls;
+
+	/* ignore scalar keys */
+	if (!(key->sk_flags & SK_SEARCHARRAY))
+		PG_RETURN_POINTER(key);
+
+	arrayval = DatumGetArrayTypeP(key->sk_argument);
+
+	get_typlenbyvalalign(ARR_ELEMTYPE(arrayval),
+						 &elmlen, &elmbyval, &elmalign);
+
+	deconstruct_array(arrayval,
+					  ARR_ELEMTYPE(arrayval),
+					  elmlen, elmbyval, elmalign,
+					  &elem_values, &elem_nulls, &num_elems);
+
+	/* eliminate NULL elements */
+	num_nonnulls = 0;
+	for (int i = 0; i < num_elems; i++)
+	{
+		/* skip NULL elements */
+		if (elem_nulls[i])
+			continue;
+
+		/* if needed, move the non-NULL ones */
+		if (num_nonnulls != i)
+			elem_values[num_nonnulls] = elem_values[i];
+
+		num_nonnulls++;
+	}
+
+	num_elems = num_nonnulls;
+
+	scanarray = palloc0(sizeof(ScanKeyArray));
+	scanarray->nelements = num_elems;
+	scanarray->elements = elem_values;
+
+	newkey = palloc0(sizeof(ScanKeyData));
+
+	ScanKeyEntryInitializeWithInfo(newkey,
+								   (key->sk_flags | SK_BRIN_ARRAY),
+								   key->sk_attno,
+								   key->sk_strategy,
+								   key->sk_subtype,
+								   key->sk_collation,
+								   &key->sk_func,
+								   PointerGetDatum(scanarray));
+
+	PG_RETURN_POINTER(newkey);
+}
+
 /*
- * BRIN inclusion consistent function
- *
- * We're no longer dealing with NULL keys in the consistent function, that is
- * now handled by the AM code. That means we should not get any all-NULL ranges
- * either, because those can't be consistent with regular (not [IS] NULL) keys.
+ * Check consistency of a single scalar value with the BRIN range.
  *
- * All of the strategies are optional.
+ * Called for both scalar scankeys and for each value in SK_SEARCHARRAY.
  */
-Datum
-brin_inclusion_consistent(PG_FUNCTION_ARGS)
+static bool
+brin_inclusion_consistent_value(BrinDesc *bdesc, BrinValues *column,
+								AttrNumber attno,
+								StrategyNumber strategy, Oid subtype,
+								Oid colloid, Datum unionval, Datum query)
 {
-	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;
 
-	/* This opclass uses the old signature with only three arguments. */
-	Assert(PG_NARGS() == 3);
-
-	/* Should not be dealing with all-NULL ranges. */
-	Assert(!column->bv_allnulls);
-
-	/* 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)
+	switch (strategy)
 	{
 			/*
 			 * Placement strategies
@@ -294,49 +352,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
@@ -352,9 +410,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS)
 		case RTSubStrategyNumber:
 		case RTSubEqualStrategyNumber:
 			finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype,
-													key->sk_strategy);
+													strategy);
 			result = FunctionCall2Coll(finfo, colloid, unionval, query);
-			PG_RETURN_DATUM(result);
+			return (DatumGetBool(result));
 
 			/*
 			 * Contained by strategies
@@ -374,9 +432,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 (column->bv_values[INCLUSION_CONTAINS_EMPTY]);
 
 			/*
 			 * Adjacent strategy
@@ -393,12 +451,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
@@ -428,9 +486,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 (column->bv_values[INCLUSION_CONTAINS_EMPTY]);
 
 		case RTSameStrategyNumber:
 		case RTEqualStrategyNumber:
@@ -438,30 +496,110 @@ 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 (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 (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);
+			elog(ERROR, "invalid strategy number %d", strategy);
+			return (false);
+	}
+}
+
+/*
+ * BRIN inclusion consistent function
+ *
+ * We're no longer dealing with NULL keys in the consistent function, that is
+ * now handled by the AM code. That means we should not get any all-NULL ranges
+ * either, because those can't be consistent with regular (not [IS] NULL) keys.
+ *
+ * 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;
+
+	/* This opclass uses the old signature with only three arguments. */
+	Assert(PG_NARGS() == 3);
+
+	/* Should not be dealing with all-NULL ranges. */
+	Assert(!column->bv_allnulls);
+
+	/* 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];
+
+	/*
+	 * For regular (scalar) scan keys, we simply compare the value to the
+	 * range min/max values, and we're done. For SK_SEARCHARRAY keys we
+	 * need to deparse the array and loop through the values.
+	 */
+	if (likely(!(key->sk_flags & SK_SEARCHARRAY)))
+	{
+		bool tmp;
+
+		tmp = brin_inclusion_consistent_value(bdesc, column, attno,
+											  key->sk_strategy,
+											  subtype, colloid,
+											  unionval, query);
+		PG_RETURN_BOOL(tmp);
+	}
+	else
+	{
+		ScanKeyArray *array = (ScanKeyArray *) query;
+		bool		matches = false;
+
+		/*
+		 * Loop through all pre-calculated hashes, check the bloom filter.
+		 *
+		 * XXX With empty cache (which can happen for IN clause with only NULL
+		 * values), we leave the matches flag set to false.
+		 */
+		for (int i = 0; i < array->nelements; i++)
+		{
+			Datum 	query_element = array->elements[i];
+
+			matches = brin_inclusion_consistent_value(bdesc, column, attno,
+													  key->sk_strategy,
+													  subtype, colloid,
+													  unionval, query_element);
+
+			if (matches)
+				break;
+		}
+
+		/* we could get here for empty array, e.g. with "@> '{}'::point[]" */
+		PG_RETURN_BOOL(matches);
 	}
 }
 
diff --git a/src/include/catalog/pg_amproc.dat b/src/include/catalog/pg_amproc.dat
index 4f17f0d58c1..ed5b21e7f9b 100644
--- a/src/include/catalog/pg_amproc.dat
+++ b/src/include/catalog/pg_amproc.dat
@@ -1478,6 +1478,9 @@
 { amprocfamily => 'brin/network_inclusion_ops', amproclefttype => 'inet',
   amprocrighttype => 'inet', amprocnum => '4',
   amproc => 'brin_inclusion_union' },
+{ amprocfamily => 'brin/network_inclusion_ops', amproclefttype => 'inet',
+  amprocrighttype => 'inet', amprocnum => '6',
+  amproc => 'brin_inclusion_preprocess' },
 { amprocfamily => 'brin/network_inclusion_ops', amproclefttype => 'inet',
   amprocrighttype => 'inet', amprocnum => '11', amproc => 'inet_merge' },
 { amprocfamily => 'brin/network_inclusion_ops', amproclefttype => 'inet',
@@ -2016,6 +2019,9 @@
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
   amprocrighttype => 'anyrange', amprocnum => '4',
   amproc => 'brin_inclusion_union' },
+{ amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
+  amprocrighttype => 'anyrange', amprocnum => '6',
+  amproc => 'brin_inclusion_preprocess' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
   amprocrighttype => 'anyrange', amprocnum => '11',
   amproc => 'range_merge(anyrange,anyrange)' },
@@ -2097,6 +2103,9 @@
 { amprocfamily => 'brin/box_inclusion_ops', amproclefttype => 'box',
   amprocrighttype => 'box', amprocnum => '4',
   amproc => 'brin_inclusion_union' },
+{ amprocfamily => 'brin/box_inclusion_ops', amproclefttype => 'box',
+  amprocrighttype => 'box', amprocnum => '6',
+  amproc => 'brin_inclusion_preprocess' },
 { amprocfamily => 'brin/box_inclusion_ops', amproclefttype => 'box',
   amprocrighttype => 'box', amprocnum => '11', amproc => 'bound_box' },
 { amprocfamily => 'brin/box_inclusion_ops', amproclefttype => 'box',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4e8d6668648..753c41d5cd0 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8610,6 +8610,10 @@
   proname => 'brin_inclusion_union', prorettype => 'bool',
   proargtypes => 'internal internal internal',
   prosrc => 'brin_inclusion_union' },
+{ oid => '9324', descr => 'BRIN inclusion support',
+  proname => 'brin_inclusion_preprocess', proisstrict => 'f',
+  prorettype => 'internal', proargtypes => 'internal internal',
+  prosrc => 'brin_inclusion_preprocess' },
 
 # BRIN bloom
 { oid => '4591', descr => 'BRIN bloom support',
diff --git a/src/test/regress/expected/brin.out b/src/test/regress/expected/brin.out
index 93d7314e599..4717c1450c8 100644
--- a/src/test/regress/expected/brin.out
+++ b/src/test/regress/expected/brin.out
@@ -1430,3 +1430,135 @@ SELECT COUNT(*) FROM brin_in_test_2 WHERE a IN (NULL, '33e75ff09dd601bbe69f35103
 
 DROP TABLE brin_in_test_2;
 RESET enable_seqscan;
+-- do some tests on IN clauses for boxes and points
+CREATE TABLE brin_in_test_3 (a BOX) WITH (fillfactor=10);
+INSERT INTO brin_in_test_3
+SELECT format('((%s,%s), (%s,%s))', x - mod(i,17), y - mod(i,13), x + mod(i,19), y + mod(i,11))::box FROM (
+  SELECT i,
+         i/10 + mod(991 * i + 617, 20) AS x,
+         i/10 + mod(853 * i + 491, 30) AS y
+    FROM generate_series(1,1000) s(i)
+) foo;
+CREATE INDEX brin_in_test_3_idx ON brin_in_test_3 USING brin (a box_inclusion_ops) WITH (pages_per_range=1);
+SET enable_seqscan=off;
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_3 WHERE a @> ANY (ARRAY['(10,10)'::point]);
+                          QUERY PLAN                           
+---------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_3
+         Recheck Cond: (a @> ANY ('{"(10,10)"}'::point[]))
+         ->  Bitmap Index Scan on brin_in_test_3_idx
+               Index Cond: (a @> ANY ('{"(10,10)"}'::point[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_3 WHERE a @> ANY (ARRAY['(10,10)'::point]);
+ count 
+-------
+    26
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_3 WHERE a @> ANY (ARRAY['(10,10)'::point, NULL]);
+                             QUERY PLAN                             
+--------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_3
+         Recheck Cond: (a @> ANY ('{"(10,10)",NULL}'::point[]))
+         ->  Bitmap Index Scan on brin_in_test_3_idx
+               Index Cond: (a @> ANY ('{"(10,10)",NULL}'::point[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_3 WHERE a @> ANY (ARRAY['(10,10)'::point, NULL]);
+ count 
+-------
+    26
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_3 WHERE a @> ANY (ARRAY[NULL::point, NULL::point]);
+                          QUERY PLAN                           
+---------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_3
+         Recheck Cond: (a @> ANY ('{NULL,NULL}'::point[]))
+         ->  Bitmap Index Scan on brin_in_test_3_idx
+               Index Cond: (a @> ANY ('{NULL,NULL}'::point[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_3 WHERE a @> ANY (ARRAY[NULL::point, NULL::point]);
+ count 
+-------
+     0
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_3 WHERE a @> ANY (ARRAY['(10,10)'::point, '(50,50)'::point]);
+                               QUERY PLAN                                
+-------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_3
+         Recheck Cond: (a @> ANY ('{"(10,10)","(50,50)"}'::point[]))
+         ->  Bitmap Index Scan on brin_in_test_3_idx
+               Index Cond: (a @> ANY ('{"(10,10)","(50,50)"}'::point[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_3 WHERE a @> ANY (ARRAY['(10,10)'::point, '(50,50)'::point]);
+ count 
+-------
+    80
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_3 WHERE a @> ANY (ARRAY['(10,10)'::point, '(50,50)'::point, NULL]);
+                                  QUERY PLAN                                  
+------------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_3
+         Recheck Cond: (a @> ANY ('{"(10,10)","(50,50)",NULL}'::point[]))
+         ->  Bitmap Index Scan on brin_in_test_3_idx
+               Index Cond: (a @> ANY ('{"(10,10)","(50,50)",NULL}'::point[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_3 WHERE a @> ANY (ARRAY['(10,10)'::point, '(50,50)'::point, NULL]);
+ count 
+-------
+    80
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_3 WHERE a @> ANY (ARRAY['(10,10)'::point, '(50,50)'::point, '(25,25)'::point]);
+                                    QUERY PLAN                                     
+-----------------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_3
+         Recheck Cond: (a @> ANY ('{"(10,10)","(50,50)","(25,25)"}'::point[]))
+         ->  Bitmap Index Scan on brin_in_test_3_idx
+               Index Cond: (a @> ANY ('{"(10,10)","(50,50)","(25,25)"}'::point[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_3 WHERE a @> ANY (ARRAY['(10,10)'::point, '(50,50)'::point, '(25,25)'::point]);
+ count 
+-------
+   129
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_3 WHERE a @> ANY (ARRAY[NULL, '(10,10)'::point, '(50,50)'::point, '(25,25)'::point]);
+                                       QUERY PLAN                                       
+----------------------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_3
+         Recheck Cond: (a @> ANY ('{NULL,"(10,10)","(50,50)","(25,25)"}'::point[]))
+         ->  Bitmap Index Scan on brin_in_test_3_idx
+               Index Cond: (a @> ANY ('{NULL,"(10,10)","(50,50)","(25,25)"}'::point[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_3 WHERE a @> ANY (ARRAY[NULL, '(10,10)'::point, '(50,50)'::point, '(25,25)'::point]);
+ count 
+-------
+   129
+(1 row)
+
+DROP TABLE brin_in_test_3;
+RESET enable_seqscan;
diff --git a/src/test/regress/sql/brin.sql b/src/test/regress/sql/brin.sql
index 95da027047e..3ca82939996 100644
--- a/src/test/regress/sql/brin.sql
+++ b/src/test/regress/sql/brin.sql
@@ -798,3 +798,56 @@ SELECT COUNT(*) FROM brin_in_test_2 WHERE a IN (NULL, '33e75ff09dd601bbe69f35103
 
 DROP TABLE brin_in_test_2;
 RESET enable_seqscan;
+
+
+-- do some tests on IN clauses for boxes and points
+CREATE TABLE brin_in_test_3 (a BOX) WITH (fillfactor=10);
+INSERT INTO brin_in_test_3
+SELECT format('((%s,%s), (%s,%s))', x - mod(i,17), y - mod(i,13), x + mod(i,19), y + mod(i,11))::box FROM (
+  SELECT i,
+         i/10 + mod(991 * i + 617, 20) AS x,
+         i/10 + mod(853 * i + 491, 30) AS y
+    FROM generate_series(1,1000) s(i)
+) foo;
+
+CREATE INDEX brin_in_test_3_idx ON brin_in_test_3 USING brin (a box_inclusion_ops) WITH (pages_per_range=1);
+
+SET enable_seqscan=off;
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_3 WHERE a @> ANY (ARRAY['(10,10)'::point]);
+
+SELECT COUNT(*) FROM brin_in_test_3 WHERE a @> ANY (ARRAY['(10,10)'::point]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_3 WHERE a @> ANY (ARRAY['(10,10)'::point, NULL]);
+
+SELECT COUNT(*) FROM brin_in_test_3 WHERE a @> ANY (ARRAY['(10,10)'::point, NULL]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_3 WHERE a @> ANY (ARRAY[NULL::point, NULL::point]);
+
+SELECT COUNT(*) FROM brin_in_test_3 WHERE a @> ANY (ARRAY[NULL::point, NULL::point]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_3 WHERE a @> ANY (ARRAY['(10,10)'::point, '(50,50)'::point]);
+
+SELECT COUNT(*) FROM brin_in_test_3 WHERE a @> ANY (ARRAY['(10,10)'::point, '(50,50)'::point]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_3 WHERE a @> ANY (ARRAY['(10,10)'::point, '(50,50)'::point, NULL]);
+
+SELECT COUNT(*) FROM brin_in_test_3 WHERE a @> ANY (ARRAY['(10,10)'::point, '(50,50)'::point, NULL]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_3 WHERE a @> ANY (ARRAY['(10,10)'::point, '(50,50)'::point, '(25,25)'::point]);
+
+SELECT COUNT(*) FROM brin_in_test_3 WHERE a @> ANY (ARRAY['(10,10)'::point, '(50,50)'::point, '(25,25)'::point]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_3 WHERE a @> ANY (ARRAY[NULL, '(10,10)'::point, '(50,50)'::point, '(25,25)'::point]);
+
+SELECT COUNT(*) FROM brin_in_test_3 WHERE a @> ANY (ARRAY[NULL, '(10,10)'::point, '(50,50)'::point, '(25,25)'::point]);
+
+DROP TABLE brin_in_test_3;
+RESET enable_seqscan;
-- 
2.39.1



  [text/x-patch] 0009-Support-SK_SEARCHARRAY-in-BRIN-bloom-20230217.patch (41.6K, ../../[email protected]/10-0009-Support-SK_SEARCHARRAY-in-BRIN-bloom-20230217.patch)
  download | inline diff:
From e2438e144dfb364b50f4965ebfc061c859f16a4e Mon Sep 17 00:00:00 2001
From: Tomas Vondra <[email protected]>
Date: Sat, 11 Feb 2023 20:50:03 +0100
Subject: [PATCH 9/9] Support SK_SEARCHARRAY in BRIN bloom

---
 src/backend/access/brin/brin_bloom.c     | 166 ++++++++--
 src/include/catalog/pg_amproc.dat        |  60 ++++
 src/include/catalog/pg_proc.dat          |   3 +
 src/test/regress/expected/brin_bloom.out | 377 +++++++++++++++++++++++
 src/test/regress/sql/brin_bloom.sql      | 135 ++++++++
 5 files changed, 715 insertions(+), 26 deletions(-)

diff --git a/src/backend/access/brin/brin_bloom.c b/src/backend/access/brin/brin_bloom.c
index 4ff80aeb0cc..5c3ba659ba2 100644
--- a/src/backend/access/brin/brin_bloom.c
+++ b/src/backend/access/brin/brin_bloom.c
@@ -125,9 +125,11 @@
 #include "access/stratnum.h"
 #include "catalog/pg_type.h"
 #include "catalog/pg_amop.h"
+#include "utils/array.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
 #include "utils/lsyscache.h"
+#include "utils/memutils.h"
 #include "utils/rel.h"
 #include "utils/syscache.h"
 
@@ -151,6 +153,13 @@
  */
 #define		PROCNUM_BASE			11
 
+/*
+ * We use some private sk_flags bits in preprocessed scan keys.  We're allowed
+ * to use bits 16-31 (see skey.h).  The uppermost bits are copied from the
+ * index's indoption[] array entry for the index attribute.
+ */
+#define SK_BRIN_HASHES	0x00010000	/* deconstructed array, calculated hashes */
+
 /*
  * Storage type for BRIN's reloptions.
  */
@@ -402,21 +411,14 @@ bloom_add_value(BloomFilter *filter, uint32 value, bool *updated)
 	return filter;
 }
 
-
 /*
  * bloom_contains_value
  * 		Check if the bloom filter contains a particular value.
  */
 static bool
-bloom_contains_value(BloomFilter *filter, uint32 value)
+bloom_contains_hashes(BloomFilter *filter, uint64 h1, uint64 h2)
 {
 	int			i;
-	uint64		h1,
-				h2;
-
-	/* calculate the two hashes */
-	h1 = hash_bytes_uint32_extended(value, BLOOM_SEED_1) % filter->nbits;
-	h2 = hash_bytes_uint32_extended(value, BLOOM_SEED_2) % filter->nbits;
 
 	/* compute the requested number of hashes */
 	for (i = 0; i < filter->nhashes; i++)
@@ -590,6 +592,104 @@ brin_bloom_add_value(PG_FUNCTION_ARGS)
 	PG_RETURN_BOOL(updated);
 }
 
+typedef struct HashCache {
+	int		nelements;
+	uint64 *h1;
+	uint64 *h2;
+} HashCache;
+
+Datum
+brin_bloom_preprocess(PG_FUNCTION_ARGS)
+{
+	BrinDesc   *bdesc = (BrinDesc *) PG_GETARG_POINTER(0);
+	ScanKey		key = (ScanKey) PG_GETARG_POINTER(1);
+	BloomOptions *opts = (BloomOptions *) PG_GET_OPCLASS_OPTIONS();
+	ScanKey		newkey;
+	HashCache  *cache = palloc0(sizeof(HashCache));
+
+	int			nbits;
+	FmgrInfo   *finfo;
+	uint32		hashValue;
+
+	/* we'll need to calculate hashes, so get the proc */
+	finfo = bloom_get_procinfo(bdesc, key->sk_attno, PROCNUM_HASH);
+
+	/*
+	 * We don't have a filter from any range yet, so we just re-calculate
+	 * the size (number of bits) just like bloom_init.
+	 */
+	bloom_filter_size(brin_bloom_get_ndistinct(bdesc, opts),
+					  BloomGetFalsePositiveRate(opts),
+					  NULL, &nbits, NULL);
+
+	/* precalculate the hash even for simple scan keys */
+	if (!(key->sk_flags & SK_SEARCHARRAY))
+	{
+		Datum value = key->sk_argument;
+
+		cache->nelements = 1;
+		cache->h1 = (uint64 *) palloc0(sizeof(uint64));
+		cache->h2 = (uint64 *) palloc0(sizeof(uint64));
+
+		hashValue = DatumGetUInt32(FunctionCall1Coll(finfo, key->sk_collation, value));
+
+		cache->h1[0] = hash_bytes_uint32_extended(hashValue, BLOOM_SEED_1) % nbits;
+		cache->h2[0] = hash_bytes_uint32_extended(hashValue, BLOOM_SEED_2) % nbits;
+	}
+	else
+	{
+		ArrayType  *arrayval;
+		int16		elmlen;
+		bool		elmbyval;
+		char		elmalign;
+		int			num_elems;
+		Datum	   *elem_values;
+		bool	   *elem_nulls;
+
+		arrayval = DatumGetArrayTypeP(key->sk_argument);
+
+		get_typlenbyvalalign(ARR_ELEMTYPE(arrayval),
+							 &elmlen, &elmbyval, &elmalign);
+
+		deconstruct_array(arrayval,
+						  ARR_ELEMTYPE(arrayval),
+						  elmlen, elmbyval, elmalign,
+						  &elem_values, &elem_nulls, &num_elems);
+
+		cache->h1 = (uint64 *) palloc0(sizeof(uint64) * num_elems);
+		cache->h2 = (uint64 *) palloc0(sizeof(uint64) * num_elems);
+
+		for (int i = 0; i < num_elems; i++)
+		{
+			Datum	element = elem_values[i];
+
+			/* ignore NULL elements */
+			if (elem_nulls[i])
+				continue;
+
+			hashValue = DatumGetUInt32(FunctionCall1Coll(finfo, key->sk_collation, element));
+
+			cache->h1[cache->nelements] = hash_bytes_uint32_extended(hashValue, BLOOM_SEED_1) % nbits;
+			cache->h2[cache->nelements] = hash_bytes_uint32_extended(hashValue, BLOOM_SEED_2) % nbits;
+
+			cache->nelements++;
+		}
+	}
+
+	newkey = palloc0(sizeof(ScanKeyData));
+
+	ScanKeyEntryInitializeWithInfo(newkey,
+								   (key->sk_flags | SK_BRIN_HASHES),
+								   key->sk_attno,
+								   key->sk_strategy,
+								   key->sk_subtype,
+								   key->sk_collation,
+								   &key->sk_func,
+								   PointerGetDatum(cache));
+
+	PG_RETURN_POINTER(newkey);
+}
+
 /*
  * Given an index tuple corresponding to a certain page range and a scan key,
  * return whether the scan key is consistent with the index tuple's bloom
@@ -598,16 +698,10 @@ brin_bloom_add_value(PG_FUNCTION_ARGS)
 Datum
 brin_bloom_consistent(PG_FUNCTION_ARGS)
 {
-	BrinDesc   *bdesc = (BrinDesc *) PG_GETARG_POINTER(0);
 	BrinValues *column = (BrinValues *) PG_GETARG_POINTER(1);
 	ScanKey    *keys = (ScanKey *) PG_GETARG_POINTER(2);
 	int			nkeys = PG_GETARG_INT32(3);
-	Oid			colloid = PG_GET_COLLATION();
-	AttrNumber	attno;
-	Datum		value;
 	bool		matches;
-	FmgrInfo   *finfo;
-	uint32		hashValue;
 	BloomFilter *filter;
 	int			keyno;
 
@@ -625,22 +719,42 @@ brin_bloom_consistent(PG_FUNCTION_ARGS)
 		/* NULL keys are handled and filtered-out in bringetbitmap */
 		Assert(!(key->sk_flags & SK_ISNULL));
 
-		attno = key->sk_attno;
-		value = key->sk_argument;
+		/*
+		 * Keys should be preprocessed into a hash cache (even a single
+		 * value scan keys, not just SK_SEARCHARRAY ones).
+		 */
+		Assert(key->sk_flags & SK_BRIN_HASHES);
 
 		switch (key->sk_strategy)
 		{
 			case BloomEqualStrategyNumber:
-
-				/*
-				 * We want to return the current page range if the bloom filter
-				 * seems to contain the value.
-				 */
-				finfo = bloom_get_procinfo(bdesc, attno, PROCNUM_HASH);
-
-				hashValue = DatumGetUInt32(FunctionCall1Coll(finfo, colloid, value));
-				matches &= bloom_contains_value(filter, hashValue);
-
+				{
+					HashCache  *cache = (HashCache *) key->sk_argument;
+
+					/* assume no match */
+					matches = false;
+
+					/*
+					 * We want to return the current page range if the bloom filter
+					 * seems to contain any of the values (or a single value).
+					 *
+					 * XXX With empty cache (which can happen for IN clause with
+					 * only NULL values), we leave the matches flag set to false.
+					 */
+					for (int i = 0; i < cache->nelements; i++)
+					{
+						bool	tmp = false;
+
+						tmp = bloom_contains_hashes(filter, cache->h1[i], cache->h2[i]);
+
+						/* if we found a matching value, we have a match */
+						if (DatumGetBool(tmp))
+						{
+							matches = BoolGetDatum(true);
+							break;
+						}
+					}
+				}
 				break;
 			default:
 				/* shouldn't happen */
diff --git a/src/include/catalog/pg_amproc.dat b/src/include/catalog/pg_amproc.dat
index ed5b21e7f9b..d951fcd1a06 100644
--- a/src/include/catalog/pg_amproc.dat
+++ b/src/include/catalog/pg_amproc.dat
@@ -822,6 +822,9 @@
 { amprocfamily => 'brin/bytea_bloom_ops', amproclefttype => 'bytea',
   amprocrighttype => 'bytea', amprocnum => '5',
   amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/bytea_bloom_ops', amproclefttype => 'bytea',
+  amprocrighttype => 'bytea', amprocnum => '6',
+  amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/bytea_bloom_ops', amproclefttype => 'bytea',
   amprocrighttype => 'bytea', amprocnum => '11', amproc => 'hashvarlena' },
 
@@ -853,6 +856,8 @@
   amprocrighttype => 'char', amprocnum => '4', amproc => 'brin_bloom_union' },
 { amprocfamily => 'brin/char_bloom_ops', amproclefttype => 'char',
   amprocrighttype => 'char', amprocnum => '5', amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/char_bloom_ops', amproclefttype => 'char',
+  amprocrighttype => 'char', amprocnum => '6', amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/char_bloom_ops', amproclefttype => 'char',
   amprocrighttype => 'char', amprocnum => '11', amproc => 'hashchar' },
 
@@ -884,6 +889,8 @@
   amprocrighttype => 'name', amprocnum => '4', amproc => 'brin_bloom_union' },
 { amprocfamily => 'brin/name_bloom_ops', amproclefttype => 'name',
   amprocrighttype => 'name', amprocnum => '5', amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/name_bloom_ops', amproclefttype => 'name',
+  amprocrighttype => 'name', amprocnum => '6', amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/name_bloom_ops', amproclefttype => 'name',
   amprocrighttype => 'name', amprocnum => '11', amproc => 'hashname' },
 
@@ -1010,6 +1017,8 @@
   amprocrighttype => 'int8', amprocnum => '4', amproc => 'brin_bloom_union' },
 { amprocfamily => 'brin/integer_bloom_ops', amproclefttype => 'int8',
   amprocrighttype => 'int8', amprocnum => '5', amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/integer_bloom_ops', amproclefttype => 'int8',
+  amprocrighttype => 'int8', amprocnum => '6', amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/integer_bloom_ops', amproclefttype => 'int8',
   amprocrighttype => 'int8', amprocnum => '11', amproc => 'hashint8' },
 
@@ -1025,6 +1034,8 @@
   amprocrighttype => 'int2', amprocnum => '4', amproc => 'brin_bloom_union' },
 { amprocfamily => 'brin/integer_bloom_ops', amproclefttype => 'int2',
   amprocrighttype => 'int2', amprocnum => '5', amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/integer_bloom_ops', amproclefttype => 'int2',
+  amprocrighttype => 'int2', amprocnum => '6', amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/integer_bloom_ops', amproclefttype => 'int2',
   amprocrighttype => 'int2', amprocnum => '11', amproc => 'hashint2' },
 
@@ -1040,6 +1051,8 @@
   amprocrighttype => 'int4', amprocnum => '4', amproc => 'brin_bloom_union' },
 { amprocfamily => 'brin/integer_bloom_ops', amproclefttype => 'int4',
   amprocrighttype => 'int4', amprocnum => '5', amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/integer_bloom_ops', amproclefttype => 'int4',
+  amprocrighttype => 'int4', amprocnum => '6', amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/integer_bloom_ops', amproclefttype => 'int4',
   amprocrighttype => 'int4', amprocnum => '11', amproc => 'hashint4' },
 
@@ -1071,6 +1084,8 @@
   amprocrighttype => 'text', amprocnum => '4', amproc => 'brin_bloom_union' },
 { amprocfamily => 'brin/text_bloom_ops', amproclefttype => 'text',
   amprocrighttype => 'text', amprocnum => '5', amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/text_bloom_ops', amproclefttype => 'text',
+  amprocrighttype => 'text', amprocnum => '6', amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/text_bloom_ops', amproclefttype => 'text',
   amprocrighttype => 'text', amprocnum => '11', amproc => 'hashtext' },
 
@@ -1124,6 +1139,8 @@
   amprocrighttype => 'oid', amprocnum => '4', amproc => 'brin_bloom_union' },
 { amprocfamily => 'brin/oid_bloom_ops', amproclefttype => 'oid',
   amprocrighttype => 'oid', amprocnum => '5', amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/oid_bloom_ops', amproclefttype => 'oid',
+  amprocrighttype => 'oid', amprocnum => '6', amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/oid_bloom_ops', amproclefttype => 'oid',
   amprocrighttype => 'oid', amprocnum => '11', amproc => 'hashoid' },
 
@@ -1154,6 +1171,8 @@
   amprocrighttype => 'tid', amprocnum => '4', amproc => 'brin_bloom_union' },
 { amprocfamily => 'brin/tid_bloom_ops', amproclefttype => 'tid',
   amprocrighttype => 'tid', amprocnum => '5', amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/tid_bloom_ops', amproclefttype => 'tid',
+  amprocrighttype => 'tid', amprocnum => '6', amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/tid_bloom_ops', amproclefttype => 'tid',
   amprocrighttype => 'tid', amprocnum => '11', amproc => 'hashtid' },
 
@@ -1273,6 +1292,9 @@
 { amprocfamily => 'brin/float_bloom_ops', amproclefttype => 'float4',
   amprocrighttype => 'float4', amprocnum => '5',
   amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/float_bloom_ops', amproclefttype => 'float4',
+  amprocrighttype => 'float4', amprocnum => '6',
+  amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/float_bloom_ops', amproclefttype => 'float4',
   amprocrighttype => 'float4', amprocnum => '11', amproc => 'hashfloat4' },
 
@@ -1290,6 +1312,9 @@
 { amprocfamily => 'brin/float_bloom_ops', amproclefttype => 'float8',
   amprocrighttype => 'float8', amprocnum => '5',
   amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/float_bloom_ops', amproclefttype => 'float8',
+  amprocrighttype => 'float8', amprocnum => '6',
+  amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/float_bloom_ops', amproclefttype => 'float8',
   amprocrighttype => 'float8', amprocnum => '11', amproc => 'hashfloat8' },
 
@@ -1349,6 +1374,9 @@
 { amprocfamily => 'brin/macaddr_bloom_ops', amproclefttype => 'macaddr',
   amprocrighttype => 'macaddr', amprocnum => '5',
   amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/macaddr_bloom_ops', amproclefttype => 'macaddr',
+  amprocrighttype => 'macaddr', amprocnum => '6',
+  amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/macaddr_bloom_ops', amproclefttype => 'macaddr',
   amprocrighttype => 'macaddr', amprocnum => '11', amproc => 'hashmacaddr' },
 
@@ -1408,6 +1436,9 @@
 { amprocfamily => 'brin/macaddr8_bloom_ops', amproclefttype => 'macaddr8',
   amprocrighttype => 'macaddr8', amprocnum => '5',
   amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/macaddr8_bloom_ops', amproclefttype => 'macaddr8',
+  amprocrighttype => 'macaddr8', amprocnum => '6',
+  amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/macaddr8_bloom_ops', amproclefttype => 'macaddr8',
   amprocrighttype => 'macaddr8', amprocnum => '11', amproc => 'hashmacaddr8' },
 
@@ -1462,6 +1493,8 @@
   amprocrighttype => 'inet', amprocnum => '4', amproc => 'brin_bloom_union' },
 { amprocfamily => 'brin/network_bloom_ops', amproclefttype => 'inet',
   amprocrighttype => 'inet', amprocnum => '5', amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/network_bloom_ops', amproclefttype => 'inet',
+  amprocrighttype => 'inet', amprocnum => '6', amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/network_bloom_ops', amproclefttype => 'inet',
   amprocrighttype => 'inet', amprocnum => '11', amproc => 'hashinet' },
 
@@ -1520,6 +1553,9 @@
 { amprocfamily => 'brin/bpchar_bloom_ops', amproclefttype => 'bpchar',
   amprocrighttype => 'bpchar', amprocnum => '5',
   amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/bpchar_bloom_ops', amproclefttype => 'bpchar',
+  amprocrighttype => 'bpchar', amprocnum => '6',
+  amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/bpchar_bloom_ops', amproclefttype => 'bpchar',
   amprocrighttype => 'bpchar', amprocnum => '11', amproc => 'hashbpchar' },
 
@@ -1574,6 +1610,8 @@
   amprocrighttype => 'time', amprocnum => '4', amproc => 'brin_bloom_union' },
 { amprocfamily => 'brin/time_bloom_ops', amproclefttype => 'time',
   amprocrighttype => 'time', amprocnum => '5', amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/time_bloom_ops', amproclefttype => 'time',
+  amprocrighttype => 'time', amprocnum => '6', amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/time_bloom_ops', amproclefttype => 'time',
   amprocrighttype => 'time', amprocnum => '11', amproc => 'time_hash' },
 
@@ -1707,6 +1745,9 @@
 { amprocfamily => 'brin/datetime_bloom_ops', amproclefttype => 'timestamp',
   amprocrighttype => 'timestamp', amprocnum => '5',
   amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/datetime_bloom_ops', amproclefttype => 'timestamp',
+  amprocrighttype => 'timestamp', amprocnum => '6',
+  amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/datetime_bloom_ops', amproclefttype => 'timestamp',
   amprocrighttype => 'timestamp', amprocnum => '11',
   amproc => 'timestamp_hash' },
@@ -1726,6 +1767,9 @@
 { amprocfamily => 'brin/datetime_bloom_ops', amproclefttype => 'timestamptz',
   amprocrighttype => 'timestamptz', amprocnum => '5',
   amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/datetime_bloom_ops', amproclefttype => 'timestamptz',
+  amprocrighttype => 'timestamptz', amprocnum => '6',
+  amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/datetime_bloom_ops', amproclefttype => 'timestamptz',
   amprocrighttype => 'timestamptz', amprocnum => '11',
   amproc => 'timestamp_hash' },
@@ -1742,6 +1786,8 @@
   amprocrighttype => 'date', amprocnum => '4', amproc => 'brin_bloom_union' },
 { amprocfamily => 'brin/datetime_bloom_ops', amproclefttype => 'date',
   amprocrighttype => 'date', amprocnum => '5', amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/datetime_bloom_ops', amproclefttype => 'date',
+  amprocrighttype => 'date', amprocnum => '6', amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/datetime_bloom_ops', amproclefttype => 'date',
   amprocrighttype => 'date', amprocnum => '11', amproc => 'hashint4' },
 
@@ -1801,6 +1847,9 @@
 { amprocfamily => 'brin/interval_bloom_ops', amproclefttype => 'interval',
   amprocrighttype => 'interval', amprocnum => '5',
   amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/interval_bloom_ops', amproclefttype => 'interval',
+  amprocrighttype => 'interval', amprocnum => '6',
+  amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/interval_bloom_ops', amproclefttype => 'interval',
   amprocrighttype => 'interval', amprocnum => '11', amproc => 'interval_hash' },
 
@@ -1859,6 +1908,9 @@
 { amprocfamily => 'brin/timetz_bloom_ops', amproclefttype => 'timetz',
   amprocrighttype => 'timetz', amprocnum => '5',
   amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/timetz_bloom_ops', amproclefttype => 'timetz',
+  amprocrighttype => 'timetz', amprocnum => '6',
+  amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/timetz_bloom_ops', amproclefttype => 'timetz',
   amprocrighttype => 'timetz', amprocnum => '11', amproc => 'timetz_hash' },
 
@@ -1949,6 +2001,9 @@
 { amprocfamily => 'brin/numeric_bloom_ops', amproclefttype => 'numeric',
   amprocrighttype => 'numeric', amprocnum => '5',
   amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/numeric_bloom_ops', amproclefttype => 'numeric',
+  amprocrighttype => 'numeric', amprocnum => '6',
+  amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/numeric_bloom_ops', amproclefttype => 'numeric',
   amprocrighttype => 'numeric', amprocnum => '11', amproc => 'hash_numeric' },
 
@@ -2003,6 +2058,8 @@
   amprocrighttype => 'uuid', amprocnum => '4', amproc => 'brin_bloom_union' },
 { amprocfamily => 'brin/uuid_bloom_ops', amproclefttype => 'uuid',
   amprocrighttype => 'uuid', amprocnum => '5', amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/uuid_bloom_ops', amproclefttype => 'uuid',
+  amprocrighttype => 'uuid', amprocnum => '6', amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/uuid_bloom_ops', amproclefttype => 'uuid',
   amprocrighttype => 'uuid', amprocnum => '11', amproc => 'uuid_hash' },
 
@@ -2087,6 +2144,9 @@
 { amprocfamily => 'brin/pg_lsn_bloom_ops', amproclefttype => 'pg_lsn',
   amprocrighttype => 'pg_lsn', amprocnum => '5',
   amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/pg_lsn_bloom_ops', amproclefttype => 'pg_lsn',
+  amprocrighttype => 'pg_lsn', amprocnum => '6',
+  amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/pg_lsn_bloom_ops', amproclefttype => 'pg_lsn',
   amprocrighttype => 'pg_lsn', amprocnum => '11', amproc => 'pg_lsn_hash' },
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 753c41d5cd0..4325229c9df 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8633,6 +8633,9 @@
 { oid => '4595', descr => 'BRIN bloom support',
   proname => 'brin_bloom_options', proisstrict => 'f', prorettype => 'void',
   proargtypes => 'internal', prosrc => 'brin_bloom_options' },
+{ oid => '9325', descr => 'BRIN bloom support',
+  proname => 'brin_bloom_preprocess', proisstrict => 'f', prorettype => 'internal',
+  proargtypes => 'internal internal', prosrc => 'brin_bloom_preprocess' },
 
 # userlock replacements
 { oid => '2880', descr => 'obtain exclusive advisory lock',
diff --git a/src/test/regress/expected/brin_bloom.out b/src/test/regress/expected/brin_bloom.out
index 32c56a996a2..b83b2bae162 100644
--- a/src/test/regress/expected/brin_bloom.out
+++ b/src/test/regress/expected/brin_bloom.out
@@ -426,3 +426,380 @@ EXPLAIN (COSTS OFF) SELECT * FROM brin_test_bloom WHERE b = 1;
    Filter: (b = 1)
 (2 rows)
 
+-- do some tests on IN clauses for simple data types
+CREATE TABLE brin_in_test_bloom_1 (a INT, b BIGINT) WITH (fillfactor=10);
+INSERT INTO brin_in_test_bloom_1
+SELECT i/5 + mod(991 * i + 617, 20),
+       i/10 + mod(853 * i + 491, 30)
+  FROM generate_series(1,1000) s(i);
+CREATE INDEX brin_in_test_bloom_1_idx_1 ON brin_in_test_bloom_1 USING brin (a int4_bloom_ops(n_distinct_per_range=50)) WITH (pages_per_range=1);
+CREATE INDEX brin_in_test_bloom_1_idx_2 ON brin_in_test_bloom_1 USING brin (b int8_bloom_ops(n_distinct_per_range=50)) WITH (pages_per_range=1);
+SET enable_seqscan=off;
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE a IN (113);
+                         QUERY PLAN                          
+-------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_bloom_1
+         Recheck Cond: (a = 113)
+         ->  Bitmap Index Scan on brin_in_test_bloom_1_idx_1
+               Index Cond: (a = 113)
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE a IN (113);
+ count 
+-------
+     8
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE a IN (113, NULL);
+                          QUERY PLAN                           
+---------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_bloom_1
+         Recheck Cond: (a = ANY ('{113,NULL}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_bloom_1_idx_1
+               Index Cond: (a = ANY ('{113,NULL}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE a IN (113, NULL);
+ count 
+-------
+     8
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE a IN (NULL, NULL);
+                           QUERY PLAN                           
+----------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_bloom_1
+         Recheck Cond: (a = ANY ('{NULL,NULL}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_bloom_1_idx_1
+               Index Cond: (a = ANY ('{NULL,NULL}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE a IN (NULL, NULL);
+ count 
+-------
+     0
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE a IN (113, 177);
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_bloom_1
+         Recheck Cond: (a = ANY ('{113,177}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_bloom_1_idx_1
+               Index Cond: (a = ANY ('{113,177}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE a IN (113, 177);
+ count 
+-------
+    16
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE a IN (113, 177, NULL);
+                            QUERY PLAN                             
+-------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_bloom_1
+         Recheck Cond: (a = ANY ('{113,177,NULL}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_bloom_1_idx_1
+               Index Cond: (a = ANY ('{113,177,NULL}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE a IN (113, 177, NULL);
+ count 
+-------
+    16
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE a IN (113, 177, 25);
+                           QUERY PLAN                            
+-----------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_bloom_1
+         Recheck Cond: (a = ANY ('{113,177,25}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_bloom_1_idx_1
+               Index Cond: (a = ANY ('{113,177,25}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE a IN (113, 177, 25);
+ count 
+-------
+    24
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE a IN (NULL, 113, 177, 25);
+                              QUERY PLAN                              
+----------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_bloom_1
+         Recheck Cond: (a = ANY ('{NULL,113,177,25}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_bloom_1_idx_1
+               Index Cond: (a = ANY ('{NULL,113,177,25}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE a IN (NULL, 113, 177, 25);
+ count 
+-------
+    24
+(1 row)
+
+-- a bit weird this requires a cast to bigint, unlike multi-value IN clause
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE b IN (82::bigint);
+                         QUERY PLAN                          
+-------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_bloom_1
+         Recheck Cond: (b = '82'::bigint)
+         ->  Bitmap Index Scan on brin_in_test_bloom_1_idx_2
+               Index Cond: (b = '82'::bigint)
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE b IN (82::bigint);
+ count 
+-------
+    10
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE b IN (82, NULL);
+                         QUERY PLAN                          
+-------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_bloom_1
+         Recheck Cond: (b = ANY ('{82,NULL}'::bigint[]))
+         ->  Bitmap Index Scan on brin_in_test_bloom_1_idx_2
+               Index Cond: (b = ANY ('{82,NULL}'::bigint[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE b IN (82, NULL);
+ count 
+-------
+    10
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE b IN (NULL, NULL);
+                          QUERY PLAN                           
+---------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_bloom_1
+         Recheck Cond: (b = ANY ('{NULL,NULL}'::bigint[]))
+         ->  Bitmap Index Scan on brin_in_test_bloom_1_idx_2
+               Index Cond: (b = ANY ('{NULL,NULL}'::bigint[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE b IN (NULL, NULL);
+ count 
+-------
+     0
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE b IN (82, 41);
+                         QUERY PLAN                          
+-------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_bloom_1
+         Recheck Cond: (b = ANY ('{82,41}'::bigint[]))
+         ->  Bitmap Index Scan on brin_in_test_bloom_1_idx_2
+               Index Cond: (b = ANY ('{82,41}'::bigint[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE b IN (82, 41);
+ count 
+-------
+    20
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE b IN (82, 41, NULL);
+                           QUERY PLAN                           
+----------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_bloom_1
+         Recheck Cond: (b = ANY ('{82,41,NULL}'::bigint[]))
+         ->  Bitmap Index Scan on brin_in_test_bloom_1_idx_2
+               Index Cond: (b = ANY ('{82,41,NULL}'::bigint[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE b IN (82, 41, NULL);
+ count 
+-------
+    20
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE b IN (82, 41, 15);
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_bloom_1
+         Recheck Cond: (b = ANY ('{82,41,15}'::bigint[]))
+         ->  Bitmap Index Scan on brin_in_test_bloom_1_idx_2
+               Index Cond: (b = ANY ('{82,41,15}'::bigint[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE b IN (82, 41, 15);
+ count 
+-------
+    25
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE b IN (NULL, 82, 41, 15);
+                            QUERY PLAN                             
+-------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_bloom_1
+         Recheck Cond: (b = ANY ('{NULL,82,41,15}'::bigint[]))
+         ->  Bitmap Index Scan on brin_in_test_bloom_1_idx_2
+               Index Cond: (b = ANY ('{NULL,82,41,15}'::bigint[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE b IN (NULL, 82, 41, 15);
+ count 
+-------
+    25
+(1 row)
+
+DROP TABLE brin_in_test_bloom_1;
+RESET enable_seqscan;
+-- do some tests on IN clauses for varlena data types
+CREATE TABLE brin_in_test_bloom_2 (a TEXT) WITH (fillfactor=10);
+INSERT INTO brin_in_test_bloom_2
+SELECT v FROM (SELECT row_number() OVER (ORDER BY v) c, v FROM (SELECT md5((i/13)::text) AS v FROM generate_series(1,1000) s(i)) foo) bar ORDER BY c + 25 * random();
+CREATE INDEX brin_in_test_bloom_2_idx ON brin_in_test_bloom_2 USING brin (a text_bloom_ops(n_distinct_per_range=50)) WITH (pages_per_range=1);
+SET enable_seqscan=off;
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189');
+                                QUERY PLAN                                
+--------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_bloom_2
+         Recheck Cond: (a = '33e75ff09dd601bbe69f351039152189'::text)
+         ->  Bitmap Index Scan on brin_in_test_bloom_2_idx
+               Index Cond: (a = '33e75ff09dd601bbe69f351039152189'::text)
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_bloom_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189');
+ count 
+-------
+    13
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', NULL);
+                                       QUERY PLAN                                        
+-----------------------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_bloom_2
+         Recheck Cond: (a = ANY ('{33e75ff09dd601bbe69f351039152189,NULL}'::text[]))
+         ->  Bitmap Index Scan on brin_in_test_bloom_2_idx
+               Index Cond: (a = ANY ('{33e75ff09dd601bbe69f351039152189,NULL}'::text[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_bloom_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', NULL);
+ count 
+-------
+    13
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_2 WHERE a IN (NULL, NULL);
+                         QUERY PLAN                          
+-------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_bloom_2
+         Recheck Cond: (a = ANY ('{NULL,NULL}'::text[]))
+         ->  Bitmap Index Scan on brin_in_test_bloom_2_idx
+               Index Cond: (a = ANY ('{NULL,NULL}'::text[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_bloom_2 WHERE a IN (NULL, NULL);
+ count 
+-------
+     0
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0');
+                                                     QUERY PLAN                                                      
+---------------------------------------------------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_bloom_2
+         Recheck Cond: (a = ANY ('{33e75ff09dd601bbe69f351039152189,f457c545a9ded88f18ecee47145a72c0}'::text[]))
+         ->  Bitmap Index Scan on brin_in_test_bloom_2_idx
+               Index Cond: (a = ANY ('{33e75ff09dd601bbe69f351039152189,f457c545a9ded88f18ecee47145a72c0}'::text[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_bloom_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0');
+ count 
+-------
+    26
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0', NULL);
+                                                        QUERY PLAN                                                        
+--------------------------------------------------------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_bloom_2
+         Recheck Cond: (a = ANY ('{33e75ff09dd601bbe69f351039152189,f457c545a9ded88f18ecee47145a72c0,NULL}'::text[]))
+         ->  Bitmap Index Scan on brin_in_test_bloom_2_idx
+               Index Cond: (a = ANY ('{33e75ff09dd601bbe69f351039152189,f457c545a9ded88f18ecee47145a72c0,NULL}'::text[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_bloom_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0', NULL);
+ count 
+-------
+    26
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0', 'c51ce410c124a10e0db5e4b97fc2af39');
+                                                                      QUERY PLAN                                                                      
+------------------------------------------------------------------------------------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_bloom_2
+         Recheck Cond: (a = ANY ('{33e75ff09dd601bbe69f351039152189,f457c545a9ded88f18ecee47145a72c0,c51ce410c124a10e0db5e4b97fc2af39}'::text[]))
+         ->  Bitmap Index Scan on brin_in_test_bloom_2_idx
+               Index Cond: (a = ANY ('{33e75ff09dd601bbe69f351039152189,f457c545a9ded88f18ecee47145a72c0,c51ce410c124a10e0db5e4b97fc2af39}'::text[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_bloom_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0', 'c51ce410c124a10e0db5e4b97fc2af39');
+ count 
+-------
+    39
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_2 WHERE a IN (NULL, '33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0', 'c51ce410c124a10e0db5e4b97fc2af39');
+                                                                        QUERY PLAN                                                                         
+-----------------------------------------------------------------------------------------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_bloom_2
+         Recheck Cond: (a = ANY ('{NULL,33e75ff09dd601bbe69f351039152189,f457c545a9ded88f18ecee47145a72c0,c51ce410c124a10e0db5e4b97fc2af39}'::text[]))
+         ->  Bitmap Index Scan on brin_in_test_bloom_2_idx
+               Index Cond: (a = ANY ('{NULL,33e75ff09dd601bbe69f351039152189,f457c545a9ded88f18ecee47145a72c0,c51ce410c124a10e0db5e4b97fc2af39}'::text[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_bloom_2 WHERE a IN (NULL, '33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0', 'c51ce410c124a10e0db5e4b97fc2af39');
+ count 
+-------
+    39
+(1 row)
+
+DROP TABLE brin_in_test_bloom_2;
+RESET enable_seqscan;
diff --git a/src/test/regress/sql/brin_bloom.sql b/src/test/regress/sql/brin_bloom.sql
index 5d499208e38..d187e89a609 100644
--- a/src/test/regress/sql/brin_bloom.sql
+++ b/src/test/regress/sql/brin_bloom.sql
@@ -374,3 +374,138 @@ VACUUM ANALYZE brin_test_bloom;
 EXPLAIN (COSTS OFF) SELECT * FROM brin_test_bloom WHERE a = 1;
 -- Ensure brin index is not used when values are not correlated
 EXPLAIN (COSTS OFF) SELECT * FROM brin_test_bloom WHERE b = 1;
+
+
+-- do some tests on IN clauses for simple data types
+CREATE TABLE brin_in_test_bloom_1 (a INT, b BIGINT) WITH (fillfactor=10);
+INSERT INTO brin_in_test_bloom_1
+SELECT i/5 + mod(991 * i + 617, 20),
+       i/10 + mod(853 * i + 491, 30)
+  FROM generate_series(1,1000) s(i);
+
+CREATE INDEX brin_in_test_bloom_1_idx_1 ON brin_in_test_bloom_1 USING brin (a int4_bloom_ops(n_distinct_per_range=50)) WITH (pages_per_range=1);
+CREATE INDEX brin_in_test_bloom_1_idx_2 ON brin_in_test_bloom_1 USING brin (b int8_bloom_ops(n_distinct_per_range=50)) WITH (pages_per_range=1);
+
+SET enable_seqscan=off;
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE a IN (113);
+
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE a IN (113);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE a IN (113, NULL);
+
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE a IN (113, NULL);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE a IN (NULL, NULL);
+
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE a IN (NULL, NULL);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE a IN (113, 177);
+
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE a IN (113, 177);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE a IN (113, 177, NULL);
+
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE a IN (113, 177, NULL);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE a IN (113, 177, 25);
+
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE a IN (113, 177, 25);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE a IN (NULL, 113, 177, 25);
+
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE a IN (NULL, 113, 177, 25);
+
+-- a bit weird this requires a cast to bigint, unlike multi-value IN clause
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE b IN (82::bigint);
+
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE b IN (82::bigint);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE b IN (82, NULL);
+
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE b IN (82, NULL);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE b IN (NULL, NULL);
+
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE b IN (NULL, NULL);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE b IN (82, 41);
+
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE b IN (82, 41);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE b IN (82, 41, NULL);
+
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE b IN (82, 41, NULL);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE b IN (82, 41, 15);
+
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE b IN (82, 41, 15);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE b IN (NULL, 82, 41, 15);
+
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE b IN (NULL, 82, 41, 15);
+
+DROP TABLE brin_in_test_bloom_1;
+RESET enable_seqscan;
+
+
+-- do some tests on IN clauses for varlena data types
+CREATE TABLE brin_in_test_bloom_2 (a TEXT) WITH (fillfactor=10);
+INSERT INTO brin_in_test_bloom_2
+SELECT v FROM (SELECT row_number() OVER (ORDER BY v) c, v FROM (SELECT md5((i/13)::text) AS v FROM generate_series(1,1000) s(i)) foo) bar ORDER BY c + 25 * random();
+
+CREATE INDEX brin_in_test_bloom_2_idx ON brin_in_test_bloom_2 USING brin (a text_bloom_ops(n_distinct_per_range=50)) WITH (pages_per_range=1);
+
+SET enable_seqscan=off;
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189');
+
+SELECT COUNT(*) FROM brin_in_test_bloom_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189');
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', NULL);
+
+SELECT COUNT(*) FROM brin_in_test_bloom_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', NULL);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_2 WHERE a IN (NULL, NULL);
+
+SELECT COUNT(*) FROM brin_in_test_bloom_2 WHERE a IN (NULL, NULL);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0');
+
+SELECT COUNT(*) FROM brin_in_test_bloom_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0');
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0', NULL);
+
+SELECT COUNT(*) FROM brin_in_test_bloom_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0', NULL);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0', 'c51ce410c124a10e0db5e4b97fc2af39');
+
+SELECT COUNT(*) FROM brin_in_test_bloom_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0', 'c51ce410c124a10e0db5e4b97fc2af39');
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_2 WHERE a IN (NULL, '33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0', 'c51ce410c124a10e0db5e4b97fc2af39');
+
+SELECT COUNT(*) FROM brin_in_test_bloom_2 WHERE a IN (NULL, '33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0', 'c51ce410c124a10e0db5e4b97fc2af39');
+
+DROP TABLE brin_in_test_bloom_2;
+RESET enable_seqscan;
-- 
2.39.1



^ permalink  raw  reply  [nested|flat] 5+ messages in thread

* Re: BRIN indexes vs. SK_SEARCHARRAY (and preprocessing scan keys)
@ 2023-02-18 19:49  Tomas Vondra <[email protected]>
  parent: Tomas Vondra <[email protected]>
  0 siblings, 0 replies; 5+ messages in thread

From: Tomas Vondra @ 2023-02-18 19:49 UTC (permalink / raw)
  To: PostgreSQL Hackers <[email protected]>

Hi,

Apparently there was a bug in handling IS [NOT] NULL scan keys in the
bloom opclass, so here's a fixed patch.


regards

-- 
Tomas Vondra
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

Attachments:

  [text/x-patch] 0001-BRIN-bloom-cleanup-20230218.patch (1.9K, ../../[email protected]/2-0001-BRIN-bloom-cleanup-20230218.patch)
  download | inline diff:
From 87cf7111f86c6bcd847fe7d001d1c27f582dcf0e Mon Sep 17 00:00:00 2001
From: Tomas Vondra <[email protected]>
Date: Wed, 15 Feb 2023 12:39:23 +0100
Subject: [PATCH 1/9] BRIN bloom cleanup

Minor cleanup of the BRIN bloom code - use the proper data type for the
boolean variable, and correct comment copied from minmax.
---
 src/backend/access/brin/brin_bloom.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/src/backend/access/brin/brin_bloom.c b/src/backend/access/brin/brin_bloom.c
index e4953a9d37b..6c716924fff 100644
--- a/src/backend/access/brin/brin_bloom.c
+++ b/src/backend/access/brin/brin_bloom.c
@@ -574,7 +574,7 @@ brin_bloom_consistent(PG_FUNCTION_ARGS)
 	Oid			colloid = PG_GET_COLLATION();
 	AttrNumber	attno;
 	Datum		value;
-	Datum		matches;
+	bool		matches;
 	FmgrInfo   *finfo;
 	uint32		hashValue;
 	BloomFilter *filter;
@@ -584,6 +584,7 @@ brin_bloom_consistent(PG_FUNCTION_ARGS)
 
 	Assert(filter);
 
+	/* assume all scan keys match */
 	matches = true;
 
 	for (keyno = 0; keyno < nkeys; keyno++)
@@ -601,9 +602,8 @@ brin_bloom_consistent(PG_FUNCTION_ARGS)
 			case BloomEqualStrategyNumber:
 
 				/*
-				 * In the equality case (WHERE col = someval), we want to
-				 * return the current page range if the minimum value in the
-				 * range <= scan key, and the maximum value >= scan key.
+				 * We want to return the current page range if the bloom filter
+				 * seems to contain the value.
 				 */
 				finfo = bloom_get_procinfo(bdesc, attno, PROCNUM_HASH);
 
@@ -614,7 +614,7 @@ brin_bloom_consistent(PG_FUNCTION_ARGS)
 			default:
 				/* shouldn't happen */
 				elog(ERROR, "invalid strategy number %d", key->sk_strategy);
-				matches = 0;
+				matches = false;
 				break;
 		}
 
@@ -622,7 +622,7 @@ brin_bloom_consistent(PG_FUNCTION_ARGS)
 			break;
 	}
 
-	PG_RETURN_DATUM(matches);
+	PG_RETURN_BOOL(matches);
 }
 
 /*
-- 
2.39.1



  [text/x-patch] 0002-BRIN-minmax-multi-cleanup-20230218.patch (2.2K, ../../[email protected]/3-0002-BRIN-minmax-multi-cleanup-20230218.patch)
  download | inline diff:
From 35c9a76d78b3f32dbed52527206fd76a791772f6 Mon Sep 17 00:00:00 2001
From: Tomas Vondra <[email protected]>
Date: Wed, 15 Feb 2023 12:53:47 +0100
Subject: [PATCH 2/9] BRIN minmax-multi cleanup

When assigning to a Datum variable, use BoolGetDatum() consistently.
Simnplify the code by using PG_RETURN_BOOL().
---
 src/backend/access/brin/brin_minmax_multi.c | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/src/backend/access/brin/brin_minmax_multi.c b/src/backend/access/brin/brin_minmax_multi.c
index 0ace6035beb..859e0022fb4 100644
--- a/src/backend/access/brin/brin_minmax_multi.c
+++ b/src/backend/access/brin/brin_minmax_multi.c
@@ -2627,7 +2627,7 @@ brin_minmax_multi_consistent(PG_FUNCTION_ARGS)
 						FmgrInfo   *cmpFn;
 
 						/* by default this range does not match */
-						matches = false;
+						matches = BoolGetDatum(false);
 
 						/*
 						 * Otherwise, need to compare the new value with
@@ -2655,7 +2655,7 @@ brin_minmax_multi_consistent(PG_FUNCTION_ARGS)
 						 * We haven't managed to eliminate this range, so
 						 * consider it matching.
 						 */
-						matches = true;
+						matches = BoolGetDatum(true);
 
 						break;
 					}
@@ -2670,7 +2670,7 @@ brin_minmax_multi_consistent(PG_FUNCTION_ARGS)
 				default:
 					/* shouldn't happen */
 					elog(ERROR, "invalid strategy number %d", key->sk_strategy);
-					matches = 0;
+					matches = BoolGetDatum(false);
 					break;
 			}
 
@@ -2686,7 +2686,7 @@ brin_minmax_multi_consistent(PG_FUNCTION_ARGS)
 		 * have we found a range matching all scan keys? if yes, we're done
 		 */
 		if (matching)
-			PG_RETURN_DATUM(BoolGetDatum(true));
+			PG_RETURN_BOOL(true);
 	}
 
 	/*
@@ -2729,7 +2729,7 @@ brin_minmax_multi_consistent(PG_FUNCTION_ARGS)
 				default:
 					/* shouldn't happen */
 					elog(ERROR, "invalid strategy number %d", key->sk_strategy);
-					matches = 0;
+					matches = BoolGetDatum(false);
 					break;
 			}
 
@@ -2743,10 +2743,10 @@ brin_minmax_multi_consistent(PG_FUNCTION_ARGS)
 
 		/* have we found a range matching all scan keys? if yes, we're done */
 		if (matching)
-			PG_RETURN_DATUM(BoolGetDatum(true));
+			PG_RETURN_BOOL(true);
 	}
 
-	PG_RETURN_DATUM(BoolGetDatum(false));
+	PG_RETURN_BOOL(false);
 }
 
 /*
-- 
2.39.1



  [text/x-patch] 0003-Introduce-bloom_filter_size-20230218.patch (3.5K, ../../[email protected]/4-0003-Introduce-bloom_filter_size-20230218.patch)
  download | inline diff:
From 6750b0c6ba872dd29cb72a72a20a9b4ef2831c92 Mon Sep 17 00:00:00 2001
From: Tomas Vondra <[email protected]>
Date: Tue, 14 Feb 2023 20:28:08 +0100
Subject: [PATCH 3/9] Introduce bloom_filter_size

Wrap calculation of Bloom filter parameters (from ndistinct and false
positive rate) into a function. We'll need to do this calculation in
other places, and this makes it more consistent.
---
 src/backend/access/brin/brin_bloom.c | 63 +++++++++++++++++++++-------
 1 file changed, 47 insertions(+), 16 deletions(-)

diff --git a/src/backend/access/brin/brin_bloom.c b/src/backend/access/brin/brin_bloom.c
index 6c716924fff..4ff80aeb0cc 100644
--- a/src/backend/access/brin/brin_bloom.c
+++ b/src/backend/access/brin/brin_bloom.c
@@ -259,6 +259,48 @@ typedef struct BloomFilter
 	char		data[FLEXIBLE_ARRAY_MEMBER];
 } BloomFilter;
 
+/*
+ * bloom_filter_size
+ *		Calculate Bloom filter parameters (nbits, nbytes, nhashes).
+ *
+ * Given expected number of distinct values and desired false positive rate,
+ * calculates the optimal parameters of the Bloom filter.
+ *
+ * The resulting parameters are returned through nbytesp (number of bytes),
+ * nbitsp (number of bits) and nhashesp (number of hash functions). If a
+ * pointer is NULL, the parameter is not returned.
+ */
+static void
+bloom_filter_size(int ndistinct, double false_positive_rate,
+				  int *nbytesp, int *nbitsp, int *nhashesp)
+{
+	double	k;
+	int		nbits,
+			nbytes;
+
+	/* sizing bloom filter: -(n * ln(p)) / (ln(2))^2 */
+	nbits = ceil(-(ndistinct * log(false_positive_rate)) / pow(log(2.0), 2));
+
+	/* round m to whole bytes */
+	nbytes = ((nbits + 7) / 8);
+	nbits = nbytes * 8;
+
+	/*
+	 * round(log(2.0) * m / ndistinct), but assume round() may not be
+	 * available on Windows
+	 */
+	k = log(2.0) * nbits / ndistinct;
+	k = (k - floor(k) >= 0.5) ? ceil(k) : floor(k);
+
+	if (nbytesp)
+		*nbytesp = nbytes;
+
+	if (nbitsp)
+		*nbitsp = nbits;
+
+	if (nhashesp)
+		*nhashesp = (int) k;
+}
 
 /*
  * bloom_init
@@ -275,19 +317,15 @@ bloom_init(int ndistinct, double false_positive_rate)
 
 	int			nbits;			/* size of filter / number of bits */
 	int			nbytes;			/* size of filter / number of bytes */
-
-	double		k;				/* number of hash functions */
+	int			nhashes;		/* number of hash functions */
 
 	Assert(ndistinct > 0);
 	Assert((false_positive_rate >= BLOOM_MIN_FALSE_POSITIVE_RATE) &&
 		   (false_positive_rate < BLOOM_MAX_FALSE_POSITIVE_RATE));
 
-	/* sizing bloom filter: -(n * ln(p)) / (ln(2))^2 */
-	nbits = ceil(-(ndistinct * log(false_positive_rate)) / pow(log(2.0), 2));
-
-	/* round m to whole bytes */
-	nbytes = ((nbits + 7) / 8);
-	nbits = nbytes * 8;
+	/* calculate bloom filter size / parameters */
+	bloom_filter_size(ndistinct, false_positive_rate,
+					  &nbytes, &nbits, &nhashes);
 
 	/*
 	 * Reject filters that are obviously too large to store on a page.
@@ -310,13 +348,6 @@ bloom_init(int ndistinct, double false_positive_rate)
 		elog(ERROR, "the bloom filter is too large (%d > %zu)", nbytes,
 			 BloomMaxFilterSize);
 
-	/*
-	 * round(log(2.0) * m / ndistinct), but assume round() may not be
-	 * available on Windows
-	 */
-	k = log(2.0) * nbits / ndistinct;
-	k = (k - floor(k) >= 0.5) ? ceil(k) : floor(k);
-
 	/*
 	 * We allocate the whole filter. Most of it is going to be 0 bits, so the
 	 * varlena is easy to compress.
@@ -326,7 +357,7 @@ bloom_init(int ndistinct, double false_positive_rate)
 	filter = (BloomFilter *) palloc0(len);
 
 	filter->flags = 0;
-	filter->nhashes = (int) k;
+	filter->nhashes = nhashes;
 	filter->nbits = nbits;
 
 	SET_VARSIZE(filter, len);
-- 
2.39.1



  [text/x-patch] 0004-Add-minmax-multi-inequality-tests-20230218.patch (16.8K, ../../[email protected]/5-0004-Add-minmax-multi-inequality-tests-20230218.patch)
  download | inline diff:
From 9371feb7462bf565399cdbf844849a41be135803 Mon Sep 17 00:00:00 2001
From: Tomas Vondra <[email protected]>
Date: Fri, 17 Feb 2023 02:25:52 +0100
Subject: [PATCH 4/9] Add minmax-multi inequality tests

Add tests exercising the other scan key strategies, to improve test
coverage.
---
 src/test/regress/expected/brin_multi.out | 363 +++++++++++++++++++++++
 src/test/regress/sql/brin_multi.sql      | 136 +++++++++
 2 files changed, 499 insertions(+)

diff --git a/src/test/regress/expected/brin_multi.out b/src/test/regress/expected/brin_multi.out
index f3309f433f8..98e1cdaa361 100644
--- a/src/test/regress/expected/brin_multi.out
+++ b/src/test/regress/expected/brin_multi.out
@@ -460,3 +460,366 @@ EXPLAIN (COSTS OFF) SELECT * FROM brin_test_multi WHERE b = 1;
    Filter: (b = 1)
 (2 rows)
 
+-- do some inequality tests
+CREATE TABLE brin_test_multi_1 (a INT, b BIGINT) WITH (fillfactor=10);
+INSERT INTO brin_test_multi_1
+SELECT i/5 + mod(911 * i + 483, 25),
+       i/10 + mod(751 * i + 221, 41)
+  FROM generate_series(1,1000) s(i);
+CREATE INDEX brin_test_multi_1_idx_1 ON brin_test_multi_1 USING brin (a int4_minmax_multi_ops) WITH (pages_per_range=5);
+CREATE INDEX brin_test_multi_1_idx_2 ON brin_test_multi_1 USING brin (b int8_minmax_multi_ops) WITH (pages_per_range=5);
+SET enable_seqscan=off;
+-- int: less than
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a < 37;
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_test_multi_1
+         Recheck Cond: (a < 37)
+         ->  Bitmap Index Scan on brin_test_multi_1_idx_1
+               Index Cond: (a < 37)
+(5 rows)
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a < 37;
+ count 
+-------
+   124
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a < 113;
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_test_multi_1
+         Recheck Cond: (a < 113)
+         ->  Bitmap Index Scan on brin_test_multi_1_idx_1
+               Index Cond: (a < 113)
+(5 rows)
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a < 113;
+ count 
+-------
+   504
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a <= 177;
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_test_multi_1
+         Recheck Cond: (a <= 177)
+         ->  Bitmap Index Scan on brin_test_multi_1_idx_1
+               Index Cond: (a <= 177)
+(5 rows)
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a <= 177;
+ count 
+-------
+   829
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a <= 25;
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_test_multi_1
+         Recheck Cond: (a <= 25)
+         ->  Bitmap Index Scan on brin_test_multi_1_idx_1
+               Index Cond: (a <= 25)
+(5 rows)
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a <= 25;
+ count 
+-------
+    69
+(1 row)
+
+-- int: greater than
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a > 120;
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_test_multi_1
+         Recheck Cond: (a > 120)
+         ->  Bitmap Index Scan on brin_test_multi_1_idx_1
+               Index Cond: (a > 120)
+(5 rows)
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a > 120;
+ count 
+-------
+   456
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a >= 180;
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_test_multi_1
+         Recheck Cond: (a >= 180)
+         ->  Bitmap Index Scan on brin_test_multi_1_idx_1
+               Index Cond: (a >= 180)
+(5 rows)
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a >= 180;
+ count 
+-------
+   161
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a > 71;
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_test_multi_1
+         Recheck Cond: (a > 71)
+         ->  Bitmap Index Scan on brin_test_multi_1_idx_1
+               Index Cond: (a > 71)
+(5 rows)
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a > 71;
+ count 
+-------
+   701
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a >= 63;
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_test_multi_1
+         Recheck Cond: (a >= 63)
+         ->  Bitmap Index Scan on brin_test_multi_1_idx_1
+               Index Cond: (a >= 63)
+(5 rows)
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a >= 63;
+ count 
+-------
+   746
+(1 row)
+
+-- bigint: less than
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b < 73;
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_test_multi_1
+         Recheck Cond: (b < 73)
+         ->  Bitmap Index Scan on brin_test_multi_1_idx_2
+               Index Cond: (b < 73)
+(5 rows)
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b < 73;
+ count 
+-------
+   529
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b <= 47;
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_test_multi_1
+         Recheck Cond: (b <= 47)
+         ->  Bitmap Index Scan on brin_test_multi_1_idx_2
+               Index Cond: (b <= 47)
+(5 rows)
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b <= 47;
+ count 
+-------
+   279
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b < 199;
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_test_multi_1
+         Recheck Cond: (b < 199)
+         ->  Bitmap Index Scan on brin_test_multi_1_idx_2
+               Index Cond: (b < 199)
+(5 rows)
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b < 199;
+ count 
+-------
+  1000
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b <= 150;
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_test_multi_1
+         Recheck Cond: (b <= 150)
+         ->  Bitmap Index Scan on brin_test_multi_1_idx_2
+               Index Cond: (b <= 150)
+(5 rows)
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b <= 150;
+ count 
+-------
+  1000
+(1 row)
+
+-- bigint: greater than
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b > 93;
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_test_multi_1
+         Recheck Cond: (b > 93)
+         ->  Bitmap Index Scan on brin_test_multi_1_idx_2
+               Index Cond: (b > 93)
+(5 rows)
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b > 93;
+ count 
+-------
+   261
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b > 37;
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_test_multi_1
+         Recheck Cond: (b > 37)
+         ->  Bitmap Index Scan on brin_test_multi_1_idx_2
+               Index Cond: (b > 37)
+(5 rows)
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b > 37;
+ count 
+-------
+   821
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b >= 215;
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_test_multi_1
+         Recheck Cond: (b >= 215)
+         ->  Bitmap Index Scan on brin_test_multi_1_idx_2
+               Index Cond: (b >= 215)
+(5 rows)
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b >= 215;
+ count 
+-------
+     0
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b > 201;
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_test_multi_1
+         Recheck Cond: (b > 201)
+         ->  Bitmap Index Scan on brin_test_multi_1_idx_2
+               Index Cond: (b > 201)
+(5 rows)
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b > 201;
+ count 
+-------
+     0
+(1 row)
+
+DROP TABLE brin_test_multi_1;
+RESET enable_seqscan;
+-- do some inequality tests for varlena data types
+CREATE TABLE brin_test_multi_2 (a UUID) WITH (fillfactor=10);
+INSERT INTO brin_test_multi_2
+SELECT v::uuid FROM (SELECT row_number() OVER (ORDER BY v) c, v FROM (SELECT md5((i/13)::text) AS v FROM generate_series(1,1000) s(i)) foo) bar ORDER BY c + 25 * random();
+CREATE INDEX brin_test_multi_2_idx ON brin_test_multi_2 USING brin (a uuid_minmax_multi_ops) WITH (pages_per_range=5);
+SET enable_seqscan=off;
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_2 WHERE a < '33e75ff0-9dd6-01bb-e69f-351039152189';
+                                  QUERY PLAN                                  
+------------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_test_multi_2
+         Recheck Cond: (a < '33e75ff0-9dd6-01bb-e69f-351039152189'::uuid)
+         ->  Bitmap Index Scan on brin_test_multi_2_idx
+               Index Cond: (a < '33e75ff0-9dd6-01bb-e69f-351039152189'::uuid)
+(5 rows)
+
+SELECT COUNT(*) FROM brin_test_multi_2 WHERE a < '33e75ff0-9dd6-01bb-e69f-351039152189';
+ count 
+-------
+   195
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_2 WHERE a > '33e75ff0-9dd6-01bb-e69f-351039152189';
+                                  QUERY PLAN                                  
+------------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_test_multi_2
+         Recheck Cond: (a > '33e75ff0-9dd6-01bb-e69f-351039152189'::uuid)
+         ->  Bitmap Index Scan on brin_test_multi_2_idx
+               Index Cond: (a > '33e75ff0-9dd6-01bb-e69f-351039152189'::uuid)
+(5 rows)
+
+SELECT COUNT(*) FROM brin_test_multi_2 WHERE a > '33e75ff0-9dd6-01bb-e69f-351039152189';
+ count 
+-------
+   792
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_2 WHERE a <= 'f457c545-a9de-d88f-18ec-ee47145a72c0';
+                                  QUERY PLAN                                   
+-------------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_test_multi_2
+         Recheck Cond: (a <= 'f457c545-a9de-d88f-18ec-ee47145a72c0'::uuid)
+         ->  Bitmap Index Scan on brin_test_multi_2_idx
+               Index Cond: (a <= 'f457c545-a9de-d88f-18ec-ee47145a72c0'::uuid)
+(5 rows)
+
+SELECT COUNT(*) FROM brin_test_multi_2 WHERE a <= 'f457c545-a9de-d88f-18ec-ee47145a72c0';
+ count 
+-------
+   961
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_2 WHERE a >= 'c51ce410-c124-a10e-0db5-e4b97fc2af39';
+                                  QUERY PLAN                                   
+-------------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_test_multi_2
+         Recheck Cond: (a >= 'c51ce410-c124-a10e-0db5-e4b97fc2af39'::uuid)
+         ->  Bitmap Index Scan on brin_test_multi_2_idx
+               Index Cond: (a >= 'c51ce410-c124-a10e-0db5-e4b97fc2af39'::uuid)
+(5 rows)
+
+SELECT COUNT(*) FROM brin_test_multi_2 WHERE a >= 'c51ce410-c124-a10e-0db5-e4b97fc2af39';
+ count 
+-------
+   272
+(1 row)
+
+DROP TABLE brin_test_multi_2;
+RESET enable_seqscan;
diff --git a/src/test/regress/sql/brin_multi.sql b/src/test/regress/sql/brin_multi.sql
index 2189b6ccf42..a59e182bc25 100644
--- a/src/test/regress/sql/brin_multi.sql
+++ b/src/test/regress/sql/brin_multi.sql
@@ -414,3 +414,139 @@ VACUUM ANALYZE brin_test_multi;
 EXPLAIN (COSTS OFF) SELECT * FROM brin_test_multi WHERE a = 1;
 -- Ensure brin index is not used when values are not correlated
 EXPLAIN (COSTS OFF) SELECT * FROM brin_test_multi WHERE b = 1;
+
+
+-- do some inequality tests
+CREATE TABLE brin_test_multi_1 (a INT, b BIGINT) WITH (fillfactor=10);
+INSERT INTO brin_test_multi_1
+SELECT i/5 + mod(911 * i + 483, 25),
+       i/10 + mod(751 * i + 221, 41)
+  FROM generate_series(1,1000) s(i);
+
+CREATE INDEX brin_test_multi_1_idx_1 ON brin_test_multi_1 USING brin (a int4_minmax_multi_ops) WITH (pages_per_range=5);
+CREATE INDEX brin_test_multi_1_idx_2 ON brin_test_multi_1 USING brin (b int8_minmax_multi_ops) WITH (pages_per_range=5);
+
+SET enable_seqscan=off;
+
+-- int: less than
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a < 37;
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a < 37;
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a < 113;
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a < 113;
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a <= 177;
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a <= 177;
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a <= 25;
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a <= 25;
+
+-- int: greater than
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a > 120;
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a > 120;
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a >= 180;
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a >= 180;
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a > 71;
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a > 71;
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a >= 63;
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE a >= 63;
+
+-- bigint: less than
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b < 73;
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b < 73;
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b <= 47;
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b <= 47;
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b < 199;
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b < 199;
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b <= 150;
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b <= 150;
+
+-- bigint: greater than
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b > 93;
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b > 93;
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b > 37;
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b > 37;
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b >= 215;
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b >= 215;
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b > 201;
+
+SELECT COUNT(*) FROM brin_test_multi_1 WHERE b > 201;
+
+DROP TABLE brin_test_multi_1;
+RESET enable_seqscan;
+
+
+-- do some inequality tests for varlena data types
+CREATE TABLE brin_test_multi_2 (a UUID) WITH (fillfactor=10);
+INSERT INTO brin_test_multi_2
+SELECT v::uuid FROM (SELECT row_number() OVER (ORDER BY v) c, v FROM (SELECT md5((i/13)::text) AS v FROM generate_series(1,1000) s(i)) foo) bar ORDER BY c + 25 * random();
+
+CREATE INDEX brin_test_multi_2_idx ON brin_test_multi_2 USING brin (a uuid_minmax_multi_ops) WITH (pages_per_range=5);
+
+SET enable_seqscan=off;
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_2 WHERE a < '33e75ff0-9dd6-01bb-e69f-351039152189';
+
+SELECT COUNT(*) FROM brin_test_multi_2 WHERE a < '33e75ff0-9dd6-01bb-e69f-351039152189';
+
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_2 WHERE a > '33e75ff0-9dd6-01bb-e69f-351039152189';
+
+SELECT COUNT(*) FROM brin_test_multi_2 WHERE a > '33e75ff0-9dd6-01bb-e69f-351039152189';
+
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_2 WHERE a <= 'f457c545-a9de-d88f-18ec-ee47145a72c0';
+
+SELECT COUNT(*) FROM brin_test_multi_2 WHERE a <= 'f457c545-a9de-d88f-18ec-ee47145a72c0';
+
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_test_multi_2 WHERE a >= 'c51ce410-c124-a10e-0db5-e4b97fc2af39';
+
+SELECT COUNT(*) FROM brin_test_multi_2 WHERE a >= 'c51ce410-c124-a10e-0db5-e4b97fc2af39';
+
+DROP TABLE brin_test_multi_2;
+RESET enable_seqscan;
-- 
2.39.1



  [text/x-patch] 0005-Introduce-BRIN_PROCNUM_PREPROCESS-procedure-20230218.patch (5.2K, ../../[email protected]/6-0005-Introduce-BRIN_PROCNUM_PREPROCESS-procedure-20230218.patch)
  download | inline diff:
From f8aa609e5ba8213613faff8faa4bddc13f829256 Mon Sep 17 00:00:00 2001
From: Tomas Vondra <[email protected]>
Date: Tue, 14 Feb 2023 20:29:33 +0100
Subject: [PATCH 5/9] Introduce BRIN_PROCNUM_PREPROCESS procedure

Allow BRIN opclasses to define an optional procedure to preprocess scan
keys, and call it from brinrescan(). This allows the opclass to modify
the keys in various ways - sort arrays, calculate hashes, ...

Note: The procedure is optional, so existing opclasses don't need to add
it. But if it uses the existing BRIN_PROCNUM_CONSISTENT function, it'll
get broken. If we want to make this backwards-compatible, we might check
if BRIN_PROCNUM_PREPROCESS exist from BRIN_PROCNUM_CONSISTENT, and
adjust behavior based on that.
---
 src/backend/access/brin/brin.c     | 86 +++++++++++++++++++++++++++---
 src/include/access/brin_internal.h |  1 +
 2 files changed, 79 insertions(+), 8 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index de1427a1e0e..ada7647f3c1 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -66,6 +66,12 @@ typedef struct BrinOpaque
 	BlockNumber bo_pagesPerRange;
 	BrinRevmap *bo_rmAccess;
 	BrinDesc   *bo_bdesc;
+
+	/* preprocessed scan keys */
+	int			bo_numScanKeys;		/* number of (preprocessed) scan keys */
+	ScanKey	   *bo_scanKeys;		/* modified copy of scan->keyData */
+	MemoryContext bo_scanKeysCxt;	/* scan-lifespan context for key data */
+
 } BrinOpaque;
 
 #define BRIN_ALL_BLOCKRANGES	InvalidBlockNumber
@@ -334,6 +340,11 @@ brinbeginscan(Relation r, int nkeys, int norderbys)
 	opaque->bo_rmAccess = brinRevmapInitialize(r, &opaque->bo_pagesPerRange,
 											   scan->xs_snapshot);
 	opaque->bo_bdesc = brin_build_desc(r);
+
+	opaque->bo_numScanKeys = 0;
+	opaque->bo_scanKeys = NULL;
+	opaque->bo_scanKeysCxt = NULL;
+
 	scan->opaque = opaque;
 
 	return scan;
@@ -456,7 +467,7 @@ bringetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
 	/* Preprocess the scan keys - split them into per-attribute arrays. */
 	for (int keyno = 0; keyno < scan->numberOfKeys; keyno++)
 	{
-		ScanKey		key = &scan->keyData[keyno];
+		ScanKey		key = opaque->bo_scanKeys[keyno];
 		AttrNumber	keyattno = key->sk_attno;
 
 		/*
@@ -728,17 +739,76 @@ void
 brinrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
 		   ScanKey orderbys, int norderbys)
 {
-	/*
-	 * Other index AMs preprocess the scan keys at this point, or sometime
-	 * early during the scan; this lets them optimize by removing redundant
-	 * keys, or doing early returns when they are impossible to satisfy; see
-	 * _bt_preprocess_keys for an example.  Something like that could be added
-	 * here someday, too.
-	 */
+	BrinOpaque *bo = (BrinOpaque *) scan->opaque;
+	Relation	idxRel = scan->indexRelation;
+	MemoryContext	oldcxt;
 
 	if (scankey && scan->numberOfKeys > 0)
 		memmove(scan->keyData, scankey,
 				scan->numberOfKeys * sizeof(ScanKeyData));
+
+	/*
+	 * Use the BRIN_PROCNUM_PREPROCESS procedure (if defined) to preprocess
+	 * the scan keys. The procedure may do anything, as long as the result
+	 * looks like a ScanKey. If there's no procedure, we keep the original
+	 * scan key.
+	 *
+	 * FIXME Probably need fixes to handle NULLs correctly.
+	 */
+	if (bo->bo_scanKeysCxt == NULL)
+		bo->bo_scanKeysCxt = AllocSetContextCreate(CurrentMemoryContext,
+												   "BRIN scan keys context",
+												   ALLOCSET_SMALL_SIZES);
+	else
+		MemoryContextReset(bo->bo_scanKeysCxt);
+
+	oldcxt = MemoryContextSwitchTo(bo->bo_scanKeysCxt);
+
+	bo->bo_scanKeys = palloc0(sizeof(ScanKey) * nscankeys);
+
+	for (int i = 0; i < nscankeys; i++)
+	{
+		FmgrInfo   *finfo;
+		ScanKey		key = &scan->keyData[i];
+		Oid			procid;
+		Datum		ret;
+
+		/*
+		 * If the scan argument is NULL, nothing to preprocess.
+		 *
+		 * XXX Maybe we should leave these checks up to the _preprocess
+		 * procedures, in case there's something smart they wan to do?
+		 * But SK_ISNULL is handled by bringetbitmap() so doing it here
+		 * seems reasonable.
+		 */
+		if (key->sk_flags & SK_ISNULL)
+		{
+			bo->bo_scanKeys[i] = key;
+			continue;
+		}
+
+		/* fetch key preprocess support procedure if specified */
+		procid = index_getprocid(idxRel, key->sk_attno,
+								 BRIN_PROCNUM_PREPROCESS);
+
+		/* not specified, just point to the original key */
+		if (!OidIsValid(procid))
+		{
+			bo->bo_scanKeys[i] = key;
+			continue;
+		}
+
+		finfo = index_getprocinfo(idxRel, key->sk_attno,
+								  BRIN_PROCNUM_PREPROCESS);
+
+		ret = FunctionCall2(finfo,
+							PointerGetDatum(bo->bo_bdesc),
+							PointerGetDatum(key));
+
+		bo->bo_scanKeys[i] = (ScanKey) DatumGetPointer(ret);
+	}
+
+	MemoryContextSwitchTo(oldcxt);
 }
 
 /*
diff --git a/src/include/access/brin_internal.h b/src/include/access/brin_internal.h
index 97ddc925b27..d6a51f2bc49 100644
--- a/src/include/access/brin_internal.h
+++ b/src/include/access/brin_internal.h
@@ -73,6 +73,7 @@ typedef struct BrinDesc
 #define BRIN_PROCNUM_UNION			4
 #define BRIN_MANDATORY_NPROCS		4
 #define BRIN_PROCNUM_OPTIONS 		5	/* optional */
+#define BRIN_PROCNUM_PREPROCESS		6	/* optional */
 /* procedure numbers up to 10 are reserved for BRIN future expansion */
 #define BRIN_FIRST_OPTIONAL_PROCNUM 11
 #define BRIN_LAST_OPTIONAL_PROCNUM	15
-- 
2.39.1



  [text/x-patch] 0006-Support-SK_SEARCHARRAY-in-BRIN-minmax-20230218.patch (69.5K, ../../[email protected]/7-0006-Support-SK_SEARCHARRAY-in-BRIN-minmax-20230218.patch)
  download | inline diff:
From 1c1f36006dea7a931f7fe66632045348cc06f2c3 Mon Sep 17 00:00:00 2001
From: Tomas Vondra <[email protected]>
Date: Fri, 10 Feb 2023 16:07:57 +0100
Subject: [PATCH 6/9] Support SK_SEARCHARRAY in BRIN minmax

Set "amsearcharray=true" for BRIN, and extend the minmax opclass to
handle both scalar values and arrays. This allows handling conditions

    ... WHERE a IN (1, 2, 43, 2132, 134)

    ... WHERE a = ANY(ARRAY[34, 45, -1, 234])

    ... WHERE a <= ANY(ARRAY[4938, 282, 2934])

more efficiently - until now we simply built the bitmap for each
scalar value, so we walked the BRIN index many times. Which may be quite
expensive for indexes with many ranges (large tables and/or low
pages_per_range).

There's a couple problems / open questions / TODO items:

- The other opclasses don't handle SK_SEARCHARRAY yet.

- The array is always searched linearly, so this may be costly for large
  arrays (with many elements).

- The array is deconstructed again for each range. We should reuse this,
  somehow.
---
 src/backend/access/brin/brin.c          |   3 +-
 src/backend/access/brin/brin_minmax.c   | 375 +++++++++--
 src/backend/access/brin/brin_validate.c |   4 +
 src/include/catalog/pg_amproc.dat       |  64 ++
 src/include/catalog/pg_proc.dat         |   3 +
 src/test/regress/expected/amutils.out   |   2 +-
 src/test/regress/expected/brin.out      | 858 ++++++++++++++++++++++++
 src/test/regress/sql/brin.sql           | 283 ++++++++
 8 files changed, 1552 insertions(+), 40 deletions(-)

diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index ada7647f3c1..9293b9e83cd 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -38,6 +38,7 @@
 #include "utils/datum.h"
 #include "utils/guc.h"
 #include "utils/index_selfuncs.h"
+#include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
 
@@ -107,7 +108,7 @@ brinhandler(PG_FUNCTION_ARGS)
 	amroutine->amcanunique = false;
 	amroutine->amcanmulticol = true;
 	amroutine->amoptionalkey = true;
-	amroutine->amsearcharray = false;
+	amroutine->amsearcharray = true;
 	amroutine->amsearchnulls = true;
 	amroutine->amstorage = true;
 	amroutine->amclusterable = false;
diff --git a/src/backend/access/brin/brin_minmax.c b/src/backend/access/brin/brin_minmax.c
index 2431591be65..d77474fa1de 100644
--- a/src/backend/access/brin/brin_minmax.c
+++ b/src/backend/access/brin/brin_minmax.c
@@ -16,11 +16,21 @@
 #include "access/stratnum.h"
 #include "catalog/pg_amop.h"
 #include "catalog/pg_type.h"
+#include "utils/array.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
 #include "utils/syscache.h"
+#include "utils/sortsupport.h"
+
+/*
+ * We use some private sk_flags bits in preprocessed scan keys.  We're allowed
+ * to use bits 16-31 (see skey.h).  The uppermost bits are copied from the
+ * index's indoption[] array entry for the index attribute.
+ */
+#define SK_BRIN_SORTED	0x00010000	/* deconstructed and sorted array */
+
 
 typedef struct MinmaxOpaque
 {
@@ -126,6 +136,158 @@ brin_minmax_add_value(PG_FUNCTION_ARGS)
 	PG_RETURN_BOOL(updated);
 }
 
+
+static int
+compare_array_values(const void *a, const void *b, void *arg)
+{
+	Datum	da = * (Datum *) a;
+	Datum	db = * (Datum *) b;
+	SortSupport	ssup = (SortSupport) arg;
+
+	return ApplySortComparator(da, false, db, false, ssup);
+}
+
+/*
+ * lower_boundary
+ *		Determine lowest index so that (values[index] >= minvalue).
+ *
+ * The array of values is expected to be sorted, so this is the first value
+ * that may fall into the [minvalue, maxvalue] range, as it exceeds minval.
+ * It's not guaranteed, though, as it might exceed maxvalue too.
+ */
+static int
+lower_boundary(Datum *values, int nvalues, Datum minvalue, SortSupport ssup)
+{
+	int		start = 0,
+			end = (nvalues - 1);
+
+	/* everything exceeds minval and might match */
+	if (compare_array_values(&minvalue, &values[start], ssup) <= 0)
+		return 0;
+
+	/* nothing could match */
+	if (compare_array_values(&minvalue, &values[end], ssup) > 0)
+		return nvalues;
+
+	while ((end - start) > 0)
+	{
+		int midpoint;
+		int r;
+
+		midpoint = start + (end - start) / 2;
+
+		r = compare_array_values(&minvalue, &values[midpoint], ssup);
+
+		if (r > 0)
+			start = Max(midpoint, start + 1);
+		else
+			end = midpoint;
+	}
+
+	/* the value should meet the (v >=minvalue) requirement */
+	Assert(compare_array_values(&values[start], &minvalue, ssup) >= 0);
+
+	/* we know start can't be 0, so it's legal to subtract 1 */
+	Assert(compare_array_values(&values[start-1], &minvalue, ssup) < 0);
+
+	return start;
+}
+
+typedef struct ScanKeyArray {
+	Oid		typeid;
+	int		nelements;
+	Datum  *elements;
+} ScanKeyArray;
+
+Datum
+brin_minmax_preprocess(PG_FUNCTION_ARGS)
+{
+	// BrinDesc   *bdesc = (BrinDesc *) PG_GETARG_POINTER(0);
+	ScanKey		key = (ScanKey) PG_GETARG_POINTER(1);
+	ScanKey		newkey;
+	ScanKeyArray *scanarray;
+
+	ArrayType  *arrayval;
+	int16		elmlen;
+	bool		elmbyval;
+	char		elmalign;
+	int			num_elems;
+	Datum	   *elem_values;
+	bool	   *elem_nulls;
+	TypeCacheEntry *type;
+	SortSupportData ssup;
+
+	/* number of non-null elements in the array */
+	int			num_nonnulls;
+
+	/*
+	 * ignore scalar keys
+	 *
+	 * XXX Maybe we should preprocess scalar keys too. It'd make the consistent
+	 * function simpler by removing the branching.
+	 */
+	if (!(key->sk_flags & SK_SEARCHARRAY))
+		PG_RETURN_POINTER(key);
+
+	arrayval = DatumGetArrayTypeP(key->sk_argument);
+
+	get_typlenbyvalalign(ARR_ELEMTYPE(arrayval),
+						 &elmlen, &elmbyval, &elmalign);
+
+	deconstruct_array(arrayval,
+					  ARR_ELEMTYPE(arrayval),
+					  elmlen, elmbyval, elmalign,
+					  &elem_values, &elem_nulls, &num_elems);
+
+	/* eliminate NULL elements */
+	num_nonnulls = 0;
+	for (int i = 0; i < num_elems; i++)
+	{
+		/* skip NULL elements */
+		if (elem_nulls[i])
+			continue;
+
+		/* if needed, move the non-NULL ones */
+		if (num_nonnulls != i)
+			elem_values[num_nonnulls] = elem_values[i];
+
+		num_nonnulls++;
+	}
+
+	num_elems = num_nonnulls;
+
+	type = lookup_type_cache(ARR_ELEMTYPE(arrayval), TYPECACHE_LT_OPR);
+
+	memset(&ssup, 0, sizeof(SortSupportData));
+
+	ssup.ssup_collation = key->sk_collation;
+	ssup.ssup_cxt = CurrentMemoryContext;
+
+	PrepareSortSupportFromOrderingOp(type->lt_opr, &ssup);
+
+	qsort_interruptible(elem_values, num_elems, sizeof(Datum),
+						compare_array_values, &ssup);
+
+	scanarray = palloc0(sizeof(ScanKeyArray));
+	scanarray->typeid = ARR_ELEMTYPE(arrayval);
+	scanarray->nelements = num_elems;
+	scanarray->elements = elem_values;
+
+	newkey = palloc0(sizeof(ScanKeyData));
+
+	ScanKeyEntryInitializeWithInfo(newkey,
+								   (key->sk_flags | SK_BRIN_SORTED),
+								   key->sk_attno,
+								   key->sk_strategy,
+								   key->sk_subtype,
+								   key->sk_collation,
+								   &key->sk_func,
+								   PointerGetDatum(scanarray));
+
+	PG_RETURN_POINTER(newkey);
+}
+
+
 /*
  * 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
@@ -157,46 +319,183 @@ brin_minmax_consistent(PG_FUNCTION_ARGS)
 	attno = key->sk_attno;
 	subtype = key->sk_subtype;
 	value = key->sk_argument;
-	switch (key->sk_strategy)
+
+	/*
+	 * For regular (scalar) scan keys, we simply compare the value to the
+	 * range min/max values, and we're done. For preprocessed SK_SEARCHARRAY
+	 * keys we need to loop through the deparsed values.
+	 */
+	if (likely(!(key->sk_flags & SK_BRIN_SORTED)))
 	{
-		case BTLessStrategyNumber:
-		case BTLessEqualStrategyNumber:
-			finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
-												 key->sk_strategy);
-			matches = FunctionCall2Coll(finfo, colloid, column->bv_values[0],
-										value);
-			break;
-		case BTEqualStrategyNumber:
-
-			/*
-			 * In the equality case (WHERE col = someval), we want to return
-			 * the current page range if the minimum value in the range <=
-			 * scan key, and the maximum value >= scan key.
-			 */
-			finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
-												 BTLessEqualStrategyNumber);
-			matches = FunctionCall2Coll(finfo, colloid, column->bv_values[0],
-										value);
-			if (!DatumGetBool(matches))
+		switch (key->sk_strategy)
+		{
+			case BTLessStrategyNumber:
+			case BTLessEqualStrategyNumber:
+				finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
+													 key->sk_strategy);
+				matches = FunctionCall2Coll(finfo, colloid, column->bv_values[0],
+											value);
+				break;
+			case BTEqualStrategyNumber:
+
+				/*
+				 * In the equality case (WHERE col = someval), we want to return
+				 * the current page range if the minimum value in the range <=
+				 * scan key, and the maximum value >= scan key.
+				 */
+				finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
+													 BTLessEqualStrategyNumber);
+				matches = FunctionCall2Coll(finfo, colloid, column->bv_values[0],
+											value);
+				if (!DatumGetBool(matches))
+					break;
+				/* max() >= scankey */
+				finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
+													 BTGreaterEqualStrategyNumber);
+				matches = FunctionCall2Coll(finfo, colloid, column->bv_values[1],
+											value);
+				break;
+			case BTGreaterEqualStrategyNumber:
+			case BTGreaterStrategyNumber:
+				finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
+													 key->sk_strategy);
+				matches = FunctionCall2Coll(finfo, colloid, column->bv_values[1],
+											value);
+				break;
+			default:
+				/* shouldn't happen */
+				elog(ERROR, "invalid strategy number %d", key->sk_strategy);
+				matches = 0;
+				break;
+		}
+	}
+	else
+	{
+		ScanKeyArray *array = (ScanKeyArray *) value;
+
+		/* can happen if the IN list contained just NULLs */
+		if (array->nelements == 0)
+			PG_RETURN_BOOL(false);
+
+		switch (key->sk_strategy)
+		{
+			case BTLessStrategyNumber:
+			case BTLessEqualStrategyNumber:
+				/*
+				 * Check the last (largest) value in the array - at least this
+				 * value has to exceed the range minval.
+				 */
+				finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
+													 key->sk_strategy);
+				matches = FunctionCall2Coll(finfo, colloid, column->bv_values[0],
+											array->elements[array->nelements-1]);
+				break;
+			case BTEqualStrategyNumber:
+
+				/*
+				 * In the equality case (WHERE col = someval), we want to return
+				 * the current page range if the minimum value in the range <=
+				 * scan key, and the maximum value >= scan key.
+				 *
+				 * We do this in two phases. We check the array min/max values to see
+				 * if there even can be a matching value, and if yes we do a binary
+				 * search to find the first value that exceeds range minval. And then
+				 * we check if it actually matches the range.
+				 *
+				 * XXX The first phase is probably unnecessary, because lower_bound()
+				 * does pretty much exactly that too.
+				 */
+				{
+					Datum val;
+					SortSupportData ssup;
+					int			lower;
+					TypeCacheEntry *type;
+
+					/* Is the first (smallest) value after the BRIN range? */
+					val = array->elements[0];
+
+					finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
+														 BTLessEqualStrategyNumber);
+					matches = FunctionCall2Coll(finfo, colloid, val, column->bv_values[1]);
+
+					/* minval > max(range values) */
+					if (!DatumGetBool(matches))
+						break;
+
+					/* Is the last (largest) value before the BRIN range? */
+					val = array->elements[array->nelements-1];
+
+					finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
+														 BTGreaterEqualStrategyNumber);
+					matches = FunctionCall2Coll(finfo, colloid, val, column->bv_values[0]);
+
+					/* maxval < min(range values) */
+					if (!DatumGetBool(matches))
+						break;
+
+					/*
+					 * OK, there might be some values matching the range. We have
+					 * to search them one by one, or perhaps try binsearch.
+					 */
+					type = lookup_type_cache(array->typeid, TYPECACHE_LT_OPR);
+
+					memset(&ssup, 0, sizeof(SortSupportData));
+
+					ssup.ssup_collation = key->sk_collation;
+					ssup.ssup_cxt = CurrentMemoryContext;
+
+					PrepareSortSupportFromOrderingOp(type->lt_opr, &ssup);
+
+					lower = lower_boundary(array->elements, array->nelements, column->bv_values[0], &ssup);
+
+					/* no elements can possibly match */
+					if (lower == array->nelements)
+					{
+						matches = BoolGetDatum(false);
+						break;
+					}
+
+					/*
+					 * OK, the first element must match the upper boundary too
+					 * (if it does not, no following elements can).
+					 */
+					val = array->elements[lower];
+
+					/*
+					 * In the equality case (WHERE col = someval), we want to return
+					 * the current page range if the minimum value in the range <=
+					 * scan key, and the maximum value >= scan key.
+					 */
+					finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
+														 BTLessEqualStrategyNumber);
+					matches = FunctionCall2Coll(finfo, colloid, column->bv_values[0],
+												val);
+					if (!DatumGetBool(matches))
+						break;
+					/* max() >= scankey */
+					finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
+														 BTGreaterEqualStrategyNumber);
+					matches = FunctionCall2Coll(finfo, colloid, column->bv_values[1],
+												val);
+					break;
+				}
+			case BTGreaterEqualStrategyNumber:
+			case BTGreaterStrategyNumber:
+				/*
+				 * Check the first (smallest) value in the array - at least this
+				 * value has to be smaller than the range maxval.
+				 */
+				finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
+													 key->sk_strategy);
+				matches = FunctionCall2Coll(finfo, colloid, column->bv_values[1],
+											array->elements[0]);
+				break;
+			default:
+				/* shouldn't happen */
+				elog(ERROR, "invalid strategy number %d", key->sk_strategy);
+				matches = 0;
 				break;
-			/* max() >= scankey */
-			finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
-												 BTGreaterEqualStrategyNumber);
-			matches = FunctionCall2Coll(finfo, colloid, column->bv_values[1],
-										value);
-			break;
-		case BTGreaterEqualStrategyNumber:
-		case BTGreaterStrategyNumber:
-			finfo = minmax_get_strategy_procinfo(bdesc, attno, subtype,
-												 key->sk_strategy);
-			matches = FunctionCall2Coll(finfo, colloid, column->bv_values[1],
-										value);
-			break;
-		default:
-			/* shouldn't happen */
-			elog(ERROR, "invalid strategy number %d", key->sk_strategy);
-			matches = 0;
-			break;
+		}
 	}
 
 	PG_RETURN_DATUM(matches);
diff --git a/src/backend/access/brin/brin_validate.c b/src/backend/access/brin/brin_validate.c
index c8edfb37591..0889e24bc01 100644
--- a/src/backend/access/brin/brin_validate.c
+++ b/src/backend/access/brin/brin_validate.c
@@ -108,6 +108,10 @@ brinvalidate(Oid opclassoid)
 			case BRIN_PROCNUM_OPTIONS:
 				ok = check_amoptsproc_signature(procform->amproc);
 				break;
+			case BRIN_PROCNUM_PREPROCESS:
+				ok = check_amproc_signature(procform->amproc, INTERNALOID, true,
+											2, 2, INTERNALOID, INTERNALOID);
+				break;
 			default:
 				/* Complain if it's not a valid optional proc number */
 				if (procform->amprocnum < BRIN_FIRST_OPTIONAL_PROCNUM ||
diff --git a/src/include/catalog/pg_amproc.dat b/src/include/catalog/pg_amproc.dat
index 5b950129de0..166681c31ef 100644
--- a/src/include/catalog/pg_amproc.dat
+++ b/src/include/catalog/pg_amproc.dat
@@ -804,6 +804,8 @@
   amproc => 'brin_minmax_consistent' },
 { amprocfamily => 'brin/bytea_minmax_ops', amproclefttype => 'bytea',
   amprocrighttype => 'bytea', amprocnum => '4', amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/bytea_minmax_ops', amproclefttype => 'bytea',
+  amprocrighttype => 'bytea', amprocnum => '6', amproc => 'brin_minmax_preprocess' },
 
 # bloom bytea
 { amprocfamily => 'brin/bytea_bloom_ops', amproclefttype => 'bytea',
@@ -835,6 +837,8 @@
   amproc => 'brin_minmax_consistent' },
 { amprocfamily => 'brin/char_minmax_ops', amproclefttype => 'char',
   amprocrighttype => 'char', amprocnum => '4', amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/char_minmax_ops', amproclefttype => 'char',
+  amprocrighttype => 'char', amprocnum => '6', amproc => 'brin_minmax_preprocess' },
 
 # bloom "char"
 { amprocfamily => 'brin/char_bloom_ops', amproclefttype => 'char',
@@ -864,6 +868,8 @@
   amproc => 'brin_minmax_consistent' },
 { amprocfamily => 'brin/name_minmax_ops', amproclefttype => 'name',
   amprocrighttype => 'name', amprocnum => '4', amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/name_minmax_ops', amproclefttype => 'name',
+  amprocrighttype => 'name', amprocnum => '6', amproc => 'brin_minmax_preprocess' },
 
 # bloom name
 { amprocfamily => 'brin/name_bloom_ops', amproclefttype => 'name',
@@ -893,6 +899,8 @@
   amproc => 'brin_minmax_consistent' },
 { amprocfamily => 'brin/integer_minmax_ops', amproclefttype => 'int8',
   amprocrighttype => 'int8', amprocnum => '4', amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/integer_minmax_ops', amproclefttype => 'int8',
+  amprocrighttype => 'int8', amprocnum => '6', amproc => 'brin_minmax_preprocess' },
 
 { amprocfamily => 'brin/integer_minmax_ops', amproclefttype => 'int2',
   amprocrighttype => 'int2', amprocnum => '1',
@@ -905,6 +913,8 @@
   amproc => 'brin_minmax_consistent' },
 { amprocfamily => 'brin/integer_minmax_ops', amproclefttype => 'int2',
   amprocrighttype => 'int2', amprocnum => '4', amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/integer_minmax_ops', amproclefttype => 'int2',
+  amprocrighttype => 'int2', amprocnum => '6', amproc => 'brin_minmax_preprocess' },
 
 { amprocfamily => 'brin/integer_minmax_ops', amproclefttype => 'int4',
   amprocrighttype => 'int4', amprocnum => '1',
@@ -917,6 +927,8 @@
   amproc => 'brin_minmax_consistent' },
 { amprocfamily => 'brin/integer_minmax_ops', amproclefttype => 'int4',
   amprocrighttype => 'int4', amprocnum => '4', amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/integer_minmax_ops', amproclefttype => 'int4',
+  amprocrighttype => 'int4', amprocnum => '6', amproc => 'brin_minmax_preprocess' },
 
 # minmax multi integer: int2, int4, int8
 { amprocfamily => 'brin/integer_minmax_multi_ops', amproclefttype => 'int2',
@@ -1034,6 +1046,8 @@
   amproc => 'brin_minmax_consistent' },
 { amprocfamily => 'brin/text_minmax_ops', amproclefttype => 'text',
   amprocrighttype => 'text', amprocnum => '4', amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/text_minmax_ops', amproclefttype => 'text',
+  amprocrighttype => 'text', amprocnum => '6', amproc => 'brin_minmax_preprocess' },
 
 # bloom text
 { amprocfamily => 'brin/text_bloom_ops', amproclefttype => 'text',
@@ -1062,6 +1076,8 @@
   amproc => 'brin_minmax_consistent' },
 { amprocfamily => 'brin/oid_minmax_ops', amproclefttype => 'oid',
   amprocrighttype => 'oid', amprocnum => '4', amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/oid_minmax_ops', amproclefttype => 'oid',
+  amprocrighttype => 'oid', amprocnum => '6', amproc => 'brin_minmax_preprocess' },
 
 # minmax multi oid
 { amprocfamily => 'brin/oid_minmax_multi_ops', amproclefttype => 'oid',
@@ -1110,6 +1126,8 @@
   amproc => 'brin_minmax_consistent' },
 { amprocfamily => 'brin/tid_minmax_ops', amproclefttype => 'tid',
   amprocrighttype => 'tid', amprocnum => '4', amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/tid_minmax_ops', amproclefttype => 'tid',
+  amprocrighttype => 'tid', amprocnum => '6', amproc => 'brin_minmax_preprocess' },
 
 # bloom tid
 { amprocfamily => 'brin/tid_bloom_ops', amproclefttype => 'tid',
@@ -1160,6 +1178,9 @@
 { amprocfamily => 'brin/float_minmax_ops', amproclefttype => 'float4',
   amprocrighttype => 'float4', amprocnum => '4',
   amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/float_minmax_ops', amproclefttype => 'float4',
+  amprocrighttype => 'float4', amprocnum => '6',
+  amproc => 'brin_minmax_preprocess' },
 
 { amprocfamily => 'brin/float_minmax_ops', amproclefttype => 'float8',
   amprocrighttype => 'float8', amprocnum => '1',
@@ -1173,6 +1194,9 @@
 { amprocfamily => 'brin/float_minmax_ops', amproclefttype => 'float8',
   amprocrighttype => 'float8', amprocnum => '4',
   amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/float_minmax_ops', amproclefttype => 'float8',
+  amprocrighttype => 'float8', amprocnum => '6',
+  amproc => 'brin_minmax_preprocess' },
 
 # minmax multi float
 { amprocfamily => 'brin/float_minmax_multi_ops', amproclefttype => 'float4',
@@ -1261,6 +1285,9 @@
 { amprocfamily => 'brin/macaddr_minmax_ops', amproclefttype => 'macaddr',
   amprocrighttype => 'macaddr', amprocnum => '4',
   amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/macaddr_minmax_ops', amproclefttype => 'macaddr',
+  amprocrighttype => 'macaddr', amprocnum => '6',
+  amproc => 'brin_minmax_preprocess' },
 
 # minmax multi macaddr
 { amprocfamily => 'brin/macaddr_minmax_multi_ops', amproclefttype => 'macaddr',
@@ -1314,6 +1341,9 @@
 { amprocfamily => 'brin/macaddr8_minmax_ops', amproclefttype => 'macaddr8',
   amprocrighttype => 'macaddr8', amprocnum => '4',
   amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/macaddr8_minmax_ops', amproclefttype => 'macaddr8',
+  amprocrighttype => 'macaddr8', amprocnum => '6',
+  amproc => 'brin_minmax_preprocess' },
 
 # minmax multi macaddr8
 { amprocfamily => 'brin/macaddr8_minmax_multi_ops',
@@ -1366,6 +1396,8 @@
   amproc => 'brin_minmax_consistent' },
 { amprocfamily => 'brin/network_minmax_ops', amproclefttype => 'inet',
   amprocrighttype => 'inet', amprocnum => '4', amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/network_minmax_ops', amproclefttype => 'inet',
+  amprocrighttype => 'inet', amprocnum => '6', amproc => 'brin_minmax_preprocess' },
 
 # minmax multi inet
 { amprocfamily => 'brin/network_minmax_multi_ops', amproclefttype => 'inet',
@@ -1436,6 +1468,9 @@
 { amprocfamily => 'brin/bpchar_minmax_ops', amproclefttype => 'bpchar',
   amprocrighttype => 'bpchar', amprocnum => '4',
   amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/bpchar_minmax_ops', amproclefttype => 'bpchar',
+  amprocrighttype => 'bpchar', amprocnum => '6',
+  amproc => 'brin_minmax_preprocess' },
 
 # bloom character
 { amprocfamily => 'brin/bpchar_bloom_ops', amproclefttype => 'bpchar',
@@ -1467,6 +1502,8 @@
   amproc => 'brin_minmax_consistent' },
 { amprocfamily => 'brin/time_minmax_ops', amproclefttype => 'time',
   amprocrighttype => 'time', amprocnum => '4', amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/time_minmax_ops', amproclefttype => 'time',
+  amprocrighttype => 'time', amprocnum => '6', amproc => 'brin_minmax_preprocess' },
 
 # minmax multi time without time zone
 { amprocfamily => 'brin/time_minmax_multi_ops', amproclefttype => 'time',
@@ -1517,6 +1554,9 @@
 { amprocfamily => 'brin/datetime_minmax_ops', amproclefttype => 'timestamp',
   amprocrighttype => 'timestamp', amprocnum => '4',
   amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/datetime_minmax_ops', amproclefttype => 'timestamp',
+  amprocrighttype => 'timestamp', amprocnum => '6',
+  amproc => 'brin_minmax_preprocess' },
 
 { amprocfamily => 'brin/datetime_minmax_ops', amproclefttype => 'timestamptz',
   amprocrighttype => 'timestamptz', amprocnum => '1',
@@ -1530,6 +1570,9 @@
 { amprocfamily => 'brin/datetime_minmax_ops', amproclefttype => 'timestamptz',
   amprocrighttype => 'timestamptz', amprocnum => '4',
   amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/datetime_minmax_ops', amproclefttype => 'timestamptz',
+  amprocrighttype => 'timestamptz', amprocnum => '6',
+  amproc => 'brin_minmax_preprocess' },
 
 { amprocfamily => 'brin/datetime_minmax_ops', amproclefttype => 'date',
   amprocrighttype => 'date', amprocnum => '1',
@@ -1542,6 +1585,8 @@
   amproc => 'brin_minmax_consistent' },
 { amprocfamily => 'brin/datetime_minmax_ops', amproclefttype => 'date',
   amprocrighttype => 'date', amprocnum => '4', amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/datetime_minmax_ops', amproclefttype => 'date',
+  amprocrighttype => 'date', amprocnum => '6', amproc => 'brin_minmax_preprocess' },
 
 # minmax multi datetime (date, timestamp, timestamptz)
 { amprocfamily => 'brin/datetime_minmax_multi_ops',
@@ -1668,6 +1713,9 @@
 { amprocfamily => 'brin/interval_minmax_ops', amproclefttype => 'interval',
   amprocrighttype => 'interval', amprocnum => '4',
   amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/interval_minmax_ops', amproclefttype => 'interval',
+  amprocrighttype => 'interval', amprocnum => '6',
+  amproc => 'brin_minmax_preprocess' },
 
 # minmax multi interval
 { amprocfamily => 'brin/interval_minmax_multi_ops',
@@ -1721,6 +1769,9 @@
 { amprocfamily => 'brin/timetz_minmax_ops', amproclefttype => 'timetz',
   amprocrighttype => 'timetz', amprocnum => '4',
   amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/timetz_minmax_ops', amproclefttype => 'timetz',
+  amprocrighttype => 'timetz', amprocnum => '6',
+  amproc => 'brin_minmax_preprocess' },
 
 # minmax multi time with time zone
 { amprocfamily => 'brin/timetz_minmax_multi_ops', amproclefttype => 'timetz',
@@ -1771,6 +1822,8 @@
   amproc => 'brin_minmax_consistent' },
 { amprocfamily => 'brin/bit_minmax_ops', amproclefttype => 'bit',
   amprocrighttype => 'bit', amprocnum => '4', amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/bit_minmax_ops', amproclefttype => 'bit',
+  amprocrighttype => 'bit', amprocnum => '6', amproc => 'brin_minmax_preprocess' },
 
 # minmax bit varying
 { amprocfamily => 'brin/varbit_minmax_ops', amproclefttype => 'varbit',
@@ -1785,6 +1838,9 @@
 { amprocfamily => 'brin/varbit_minmax_ops', amproclefttype => 'varbit',
   amprocrighttype => 'varbit', amprocnum => '4',
   amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/varbit_minmax_ops', amproclefttype => 'varbit',
+  amprocrighttype => 'varbit', amprocnum => '6',
+  amproc => 'brin_minmax_preprocess' },
 
 # minmax numeric
 { amprocfamily => 'brin/numeric_minmax_ops', amproclefttype => 'numeric',
@@ -1799,6 +1855,9 @@
 { amprocfamily => 'brin/numeric_minmax_ops', amproclefttype => 'numeric',
   amprocrighttype => 'numeric', amprocnum => '4',
   amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/numeric_minmax_ops', amproclefttype => 'numeric',
+  amprocrighttype => 'numeric', amprocnum => '6',
+  amproc => 'brin_minmax_preprocess' },
 
 # minmax multi numeric
 { amprocfamily => 'brin/numeric_minmax_multi_ops', amproclefttype => 'numeric',
@@ -1851,6 +1910,8 @@
   amproc => 'brin_minmax_consistent' },
 { amprocfamily => 'brin/uuid_minmax_ops', amproclefttype => 'uuid',
   amprocrighttype => 'uuid', amprocnum => '4', amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/uuid_minmax_ops', amproclefttype => 'uuid',
+  amprocrighttype => 'uuid', amprocnum => '6', amproc => 'brin_minmax_preprocess' },
 
 # minmax multi uuid
 { amprocfamily => 'brin/uuid_minmax_multi_ops', amproclefttype => 'uuid',
@@ -1924,6 +1985,9 @@
 { amprocfamily => 'brin/pg_lsn_minmax_ops', amproclefttype => 'pg_lsn',
   amprocrighttype => 'pg_lsn', amprocnum => '4',
   amproc => 'brin_minmax_union' },
+{ amprocfamily => 'brin/pg_lsn_minmax_ops', amproclefttype => 'pg_lsn',
+  amprocrighttype => 'pg_lsn', amprocnum => '6',
+  amproc => 'brin_minmax_preprocess' },
 
 # minmax multi pg_lsn
 { amprocfamily => 'brin/pg_lsn_minmax_multi_ops', amproclefttype => 'pg_lsn',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 66b73c3900d..6638552bd05 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8496,6 +8496,9 @@
 { oid => '3386', descr => 'BRIN minmax support',
   proname => 'brin_minmax_union', prorettype => 'bool',
   proargtypes => 'internal internal internal', prosrc => 'brin_minmax_union' },
+{ oid => '9327', descr => 'BRIN minmax support',
+  proname => 'brin_minmax_preprocess', prorettype => 'internal',
+  proargtypes => 'internal internal', prosrc => 'brin_minmax_preprocess' },
 
 # BRIN minmax multi
 { oid => '4616', descr => 'BRIN multi minmax support',
diff --git a/src/test/regress/expected/amutils.out b/src/test/regress/expected/amutils.out
index 7ab6113c619..f3e1fbd2ae3 100644
--- a/src/test/regress/expected/amutils.out
+++ b/src/test/regress/expected/amutils.out
@@ -102,7 +102,7 @@ select prop,
  orderable          | t     | f    | f    | f            | f           | f   | f
  distance_orderable | f     | f    | t    | f            | t           | f   | f
  returnable         | t     | f    | f    | t            | t           | f   | f
- search_array       | t     | f    | f    | f            | f           | f   | f
+ search_array       | t     | f    | f    | f            | f           | f   | t
  search_nulls       | t     | f    | t    | t            | t           | f   | t
  bogus              |       |      |      |              |             |     | 
 (10 rows)
diff --git a/src/test/regress/expected/brin.out b/src/test/regress/expected/brin.out
index 73fa38396e4..93d7314e599 100644
--- a/src/test/regress/expected/brin.out
+++ b/src/test/regress/expected/brin.out
@@ -572,3 +572,861 @@ CREATE UNLOGGED TABLE brintest_unlogged (n numrange);
 CREATE INDEX brinidx_unlogged ON brintest_unlogged USING brin (n);
 INSERT INTO brintest_unlogged VALUES (numrange(0, 2^1000::numeric));
 DROP TABLE brintest_unlogged;
+-- do some tests on IN clauses for simple data types
+CREATE TABLE brin_in_test_1 (a INT, b BIGINT) WITH (fillfactor=10);
+INSERT INTO brin_in_test_1
+SELECT i/5 + mod(991 * i + 617, 20),
+       i/10 + mod(853 * i + 491, 30)
+  FROM generate_series(1,1000) s(i);
+CREATE INDEX brin_in_test_1_idx_1 ON brin_in_test_1 USING brin (a int4_minmax_ops) WITH (pages_per_range=1);
+CREATE INDEX brin_in_test_1_idx_2 ON brin_in_test_1 USING brin (b int8_minmax_ops) WITH (pages_per_range=1);
+SET enable_seqscan=off;
+-- int: equalities
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a IN (113);
+                      QUERY PLAN                       
+-------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (a = 113)
+         ->  Bitmap Index Scan on brin_in_test_1_idx_1
+               Index Cond: (a = 113)
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a IN (113);
+ count 
+-------
+     8
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a IN (113, NULL);
+                          QUERY PLAN                           
+---------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (a = ANY ('{113,NULL}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_1
+               Index Cond: (a = ANY ('{113,NULL}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a IN (113, NULL);
+ count 
+-------
+     8
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a IN (NULL, NULL);
+                           QUERY PLAN                           
+----------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (a = ANY ('{NULL,NULL}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_1
+               Index Cond: (a = ANY ('{NULL,NULL}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a IN (NULL, NULL);
+ count 
+-------
+     0
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a IN (113, 177);
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (a = ANY ('{113,177}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_1
+               Index Cond: (a = ANY ('{113,177}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a IN (113, 177);
+ count 
+-------
+    16
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a IN (113, 177, NULL);
+                            QUERY PLAN                             
+-------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (a = ANY ('{113,177,NULL}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_1
+               Index Cond: (a = ANY ('{113,177,NULL}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a IN (113, 177, NULL);
+ count 
+-------
+    16
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a IN (113, 177, 25);
+                           QUERY PLAN                            
+-----------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (a = ANY ('{113,177,25}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_1
+               Index Cond: (a = ANY ('{113,177,25}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a IN (113, 177, 25);
+ count 
+-------
+    24
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a IN (NULL, 113, 177, 25);
+                              QUERY PLAN                              
+----------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (a = ANY ('{NULL,113,177,25}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_1
+               Index Cond: (a = ANY ('{NULL,113,177,25}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a IN (NULL, 113, 177, 25);
+ count 
+-------
+    24
+(1 row)
+
+-- int: less than
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a < ANY (ARRAY[30]);
+                       QUERY PLAN                        
+---------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (a < ANY ('{30}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_1
+               Index Cond: (a < ANY ('{30}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a < ANY (ARRAY[30]);
+ count 
+-------
+   103
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a < ANY (ARRAY[20, NULL]);
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (a < ANY ('{20,NULL}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_1
+               Index Cond: (a < ANY ('{20,NULL}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a < ANY (ARRAY[20, NULL]);
+ count 
+-------
+    51
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a < ANY (ARRAY[NULL, NULL]::int[]);
+                           QUERY PLAN                           
+----------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (a < ANY ('{NULL,NULL}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_1
+               Index Cond: (a < ANY ('{NULL,NULL}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a < ANY (ARRAY[NULL, NULL]::int[]);
+ count 
+-------
+     0
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a < ANY (ARRAY[35, 29]);
+                         QUERY PLAN                         
+------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (a < ANY ('{35,29}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_1
+               Index Cond: (a < ANY ('{35,29}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a < ANY (ARRAY[35, 29]);
+ count 
+-------
+   127
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a <= ANY (ARRAY[45, 60, NULL]);
+                            QUERY PLAN                            
+------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (a <= ANY ('{45,60,NULL}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_1
+               Index Cond: (a <= ANY ('{45,60,NULL}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a <= ANY (ARRAY[45, 60, NULL]);
+ count 
+-------
+   255
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a < ANY (ARRAY[41, 37, 55]);
+                          QUERY PLAN                           
+---------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (a < ANY ('{41,37,55}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_1
+               Index Cond: (a < ANY ('{41,37,55}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a < ANY (ARRAY[41, 37, 55]);
+ count 
+-------
+   227
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a <= ANY (ARRAY[NULL, 60, 43, 94]);
+                             QUERY PLAN                              
+---------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (a <= ANY ('{NULL,60,43,94}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_1
+               Index Cond: (a <= ANY ('{NULL,60,43,94}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a <= ANY (ARRAY[NULL, 60, 43, 94]);
+ count 
+-------
+   427
+(1 row)
+
+-- int: greater than
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a > ANY (ARRAY[200]);
+                        QUERY PLAN                        
+----------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (a > ANY ('{200}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_1
+               Index Cond: (a > ANY ('{200}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a > ANY (ARRAY[200]);
+ count 
+-------
+    45
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a > ANY (ARRAY[177, NULL]);
+                          QUERY PLAN                           
+---------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (a > ANY ('{177,NULL}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_1
+               Index Cond: (a > ANY ('{177,NULL}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a > ANY (ARRAY[177, NULL]);
+ count 
+-------
+   157
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a > ANY (ARRAY[NULL, NULL]::int[]);
+                           QUERY PLAN                           
+----------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (a > ANY ('{NULL,NULL}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_1
+               Index Cond: (a > ANY ('{NULL,NULL}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a > ANY (ARRAY[NULL, NULL]::int[]);
+ count 
+-------
+     0
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a > ANY (ARRAY[153, 140]);
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (a > ANY ('{153,140}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_1
+               Index Cond: (a > ANY ('{153,140}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a > ANY (ARRAY[153, 140]);
+ count 
+-------
+   345
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a >= ANY (ARRAY[173, 191, NULL]);
+                             QUERY PLAN                             
+--------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (a >= ANY ('{173,191,NULL}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_1
+               Index Cond: (a >= ANY ('{173,191,NULL}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a >= ANY (ARRAY[173, 191, NULL]);
+ count 
+-------
+   185
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a > ANY (ARRAY[120, 184, 164]);
+                            QUERY PLAN                            
+------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (a > ANY ('{120,184,164}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_1
+               Index Cond: (a > ANY ('{120,184,164}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a > ANY (ARRAY[120, 184, 164]);
+ count 
+-------
+   445
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a >= ANY (ARRAY[NULL, 130, 181, 169]);
+                               QUERY PLAN                               
+------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (a >= ANY ('{NULL,130,181,169}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_1
+               Index Cond: (a >= ANY ('{NULL,130,181,169}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a >= ANY (ARRAY[NULL, 130, 181, 169]);
+ count 
+-------
+   397
+(1 row)
+
+-- bigint: eqalities
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b IN (82);
+                      QUERY PLAN                       
+-------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (b = 82)
+         ->  Bitmap Index Scan on brin_in_test_1_idx_2
+               Index Cond: (b = 82)
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b IN (82);
+ count 
+-------
+    10
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b IN (82, NULL);
+                         QUERY PLAN                          
+-------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (b = ANY ('{82,NULL}'::bigint[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_2
+               Index Cond: (b = ANY ('{82,NULL}'::bigint[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b IN (82, NULL);
+ count 
+-------
+    10
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b IN (NULL, NULL);
+                          QUERY PLAN                           
+---------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (b = ANY ('{NULL,NULL}'::bigint[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_2
+               Index Cond: (b = ANY ('{NULL,NULL}'::bigint[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b IN (NULL, NULL);
+ count 
+-------
+     0
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b IN (82, 41);
+                        QUERY PLAN                         
+-----------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (b = ANY ('{82,41}'::bigint[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_2
+               Index Cond: (b = ANY ('{82,41}'::bigint[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b IN (82, 41);
+ count 
+-------
+    20
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b IN (82, 41, NULL);
+                           QUERY PLAN                           
+----------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (b = ANY ('{82,41,NULL}'::bigint[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_2
+               Index Cond: (b = ANY ('{82,41,NULL}'::bigint[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b IN (82, 41, NULL);
+ count 
+-------
+    20
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b IN (82, 41, 15);
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (b = ANY ('{82,41,15}'::bigint[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_2
+               Index Cond: (b = ANY ('{82,41,15}'::bigint[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b IN (82, 41, 15);
+ count 
+-------
+    25
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b IN (NULL, 82, 41, 15);
+                            QUERY PLAN                             
+-------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (b = ANY ('{NULL,82,41,15}'::bigint[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_2
+               Index Cond: (b = ANY ('{NULL,82,41,15}'::bigint[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b IN (NULL, 82, 41, 15);
+ count 
+-------
+    25
+(1 row)
+
+-- bigint: less than
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b < ANY (ARRAY[31]);
+                       QUERY PLAN                        
+---------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (b < ANY ('{31}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_2
+               Index Cond: (b < ANY ('{31}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b < ANY (ARRAY[31]);
+ count 
+-------
+   164
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b < ANY (ARRAY[55, NULL]);
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (b < ANY ('{55,NULL}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_2
+               Index Cond: (b < ANY ('{55,NULL}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b < ANY (ARRAY[55, NULL]);
+ count 
+-------
+   404
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b < ANY (ARRAY[NULL, NULL]::bigint[]);
+                          QUERY PLAN                           
+---------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (b < ANY ('{NULL,NULL}'::bigint[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_2
+               Index Cond: (b < ANY ('{NULL,NULL}'::bigint[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b < ANY (ARRAY[NULL, NULL]::bigint[]);
+ count 
+-------
+     0
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b <= ANY (ARRAY[73, 51]);
+                         QUERY PLAN                          
+-------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (b <= ANY ('{73,51}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_2
+               Index Cond: (b <= ANY ('{73,51}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b <= ANY (ARRAY[73, 51]);
+ count 
+-------
+   594
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b < ANY (ARRAY[69, 87, NULL]);
+                           QUERY PLAN                            
+-----------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (b < ANY ('{69,87,NULL}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_2
+               Index Cond: (b < ANY ('{69,87,NULL}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b < ANY (ARRAY[69, 87, NULL]);
+ count 
+-------
+   724
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b <= ANY (ARRAY[82, 91, 35]);
+                           QUERY PLAN                           
+----------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (b <= ANY ('{82,91,35}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_2
+               Index Cond: (b <= ANY ('{82,91,35}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b <= ANY (ARRAY[82, 91, 35]);
+ count 
+-------
+   774
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b < ANY (ARRAY[NULL, 63, 21, 85]);
+                             QUERY PLAN                             
+--------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (b < ANY ('{NULL,63,21,85}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_2
+               Index Cond: (b < ANY ('{NULL,63,21,85}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b < ANY (ARRAY[NULL, 63, 21, 85]);
+ count 
+-------
+   704
+(1 row)
+
+-- bigint: greater than
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b > ANY (ARRAY[94]);
+                       QUERY PLAN                        
+---------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (b > ANY ('{94}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_2
+               Index Cond: (b > ANY ('{94}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b > ANY (ARRAY[94]);
+ count 
+-------
+   196
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b > ANY (ARRAY[80, NULL]);
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (b > ANY ('{80,NULL}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_2
+               Index Cond: (b > ANY ('{80,NULL}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b > ANY (ARRAY[80, NULL]);
+ count 
+-------
+   336
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b > ANY (ARRAY[NULL, NULL]::bigint[]);
+                          QUERY PLAN                           
+---------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (b > ANY ('{NULL,NULL}'::bigint[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_2
+               Index Cond: (b > ANY ('{NULL,NULL}'::bigint[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b > ANY (ARRAY[NULL, NULL]::bigint[]);
+ count 
+-------
+     0
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b > ANY (ARRAY[199, 107]);
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (b > ANY ('{199,107}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_2
+               Index Cond: (b > ANY ('{199,107}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b > ANY (ARRAY[199, 107]);
+ count 
+-------
+    78
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b >= ANY (ARRAY[182, 101, NULL]);
+                             QUERY PLAN                             
+--------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (b >= ANY ('{182,101,NULL}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_2
+               Index Cond: (b >= ANY ('{182,101,NULL}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b >= ANY (ARRAY[182, 101, NULL]);
+ count 
+-------
+   137
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b > ANY (ARRAY[300, 106, 251]);
+                            QUERY PLAN                            
+------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (b > ANY ('{300,106,251}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_2
+               Index Cond: (b > ANY ('{300,106,251}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b > ANY (ARRAY[300, 106, 251]);
+ count 
+-------
+    86
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b > ANY (ARRAY[NULL, 182, 101, 155]);
+                              QUERY PLAN                               
+-----------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_1
+         Recheck Cond: (b > ANY ('{NULL,182,101,155}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_1_idx_2
+               Index Cond: (b > ANY ('{NULL,182,101,155}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b > ANY (ARRAY[NULL, 182, 101, 155]);
+ count 
+-------
+   127
+(1 row)
+
+DROP TABLE brin_in_test_1;
+RESET enable_seqscan;
+-- do some tests on IN clauses for varlena data types
+CREATE TABLE brin_in_test_2 (a TEXT) WITH (fillfactor=10);
+INSERT INTO brin_in_test_2
+SELECT v FROM (SELECT row_number() OVER (ORDER BY v) c, v FROM (SELECT md5((i/13)::text) AS v FROM generate_series(1,1000) s(i)) foo) bar ORDER BY c + 25 * random();
+CREATE INDEX brin_in_test_2_idx ON brin_in_test_2 USING brin (a text_minmax_ops) WITH (pages_per_range=1);
+SET enable_seqscan=off;
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189');
+                                QUERY PLAN                                
+--------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_2
+         Recheck Cond: (a = '33e75ff09dd601bbe69f351039152189'::text)
+         ->  Bitmap Index Scan on brin_in_test_2_idx
+               Index Cond: (a = '33e75ff09dd601bbe69f351039152189'::text)
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189');
+ count 
+-------
+    13
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', NULL);
+                                       QUERY PLAN                                        
+-----------------------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_2
+         Recheck Cond: (a = ANY ('{33e75ff09dd601bbe69f351039152189,NULL}'::text[]))
+         ->  Bitmap Index Scan on brin_in_test_2_idx
+               Index Cond: (a = ANY ('{33e75ff09dd601bbe69f351039152189,NULL}'::text[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', NULL);
+ count 
+-------
+    13
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_2 WHERE a IN (NULL, NULL);
+                         QUERY PLAN                          
+-------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_2
+         Recheck Cond: (a = ANY ('{NULL,NULL}'::text[]))
+         ->  Bitmap Index Scan on brin_in_test_2_idx
+               Index Cond: (a = ANY ('{NULL,NULL}'::text[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_2 WHERE a IN (NULL, NULL);
+ count 
+-------
+     0
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0');
+                                                     QUERY PLAN                                                      
+---------------------------------------------------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_2
+         Recheck Cond: (a = ANY ('{33e75ff09dd601bbe69f351039152189,f457c545a9ded88f18ecee47145a72c0}'::text[]))
+         ->  Bitmap Index Scan on brin_in_test_2_idx
+               Index Cond: (a = ANY ('{33e75ff09dd601bbe69f351039152189,f457c545a9ded88f18ecee47145a72c0}'::text[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0');
+ count 
+-------
+    26
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0', NULL);
+                                                        QUERY PLAN                                                        
+--------------------------------------------------------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_2
+         Recheck Cond: (a = ANY ('{33e75ff09dd601bbe69f351039152189,f457c545a9ded88f18ecee47145a72c0,NULL}'::text[]))
+         ->  Bitmap Index Scan on brin_in_test_2_idx
+               Index Cond: (a = ANY ('{33e75ff09dd601bbe69f351039152189,f457c545a9ded88f18ecee47145a72c0,NULL}'::text[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0', NULL);
+ count 
+-------
+    26
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0', 'c51ce410c124a10e0db5e4b97fc2af39');
+                                                                      QUERY PLAN                                                                      
+------------------------------------------------------------------------------------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_2
+         Recheck Cond: (a = ANY ('{33e75ff09dd601bbe69f351039152189,f457c545a9ded88f18ecee47145a72c0,c51ce410c124a10e0db5e4b97fc2af39}'::text[]))
+         ->  Bitmap Index Scan on brin_in_test_2_idx
+               Index Cond: (a = ANY ('{33e75ff09dd601bbe69f351039152189,f457c545a9ded88f18ecee47145a72c0,c51ce410c124a10e0db5e4b97fc2af39}'::text[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0', 'c51ce410c124a10e0db5e4b97fc2af39');
+ count 
+-------
+    39
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_2 WHERE a IN (NULL, '33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0', 'c51ce410c124a10e0db5e4b97fc2af39');
+                                                                        QUERY PLAN                                                                         
+-----------------------------------------------------------------------------------------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_2
+         Recheck Cond: (a = ANY ('{NULL,33e75ff09dd601bbe69f351039152189,f457c545a9ded88f18ecee47145a72c0,c51ce410c124a10e0db5e4b97fc2af39}'::text[]))
+         ->  Bitmap Index Scan on brin_in_test_2_idx
+               Index Cond: (a = ANY ('{NULL,33e75ff09dd601bbe69f351039152189,f457c545a9ded88f18ecee47145a72c0,c51ce410c124a10e0db5e4b97fc2af39}'::text[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_2 WHERE a IN (NULL, '33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0', 'c51ce410c124a10e0db5e4b97fc2af39');
+ count 
+-------
+    39
+(1 row)
+
+DROP TABLE brin_in_test_2;
+RESET enable_seqscan;
diff --git a/src/test/regress/sql/brin.sql b/src/test/regress/sql/brin.sql
index e68e9e18df5..95da027047e 100644
--- a/src/test/regress/sql/brin.sql
+++ b/src/test/regress/sql/brin.sql
@@ -515,3 +515,286 @@ CREATE UNLOGGED TABLE brintest_unlogged (n numrange);
 CREATE INDEX brinidx_unlogged ON brintest_unlogged USING brin (n);
 INSERT INTO brintest_unlogged VALUES (numrange(0, 2^1000::numeric));
 DROP TABLE brintest_unlogged;
+
+-- do some tests on IN clauses for simple data types
+CREATE TABLE brin_in_test_1 (a INT, b BIGINT) WITH (fillfactor=10);
+INSERT INTO brin_in_test_1
+SELECT i/5 + mod(991 * i + 617, 20),
+       i/10 + mod(853 * i + 491, 30)
+  FROM generate_series(1,1000) s(i);
+
+CREATE INDEX brin_in_test_1_idx_1 ON brin_in_test_1 USING brin (a int4_minmax_ops) WITH (pages_per_range=1);
+CREATE INDEX brin_in_test_1_idx_2 ON brin_in_test_1 USING brin (b int8_minmax_ops) WITH (pages_per_range=1);
+
+SET enable_seqscan=off;
+
+-- int: equalities
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a IN (113);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a IN (113);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a IN (113, NULL);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a IN (113, NULL);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a IN (NULL, NULL);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a IN (NULL, NULL);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a IN (113, 177);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a IN (113, 177);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a IN (113, 177, NULL);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a IN (113, 177, NULL);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a IN (113, 177, 25);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a IN (113, 177, 25);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a IN (NULL, 113, 177, 25);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a IN (NULL, 113, 177, 25);
+
+-- int: less than
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a < ANY (ARRAY[30]);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a < ANY (ARRAY[30]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a < ANY (ARRAY[20, NULL]);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a < ANY (ARRAY[20, NULL]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a < ANY (ARRAY[NULL, NULL]::int[]);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a < ANY (ARRAY[NULL, NULL]::int[]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a < ANY (ARRAY[35, 29]);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a < ANY (ARRAY[35, 29]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a <= ANY (ARRAY[45, 60, NULL]);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a <= ANY (ARRAY[45, 60, NULL]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a < ANY (ARRAY[41, 37, 55]);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a < ANY (ARRAY[41, 37, 55]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a <= ANY (ARRAY[NULL, 60, 43, 94]);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a <= ANY (ARRAY[NULL, 60, 43, 94]);
+
+
+-- int: greater than
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a > ANY (ARRAY[200]);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a > ANY (ARRAY[200]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a > ANY (ARRAY[177, NULL]);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a > ANY (ARRAY[177, NULL]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a > ANY (ARRAY[NULL, NULL]::int[]);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a > ANY (ARRAY[NULL, NULL]::int[]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a > ANY (ARRAY[153, 140]);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a > ANY (ARRAY[153, 140]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a >= ANY (ARRAY[173, 191, NULL]);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a >= ANY (ARRAY[173, 191, NULL]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a > ANY (ARRAY[120, 184, 164]);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a > ANY (ARRAY[120, 184, 164]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a >= ANY (ARRAY[NULL, 130, 181, 169]);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE a >= ANY (ARRAY[NULL, 130, 181, 169]);
+
+
+-- bigint: eqalities
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b IN (82);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b IN (82);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b IN (82, NULL);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b IN (82, NULL);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b IN (NULL, NULL);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b IN (NULL, NULL);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b IN (82, 41);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b IN (82, 41);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b IN (82, 41, NULL);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b IN (82, 41, NULL);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b IN (82, 41, 15);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b IN (82, 41, 15);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b IN (NULL, 82, 41, 15);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b IN (NULL, 82, 41, 15);
+
+
+-- bigint: less than
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b < ANY (ARRAY[31]);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b < ANY (ARRAY[31]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b < ANY (ARRAY[55, NULL]);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b < ANY (ARRAY[55, NULL]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b < ANY (ARRAY[NULL, NULL]::bigint[]);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b < ANY (ARRAY[NULL, NULL]::bigint[]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b <= ANY (ARRAY[73, 51]);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b <= ANY (ARRAY[73, 51]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b < ANY (ARRAY[69, 87, NULL]);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b < ANY (ARRAY[69, 87, NULL]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b <= ANY (ARRAY[82, 91, 35]);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b <= ANY (ARRAY[82, 91, 35]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b < ANY (ARRAY[NULL, 63, 21, 85]);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b < ANY (ARRAY[NULL, 63, 21, 85]);
+
+
+-- bigint: greater than
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b > ANY (ARRAY[94]);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b > ANY (ARRAY[94]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b > ANY (ARRAY[80, NULL]);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b > ANY (ARRAY[80, NULL]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b > ANY (ARRAY[NULL, NULL]::bigint[]);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b > ANY (ARRAY[NULL, NULL]::bigint[]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b > ANY (ARRAY[199, 107]);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b > ANY (ARRAY[199, 107]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b >= ANY (ARRAY[182, 101, NULL]);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b >= ANY (ARRAY[182, 101, NULL]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b > ANY (ARRAY[300, 106, 251]);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b > ANY (ARRAY[300, 106, 251]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b > ANY (ARRAY[NULL, 182, 101, 155]);
+
+SELECT COUNT(*) FROM brin_in_test_1 WHERE b > ANY (ARRAY[NULL, 182, 101, 155]);
+
+
+DROP TABLE brin_in_test_1;
+RESET enable_seqscan;
+
+-- do some tests on IN clauses for varlena data types
+CREATE TABLE brin_in_test_2 (a TEXT) WITH (fillfactor=10);
+INSERT INTO brin_in_test_2
+SELECT v FROM (SELECT row_number() OVER (ORDER BY v) c, v FROM (SELECT md5((i/13)::text) AS v FROM generate_series(1,1000) s(i)) foo) bar ORDER BY c + 25 * random();
+
+CREATE INDEX brin_in_test_2_idx ON brin_in_test_2 USING brin (a text_minmax_ops) WITH (pages_per_range=1);
+
+SET enable_seqscan=off;
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189');
+
+SELECT COUNT(*) FROM brin_in_test_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189');
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', NULL);
+
+SELECT COUNT(*) FROM brin_in_test_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', NULL);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_2 WHERE a IN (NULL, NULL);
+
+SELECT COUNT(*) FROM brin_in_test_2 WHERE a IN (NULL, NULL);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0');
+
+SELECT COUNT(*) FROM brin_in_test_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0');
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0', NULL);
+
+SELECT COUNT(*) FROM brin_in_test_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0', NULL);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0', 'c51ce410c124a10e0db5e4b97fc2af39');
+
+SELECT COUNT(*) FROM brin_in_test_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0', 'c51ce410c124a10e0db5e4b97fc2af39');
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_2 WHERE a IN (NULL, '33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0', 'c51ce410c124a10e0db5e4b97fc2af39');
+
+SELECT COUNT(*) FROM brin_in_test_2 WHERE a IN (NULL, '33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0', 'c51ce410c124a10e0db5e4b97fc2af39');
+
+DROP TABLE brin_in_test_2;
+RESET enable_seqscan;
-- 
2.39.1



  [text/x-patch] 0007-Support-SK_SEARCHARRAY-in-BRIN-minmax-multi-20230218.patch (75.1K, ../../[email protected]/8-0007-Support-SK_SEARCHARRAY-in-BRIN-minmax-multi-20230218.patch)
  download | inline diff:
From 212d916166223777fa7c1ee8ea9f5d2f86e325f5 Mon Sep 17 00:00:00 2001
From: Tomas Vondra <[email protected]>
Date: Fri, 17 Feb 2023 02:45:14 +0100
Subject: [PATCH 7/9] Support SK_SEARCHARRAY in BRIN minmax-multi

Similar approach to minmax, but the issues with deconstructing the array
over and over are even more serious.
---
 src/backend/access/brin/brin_minmax_multi.c | 502 +++++++++--
 src/include/catalog/pg_amproc.dat           |  57 ++
 src/include/catalog/pg_proc.dat             |   4 +
 src/test/regress/expected/brin_multi.out    | 926 ++++++++++++++++++++
 src/test/regress/sql/brin_multi.sql         | 301 +++++++
 5 files changed, 1725 insertions(+), 65 deletions(-)

diff --git a/src/backend/access/brin/brin_minmax_multi.c b/src/backend/access/brin/brin_minmax_multi.c
index 859e0022fb4..dd22b3e3c02 100644
--- a/src/backend/access/brin/brin_minmax_multi.c
+++ b/src/backend/access/brin/brin_minmax_multi.c
@@ -109,6 +109,14 @@
 #define		MINMAX_BUFFER_MAX				8192
 #define		MINMAX_BUFFER_LOAD_FACTOR		0.5
 
+/*
+ * We use some private sk_flags bits in preprocessed scan keys.  We're allowed
+ * to use bits 16-31 (see skey.h).  The uppermost bits are copied from the
+ * index's indoption[] array entry for the index attribute.
+ */
+#define SK_BRIN_SORTED	0x00010000	/* deconstructed and sorted array */
+
+
 typedef struct MinmaxMultiOpaque
 {
 	FmgrInfo	extra_procinfos[MINMAX_MAX_PROCNUMS];
@@ -2562,6 +2570,157 @@ brin_minmax_multi_add_value(PG_FUNCTION_ARGS)
 	PG_RETURN_BOOL(modified);
 }
 
+
+static int
+compare_array_values(const void *a, const void *b, void *arg)
+{
+	Datum	da = * (Datum *) a;
+	Datum	db = * (Datum *) b;
+	SortSupport	ssup = (SortSupport) arg;
+
+	return ApplySortComparator(da, false, db, false, ssup);
+}
+
+/*
+ * lower_boundary
+ *		Determine lowest index so that (values[index] >= minvalue).
+ *
+ * The array of values is expected to be sorted, so this is the first value
+ * that may fall into the [minvalue, maxvalue] range, as it exceeds minval.
+ * It's not guaranteed, though, as it might exceed maxvalue too.
+ */
+static int
+lower_boundary(Datum *values, int nvalues, Datum minvalue, SortSupport ssup)
+{
+	int		start = 0,
+			end = (nvalues - 1);
+
+	/* everything exceeds minval and might match */
+	if (compare_array_values(&minvalue, &values[start], ssup) <= 0)
+		return 0;
+
+	/* nothing could match */
+	if (compare_array_values(&minvalue, &values[end], ssup) > 0)
+		return nvalues;
+
+	while ((end - start) > 0)
+	{
+		int midpoint;
+		int r;
+
+		midpoint = start + (end - start) / 2;
+
+		r = compare_array_values(&minvalue, &values[midpoint], ssup);
+
+		if (r > 0)
+			start = Max(midpoint, start + 1);
+		else
+			end = midpoint;
+	}
+
+	/* the value should meet the (v >=minvalue) requirement */
+	Assert(compare_array_values(&values[start], &minvalue, ssup) >= 0);
+
+	/* we know start can't be 0, so it's legal to subtract 1 */
+	Assert(compare_array_values(&values[start-1], &minvalue, ssup) < 0);
+
+	return start;
+}
+
+typedef struct ScanKeyArray {
+	Oid		typeid;
+	int		nelements;
+	Datum  *elements;
+} ScanKeyArray;
+
+Datum
+brin_minmax_multi_preprocess(PG_FUNCTION_ARGS)
+{
+	// BrinDesc   *bdesc = (BrinDesc *) PG_GETARG_POINTER(0);
+	ScanKey		key = (ScanKey) PG_GETARG_POINTER(1);
+	ScanKey		newkey;
+	ScanKeyArray *scanarray;
+
+	ArrayType  *arrayval;
+	int16		elmlen;
+	bool		elmbyval;
+	char		elmalign;
+	int			num_elems;
+	Datum	   *elem_values;
+	bool	   *elem_nulls;
+	TypeCacheEntry *type;
+	SortSupportData ssup;
+
+	/* number of non-null elements in the array */
+	int			num_nonnulls;
+
+	/*
+	 * ignore scalar keys
+	 *
+	 * XXX Maybe we should preprocess scalar keys too. It'd make the consistent
+	 * function simpler by removing the branching.
+	 */
+	if (!(key->sk_flags & SK_SEARCHARRAY))
+		PG_RETURN_POINTER(key);
+
+	arrayval = DatumGetArrayTypeP(key->sk_argument);
+
+	get_typlenbyvalalign(ARR_ELEMTYPE(arrayval),
+						 &elmlen, &elmbyval, &elmalign);
+
+	deconstruct_array(arrayval,
+					  ARR_ELEMTYPE(arrayval),
+					  elmlen, elmbyval, elmalign,
+					  &elem_values, &elem_nulls, &num_elems);
+
+	/* eliminate NULL elements */
+	num_nonnulls = 0;
+	for (int i = 0; i < num_elems; i++)
+	{
+		/* skip NULL elements */
+		if (elem_nulls[i])
+			continue;
+
+		/* if needed, move the non-NULL ones */
+		if (num_nonnulls != i)
+			elem_values[num_nonnulls] = elem_values[i];
+
+		num_nonnulls++;
+	}
+
+	num_elems = num_nonnulls;
+
+	type = lookup_type_cache(ARR_ELEMTYPE(arrayval), TYPECACHE_LT_OPR);
+
+	memset(&ssup, 0, sizeof(SortSupportData));
+
+	ssup.ssup_collation = key->sk_collation;
+	ssup.ssup_cxt = CurrentMemoryContext;
+
+	PrepareSortSupportFromOrderingOp(type->lt_opr, &ssup);
+
+	qsort_interruptible(elem_values, num_elems, sizeof(Datum),
+						compare_array_values, &ssup);
+
+	scanarray = palloc0(sizeof(ScanKeyArray));
+	scanarray->typeid = ARR_ELEMTYPE(arrayval);
+	scanarray->nelements = num_elems;
+	scanarray->elements = elem_values;
+
+	newkey = palloc0(sizeof(ScanKeyData));
+
+	ScanKeyEntryInitializeWithInfo(newkey,
+								   (key->sk_flags | SK_BRIN_SORTED),
+								   key->sk_attno,
+								   key->sk_strategy,
+								   key->sk_subtype,
+								   key->sk_collation,
+								   &key->sk_func,
+								   PointerGetDatum(scanarray));
+
+	PG_RETURN_POINTER(newkey);
+}
+
 /*
  * 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
@@ -2591,6 +2750,15 @@ brin_minmax_multi_consistent(PG_FUNCTION_ARGS)
 	serialized = (SerializedRanges *) PG_DETOAST_DATUM(column->bv_values[0]);
 	ranges = brin_range_deserialize(serialized->maxvalues, serialized);
 
+	/*
+	 * XXX Would it make sense to have a quick initial check on the whole
+	 * summary? We know most page ranges are not expected to match, and we
+	 * know the ranges/values are sorted so we could check global min/max
+	 * (essentially what regular minmax is doing) and bail if no match is
+	 * possible. That should be cheap and might save a lot on inspecting
+	 * the individual ranges/values.
+	 */
+
 	/* inspect the ranges, and for each one evaluate the scan keys */
 	for (rangeno = 0; rangeno < ranges->nranges; rangeno++)
 	{
@@ -2611,67 +2779,183 @@ brin_minmax_multi_consistent(PG_FUNCTION_ARGS)
 			attno = key->sk_attno;
 			subtype = key->sk_subtype;
 			value = key->sk_argument;
-			switch (key->sk_strategy)
+
+			if (likely(!(key->sk_flags & SK_BRIN_SORTED)))
 			{
-				case BTLessStrategyNumber:
-				case BTLessEqualStrategyNumber:
-					finfo = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
-															   key->sk_strategy);
-					/* first value from the array */
-					matches = FunctionCall2Coll(finfo, colloid, minval, value);
-					break;
+				switch (key->sk_strategy)
+				{
+					case BTLessStrategyNumber:
+					case BTLessEqualStrategyNumber:
+						finfo = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
+																   key->sk_strategy);
+						/* first value from the array */
+						matches = FunctionCall2Coll(finfo, colloid, minval, value);
+						break;
 
-				case BTEqualStrategyNumber:
-					{
-						Datum		compar;
-						FmgrInfo   *cmpFn;
+					case BTEqualStrategyNumber:
+						{
+							Datum		compar;
+							FmgrInfo   *cmpFn;
+
+							/* by default this range does not match */
+							matches = BoolGetDatum(false);
+
+							/*
+							 * Otherwise, need to compare the new value with
+							 * boundaries of all the ranges. First check if it's
+							 * less than the absolute minimum, which is the first
+							 * value in the array.
+							 */
+							cmpFn = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
+																	   BTGreaterStrategyNumber);
+							compar = FunctionCall2Coll(cmpFn, colloid, minval, value);
+
+							/* smaller than the smallest value in this range */
+							if (DatumGetBool(compar))
+								break;
+
+							cmpFn = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
+																	   BTLessStrategyNumber);
+							compar = FunctionCall2Coll(cmpFn, colloid, maxval, value);
+
+							/* larger than the largest value in this range */
+							if (DatumGetBool(compar))
+								break;
+
+							/*
+							 * We haven't managed to eliminate this range, so
+							 * consider it matching.
+							 */
+							matches = BoolGetDatum(true);
 
-						/* by default this range does not match */
-						matches = BoolGetDatum(false);
+							break;
+						}
+					case BTGreaterEqualStrategyNumber:
+					case BTGreaterStrategyNumber:
+						finfo = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
+																   key->sk_strategy);
+						/* last value from the array */
+						matches = FunctionCall2Coll(finfo, colloid, maxval, value);
+						break;
 
-						/*
-						 * Otherwise, need to compare the new value with
-						 * boundaries of all the ranges. First check if it's
-						 * less than the absolute minimum, which is the first
-						 * value in the array.
-						 */
-						cmpFn = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
-																   BTGreaterStrategyNumber);
-						compar = FunctionCall2Coll(cmpFn, colloid, minval, value);
+					default:
+						/* shouldn't happen */
+						elog(ERROR, "invalid strategy number %d", key->sk_strategy);
+						matches = BoolGetDatum(false);
+						break;
+				}
+			}
+			else
+			{
+				ScanKeyArray *array = (ScanKeyArray *) value;
 
-						/* smaller than the smallest value in this range */
-						if (DatumGetBool(compar))
-							break;
+				/* can happen if the IN list contained just NULLs */
+				if (array->nelements == 0)
+					PG_RETURN_BOOL(false);
 
-						cmpFn = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
-																   BTLessStrategyNumber);
-						compar = FunctionCall2Coll(cmpFn, colloid, maxval, value);
+				switch (key->sk_strategy)
+				{
+					case BTLessStrategyNumber:
+					case BTLessEqualStrategyNumber:
+						finfo = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
+																   key->sk_strategy);
+						/* first value from the array */
+						matches = FunctionCall2Coll(finfo, colloid, minval,
+													array->elements[array->nelements-1]);
+						break;
 
-						/* larger than the largest value in this range */
-						if (DatumGetBool(compar))
-							break;
+					case BTEqualStrategyNumber:
 
 						/*
-						 * We haven't managed to eliminate this range, so
-						 * consider it matching.
+						 * See brin_minmax.c for description of what this is doing.
 						 */
-						matches = BoolGetDatum(true);
-
+						{
+							Datum val;
+							SortSupportData ssup;
+							int			lower;
+							TypeCacheEntry *type;
+
+							/* Is the first (smallest) value after the BRIN range? */
+							val = array->elements[0];
+
+							finfo = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
+																	   BTLessEqualStrategyNumber);
+							matches = FunctionCall2Coll(finfo, colloid, val, maxval);
+
+							/* minval > max(range values) */
+							if (!DatumGetBool(matches))
+								break;
+
+							/* Is the last (largest) value before the BRIN range? */
+							val = array->elements[array->nelements-1];
+
+							finfo = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
+																	   BTGreaterEqualStrategyNumber);
+							matches = FunctionCall2Coll(finfo, colloid, val, minval);
+
+							/* maxval < min(range values) */
+							if (!DatumGetBool(matches))
+								break;
+
+							/*
+							 * OK, there might be some values matching the range. We have
+							 * to search them one by one, or perhaps try binsearch.
+							 */
+							type = lookup_type_cache(array->typeid, TYPECACHE_LT_OPR);
+
+							memset(&ssup, 0, sizeof(SortSupportData));
+
+							ssup.ssup_collation = key->sk_collation;
+							ssup.ssup_cxt = CurrentMemoryContext;
+
+							PrepareSortSupportFromOrderingOp(type->lt_opr, &ssup);
+
+							lower = lower_boundary(array->elements, array->nelements, minval, &ssup);
+
+							/* no elements can possibly match */
+							if (lower == array->nelements)
+							{
+								matches = BoolGetDatum(false);
+								break;
+							}
+
+							/*
+							 * OK, the first element must match the upper boundary too
+							 * (if it does not, no following elements can).
+							 */
+							val = array->elements[lower];
+
+							/*
+							 * In the equality case (WHERE col = someval), we want to return
+							 * the current page range if the minimum value in the range <=
+							 * scan key, and the maximum value >= scan key.
+							 */
+							finfo = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
+																	   BTLessEqualStrategyNumber);
+							matches = FunctionCall2Coll(finfo, colloid, minval, val);
+							if (!DatumGetBool(matches))
+								break;
+							/* max() >= scankey */
+							finfo = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
+																	   BTGreaterEqualStrategyNumber);
+							matches = FunctionCall2Coll(finfo, colloid, maxval, val);
+							break;
+						}
+					case BTGreaterEqualStrategyNumber:
+					case BTGreaterStrategyNumber:
+						finfo = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
+																   key->sk_strategy);
+						/* last value from the array */
+						matches = FunctionCall2Coll(finfo, colloid, maxval,
+													array->elements[0]);
 						break;
-					}
-				case BTGreaterEqualStrategyNumber:
-				case BTGreaterStrategyNumber:
-					finfo = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
-															   key->sk_strategy);
-					/* last value from the array */
-					matches = FunctionCall2Coll(finfo, colloid, maxval, value);
-					break;
 
-				default:
-					/* shouldn't happen */
-					elog(ERROR, "invalid strategy number %d", key->sk_strategy);
-					matches = BoolGetDatum(false);
-					break;
+					default:
+						/* shouldn't happen */
+						elog(ERROR, "invalid strategy number %d", key->sk_strategy);
+						matches = BoolGetDatum(false);
+						break;
+				}
 			}
 
 			/* the range has to match all the scan keys */
@@ -2713,24 +2997,112 @@ brin_minmax_multi_consistent(PG_FUNCTION_ARGS)
 			attno = key->sk_attno;
 			subtype = key->sk_subtype;
 			value = key->sk_argument;
-			switch (key->sk_strategy)
+			if (likely(!(key->sk_flags & SK_SEARCHARRAY)))
 			{
-				case BTLessStrategyNumber:
-				case BTLessEqualStrategyNumber:
-				case BTEqualStrategyNumber:
-				case BTGreaterEqualStrategyNumber:
-				case BTGreaterStrategyNumber:
-
-					finfo = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
-															   key->sk_strategy);
-					matches = FunctionCall2Coll(finfo, colloid, val, value);
-					break;
+				switch (key->sk_strategy)
+				{
+					case BTLessStrategyNumber:
+					case BTLessEqualStrategyNumber:
+					case BTEqualStrategyNumber:
+					case BTGreaterEqualStrategyNumber:
+					case BTGreaterStrategyNumber:
+
+						finfo = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
+																   key->sk_strategy);
+						matches = FunctionCall2Coll(finfo, colloid, val, value);
+						break;
 
-				default:
-					/* shouldn't happen */
-					elog(ERROR, "invalid strategy number %d", key->sk_strategy);
-					matches = BoolGetDatum(false);
-					break;
+					default:
+						/* shouldn't happen */
+						elog(ERROR, "invalid strategy number %d", key->sk_strategy);
+						matches = BoolGetDatum(false);
+						break;
+				}
+			}
+			else
+			{
+				ScanKeyArray *array = (ScanKeyArray *) value;
+
+				/* can happen if the IN list contained just NULLs */
+				if (array->nelements == 0)
+					PG_RETURN_BOOL(false);
+
+				/*
+				 * XXX We should be able to be smarter for the scalar values, as
+				 * we keep them sorted too. So we should be able to quickly check
+				 * if any of the values can match the sorted key values.
+				 */
+				switch (key->sk_strategy)
+				{
+					case BTLessStrategyNumber:
+					case BTLessEqualStrategyNumber:
+						finfo = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
+																   key->sk_strategy);
+						/* first value from the array */
+						matches = FunctionCall2Coll(finfo, colloid, val,
+													array->elements[array->nelements-1]);
+						break;
+
+					case BTEqualStrategyNumber:
+
+						/*
+						 * See brin_minmax.c for description of what this is doing.
+						 */
+						{
+							SortSupportData ssup;
+							int			lower;
+							TypeCacheEntry *type;
+
+							/*
+							 * OK, there might be some values matching the range. We have
+							 * to search them one by one, or perhaps try binsearch.
+							 */
+							type = lookup_type_cache(array->typeid, TYPECACHE_LT_OPR);
+
+							memset(&ssup, 0, sizeof(SortSupportData));
+
+							ssup.ssup_collation = key->sk_collation;
+							ssup.ssup_cxt = CurrentMemoryContext;
+
+							PrepareSortSupportFromOrderingOp(type->lt_opr, &ssup);
+
+							lower = lower_boundary(array->elements, array->nelements, val, &ssup);
+
+							/* no elements can possibly match */
+							if (lower == array->nelements)
+							{
+								matches = BoolGetDatum(false);
+								break;
+							}
+
+							/*
+							 * OK, check the first element must match the upper boundary too
+							 * (if it does not, no following elements can).
+							 *
+							 * In the equality case (WHERE col = someval), we want to return
+							 * the current page range if the minimum value in the range <=
+							 * scan key, and the maximum value >= scan key.
+							 */
+							finfo = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
+																	   BTEqualStrategyNumber);
+							matches = FunctionCall2Coll(finfo, colloid, val, array->elements[lower]);
+							break;
+						}
+					case BTGreaterEqualStrategyNumber:
+					case BTGreaterStrategyNumber:
+						finfo = minmax_multi_get_strategy_procinfo(bdesc, attno, subtype,
+																   key->sk_strategy);
+						/* last value from the array */
+						matches = FunctionCall2Coll(finfo, colloid, val,
+													array->elements[0]);
+						break;
+
+					default:
+						/* shouldn't happen */
+						elog(ERROR, "invalid strategy number %d", key->sk_strategy);
+						matches = BoolGetDatum(false);
+						break;
+				}
 			}
 
 			/* the range has to match all the scan keys */
diff --git a/src/include/catalog/pg_amproc.dat b/src/include/catalog/pg_amproc.dat
index 166681c31ef..4f17f0d58c1 100644
--- a/src/include/catalog/pg_amproc.dat
+++ b/src/include/catalog/pg_amproc.dat
@@ -946,6 +946,9 @@
 { amprocfamily => 'brin/integer_minmax_multi_ops', amproclefttype => 'int2',
   amprocrighttype => 'int2', amprocnum => '5',
   amproc => 'brin_minmax_multi_options' },
+{ amprocfamily => 'brin/integer_minmax_multi_ops', amproclefttype => 'int2',
+  amprocrighttype => 'int2', amprocnum => '6',
+  amproc => 'brin_minmax_multi_preprocess' },
 { amprocfamily => 'brin/integer_minmax_multi_ops', amproclefttype => 'int2',
   amprocrighttype => 'int2', amprocnum => '11',
   amproc => 'brin_minmax_multi_distance_int2' },
@@ -965,6 +968,9 @@
 { amprocfamily => 'brin/integer_minmax_multi_ops', amproclefttype => 'int4',
   amprocrighttype => 'int4', amprocnum => '5',
   amproc => 'brin_minmax_multi_options' },
+{ amprocfamily => 'brin/integer_minmax_multi_ops', amproclefttype => 'int4',
+  amprocrighttype => 'int4', amprocnum => '6',
+  amproc => 'brin_minmax_multi_preprocess' },
 { amprocfamily => 'brin/integer_minmax_multi_ops', amproclefttype => 'int4',
   amprocrighttype => 'int4', amprocnum => '11',
   amproc => 'brin_minmax_multi_distance_int4' },
@@ -984,6 +990,9 @@
 { amprocfamily => 'brin/integer_minmax_multi_ops', amproclefttype => 'int8',
   amprocrighttype => 'int8', amprocnum => '5',
   amproc => 'brin_minmax_multi_options' },
+{ amprocfamily => 'brin/integer_minmax_multi_ops', amproclefttype => 'int8',
+  amprocrighttype => 'int8', amprocnum => '6',
+  amproc => 'brin_minmax_multi_preprocess' },
 { amprocfamily => 'brin/integer_minmax_multi_ops', amproclefttype => 'int8',
   amprocrighttype => 'int8', amprocnum => '11',
   amproc => 'brin_minmax_multi_distance_int8' },
@@ -1095,6 +1104,9 @@
 { amprocfamily => 'brin/oid_minmax_multi_ops', amproclefttype => 'oid',
   amprocrighttype => 'oid', amprocnum => '5',
   amproc => 'brin_minmax_multi_options' },
+{ amprocfamily => 'brin/oid_minmax_multi_ops', amproclefttype => 'oid',
+  amprocrighttype => 'oid', amprocnum => '6',
+  amproc => 'brin_minmax_multi_preprocess' },
 { amprocfamily => 'brin/oid_minmax_multi_ops', amproclefttype => 'oid',
   amprocrighttype => 'oid', amprocnum => '11',
   amproc => 'brin_minmax_multi_distance_int4' },
@@ -1161,6 +1173,9 @@
 { amprocfamily => 'brin/tid_minmax_multi_ops', amproclefttype => 'tid',
   amprocrighttype => 'tid', amprocnum => '5',
   amproc => 'brin_minmax_multi_options' },
+{ amprocfamily => 'brin/tid_minmax_multi_ops', amproclefttype => 'tid',
+  amprocrighttype => 'tid', amprocnum => '6',
+  amproc => 'brin_minmax_multi_preprocess' },
 { amprocfamily => 'brin/tid_minmax_multi_ops', amproclefttype => 'tid',
   amprocrighttype => 'tid', amprocnum => '11',
   amproc => 'brin_minmax_multi_distance_tid' },
@@ -1214,6 +1229,9 @@
 { amprocfamily => 'brin/float_minmax_multi_ops', amproclefttype => 'float4',
   amprocrighttype => 'float4', amprocnum => '5',
   amproc => 'brin_minmax_multi_options' },
+{ amprocfamily => 'brin/float_minmax_multi_ops', amproclefttype => 'float4',
+  amprocrighttype => 'float4', amprocnum => '6',
+  amproc => 'brin_minmax_multi_preprocess' },
 { amprocfamily => 'brin/float_minmax_multi_ops', amproclefttype => 'float4',
   amprocrighttype => 'float4', amprocnum => '11',
   amproc => 'brin_minmax_multi_distance_float4' },
@@ -1233,6 +1251,9 @@
 { amprocfamily => 'brin/float_minmax_multi_ops', amproclefttype => 'float8',
   amprocrighttype => 'float8', amprocnum => '5',
   amproc => 'brin_minmax_multi_options' },
+{ amprocfamily => 'brin/float_minmax_multi_ops', amproclefttype => 'float8',
+  amprocrighttype => 'float8', amprocnum => '6',
+  amproc => 'brin_minmax_multi_preprocess' },
 { amprocfamily => 'brin/float_minmax_multi_ops', amproclefttype => 'float8',
   amprocrighttype => 'float8', amprocnum => '11',
   amproc => 'brin_minmax_multi_distance_float8' },
@@ -1305,6 +1326,9 @@
 { amprocfamily => 'brin/macaddr_minmax_multi_ops', amproclefttype => 'macaddr',
   amprocrighttype => 'macaddr', amprocnum => '5',
   amproc => 'brin_minmax_multi_options' },
+{ amprocfamily => 'brin/macaddr_minmax_multi_ops', amproclefttype => 'macaddr',
+  amprocrighttype => 'macaddr', amprocnum => '6',
+  amproc => 'brin_minmax_multi_preprocess' },
 { amprocfamily => 'brin/macaddr_minmax_multi_ops', amproclefttype => 'macaddr',
   amprocrighttype => 'macaddr', amprocnum => '11',
   amproc => 'brin_minmax_multi_distance_macaddr' },
@@ -1361,6 +1385,9 @@
 { amprocfamily => 'brin/macaddr8_minmax_multi_ops',
   amproclefttype => 'macaddr8', amprocrighttype => 'macaddr8', amprocnum => '5',
   amproc => 'brin_minmax_multi_options' },
+{ amprocfamily => 'brin/macaddr8_minmax_multi_ops',
+  amproclefttype => 'macaddr8', amprocrighttype => 'macaddr8', amprocnum => '6',
+  amproc => 'brin_minmax_multi_preprocess' },
 { amprocfamily => 'brin/macaddr8_minmax_multi_ops',
   amproclefttype => 'macaddr8', amprocrighttype => 'macaddr8',
   amprocnum => '11', amproc => 'brin_minmax_multi_distance_macaddr8' },
@@ -1415,6 +1442,9 @@
 { amprocfamily => 'brin/network_minmax_multi_ops', amproclefttype => 'inet',
   amprocrighttype => 'inet', amprocnum => '5',
   amproc => 'brin_minmax_multi_options' },
+{ amprocfamily => 'brin/network_minmax_multi_ops', amproclefttype => 'inet',
+  amprocrighttype => 'inet', amprocnum => '6',
+  amproc => 'brin_minmax_multi_preprocess' },
 { amprocfamily => 'brin/network_minmax_multi_ops', amproclefttype => 'inet',
   amprocrighttype => 'inet', amprocnum => '11',
   amproc => 'brin_minmax_multi_distance_inet' },
@@ -1521,6 +1551,9 @@
 { amprocfamily => 'brin/time_minmax_multi_ops', amproclefttype => 'time',
   amprocrighttype => 'time', amprocnum => '5',
   amproc => 'brin_minmax_multi_options' },
+{ amprocfamily => 'brin/time_minmax_multi_ops', amproclefttype => 'time',
+  amprocrighttype => 'time', amprocnum => '6',
+  amproc => 'brin_minmax_multi_preprocess' },
 { amprocfamily => 'brin/time_minmax_multi_ops', amproclefttype => 'time',
   amprocrighttype => 'time', amprocnum => '11',
   amproc => 'brin_minmax_multi_distance_time' },
@@ -1604,6 +1637,9 @@
 { amprocfamily => 'brin/datetime_minmax_multi_ops',
   amproclefttype => 'timestamp', amprocrighttype => 'timestamp',
   amprocnum => '5', amproc => 'brin_minmax_multi_options' },
+{ amprocfamily => 'brin/datetime_minmax_multi_ops',
+  amproclefttype => 'timestamp', amprocrighttype => 'timestamp',
+  amprocnum => '6', amproc => 'brin_minmax_multi_preprocess' },
 { amprocfamily => 'brin/datetime_minmax_multi_ops',
   amproclefttype => 'timestamp', amprocrighttype => 'timestamp',
   amprocnum => '11', amproc => 'brin_minmax_multi_distance_timestamp' },
@@ -1623,6 +1659,9 @@
 { amprocfamily => 'brin/datetime_minmax_multi_ops',
   amproclefttype => 'timestamptz', amprocrighttype => 'timestamptz',
   amprocnum => '5', amproc => 'brin_minmax_multi_options' },
+{ amprocfamily => 'brin/datetime_minmax_multi_ops',
+  amproclefttype => 'timestamptz', amprocrighttype => 'timestamptz',
+  amprocnum => '6', amproc => 'brin_minmax_multi_preprocess' },
 { amprocfamily => 'brin/datetime_minmax_multi_ops',
   amproclefttype => 'timestamptz', amprocrighttype => 'timestamptz',
   amprocnum => '11', amproc => 'brin_minmax_multi_distance_timestamp' },
@@ -1642,6 +1681,9 @@
 { amprocfamily => 'brin/datetime_minmax_multi_ops', amproclefttype => 'date',
   amprocrighttype => 'date', amprocnum => '5',
   amproc => 'brin_minmax_multi_options' },
+{ amprocfamily => 'brin/datetime_minmax_multi_ops', amproclefttype => 'date',
+  amprocrighttype => 'date', amprocnum => '6',
+  amproc => 'brin_minmax_multi_preprocess' },
 { amprocfamily => 'brin/datetime_minmax_multi_ops', amproclefttype => 'date',
   amprocrighttype => 'date', amprocnum => '11',
   amproc => 'brin_minmax_multi_distance_date' },
@@ -1733,6 +1775,9 @@
 { amprocfamily => 'brin/interval_minmax_multi_ops',
   amproclefttype => 'interval', amprocrighttype => 'interval', amprocnum => '5',
   amproc => 'brin_minmax_multi_options' },
+{ amprocfamily => 'brin/interval_minmax_multi_ops',
+  amproclefttype => 'interval', amprocrighttype => 'interval', amprocnum => '6',
+  amproc => 'brin_minmax_multi_preprocess' },
 { amprocfamily => 'brin/interval_minmax_multi_ops',
   amproclefttype => 'interval', amprocrighttype => 'interval',
   amprocnum => '11', amproc => 'brin_minmax_multi_distance_interval' },
@@ -1789,6 +1834,9 @@
 { amprocfamily => 'brin/timetz_minmax_multi_ops', amproclefttype => 'timetz',
   amprocrighttype => 'timetz', amprocnum => '5',
   amproc => 'brin_minmax_multi_options' },
+{ amprocfamily => 'brin/timetz_minmax_multi_ops', amproclefttype => 'timetz',
+  amprocrighttype => 'timetz', amprocnum => '6',
+  amproc => 'brin_minmax_multi_preprocess' },
 { amprocfamily => 'brin/timetz_minmax_multi_ops', amproclefttype => 'timetz',
   amprocrighttype => 'timetz', amprocnum => '11',
   amproc => 'brin_minmax_multi_distance_timetz' },
@@ -1875,6 +1923,9 @@
 { amprocfamily => 'brin/numeric_minmax_multi_ops', amproclefttype => 'numeric',
   amprocrighttype => 'numeric', amprocnum => '5',
   amproc => 'brin_minmax_multi_options' },
+{ amprocfamily => 'brin/numeric_minmax_multi_ops', amproclefttype => 'numeric',
+  amprocrighttype => 'numeric', amprocnum => '6',
+  amproc => 'brin_minmax_multi_preprocess' },
 { amprocfamily => 'brin/numeric_minmax_multi_ops', amproclefttype => 'numeric',
   amprocrighttype => 'numeric', amprocnum => '11',
   amproc => 'brin_minmax_multi_distance_numeric' },
@@ -1929,6 +1980,9 @@
 { amprocfamily => 'brin/uuid_minmax_multi_ops', amproclefttype => 'uuid',
   amprocrighttype => 'uuid', amprocnum => '5',
   amproc => 'brin_minmax_multi_options' },
+{ amprocfamily => 'brin/uuid_minmax_multi_ops', amproclefttype => 'uuid',
+  amprocrighttype => 'uuid', amprocnum => '6',
+  amproc => 'brin_minmax_multi_preprocess' },
 { amprocfamily => 'brin/uuid_minmax_multi_ops', amproclefttype => 'uuid',
   amprocrighttype => 'uuid', amprocnum => '11',
   amproc => 'brin_minmax_multi_distance_uuid' },
@@ -2005,6 +2059,9 @@
 { amprocfamily => 'brin/pg_lsn_minmax_multi_ops', amproclefttype => 'pg_lsn',
   amprocrighttype => 'pg_lsn', amprocnum => '5',
   amproc => 'brin_minmax_multi_options' },
+{ amprocfamily => 'brin/pg_lsn_minmax_multi_ops', amproclefttype => 'pg_lsn',
+  amprocrighttype => 'pg_lsn', amprocnum => '6',
+  amproc => 'brin_minmax_multi_preprocess' },
 { amprocfamily => 'brin/pg_lsn_minmax_multi_ops', amproclefttype => 'pg_lsn',
   amprocrighttype => 'pg_lsn', amprocnum => '11',
   amproc => 'brin_minmax_multi_distance_pg_lsn' },
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 6638552bd05..4e8d6668648 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8520,6 +8520,10 @@
   proname => 'brin_minmax_multi_options', proisstrict => 'f',
   prorettype => 'void', proargtypes => 'internal',
   prosrc => 'brin_minmax_multi_options' },
+{ oid => '9326', descr => 'BRIN multi minmax support',
+  proname => 'brin_minmax_multi_preprocess', proisstrict => 'f',
+  prorettype => 'internal', proargtypes => 'internal internal',
+  prosrc => 'brin_minmax_multi_preprocess' },
 
 { oid => '4621', descr => 'BRIN multi minmax int2 distance',
   proname => 'brin_minmax_multi_distance_int2', prorettype => 'float8',
diff --git a/src/test/regress/expected/brin_multi.out b/src/test/regress/expected/brin_multi.out
index 98e1cdaa361..15355b699d7 100644
--- a/src/test/regress/expected/brin_multi.out
+++ b/src/test/regress/expected/brin_multi.out
@@ -823,3 +823,929 @@ SELECT COUNT(*) FROM brin_test_multi_2 WHERE a >= 'c51ce410-c124-a10e-0db5-e4b97
 
 DROP TABLE brin_test_multi_2;
 RESET enable_seqscan;
+-- do some tests on IN clauses for simple data types
+CREATE TABLE brin_in_test_multi_1 (a INT, b BIGINT) WITH (fillfactor=10);
+INSERT INTO brin_in_test_multi_1
+SELECT i/5 + mod(991 * i + 617, 20),
+       i/10 + mod(853 * i + 491, 30)
+  FROM generate_series(1,1000) s(i);
+CREATE INDEX brin_in_test_multi_1_idx_1 ON brin_in_test_multi_1 USING brin (a int4_minmax_multi_ops) WITH (pages_per_range=1);
+CREATE INDEX brin_in_test_multi_1_idx_2 ON brin_in_test_multi_1 USING brin (b int8_minmax_multi_ops) WITH (pages_per_range=1);
+SET enable_seqscan=off;
+-- int: equality
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (113);
+                         QUERY PLAN                          
+-------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (a = 113)
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_1
+               Index Cond: (a = 113)
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (113);
+ count 
+-------
+     8
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (113, NULL);
+                          QUERY PLAN                           
+---------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (a = ANY ('{113,NULL}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_1
+               Index Cond: (a = ANY ('{113,NULL}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (113, NULL);
+ count 
+-------
+     8
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (NULL, NULL);
+                           QUERY PLAN                           
+----------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (a = ANY ('{NULL,NULL}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_1
+               Index Cond: (a = ANY ('{NULL,NULL}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (NULL, NULL);
+ count 
+-------
+     0
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (113, 177);
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (a = ANY ('{113,177}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_1
+               Index Cond: (a = ANY ('{113,177}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (113, 177);
+ count 
+-------
+    16
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (-113, -177);
+                           QUERY PLAN                           
+----------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (a = ANY ('{-113,-177}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_1
+               Index Cond: (a = ANY ('{-113,-177}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (-113, -177);
+ count 
+-------
+     0
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (313, 377);
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (a = ANY ('{313,377}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_1
+               Index Cond: (a = ANY ('{313,377}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (313, 377);
+ count 
+-------
+     0
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (113, 177, NULL);
+                            QUERY PLAN                             
+-------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (a = ANY ('{113,177,NULL}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_1
+               Index Cond: (a = ANY ('{113,177,NULL}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (113, 177, NULL);
+ count 
+-------
+    16
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (113, 177, 25);
+                           QUERY PLAN                            
+-----------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (a = ANY ('{113,177,25}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_1
+               Index Cond: (a = ANY ('{113,177,25}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (113, 177, 25);
+ count 
+-------
+    24
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (NULL, 113, 177, 25);
+                              QUERY PLAN                              
+----------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (a = ANY ('{NULL,113,177,25}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_1
+               Index Cond: (a = ANY ('{NULL,113,177,25}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (NULL, 113, 177, 25);
+ count 
+-------
+    24
+(1 row)
+
+-- int: less than
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a < ANY(ARRAY[113]);
+                         QUERY PLAN                          
+-------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (a < ANY ('{113}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_1
+               Index Cond: (a < ANY ('{113}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a < ANY(ARRAY[113]);
+ count 
+-------
+   515
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a < ANY(ARRAY[113, NULL]);
+                          QUERY PLAN                           
+---------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (a < ANY ('{113,NULL}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_1
+               Index Cond: (a < ANY ('{113,NULL}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a < ANY(ARRAY[113, NULL]);
+ count 
+-------
+   515
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a < ANY(ARRAY[NULL, NULL]::int[]);
+                           QUERY PLAN                           
+----------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (a < ANY ('{NULL,NULL}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_1
+               Index Cond: (a < ANY ('{NULL,NULL}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a < ANY(ARRAY[NULL, NULL]::int[]);
+ count 
+-------
+     0
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a <= ANY(ARRAY[113, 177]);
+                          QUERY PLAN                           
+---------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (a <= ANY ('{113,177}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_1
+               Index Cond: (a <= ANY ('{113,177}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a <= ANY(ARRAY[113, 177]);
+ count 
+-------
+   843
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a < ANY(ARRAY[113, 177, NULL]);
+                            QUERY PLAN                             
+-------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (a < ANY ('{113,177,NULL}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_1
+               Index Cond: (a < ANY ('{113,177,NULL}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a < ANY(ARRAY[113, 177, NULL]);
+ count 
+-------
+   835
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a <= ANY(ARRAY[113, 177, 25]);
+                            QUERY PLAN                            
+------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (a <= ANY ('{113,177,25}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_1
+               Index Cond: (a <= ANY ('{113,177,25}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a <= ANY(ARRAY[113, 177, 25]);
+ count 
+-------
+   843
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a < ANY(ARRAY[NULL, 113, 177, 25]);
+                              QUERY PLAN                              
+----------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (a < ANY ('{NULL,113,177,25}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_1
+               Index Cond: (a < ANY ('{NULL,113,177,25}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a < ANY(ARRAY[NULL, 113, 177, 25]);
+ count 
+-------
+   835
+(1 row)
+
+-- int: greater than
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a > ANY(ARRAY[113]);
+                         QUERY PLAN                          
+-------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (a > ANY ('{113}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_1
+               Index Cond: (a > ANY ('{113}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a > ANY(ARRAY[113]);
+ count 
+-------
+   477
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a > ANY(ARRAY[113, NULL]);
+                          QUERY PLAN                           
+---------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (a > ANY ('{113,NULL}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_1
+               Index Cond: (a > ANY ('{113,NULL}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a > ANY(ARRAY[113, NULL]);
+ count 
+-------
+   477
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a > ANY(ARRAY[NULL, NULL]::int[]);
+                           QUERY PLAN                           
+----------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (a > ANY ('{NULL,NULL}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_1
+               Index Cond: (a > ANY ('{NULL,NULL}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a > ANY(ARRAY[NULL, NULL]::int[]);
+ count 
+-------
+     0
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a >= ANY(ARRAY[113, 177]);
+                          QUERY PLAN                           
+---------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (a >= ANY ('{113,177}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_1
+               Index Cond: (a >= ANY ('{113,177}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a >= ANY(ARRAY[113, 177]);
+ count 
+-------
+   485
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a > ANY(ARRAY[113, 177, NULL]);
+                            QUERY PLAN                             
+-------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (a > ANY ('{113,177,NULL}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_1
+               Index Cond: (a > ANY ('{113,177,NULL}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a > ANY(ARRAY[113, 177, NULL]);
+ count 
+-------
+   477
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a > ANY(ARRAY[113, 177, 25]);
+                           QUERY PLAN                            
+-----------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (a > ANY ('{113,177,25}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_1
+               Index Cond: (a > ANY ('{113,177,25}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a > ANY(ARRAY[113, 177, 25]);
+ count 
+-------
+   917
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a >= ANY(ARRAY[NULL, 113, 177, 25]);
+                              QUERY PLAN                               
+-----------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (a >= ANY ('{NULL,113,177,25}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_1
+               Index Cond: (a >= ANY ('{NULL,113,177,25}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a >= ANY(ARRAY[NULL, 113, 177, 25]);
+ count 
+-------
+   925
+(1 row)
+
+-- bigint: equality
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (82);
+                         QUERY PLAN                          
+-------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (b = 82)
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_2
+               Index Cond: (b = 82)
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (82);
+ count 
+-------
+    10
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (82, NULL);
+                         QUERY PLAN                          
+-------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (b = ANY ('{82,NULL}'::bigint[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_2
+               Index Cond: (b = ANY ('{82,NULL}'::bigint[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (82, NULL);
+ count 
+-------
+    10
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (NULL, NULL);
+                          QUERY PLAN                           
+---------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (b = ANY ('{NULL,NULL}'::bigint[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_2
+               Index Cond: (b = ANY ('{NULL,NULL}'::bigint[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (NULL, NULL);
+ count 
+-------
+     0
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (82, 41);
+                         QUERY PLAN                          
+-------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (b = ANY ('{82,41}'::bigint[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_2
+               Index Cond: (b = ANY ('{82,41}'::bigint[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (82, 41);
+ count 
+-------
+    20
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (-82, -141);
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (b = ANY ('{-82,-141}'::bigint[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_2
+               Index Cond: (b = ANY ('{-82,-141}'::bigint[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (-82, -141);
+ count 
+-------
+     0
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (382, 441);
+                         QUERY PLAN                          
+-------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (b = ANY ('{382,441}'::bigint[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_2
+               Index Cond: (b = ANY ('{382,441}'::bigint[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (382, 441);
+ count 
+-------
+     0
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (82, 41, NULL);
+                           QUERY PLAN                           
+----------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (b = ANY ('{82,41,NULL}'::bigint[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_2
+               Index Cond: (b = ANY ('{82,41,NULL}'::bigint[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (82, 41, NULL);
+ count 
+-------
+    20
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (82, 41, 15);
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (b = ANY ('{82,41,15}'::bigint[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_2
+               Index Cond: (b = ANY ('{82,41,15}'::bigint[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (82, 41, 15);
+ count 
+-------
+    25
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (NULL, 82, 41, 15);
+                            QUERY PLAN                             
+-------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (b = ANY ('{NULL,82,41,15}'::bigint[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_2
+               Index Cond: (b = ANY ('{NULL,82,41,15}'::bigint[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (NULL, 82, 41, 15);
+ count 
+-------
+    25
+(1 row)
+
+-- bigint: less than
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b < ANY(ARRAY[82]);
+                         QUERY PLAN                          
+-------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (b < ANY ('{82}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_2
+               Index Cond: (b < ANY ('{82}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b < ANY(ARRAY[82]);
+ count 
+-------
+   674
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b < ANY(ARRAY[82, NULL]);
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (b < ANY ('{82,NULL}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_2
+               Index Cond: (b < ANY ('{82,NULL}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b < ANY(ARRAY[82, NULL]);
+ count 
+-------
+   674
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b < ANY(ARRAY[NULL, NULL]::bigint[]);
+                          QUERY PLAN                           
+---------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (b < ANY ('{NULL,NULL}'::bigint[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_2
+               Index Cond: (b < ANY ('{NULL,NULL}'::bigint[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b < ANY(ARRAY[NULL, NULL]::bigint[]);
+ count 
+-------
+     0
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b <= ANY(ARRAY[82, 41]);
+                         QUERY PLAN                          
+-------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (b <= ANY ('{82,41}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_2
+               Index Cond: (b <= ANY ('{82,41}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b <= ANY(ARRAY[82, 41]);
+ count 
+-------
+   684
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b < ANY(ARRAY[82, 41, NULL]);
+                           QUERY PLAN                            
+-----------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (b < ANY ('{82,41,NULL}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_2
+               Index Cond: (b < ANY ('{82,41,NULL}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b < ANY(ARRAY[82, 41, NULL]);
+ count 
+-------
+   674
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b <= ANY(ARRAY[82, 41, 15]);
+                           QUERY PLAN                           
+----------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (b <= ANY ('{82,41,15}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_2
+               Index Cond: (b <= ANY ('{82,41,15}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b <= ANY(ARRAY[82, 41, 15]);
+ count 
+-------
+   684
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b < ANY(ARRAY[NULL, 82, 41, 15]);
+                             QUERY PLAN                             
+--------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (b < ANY ('{NULL,82,41,15}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_2
+               Index Cond: (b < ANY ('{NULL,82,41,15}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b < ANY(ARRAY[NULL, 82, 41, 15]);
+ count 
+-------
+   674
+(1 row)
+
+-- bigint: greater than
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b > ANY(ARRAY[82]);
+                         QUERY PLAN                          
+-------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (b > ANY ('{82}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_2
+               Index Cond: (b > ANY ('{82}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b > ANY(ARRAY[82]);
+ count 
+-------
+   316
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b > ANY(ARRAY[82, NULL]);
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (b > ANY ('{82,NULL}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_2
+               Index Cond: (b > ANY ('{82,NULL}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b > ANY(ARRAY[82, NULL]);
+ count 
+-------
+   316
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b > ANY(ARRAY[NULL, NULL]::bigint[]);
+                          QUERY PLAN                           
+---------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (b > ANY ('{NULL,NULL}'::bigint[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_2
+               Index Cond: (b > ANY ('{NULL,NULL}'::bigint[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b > ANY(ARRAY[NULL, NULL]::bigint[]);
+ count 
+-------
+     0
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b > ANY(ARRAY[82, 41]);
+                         QUERY PLAN                          
+-------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (b > ANY ('{82,41}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_2
+               Index Cond: (b > ANY ('{82,41}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b > ANY(ARRAY[82, 41]);
+ count 
+-------
+   726
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b >= ANY(ARRAY[82, 41, NULL]);
+                            QUERY PLAN                            
+------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (b >= ANY ('{82,41,NULL}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_2
+               Index Cond: (b >= ANY ('{82,41,NULL}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b >= ANY(ARRAY[82, 41, NULL]);
+ count 
+-------
+   736
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b >= ANY(ARRAY[82, 41, 15]);
+                           QUERY PLAN                           
+----------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (b >= ANY ('{82,41,15}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_2
+               Index Cond: (b >= ANY ('{82,41,15}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b >= ANY(ARRAY[82, 41, 15]);
+ count 
+-------
+   961
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b > ANY(ARRAY[NULL, 82, 41, 15]);
+                             QUERY PLAN                             
+--------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_1
+         Recheck Cond: (b > ANY ('{NULL,82,41,15}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_1_idx_2
+               Index Cond: (b > ANY ('{NULL,82,41,15}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b > ANY(ARRAY[NULL, 82, 41, 15]);
+ count 
+-------
+   956
+(1 row)
+
+DROP TABLE brin_in_test_multi_1;
+RESET enable_seqscan;
+-- do some tests on IN clauses for varlena data types
+CREATE TABLE brin_in_test_multi_2 (a UUID) WITH (fillfactor=10);
+INSERT INTO brin_in_test_multi_2
+SELECT v::uuid FROM (SELECT row_number() OVER (ORDER BY v) c, v FROM (SELECT md5((i/13)::text) AS v FROM generate_series(1,1000) s(i)) foo) bar ORDER BY c + 25 * random();
+CREATE INDEX brin_in_test_multi_2_idx ON brin_in_test_multi_2 USING brin (a uuid_minmax_multi_ops) WITH (pages_per_range=1);
+SET enable_seqscan=off;
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_2 WHERE a IN ('33e75ff0-9dd6-01bb-e69f-351039152189');
+                                  QUERY PLAN                                  
+------------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_2
+         Recheck Cond: (a = '33e75ff0-9dd6-01bb-e69f-351039152189'::uuid)
+         ->  Bitmap Index Scan on brin_in_test_multi_2_idx
+               Index Cond: (a = '33e75ff0-9dd6-01bb-e69f-351039152189'::uuid)
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_2 WHERE a IN ('33e75ff0-9dd6-01bb-e69f-351039152189');
+ count 
+-------
+    13
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_2 WHERE a IN ('33e75ff0-9dd6-01bb-e69f-351039152189', NULL);
+                                         QUERY PLAN                                          
+---------------------------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_2
+         Recheck Cond: (a = ANY ('{33e75ff0-9dd6-01bb-e69f-351039152189,NULL}'::uuid[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_2_idx
+               Index Cond: (a = ANY ('{33e75ff0-9dd6-01bb-e69f-351039152189,NULL}'::uuid[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_2 WHERE a IN ('33e75ff0-9dd6-01bb-e69f-351039152189', NULL);
+ count 
+-------
+    13
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_2 WHERE a IN (NULL, NULL);
+                         QUERY PLAN                          
+-------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_2
+         Recheck Cond: (a = ANY ('{NULL,NULL}'::uuid[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_2_idx
+               Index Cond: (a = ANY ('{NULL,NULL}'::uuid[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_2 WHERE a IN (NULL, NULL);
+ count 
+-------
+     0
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_2 WHERE a IN ('33e75ff0-9dd6-01bb-e69f-351039152189', 'f457c545-a9de-d88f-18ec-ee47145a72c0');
+                                                         QUERY PLAN                                                          
+-----------------------------------------------------------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_2
+         Recheck Cond: (a = ANY ('{33e75ff0-9dd6-01bb-e69f-351039152189,f457c545-a9de-d88f-18ec-ee47145a72c0}'::uuid[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_2_idx
+               Index Cond: (a = ANY ('{33e75ff0-9dd6-01bb-e69f-351039152189,f457c545-a9de-d88f-18ec-ee47145a72c0}'::uuid[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_2 WHERE a IN ('33e75ff0-9dd6-01bb-e69f-351039152189', 'f457c545-a9de-d88f-18ec-ee47145a72c0');
+ count 
+-------
+    26
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_2 WHERE a IN ('33e75ff0-9dd6-01bb-e69f-351039152189', 'f457c545-a9de-d88f-18ec-ee47145a72c0', NULL);
+                                                            QUERY PLAN                                                            
+----------------------------------------------------------------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_2
+         Recheck Cond: (a = ANY ('{33e75ff0-9dd6-01bb-e69f-351039152189,f457c545-a9de-d88f-18ec-ee47145a72c0,NULL}'::uuid[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_2_idx
+               Index Cond: (a = ANY ('{33e75ff0-9dd6-01bb-e69f-351039152189,f457c545-a9de-d88f-18ec-ee47145a72c0,NULL}'::uuid[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_2 WHERE a IN ('33e75ff0-9dd6-01bb-e69f-351039152189', 'f457c545-a9de-d88f-18ec-ee47145a72c0', NULL);
+ count 
+-------
+    26
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_2 WHERE a IN ('33e75ff0-9dd6-01bb-e69f-351039152189', 'f457c545-a9de-d88f-18ec-ee47145a72c0', 'c51ce410-c124-a10e-0db5-e4b97fc2af39');
+                                                                            QUERY PLAN                                                                            
+------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_2
+         Recheck Cond: (a = ANY ('{33e75ff0-9dd6-01bb-e69f-351039152189,f457c545-a9de-d88f-18ec-ee47145a72c0,c51ce410-c124-a10e-0db5-e4b97fc2af39}'::uuid[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_2_idx
+               Index Cond: (a = ANY ('{33e75ff0-9dd6-01bb-e69f-351039152189,f457c545-a9de-d88f-18ec-ee47145a72c0,c51ce410-c124-a10e-0db5-e4b97fc2af39}'::uuid[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_2 WHERE a IN ('33e75ff0-9dd6-01bb-e69f-351039152189', 'f457c545-a9de-d88f-18ec-ee47145a72c0', 'c51ce410-c124-a10e-0db5-e4b97fc2af39');
+ count 
+-------
+    39
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_2 WHERE a IN (NULL, '33e75ff0-9dd6-01bb-e69f-351039152189', 'f457c545-a9de-d88f-18ec-ee47145a72c0', 'c51ce410-c124-a10e-0db5-e4b97fc2af39');
+                                                                              QUERY PLAN                                                                               
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_multi_2
+         Recheck Cond: (a = ANY ('{NULL,33e75ff0-9dd6-01bb-e69f-351039152189,f457c545-a9de-d88f-18ec-ee47145a72c0,c51ce410-c124-a10e-0db5-e4b97fc2af39}'::uuid[]))
+         ->  Bitmap Index Scan on brin_in_test_multi_2_idx
+               Index Cond: (a = ANY ('{NULL,33e75ff0-9dd6-01bb-e69f-351039152189,f457c545-a9de-d88f-18ec-ee47145a72c0,c51ce410-c124-a10e-0db5-e4b97fc2af39}'::uuid[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_multi_2 WHERE a IN (NULL, '33e75ff0-9dd6-01bb-e69f-351039152189', 'f457c545-a9de-d88f-18ec-ee47145a72c0', 'c51ce410-c124-a10e-0db5-e4b97fc2af39');
+ count 
+-------
+    39
+(1 row)
+
+DROP TABLE brin_in_test_multi_2;
+RESET enable_seqscan;
diff --git a/src/test/regress/sql/brin_multi.sql b/src/test/regress/sql/brin_multi.sql
index a59e182bc25..fa1d5f7dfef 100644
--- a/src/test/regress/sql/brin_multi.sql
+++ b/src/test/regress/sql/brin_multi.sql
@@ -550,3 +550,304 @@ SELECT COUNT(*) FROM brin_test_multi_2 WHERE a >= 'c51ce410-c124-a10e-0db5-e4b97
 
 DROP TABLE brin_test_multi_2;
 RESET enable_seqscan;
+
+
+-- do some tests on IN clauses for simple data types
+CREATE TABLE brin_in_test_multi_1 (a INT, b BIGINT) WITH (fillfactor=10);
+INSERT INTO brin_in_test_multi_1
+SELECT i/5 + mod(991 * i + 617, 20),
+       i/10 + mod(853 * i + 491, 30)
+  FROM generate_series(1,1000) s(i);
+
+CREATE INDEX brin_in_test_multi_1_idx_1 ON brin_in_test_multi_1 USING brin (a int4_minmax_multi_ops) WITH (pages_per_range=1);
+CREATE INDEX brin_in_test_multi_1_idx_2 ON brin_in_test_multi_1 USING brin (b int8_minmax_multi_ops) WITH (pages_per_range=1);
+
+SET enable_seqscan=off;
+
+-- int: equality
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (113);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (113);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (113, NULL);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (113, NULL);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (NULL, NULL);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (NULL, NULL);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (113, 177);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (113, 177);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (-113, -177);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (-113, -177);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (313, 377);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (313, 377);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (113, 177, NULL);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (113, 177, NULL);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (113, 177, 25);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (113, 177, 25);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (NULL, 113, 177, 25);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a IN (NULL, 113, 177, 25);
+
+-- int: less than
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a < ANY(ARRAY[113]);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a < ANY(ARRAY[113]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a < ANY(ARRAY[113, NULL]);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a < ANY(ARRAY[113, NULL]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a < ANY(ARRAY[NULL, NULL]::int[]);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a < ANY(ARRAY[NULL, NULL]::int[]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a <= ANY(ARRAY[113, 177]);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a <= ANY(ARRAY[113, 177]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a < ANY(ARRAY[113, 177, NULL]);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a < ANY(ARRAY[113, 177, NULL]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a <= ANY(ARRAY[113, 177, 25]);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a <= ANY(ARRAY[113, 177, 25]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a < ANY(ARRAY[NULL, 113, 177, 25]);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a < ANY(ARRAY[NULL, 113, 177, 25]);
+
+-- int: greater than
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a > ANY(ARRAY[113]);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a > ANY(ARRAY[113]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a > ANY(ARRAY[113, NULL]);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a > ANY(ARRAY[113, NULL]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a > ANY(ARRAY[NULL, NULL]::int[]);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a > ANY(ARRAY[NULL, NULL]::int[]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a >= ANY(ARRAY[113, 177]);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a >= ANY(ARRAY[113, 177]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a > ANY(ARRAY[113, 177, NULL]);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a > ANY(ARRAY[113, 177, NULL]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a > ANY(ARRAY[113, 177, 25]);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a > ANY(ARRAY[113, 177, 25]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a >= ANY(ARRAY[NULL, 113, 177, 25]);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE a >= ANY(ARRAY[NULL, 113, 177, 25]);
+
+-- bigint: equality
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (82);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (82);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (82, NULL);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (82, NULL);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (NULL, NULL);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (NULL, NULL);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (82, 41);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (82, 41);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (-82, -141);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (-82, -141);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (382, 441);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (382, 441);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (82, 41, NULL);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (82, 41, NULL);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (82, 41, 15);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (82, 41, 15);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (NULL, 82, 41, 15);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b IN (NULL, 82, 41, 15);
+
+-- bigint: less than
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b < ANY(ARRAY[82]);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b < ANY(ARRAY[82]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b < ANY(ARRAY[82, NULL]);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b < ANY(ARRAY[82, NULL]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b < ANY(ARRAY[NULL, NULL]::bigint[]);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b < ANY(ARRAY[NULL, NULL]::bigint[]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b <= ANY(ARRAY[82, 41]);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b <= ANY(ARRAY[82, 41]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b < ANY(ARRAY[82, 41, NULL]);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b < ANY(ARRAY[82, 41, NULL]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b <= ANY(ARRAY[82, 41, 15]);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b <= ANY(ARRAY[82, 41, 15]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b < ANY(ARRAY[NULL, 82, 41, 15]);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b < ANY(ARRAY[NULL, 82, 41, 15]);
+
+-- bigint: greater than
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b > ANY(ARRAY[82]);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b > ANY(ARRAY[82]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b > ANY(ARRAY[82, NULL]);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b > ANY(ARRAY[82, NULL]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b > ANY(ARRAY[NULL, NULL]::bigint[]);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b > ANY(ARRAY[NULL, NULL]::bigint[]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b > ANY(ARRAY[82, 41]);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b > ANY(ARRAY[82, 41]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b >= ANY(ARRAY[82, 41, NULL]);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b >= ANY(ARRAY[82, 41, NULL]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b >= ANY(ARRAY[82, 41, 15]);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b >= ANY(ARRAY[82, 41, 15]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b > ANY(ARRAY[NULL, 82, 41, 15]);
+
+SELECT COUNT(*) FROM brin_in_test_multi_1 WHERE b > ANY(ARRAY[NULL, 82, 41, 15]);
+
+
+DROP TABLE brin_in_test_multi_1;
+RESET enable_seqscan;
+
+
+-- do some tests on IN clauses for varlena data types
+CREATE TABLE brin_in_test_multi_2 (a UUID) WITH (fillfactor=10);
+INSERT INTO brin_in_test_multi_2
+SELECT v::uuid FROM (SELECT row_number() OVER (ORDER BY v) c, v FROM (SELECT md5((i/13)::text) AS v FROM generate_series(1,1000) s(i)) foo) bar ORDER BY c + 25 * random();
+
+CREATE INDEX brin_in_test_multi_2_idx ON brin_in_test_multi_2 USING brin (a uuid_minmax_multi_ops) WITH (pages_per_range=1);
+
+SET enable_seqscan=off;
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_2 WHERE a IN ('33e75ff0-9dd6-01bb-e69f-351039152189');
+
+SELECT COUNT(*) FROM brin_in_test_multi_2 WHERE a IN ('33e75ff0-9dd6-01bb-e69f-351039152189');
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_2 WHERE a IN ('33e75ff0-9dd6-01bb-e69f-351039152189', NULL);
+
+SELECT COUNT(*) FROM brin_in_test_multi_2 WHERE a IN ('33e75ff0-9dd6-01bb-e69f-351039152189', NULL);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_2 WHERE a IN (NULL, NULL);
+
+SELECT COUNT(*) FROM brin_in_test_multi_2 WHERE a IN (NULL, NULL);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_2 WHERE a IN ('33e75ff0-9dd6-01bb-e69f-351039152189', 'f457c545-a9de-d88f-18ec-ee47145a72c0');
+
+SELECT COUNT(*) FROM brin_in_test_multi_2 WHERE a IN ('33e75ff0-9dd6-01bb-e69f-351039152189', 'f457c545-a9de-d88f-18ec-ee47145a72c0');
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_2 WHERE a IN ('33e75ff0-9dd6-01bb-e69f-351039152189', 'f457c545-a9de-d88f-18ec-ee47145a72c0', NULL);
+
+SELECT COUNT(*) FROM brin_in_test_multi_2 WHERE a IN ('33e75ff0-9dd6-01bb-e69f-351039152189', 'f457c545-a9de-d88f-18ec-ee47145a72c0', NULL);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_2 WHERE a IN ('33e75ff0-9dd6-01bb-e69f-351039152189', 'f457c545-a9de-d88f-18ec-ee47145a72c0', 'c51ce410-c124-a10e-0db5-e4b97fc2af39');
+
+SELECT COUNT(*) FROM brin_in_test_multi_2 WHERE a IN ('33e75ff0-9dd6-01bb-e69f-351039152189', 'f457c545-a9de-d88f-18ec-ee47145a72c0', 'c51ce410-c124-a10e-0db5-e4b97fc2af39');
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_multi_2 WHERE a IN (NULL, '33e75ff0-9dd6-01bb-e69f-351039152189', 'f457c545-a9de-d88f-18ec-ee47145a72c0', 'c51ce410-c124-a10e-0db5-e4b97fc2af39');
+
+SELECT COUNT(*) FROM brin_in_test_multi_2 WHERE a IN (NULL, '33e75ff0-9dd6-01bb-e69f-351039152189', 'f457c545-a9de-d88f-18ec-ee47145a72c0', 'c51ce410-c124-a10e-0db5-e4b97fc2af39');
+
+DROP TABLE brin_in_test_multi_2;
+RESET enable_seqscan;
-- 
2.39.1



  [text/x-patch] 0008-Support-SK_SEARCHARRAY-in-BRIN-inclusion-20230218.patch (22.8K, ../../[email protected]/9-0008-Support-SK_SEARCHARRAY-in-BRIN-inclusion-20230218.patch)
  download | inline diff:
From 7baef068b7fa2b6598642555eafb5228bf35b423 Mon Sep 17 00:00:00 2001
From: Tomas Vondra <[email protected]>
Date: Sat, 11 Feb 2023 15:23:25 +0100
Subject: [PATCH 8/9] Support SK_SEARCHARRAY in BRIN inclusion

---
 src/backend/access/brin/brin_inclusion.c | 246 ++++++++++++++++++-----
 src/include/catalog/pg_amproc.dat        |   9 +
 src/include/catalog/pg_proc.dat          |   4 +
 src/test/regress/expected/brin.out       | 132 ++++++++++++
 src/test/regress/sql/brin.sql            |  53 +++++
 5 files changed, 390 insertions(+), 54 deletions(-)

diff --git a/src/backend/access/brin/brin_inclusion.c b/src/backend/access/brin/brin_inclusion.c
index 248116c1494..84858430950 100644
--- a/src/backend/access/brin/brin_inclusion.c
+++ b/src/backend/access/brin/brin_inclusion.c
@@ -30,6 +30,7 @@
 #include "access/skey.h"
 #include "catalog/pg_amop.h"
 #include "catalog/pg_type.h"
+#include "utils/array.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
 #include "utils/lsyscache.h"
@@ -72,6 +73,13 @@
 #define INCLUSION_UNMERGEABLE		1
 #define INCLUSION_CONTAINS_EMPTY	2
 
+/*
+ * We use some private sk_flags bits in preprocessed scan keys.  We're allowed
+ * to use bits 16-31 (see skey.h).  The uppermost bits are copied from the
+ * index's indoption[] array entry for the index attribute.
+ */
+#define SK_BRIN_ARRAY	0x00010000	/* deconstructed array */
+
 
 typedef struct InclusionOpaque
 {
@@ -238,44 +246,94 @@ brin_inclusion_add_value(PG_FUNCTION_ARGS)
 	PG_RETURN_BOOL(true);
 }
 
+typedef struct ScanKeyArray {
+	int		nelements;
+	Datum  *elements;
+} ScanKeyArray;
+
+Datum
+brin_inclusion_preprocess(PG_FUNCTION_ARGS)
+{
+	// BrinDesc   *bdesc = (BrinDesc *) PG_GETARG_POINTER(0);
+	ScanKey		key = (ScanKey) PG_GETARG_POINTER(1);
+	ScanKey		newkey;
+	ScanKeyArray *scanarray;
+
+	ArrayType  *arrayval;
+	int16		elmlen;
+	bool		elmbyval;
+	char		elmalign;
+	int			num_elems;
+	Datum	   *elem_values;
+	bool	   *elem_nulls;
+
+	/* number of non-null elements in the array */
+	int			num_nonnulls;
+
+	/* ignore scalar keys */
+	if (!(key->sk_flags & SK_SEARCHARRAY))
+		PG_RETURN_POINTER(key);
+
+	arrayval = DatumGetArrayTypeP(key->sk_argument);
+
+	get_typlenbyvalalign(ARR_ELEMTYPE(arrayval),
+						 &elmlen, &elmbyval, &elmalign);
+
+	deconstruct_array(arrayval,
+					  ARR_ELEMTYPE(arrayval),
+					  elmlen, elmbyval, elmalign,
+					  &elem_values, &elem_nulls, &num_elems);
+
+	/* eliminate NULL elements */
+	num_nonnulls = 0;
+	for (int i = 0; i < num_elems; i++)
+	{
+		/* skip NULL elements */
+		if (elem_nulls[i])
+			continue;
+
+		/* if needed, move the non-NULL ones */
+		if (num_nonnulls != i)
+			elem_values[num_nonnulls] = elem_values[i];
+
+		num_nonnulls++;
+	}
+
+	num_elems = num_nonnulls;
+
+	scanarray = palloc0(sizeof(ScanKeyArray));
+	scanarray->nelements = num_elems;
+	scanarray->elements = elem_values;
+
+	newkey = palloc0(sizeof(ScanKeyData));
+
+	ScanKeyEntryInitializeWithInfo(newkey,
+								   (key->sk_flags | SK_BRIN_ARRAY),
+								   key->sk_attno,
+								   key->sk_strategy,
+								   key->sk_subtype,
+								   key->sk_collation,
+								   &key->sk_func,
+								   PointerGetDatum(scanarray));
+
+	PG_RETURN_POINTER(newkey);
+}
+
 /*
- * BRIN inclusion consistent function
- *
- * We're no longer dealing with NULL keys in the consistent function, that is
- * now handled by the AM code. That means we should not get any all-NULL ranges
- * either, because those can't be consistent with regular (not [IS] NULL) keys.
+ * Check consistency of a single scalar value with the BRIN range.
  *
- * All of the strategies are optional.
+ * Called for both scalar scankeys and for each value in SK_SEARCHARRAY.
  */
-Datum
-brin_inclusion_consistent(PG_FUNCTION_ARGS)
+static bool
+brin_inclusion_consistent_value(BrinDesc *bdesc, BrinValues *column,
+								AttrNumber attno,
+								StrategyNumber strategy, Oid subtype,
+								Oid colloid, Datum unionval, Datum query)
 {
-	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;
 
-	/* This opclass uses the old signature with only three arguments. */
-	Assert(PG_NARGS() == 3);
-
-	/* Should not be dealing with all-NULL ranges. */
-	Assert(!column->bv_allnulls);
-
-	/* 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)
+	switch (strategy)
 	{
 			/*
 			 * Placement strategies
@@ -294,49 +352,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
@@ -352,9 +410,9 @@ brin_inclusion_consistent(PG_FUNCTION_ARGS)
 		case RTSubStrategyNumber:
 		case RTSubEqualStrategyNumber:
 			finfo = inclusion_get_strategy_procinfo(bdesc, attno, subtype,
-													key->sk_strategy);
+													strategy);
 			result = FunctionCall2Coll(finfo, colloid, unionval, query);
-			PG_RETURN_DATUM(result);
+			return (DatumGetBool(result));
 
 			/*
 			 * Contained by strategies
@@ -374,9 +432,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 (column->bv_values[INCLUSION_CONTAINS_EMPTY]);
 
 			/*
 			 * Adjacent strategy
@@ -393,12 +451,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
@@ -428,9 +486,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 (column->bv_values[INCLUSION_CONTAINS_EMPTY]);
 
 		case RTSameStrategyNumber:
 		case RTEqualStrategyNumber:
@@ -438,30 +496,110 @@ 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 (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 (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);
+			elog(ERROR, "invalid strategy number %d", strategy);
+			return (false);
+	}
+}
+
+/*
+ * BRIN inclusion consistent function
+ *
+ * We're no longer dealing with NULL keys in the consistent function, that is
+ * now handled by the AM code. That means we should not get any all-NULL ranges
+ * either, because those can't be consistent with regular (not [IS] NULL) keys.
+ *
+ * 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;
+
+	/* This opclass uses the old signature with only three arguments. */
+	Assert(PG_NARGS() == 3);
+
+	/* Should not be dealing with all-NULL ranges. */
+	Assert(!column->bv_allnulls);
+
+	/* 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];
+
+	/*
+	 * For regular (scalar) scan keys, we simply compare the value to the
+	 * range min/max values, and we're done. For SK_SEARCHARRAY keys we
+	 * need to deparse the array and loop through the values.
+	 */
+	if (likely(!(key->sk_flags & SK_SEARCHARRAY)))
+	{
+		bool tmp;
+
+		tmp = brin_inclusion_consistent_value(bdesc, column, attno,
+											  key->sk_strategy,
+											  subtype, colloid,
+											  unionval, query);
+		PG_RETURN_BOOL(tmp);
+	}
+	else
+	{
+		ScanKeyArray *array = (ScanKeyArray *) query;
+		bool		matches = false;
+
+		/*
+		 * Loop through all pre-calculated hashes, check the bloom filter.
+		 *
+		 * XXX With empty cache (which can happen for IN clause with only NULL
+		 * values), we leave the matches flag set to false.
+		 */
+		for (int i = 0; i < array->nelements; i++)
+		{
+			Datum 	query_element = array->elements[i];
+
+			matches = brin_inclusion_consistent_value(bdesc, column, attno,
+													  key->sk_strategy,
+													  subtype, colloid,
+													  unionval, query_element);
+
+			if (matches)
+				break;
+		}
+
+		/* we could get here for empty array, e.g. with "@> '{}'::point[]" */
+		PG_RETURN_BOOL(matches);
 	}
 }
 
diff --git a/src/include/catalog/pg_amproc.dat b/src/include/catalog/pg_amproc.dat
index 4f17f0d58c1..ed5b21e7f9b 100644
--- a/src/include/catalog/pg_amproc.dat
+++ b/src/include/catalog/pg_amproc.dat
@@ -1478,6 +1478,9 @@
 { amprocfamily => 'brin/network_inclusion_ops', amproclefttype => 'inet',
   amprocrighttype => 'inet', amprocnum => '4',
   amproc => 'brin_inclusion_union' },
+{ amprocfamily => 'brin/network_inclusion_ops', amproclefttype => 'inet',
+  amprocrighttype => 'inet', amprocnum => '6',
+  amproc => 'brin_inclusion_preprocess' },
 { amprocfamily => 'brin/network_inclusion_ops', amproclefttype => 'inet',
   amprocrighttype => 'inet', amprocnum => '11', amproc => 'inet_merge' },
 { amprocfamily => 'brin/network_inclusion_ops', amproclefttype => 'inet',
@@ -2016,6 +2019,9 @@
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
   amprocrighttype => 'anyrange', amprocnum => '4',
   amproc => 'brin_inclusion_union' },
+{ amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
+  amprocrighttype => 'anyrange', amprocnum => '6',
+  amproc => 'brin_inclusion_preprocess' },
 { amprocfamily => 'brin/range_inclusion_ops', amproclefttype => 'anyrange',
   amprocrighttype => 'anyrange', amprocnum => '11',
   amproc => 'range_merge(anyrange,anyrange)' },
@@ -2097,6 +2103,9 @@
 { amprocfamily => 'brin/box_inclusion_ops', amproclefttype => 'box',
   amprocrighttype => 'box', amprocnum => '4',
   amproc => 'brin_inclusion_union' },
+{ amprocfamily => 'brin/box_inclusion_ops', amproclefttype => 'box',
+  amprocrighttype => 'box', amprocnum => '6',
+  amproc => 'brin_inclusion_preprocess' },
 { amprocfamily => 'brin/box_inclusion_ops', amproclefttype => 'box',
   amprocrighttype => 'box', amprocnum => '11', amproc => 'bound_box' },
 { amprocfamily => 'brin/box_inclusion_ops', amproclefttype => 'box',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 4e8d6668648..753c41d5cd0 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8610,6 +8610,10 @@
   proname => 'brin_inclusion_union', prorettype => 'bool',
   proargtypes => 'internal internal internal',
   prosrc => 'brin_inclusion_union' },
+{ oid => '9324', descr => 'BRIN inclusion support',
+  proname => 'brin_inclusion_preprocess', proisstrict => 'f',
+  prorettype => 'internal', proargtypes => 'internal internal',
+  prosrc => 'brin_inclusion_preprocess' },
 
 # BRIN bloom
 { oid => '4591', descr => 'BRIN bloom support',
diff --git a/src/test/regress/expected/brin.out b/src/test/regress/expected/brin.out
index 93d7314e599..4717c1450c8 100644
--- a/src/test/regress/expected/brin.out
+++ b/src/test/regress/expected/brin.out
@@ -1430,3 +1430,135 @@ SELECT COUNT(*) FROM brin_in_test_2 WHERE a IN (NULL, '33e75ff09dd601bbe69f35103
 
 DROP TABLE brin_in_test_2;
 RESET enable_seqscan;
+-- do some tests on IN clauses for boxes and points
+CREATE TABLE brin_in_test_3 (a BOX) WITH (fillfactor=10);
+INSERT INTO brin_in_test_3
+SELECT format('((%s,%s), (%s,%s))', x - mod(i,17), y - mod(i,13), x + mod(i,19), y + mod(i,11))::box FROM (
+  SELECT i,
+         i/10 + mod(991 * i + 617, 20) AS x,
+         i/10 + mod(853 * i + 491, 30) AS y
+    FROM generate_series(1,1000) s(i)
+) foo;
+CREATE INDEX brin_in_test_3_idx ON brin_in_test_3 USING brin (a box_inclusion_ops) WITH (pages_per_range=1);
+SET enable_seqscan=off;
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_3 WHERE a @> ANY (ARRAY['(10,10)'::point]);
+                          QUERY PLAN                           
+---------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_3
+         Recheck Cond: (a @> ANY ('{"(10,10)"}'::point[]))
+         ->  Bitmap Index Scan on brin_in_test_3_idx
+               Index Cond: (a @> ANY ('{"(10,10)"}'::point[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_3 WHERE a @> ANY (ARRAY['(10,10)'::point]);
+ count 
+-------
+    26
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_3 WHERE a @> ANY (ARRAY['(10,10)'::point, NULL]);
+                             QUERY PLAN                             
+--------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_3
+         Recheck Cond: (a @> ANY ('{"(10,10)",NULL}'::point[]))
+         ->  Bitmap Index Scan on brin_in_test_3_idx
+               Index Cond: (a @> ANY ('{"(10,10)",NULL}'::point[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_3 WHERE a @> ANY (ARRAY['(10,10)'::point, NULL]);
+ count 
+-------
+    26
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_3 WHERE a @> ANY (ARRAY[NULL::point, NULL::point]);
+                          QUERY PLAN                           
+---------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_3
+         Recheck Cond: (a @> ANY ('{NULL,NULL}'::point[]))
+         ->  Bitmap Index Scan on brin_in_test_3_idx
+               Index Cond: (a @> ANY ('{NULL,NULL}'::point[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_3 WHERE a @> ANY (ARRAY[NULL::point, NULL::point]);
+ count 
+-------
+     0
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_3 WHERE a @> ANY (ARRAY['(10,10)'::point, '(50,50)'::point]);
+                               QUERY PLAN                                
+-------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_3
+         Recheck Cond: (a @> ANY ('{"(10,10)","(50,50)"}'::point[]))
+         ->  Bitmap Index Scan on brin_in_test_3_idx
+               Index Cond: (a @> ANY ('{"(10,10)","(50,50)"}'::point[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_3 WHERE a @> ANY (ARRAY['(10,10)'::point, '(50,50)'::point]);
+ count 
+-------
+    80
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_3 WHERE a @> ANY (ARRAY['(10,10)'::point, '(50,50)'::point, NULL]);
+                                  QUERY PLAN                                  
+------------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_3
+         Recheck Cond: (a @> ANY ('{"(10,10)","(50,50)",NULL}'::point[]))
+         ->  Bitmap Index Scan on brin_in_test_3_idx
+               Index Cond: (a @> ANY ('{"(10,10)","(50,50)",NULL}'::point[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_3 WHERE a @> ANY (ARRAY['(10,10)'::point, '(50,50)'::point, NULL]);
+ count 
+-------
+    80
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_3 WHERE a @> ANY (ARRAY['(10,10)'::point, '(50,50)'::point, '(25,25)'::point]);
+                                    QUERY PLAN                                     
+-----------------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_3
+         Recheck Cond: (a @> ANY ('{"(10,10)","(50,50)","(25,25)"}'::point[]))
+         ->  Bitmap Index Scan on brin_in_test_3_idx
+               Index Cond: (a @> ANY ('{"(10,10)","(50,50)","(25,25)"}'::point[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_3 WHERE a @> ANY (ARRAY['(10,10)'::point, '(50,50)'::point, '(25,25)'::point]);
+ count 
+-------
+   129
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_3 WHERE a @> ANY (ARRAY[NULL, '(10,10)'::point, '(50,50)'::point, '(25,25)'::point]);
+                                       QUERY PLAN                                       
+----------------------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_3
+         Recheck Cond: (a @> ANY ('{NULL,"(10,10)","(50,50)","(25,25)"}'::point[]))
+         ->  Bitmap Index Scan on brin_in_test_3_idx
+               Index Cond: (a @> ANY ('{NULL,"(10,10)","(50,50)","(25,25)"}'::point[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_3 WHERE a @> ANY (ARRAY[NULL, '(10,10)'::point, '(50,50)'::point, '(25,25)'::point]);
+ count 
+-------
+   129
+(1 row)
+
+DROP TABLE brin_in_test_3;
+RESET enable_seqscan;
diff --git a/src/test/regress/sql/brin.sql b/src/test/regress/sql/brin.sql
index 95da027047e..3ca82939996 100644
--- a/src/test/regress/sql/brin.sql
+++ b/src/test/regress/sql/brin.sql
@@ -798,3 +798,56 @@ SELECT COUNT(*) FROM brin_in_test_2 WHERE a IN (NULL, '33e75ff09dd601bbe69f35103
 
 DROP TABLE brin_in_test_2;
 RESET enable_seqscan;
+
+
+-- do some tests on IN clauses for boxes and points
+CREATE TABLE brin_in_test_3 (a BOX) WITH (fillfactor=10);
+INSERT INTO brin_in_test_3
+SELECT format('((%s,%s), (%s,%s))', x - mod(i,17), y - mod(i,13), x + mod(i,19), y + mod(i,11))::box FROM (
+  SELECT i,
+         i/10 + mod(991 * i + 617, 20) AS x,
+         i/10 + mod(853 * i + 491, 30) AS y
+    FROM generate_series(1,1000) s(i)
+) foo;
+
+CREATE INDEX brin_in_test_3_idx ON brin_in_test_3 USING brin (a box_inclusion_ops) WITH (pages_per_range=1);
+
+SET enable_seqscan=off;
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_3 WHERE a @> ANY (ARRAY['(10,10)'::point]);
+
+SELECT COUNT(*) FROM brin_in_test_3 WHERE a @> ANY (ARRAY['(10,10)'::point]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_3 WHERE a @> ANY (ARRAY['(10,10)'::point, NULL]);
+
+SELECT COUNT(*) FROM brin_in_test_3 WHERE a @> ANY (ARRAY['(10,10)'::point, NULL]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_3 WHERE a @> ANY (ARRAY[NULL::point, NULL::point]);
+
+SELECT COUNT(*) FROM brin_in_test_3 WHERE a @> ANY (ARRAY[NULL::point, NULL::point]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_3 WHERE a @> ANY (ARRAY['(10,10)'::point, '(50,50)'::point]);
+
+SELECT COUNT(*) FROM brin_in_test_3 WHERE a @> ANY (ARRAY['(10,10)'::point, '(50,50)'::point]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_3 WHERE a @> ANY (ARRAY['(10,10)'::point, '(50,50)'::point, NULL]);
+
+SELECT COUNT(*) FROM brin_in_test_3 WHERE a @> ANY (ARRAY['(10,10)'::point, '(50,50)'::point, NULL]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_3 WHERE a @> ANY (ARRAY['(10,10)'::point, '(50,50)'::point, '(25,25)'::point]);
+
+SELECT COUNT(*) FROM brin_in_test_3 WHERE a @> ANY (ARRAY['(10,10)'::point, '(50,50)'::point, '(25,25)'::point]);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_3 WHERE a @> ANY (ARRAY[NULL, '(10,10)'::point, '(50,50)'::point, '(25,25)'::point]);
+
+SELECT COUNT(*) FROM brin_in_test_3 WHERE a @> ANY (ARRAY[NULL, '(10,10)'::point, '(50,50)'::point, '(25,25)'::point]);
+
+DROP TABLE brin_in_test_3;
+RESET enable_seqscan;
-- 
2.39.1



  [text/x-patch] 0009-Support-SK_SEARCHARRAY-in-BRIN-bloom-20230218.patch (41.6K, ../../[email protected]/10-0009-Support-SK_SEARCHARRAY-in-BRIN-bloom-20230218.patch)
  download | inline diff:
From 1adda80c9fbc9caa105b6233de4beeabdd57491f Mon Sep 17 00:00:00 2001
From: Tomas Vondra <[email protected]>
Date: Sat, 11 Feb 2023 20:50:03 +0100
Subject: [PATCH 9/9] Support SK_SEARCHARRAY in BRIN bloom

---
 src/backend/access/brin/brin_bloom.c     | 166 ++++++++--
 src/include/catalog/pg_amproc.dat        |  60 ++++
 src/include/catalog/pg_proc.dat          |   3 +
 src/test/regress/expected/brin_bloom.out | 377 +++++++++++++++++++++++
 src/test/regress/sql/brin_bloom.sql      | 135 ++++++++
 5 files changed, 715 insertions(+), 26 deletions(-)

diff --git a/src/backend/access/brin/brin_bloom.c b/src/backend/access/brin/brin_bloom.c
index 4ff80aeb0cc..5c3ba659ba2 100644
--- a/src/backend/access/brin/brin_bloom.c
+++ b/src/backend/access/brin/brin_bloom.c
@@ -125,9 +125,11 @@
 #include "access/stratnum.h"
 #include "catalog/pg_type.h"
 #include "catalog/pg_amop.h"
+#include "utils/array.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
 #include "utils/lsyscache.h"
+#include "utils/memutils.h"
 #include "utils/rel.h"
 #include "utils/syscache.h"
 
@@ -151,6 +153,13 @@
  */
 #define		PROCNUM_BASE			11
 
+/*
+ * We use some private sk_flags bits in preprocessed scan keys.  We're allowed
+ * to use bits 16-31 (see skey.h).  The uppermost bits are copied from the
+ * index's indoption[] array entry for the index attribute.
+ */
+#define SK_BRIN_HASHES	0x00010000	/* deconstructed array, calculated hashes */
+
 /*
  * Storage type for BRIN's reloptions.
  */
@@ -402,21 +411,14 @@ bloom_add_value(BloomFilter *filter, uint32 value, bool *updated)
 	return filter;
 }
 
-
 /*
  * bloom_contains_value
  * 		Check if the bloom filter contains a particular value.
  */
 static bool
-bloom_contains_value(BloomFilter *filter, uint32 value)
+bloom_contains_hashes(BloomFilter *filter, uint64 h1, uint64 h2)
 {
 	int			i;
-	uint64		h1,
-				h2;
-
-	/* calculate the two hashes */
-	h1 = hash_bytes_uint32_extended(value, BLOOM_SEED_1) % filter->nbits;
-	h2 = hash_bytes_uint32_extended(value, BLOOM_SEED_2) % filter->nbits;
 
 	/* compute the requested number of hashes */
 	for (i = 0; i < filter->nhashes; i++)
@@ -590,6 +592,104 @@ brin_bloom_add_value(PG_FUNCTION_ARGS)
 	PG_RETURN_BOOL(updated);
 }
 
+typedef struct HashCache {
+	int		nelements;
+	uint64 *h1;
+	uint64 *h2;
+} HashCache;
+
+Datum
+brin_bloom_preprocess(PG_FUNCTION_ARGS)
+{
+	BrinDesc   *bdesc = (BrinDesc *) PG_GETARG_POINTER(0);
+	ScanKey		key = (ScanKey) PG_GETARG_POINTER(1);
+	BloomOptions *opts = (BloomOptions *) PG_GET_OPCLASS_OPTIONS();
+	ScanKey		newkey;
+	HashCache  *cache = palloc0(sizeof(HashCache));
+
+	int			nbits;
+	FmgrInfo   *finfo;
+	uint32		hashValue;
+
+	/* we'll need to calculate hashes, so get the proc */
+	finfo = bloom_get_procinfo(bdesc, key->sk_attno, PROCNUM_HASH);
+
+	/*
+	 * We don't have a filter from any range yet, so we just re-calculate
+	 * the size (number of bits) just like bloom_init.
+	 */
+	bloom_filter_size(brin_bloom_get_ndistinct(bdesc, opts),
+					  BloomGetFalsePositiveRate(opts),
+					  NULL, &nbits, NULL);
+
+	/* precalculate the hash even for simple scan keys */
+	if (!(key->sk_flags & SK_SEARCHARRAY))
+	{
+		Datum value = key->sk_argument;
+
+		cache->nelements = 1;
+		cache->h1 = (uint64 *) palloc0(sizeof(uint64));
+		cache->h2 = (uint64 *) palloc0(sizeof(uint64));
+
+		hashValue = DatumGetUInt32(FunctionCall1Coll(finfo, key->sk_collation, value));
+
+		cache->h1[0] = hash_bytes_uint32_extended(hashValue, BLOOM_SEED_1) % nbits;
+		cache->h2[0] = hash_bytes_uint32_extended(hashValue, BLOOM_SEED_2) % nbits;
+	}
+	else
+	{
+		ArrayType  *arrayval;
+		int16		elmlen;
+		bool		elmbyval;
+		char		elmalign;
+		int			num_elems;
+		Datum	   *elem_values;
+		bool	   *elem_nulls;
+
+		arrayval = DatumGetArrayTypeP(key->sk_argument);
+
+		get_typlenbyvalalign(ARR_ELEMTYPE(arrayval),
+							 &elmlen, &elmbyval, &elmalign);
+
+		deconstruct_array(arrayval,
+						  ARR_ELEMTYPE(arrayval),
+						  elmlen, elmbyval, elmalign,
+						  &elem_values, &elem_nulls, &num_elems);
+
+		cache->h1 = (uint64 *) palloc0(sizeof(uint64) * num_elems);
+		cache->h2 = (uint64 *) palloc0(sizeof(uint64) * num_elems);
+
+		for (int i = 0; i < num_elems; i++)
+		{
+			Datum	element = elem_values[i];
+
+			/* ignore NULL elements */
+			if (elem_nulls[i])
+				continue;
+
+			hashValue = DatumGetUInt32(FunctionCall1Coll(finfo, key->sk_collation, element));
+
+			cache->h1[cache->nelements] = hash_bytes_uint32_extended(hashValue, BLOOM_SEED_1) % nbits;
+			cache->h2[cache->nelements] = hash_bytes_uint32_extended(hashValue, BLOOM_SEED_2) % nbits;
+
+			cache->nelements++;
+		}
+	}
+
+	newkey = palloc0(sizeof(ScanKeyData));
+
+	ScanKeyEntryInitializeWithInfo(newkey,
+								   (key->sk_flags | SK_BRIN_HASHES),
+								   key->sk_attno,
+								   key->sk_strategy,
+								   key->sk_subtype,
+								   key->sk_collation,
+								   &key->sk_func,
+								   PointerGetDatum(cache));
+
+	PG_RETURN_POINTER(newkey);
+}
+
 /*
  * Given an index tuple corresponding to a certain page range and a scan key,
  * return whether the scan key is consistent with the index tuple's bloom
@@ -598,16 +698,10 @@ brin_bloom_add_value(PG_FUNCTION_ARGS)
 Datum
 brin_bloom_consistent(PG_FUNCTION_ARGS)
 {
-	BrinDesc   *bdesc = (BrinDesc *) PG_GETARG_POINTER(0);
 	BrinValues *column = (BrinValues *) PG_GETARG_POINTER(1);
 	ScanKey    *keys = (ScanKey *) PG_GETARG_POINTER(2);
 	int			nkeys = PG_GETARG_INT32(3);
-	Oid			colloid = PG_GET_COLLATION();
-	AttrNumber	attno;
-	Datum		value;
 	bool		matches;
-	FmgrInfo   *finfo;
-	uint32		hashValue;
 	BloomFilter *filter;
 	int			keyno;
 
@@ -625,22 +719,42 @@ brin_bloom_consistent(PG_FUNCTION_ARGS)
 		/* NULL keys are handled and filtered-out in bringetbitmap */
 		Assert(!(key->sk_flags & SK_ISNULL));
 
-		attno = key->sk_attno;
-		value = key->sk_argument;
+		/*
+		 * Keys should be preprocessed into a hash cache (even a single
+		 * value scan keys, not just SK_SEARCHARRAY ones).
+		 */
+		Assert(key->sk_flags & SK_BRIN_HASHES);
 
 		switch (key->sk_strategy)
 		{
 			case BloomEqualStrategyNumber:
-
-				/*
-				 * We want to return the current page range if the bloom filter
-				 * seems to contain the value.
-				 */
-				finfo = bloom_get_procinfo(bdesc, attno, PROCNUM_HASH);
-
-				hashValue = DatumGetUInt32(FunctionCall1Coll(finfo, colloid, value));
-				matches &= bloom_contains_value(filter, hashValue);
-
+				{
+					HashCache  *cache = (HashCache *) key->sk_argument;
+
+					/* assume no match */
+					matches = false;
+
+					/*
+					 * We want to return the current page range if the bloom filter
+					 * seems to contain any of the values (or a single value).
+					 *
+					 * XXX With empty cache (which can happen for IN clause with
+					 * only NULL values), we leave the matches flag set to false.
+					 */
+					for (int i = 0; i < cache->nelements; i++)
+					{
+						bool	tmp = false;
+
+						tmp = bloom_contains_hashes(filter, cache->h1[i], cache->h2[i]);
+
+						/* if we found a matching value, we have a match */
+						if (DatumGetBool(tmp))
+						{
+							matches = BoolGetDatum(true);
+							break;
+						}
+					}
+				}
 				break;
 			default:
 				/* shouldn't happen */
diff --git a/src/include/catalog/pg_amproc.dat b/src/include/catalog/pg_amproc.dat
index ed5b21e7f9b..d951fcd1a06 100644
--- a/src/include/catalog/pg_amproc.dat
+++ b/src/include/catalog/pg_amproc.dat
@@ -822,6 +822,9 @@
 { amprocfamily => 'brin/bytea_bloom_ops', amproclefttype => 'bytea',
   amprocrighttype => 'bytea', amprocnum => '5',
   amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/bytea_bloom_ops', amproclefttype => 'bytea',
+  amprocrighttype => 'bytea', amprocnum => '6',
+  amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/bytea_bloom_ops', amproclefttype => 'bytea',
   amprocrighttype => 'bytea', amprocnum => '11', amproc => 'hashvarlena' },
 
@@ -853,6 +856,8 @@
   amprocrighttype => 'char', amprocnum => '4', amproc => 'brin_bloom_union' },
 { amprocfamily => 'brin/char_bloom_ops', amproclefttype => 'char',
   amprocrighttype => 'char', amprocnum => '5', amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/char_bloom_ops', amproclefttype => 'char',
+  amprocrighttype => 'char', amprocnum => '6', amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/char_bloom_ops', amproclefttype => 'char',
   amprocrighttype => 'char', amprocnum => '11', amproc => 'hashchar' },
 
@@ -884,6 +889,8 @@
   amprocrighttype => 'name', amprocnum => '4', amproc => 'brin_bloom_union' },
 { amprocfamily => 'brin/name_bloom_ops', amproclefttype => 'name',
   amprocrighttype => 'name', amprocnum => '5', amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/name_bloom_ops', amproclefttype => 'name',
+  amprocrighttype => 'name', amprocnum => '6', amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/name_bloom_ops', amproclefttype => 'name',
   amprocrighttype => 'name', amprocnum => '11', amproc => 'hashname' },
 
@@ -1010,6 +1017,8 @@
   amprocrighttype => 'int8', amprocnum => '4', amproc => 'brin_bloom_union' },
 { amprocfamily => 'brin/integer_bloom_ops', amproclefttype => 'int8',
   amprocrighttype => 'int8', amprocnum => '5', amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/integer_bloom_ops', amproclefttype => 'int8',
+  amprocrighttype => 'int8', amprocnum => '6', amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/integer_bloom_ops', amproclefttype => 'int8',
   amprocrighttype => 'int8', amprocnum => '11', amproc => 'hashint8' },
 
@@ -1025,6 +1034,8 @@
   amprocrighttype => 'int2', amprocnum => '4', amproc => 'brin_bloom_union' },
 { amprocfamily => 'brin/integer_bloom_ops', amproclefttype => 'int2',
   amprocrighttype => 'int2', amprocnum => '5', amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/integer_bloom_ops', amproclefttype => 'int2',
+  amprocrighttype => 'int2', amprocnum => '6', amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/integer_bloom_ops', amproclefttype => 'int2',
   amprocrighttype => 'int2', amprocnum => '11', amproc => 'hashint2' },
 
@@ -1040,6 +1051,8 @@
   amprocrighttype => 'int4', amprocnum => '4', amproc => 'brin_bloom_union' },
 { amprocfamily => 'brin/integer_bloom_ops', amproclefttype => 'int4',
   amprocrighttype => 'int4', amprocnum => '5', amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/integer_bloom_ops', amproclefttype => 'int4',
+  amprocrighttype => 'int4', amprocnum => '6', amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/integer_bloom_ops', amproclefttype => 'int4',
   amprocrighttype => 'int4', amprocnum => '11', amproc => 'hashint4' },
 
@@ -1071,6 +1084,8 @@
   amprocrighttype => 'text', amprocnum => '4', amproc => 'brin_bloom_union' },
 { amprocfamily => 'brin/text_bloom_ops', amproclefttype => 'text',
   amprocrighttype => 'text', amprocnum => '5', amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/text_bloom_ops', amproclefttype => 'text',
+  amprocrighttype => 'text', amprocnum => '6', amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/text_bloom_ops', amproclefttype => 'text',
   amprocrighttype => 'text', amprocnum => '11', amproc => 'hashtext' },
 
@@ -1124,6 +1139,8 @@
   amprocrighttype => 'oid', amprocnum => '4', amproc => 'brin_bloom_union' },
 { amprocfamily => 'brin/oid_bloom_ops', amproclefttype => 'oid',
   amprocrighttype => 'oid', amprocnum => '5', amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/oid_bloom_ops', amproclefttype => 'oid',
+  amprocrighttype => 'oid', amprocnum => '6', amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/oid_bloom_ops', amproclefttype => 'oid',
   amprocrighttype => 'oid', amprocnum => '11', amproc => 'hashoid' },
 
@@ -1154,6 +1171,8 @@
   amprocrighttype => 'tid', amprocnum => '4', amproc => 'brin_bloom_union' },
 { amprocfamily => 'brin/tid_bloom_ops', amproclefttype => 'tid',
   amprocrighttype => 'tid', amprocnum => '5', amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/tid_bloom_ops', amproclefttype => 'tid',
+  amprocrighttype => 'tid', amprocnum => '6', amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/tid_bloom_ops', amproclefttype => 'tid',
   amprocrighttype => 'tid', amprocnum => '11', amproc => 'hashtid' },
 
@@ -1273,6 +1292,9 @@
 { amprocfamily => 'brin/float_bloom_ops', amproclefttype => 'float4',
   amprocrighttype => 'float4', amprocnum => '5',
   amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/float_bloom_ops', amproclefttype => 'float4',
+  amprocrighttype => 'float4', amprocnum => '6',
+  amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/float_bloom_ops', amproclefttype => 'float4',
   amprocrighttype => 'float4', amprocnum => '11', amproc => 'hashfloat4' },
 
@@ -1290,6 +1312,9 @@
 { amprocfamily => 'brin/float_bloom_ops', amproclefttype => 'float8',
   amprocrighttype => 'float8', amprocnum => '5',
   amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/float_bloom_ops', amproclefttype => 'float8',
+  amprocrighttype => 'float8', amprocnum => '6',
+  amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/float_bloom_ops', amproclefttype => 'float8',
   amprocrighttype => 'float8', amprocnum => '11', amproc => 'hashfloat8' },
 
@@ -1349,6 +1374,9 @@
 { amprocfamily => 'brin/macaddr_bloom_ops', amproclefttype => 'macaddr',
   amprocrighttype => 'macaddr', amprocnum => '5',
   amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/macaddr_bloom_ops', amproclefttype => 'macaddr',
+  amprocrighttype => 'macaddr', amprocnum => '6',
+  amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/macaddr_bloom_ops', amproclefttype => 'macaddr',
   amprocrighttype => 'macaddr', amprocnum => '11', amproc => 'hashmacaddr' },
 
@@ -1408,6 +1436,9 @@
 { amprocfamily => 'brin/macaddr8_bloom_ops', amproclefttype => 'macaddr8',
   amprocrighttype => 'macaddr8', amprocnum => '5',
   amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/macaddr8_bloom_ops', amproclefttype => 'macaddr8',
+  amprocrighttype => 'macaddr8', amprocnum => '6',
+  amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/macaddr8_bloom_ops', amproclefttype => 'macaddr8',
   amprocrighttype => 'macaddr8', amprocnum => '11', amproc => 'hashmacaddr8' },
 
@@ -1462,6 +1493,8 @@
   amprocrighttype => 'inet', amprocnum => '4', amproc => 'brin_bloom_union' },
 { amprocfamily => 'brin/network_bloom_ops', amproclefttype => 'inet',
   amprocrighttype => 'inet', amprocnum => '5', amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/network_bloom_ops', amproclefttype => 'inet',
+  amprocrighttype => 'inet', amprocnum => '6', amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/network_bloom_ops', amproclefttype => 'inet',
   amprocrighttype => 'inet', amprocnum => '11', amproc => 'hashinet' },
 
@@ -1520,6 +1553,9 @@
 { amprocfamily => 'brin/bpchar_bloom_ops', amproclefttype => 'bpchar',
   amprocrighttype => 'bpchar', amprocnum => '5',
   amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/bpchar_bloom_ops', amproclefttype => 'bpchar',
+  amprocrighttype => 'bpchar', amprocnum => '6',
+  amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/bpchar_bloom_ops', amproclefttype => 'bpchar',
   amprocrighttype => 'bpchar', amprocnum => '11', amproc => 'hashbpchar' },
 
@@ -1574,6 +1610,8 @@
   amprocrighttype => 'time', amprocnum => '4', amproc => 'brin_bloom_union' },
 { amprocfamily => 'brin/time_bloom_ops', amproclefttype => 'time',
   amprocrighttype => 'time', amprocnum => '5', amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/time_bloom_ops', amproclefttype => 'time',
+  amprocrighttype => 'time', amprocnum => '6', amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/time_bloom_ops', amproclefttype => 'time',
   amprocrighttype => 'time', amprocnum => '11', amproc => 'time_hash' },
 
@@ -1707,6 +1745,9 @@
 { amprocfamily => 'brin/datetime_bloom_ops', amproclefttype => 'timestamp',
   amprocrighttype => 'timestamp', amprocnum => '5',
   amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/datetime_bloom_ops', amproclefttype => 'timestamp',
+  amprocrighttype => 'timestamp', amprocnum => '6',
+  amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/datetime_bloom_ops', amproclefttype => 'timestamp',
   amprocrighttype => 'timestamp', amprocnum => '11',
   amproc => 'timestamp_hash' },
@@ -1726,6 +1767,9 @@
 { amprocfamily => 'brin/datetime_bloom_ops', amproclefttype => 'timestamptz',
   amprocrighttype => 'timestamptz', amprocnum => '5',
   amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/datetime_bloom_ops', amproclefttype => 'timestamptz',
+  amprocrighttype => 'timestamptz', amprocnum => '6',
+  amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/datetime_bloom_ops', amproclefttype => 'timestamptz',
   amprocrighttype => 'timestamptz', amprocnum => '11',
   amproc => 'timestamp_hash' },
@@ -1742,6 +1786,8 @@
   amprocrighttype => 'date', amprocnum => '4', amproc => 'brin_bloom_union' },
 { amprocfamily => 'brin/datetime_bloom_ops', amproclefttype => 'date',
   amprocrighttype => 'date', amprocnum => '5', amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/datetime_bloom_ops', amproclefttype => 'date',
+  amprocrighttype => 'date', amprocnum => '6', amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/datetime_bloom_ops', amproclefttype => 'date',
   amprocrighttype => 'date', amprocnum => '11', amproc => 'hashint4' },
 
@@ -1801,6 +1847,9 @@
 { amprocfamily => 'brin/interval_bloom_ops', amproclefttype => 'interval',
   amprocrighttype => 'interval', amprocnum => '5',
   amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/interval_bloom_ops', amproclefttype => 'interval',
+  amprocrighttype => 'interval', amprocnum => '6',
+  amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/interval_bloom_ops', amproclefttype => 'interval',
   amprocrighttype => 'interval', amprocnum => '11', amproc => 'interval_hash' },
 
@@ -1859,6 +1908,9 @@
 { amprocfamily => 'brin/timetz_bloom_ops', amproclefttype => 'timetz',
   amprocrighttype => 'timetz', amprocnum => '5',
   amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/timetz_bloom_ops', amproclefttype => 'timetz',
+  amprocrighttype => 'timetz', amprocnum => '6',
+  amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/timetz_bloom_ops', amproclefttype => 'timetz',
   amprocrighttype => 'timetz', amprocnum => '11', amproc => 'timetz_hash' },
 
@@ -1949,6 +2001,9 @@
 { amprocfamily => 'brin/numeric_bloom_ops', amproclefttype => 'numeric',
   amprocrighttype => 'numeric', amprocnum => '5',
   amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/numeric_bloom_ops', amproclefttype => 'numeric',
+  amprocrighttype => 'numeric', amprocnum => '6',
+  amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/numeric_bloom_ops', amproclefttype => 'numeric',
   amprocrighttype => 'numeric', amprocnum => '11', amproc => 'hash_numeric' },
 
@@ -2003,6 +2058,8 @@
   amprocrighttype => 'uuid', amprocnum => '4', amproc => 'brin_bloom_union' },
 { amprocfamily => 'brin/uuid_bloom_ops', amproclefttype => 'uuid',
   amprocrighttype => 'uuid', amprocnum => '5', amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/uuid_bloom_ops', amproclefttype => 'uuid',
+  amprocrighttype => 'uuid', amprocnum => '6', amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/uuid_bloom_ops', amproclefttype => 'uuid',
   amprocrighttype => 'uuid', amprocnum => '11', amproc => 'uuid_hash' },
 
@@ -2087,6 +2144,9 @@
 { amprocfamily => 'brin/pg_lsn_bloom_ops', amproclefttype => 'pg_lsn',
   amprocrighttype => 'pg_lsn', amprocnum => '5',
   amproc => 'brin_bloom_options' },
+{ amprocfamily => 'brin/pg_lsn_bloom_ops', amproclefttype => 'pg_lsn',
+  amprocrighttype => 'pg_lsn', amprocnum => '6',
+  amproc => 'brin_bloom_preprocess' },
 { amprocfamily => 'brin/pg_lsn_bloom_ops', amproclefttype => 'pg_lsn',
   amprocrighttype => 'pg_lsn', amprocnum => '11', amproc => 'pg_lsn_hash' },
 
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 753c41d5cd0..4325229c9df 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8633,6 +8633,9 @@
 { oid => '4595', descr => 'BRIN bloom support',
   proname => 'brin_bloom_options', proisstrict => 'f', prorettype => 'void',
   proargtypes => 'internal', prosrc => 'brin_bloom_options' },
+{ oid => '9325', descr => 'BRIN bloom support',
+  proname => 'brin_bloom_preprocess', proisstrict => 'f', prorettype => 'internal',
+  proargtypes => 'internal internal', prosrc => 'brin_bloom_preprocess' },
 
 # userlock replacements
 { oid => '2880', descr => 'obtain exclusive advisory lock',
diff --git a/src/test/regress/expected/brin_bloom.out b/src/test/regress/expected/brin_bloom.out
index 32c56a996a2..b83b2bae162 100644
--- a/src/test/regress/expected/brin_bloom.out
+++ b/src/test/regress/expected/brin_bloom.out
@@ -426,3 +426,380 @@ EXPLAIN (COSTS OFF) SELECT * FROM brin_test_bloom WHERE b = 1;
    Filter: (b = 1)
 (2 rows)
 
+-- do some tests on IN clauses for simple data types
+CREATE TABLE brin_in_test_bloom_1 (a INT, b BIGINT) WITH (fillfactor=10);
+INSERT INTO brin_in_test_bloom_1
+SELECT i/5 + mod(991 * i + 617, 20),
+       i/10 + mod(853 * i + 491, 30)
+  FROM generate_series(1,1000) s(i);
+CREATE INDEX brin_in_test_bloom_1_idx_1 ON brin_in_test_bloom_1 USING brin (a int4_bloom_ops(n_distinct_per_range=50)) WITH (pages_per_range=1);
+CREATE INDEX brin_in_test_bloom_1_idx_2 ON brin_in_test_bloom_1 USING brin (b int8_bloom_ops(n_distinct_per_range=50)) WITH (pages_per_range=1);
+SET enable_seqscan=off;
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE a IN (113);
+                         QUERY PLAN                          
+-------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_bloom_1
+         Recheck Cond: (a = 113)
+         ->  Bitmap Index Scan on brin_in_test_bloom_1_idx_1
+               Index Cond: (a = 113)
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE a IN (113);
+ count 
+-------
+     8
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE a IN (113, NULL);
+                          QUERY PLAN                           
+---------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_bloom_1
+         Recheck Cond: (a = ANY ('{113,NULL}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_bloom_1_idx_1
+               Index Cond: (a = ANY ('{113,NULL}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE a IN (113, NULL);
+ count 
+-------
+     8
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE a IN (NULL, NULL);
+                           QUERY PLAN                           
+----------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_bloom_1
+         Recheck Cond: (a = ANY ('{NULL,NULL}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_bloom_1_idx_1
+               Index Cond: (a = ANY ('{NULL,NULL}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE a IN (NULL, NULL);
+ count 
+-------
+     0
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE a IN (113, 177);
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_bloom_1
+         Recheck Cond: (a = ANY ('{113,177}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_bloom_1_idx_1
+               Index Cond: (a = ANY ('{113,177}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE a IN (113, 177);
+ count 
+-------
+    16
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE a IN (113, 177, NULL);
+                            QUERY PLAN                             
+-------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_bloom_1
+         Recheck Cond: (a = ANY ('{113,177,NULL}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_bloom_1_idx_1
+               Index Cond: (a = ANY ('{113,177,NULL}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE a IN (113, 177, NULL);
+ count 
+-------
+    16
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE a IN (113, 177, 25);
+                           QUERY PLAN                            
+-----------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_bloom_1
+         Recheck Cond: (a = ANY ('{113,177,25}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_bloom_1_idx_1
+               Index Cond: (a = ANY ('{113,177,25}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE a IN (113, 177, 25);
+ count 
+-------
+    24
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE a IN (NULL, 113, 177, 25);
+                              QUERY PLAN                              
+----------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_bloom_1
+         Recheck Cond: (a = ANY ('{NULL,113,177,25}'::integer[]))
+         ->  Bitmap Index Scan on brin_in_test_bloom_1_idx_1
+               Index Cond: (a = ANY ('{NULL,113,177,25}'::integer[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE a IN (NULL, 113, 177, 25);
+ count 
+-------
+    24
+(1 row)
+
+-- a bit weird this requires a cast to bigint, unlike multi-value IN clause
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE b IN (82::bigint);
+                         QUERY PLAN                          
+-------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_bloom_1
+         Recheck Cond: (b = '82'::bigint)
+         ->  Bitmap Index Scan on brin_in_test_bloom_1_idx_2
+               Index Cond: (b = '82'::bigint)
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE b IN (82::bigint);
+ count 
+-------
+    10
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE b IN (82, NULL);
+                         QUERY PLAN                          
+-------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_bloom_1
+         Recheck Cond: (b = ANY ('{82,NULL}'::bigint[]))
+         ->  Bitmap Index Scan on brin_in_test_bloom_1_idx_2
+               Index Cond: (b = ANY ('{82,NULL}'::bigint[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE b IN (82, NULL);
+ count 
+-------
+    10
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE b IN (NULL, NULL);
+                          QUERY PLAN                           
+---------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_bloom_1
+         Recheck Cond: (b = ANY ('{NULL,NULL}'::bigint[]))
+         ->  Bitmap Index Scan on brin_in_test_bloom_1_idx_2
+               Index Cond: (b = ANY ('{NULL,NULL}'::bigint[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE b IN (NULL, NULL);
+ count 
+-------
+     0
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE b IN (82, 41);
+                         QUERY PLAN                          
+-------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_bloom_1
+         Recheck Cond: (b = ANY ('{82,41}'::bigint[]))
+         ->  Bitmap Index Scan on brin_in_test_bloom_1_idx_2
+               Index Cond: (b = ANY ('{82,41}'::bigint[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE b IN (82, 41);
+ count 
+-------
+    20
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE b IN (82, 41, NULL);
+                           QUERY PLAN                           
+----------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_bloom_1
+         Recheck Cond: (b = ANY ('{82,41,NULL}'::bigint[]))
+         ->  Bitmap Index Scan on brin_in_test_bloom_1_idx_2
+               Index Cond: (b = ANY ('{82,41,NULL}'::bigint[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE b IN (82, 41, NULL);
+ count 
+-------
+    20
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE b IN (82, 41, 15);
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_bloom_1
+         Recheck Cond: (b = ANY ('{82,41,15}'::bigint[]))
+         ->  Bitmap Index Scan on brin_in_test_bloom_1_idx_2
+               Index Cond: (b = ANY ('{82,41,15}'::bigint[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE b IN (82, 41, 15);
+ count 
+-------
+    25
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE b IN (NULL, 82, 41, 15);
+                            QUERY PLAN                             
+-------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_bloom_1
+         Recheck Cond: (b = ANY ('{NULL,82,41,15}'::bigint[]))
+         ->  Bitmap Index Scan on brin_in_test_bloom_1_idx_2
+               Index Cond: (b = ANY ('{NULL,82,41,15}'::bigint[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE b IN (NULL, 82, 41, 15);
+ count 
+-------
+    25
+(1 row)
+
+DROP TABLE brin_in_test_bloom_1;
+RESET enable_seqscan;
+-- do some tests on IN clauses for varlena data types
+CREATE TABLE brin_in_test_bloom_2 (a TEXT) WITH (fillfactor=10);
+INSERT INTO brin_in_test_bloom_2
+SELECT v FROM (SELECT row_number() OVER (ORDER BY v) c, v FROM (SELECT md5((i/13)::text) AS v FROM generate_series(1,1000) s(i)) foo) bar ORDER BY c + 25 * random();
+CREATE INDEX brin_in_test_bloom_2_idx ON brin_in_test_bloom_2 USING brin (a text_bloom_ops(n_distinct_per_range=50)) WITH (pages_per_range=1);
+SET enable_seqscan=off;
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189');
+                                QUERY PLAN                                
+--------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_bloom_2
+         Recheck Cond: (a = '33e75ff09dd601bbe69f351039152189'::text)
+         ->  Bitmap Index Scan on brin_in_test_bloom_2_idx
+               Index Cond: (a = '33e75ff09dd601bbe69f351039152189'::text)
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_bloom_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189');
+ count 
+-------
+    13
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', NULL);
+                                       QUERY PLAN                                        
+-----------------------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_bloom_2
+         Recheck Cond: (a = ANY ('{33e75ff09dd601bbe69f351039152189,NULL}'::text[]))
+         ->  Bitmap Index Scan on brin_in_test_bloom_2_idx
+               Index Cond: (a = ANY ('{33e75ff09dd601bbe69f351039152189,NULL}'::text[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_bloom_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', NULL);
+ count 
+-------
+    13
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_2 WHERE a IN (NULL, NULL);
+                         QUERY PLAN                          
+-------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_bloom_2
+         Recheck Cond: (a = ANY ('{NULL,NULL}'::text[]))
+         ->  Bitmap Index Scan on brin_in_test_bloom_2_idx
+               Index Cond: (a = ANY ('{NULL,NULL}'::text[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_bloom_2 WHERE a IN (NULL, NULL);
+ count 
+-------
+     0
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0');
+                                                     QUERY PLAN                                                      
+---------------------------------------------------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_bloom_2
+         Recheck Cond: (a = ANY ('{33e75ff09dd601bbe69f351039152189,f457c545a9ded88f18ecee47145a72c0}'::text[]))
+         ->  Bitmap Index Scan on brin_in_test_bloom_2_idx
+               Index Cond: (a = ANY ('{33e75ff09dd601bbe69f351039152189,f457c545a9ded88f18ecee47145a72c0}'::text[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_bloom_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0');
+ count 
+-------
+    26
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0', NULL);
+                                                        QUERY PLAN                                                        
+--------------------------------------------------------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_bloom_2
+         Recheck Cond: (a = ANY ('{33e75ff09dd601bbe69f351039152189,f457c545a9ded88f18ecee47145a72c0,NULL}'::text[]))
+         ->  Bitmap Index Scan on brin_in_test_bloom_2_idx
+               Index Cond: (a = ANY ('{33e75ff09dd601bbe69f351039152189,f457c545a9ded88f18ecee47145a72c0,NULL}'::text[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_bloom_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0', NULL);
+ count 
+-------
+    26
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0', 'c51ce410c124a10e0db5e4b97fc2af39');
+                                                                      QUERY PLAN                                                                      
+------------------------------------------------------------------------------------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_bloom_2
+         Recheck Cond: (a = ANY ('{33e75ff09dd601bbe69f351039152189,f457c545a9ded88f18ecee47145a72c0,c51ce410c124a10e0db5e4b97fc2af39}'::text[]))
+         ->  Bitmap Index Scan on brin_in_test_bloom_2_idx
+               Index Cond: (a = ANY ('{33e75ff09dd601bbe69f351039152189,f457c545a9ded88f18ecee47145a72c0,c51ce410c124a10e0db5e4b97fc2af39}'::text[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_bloom_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0', 'c51ce410c124a10e0db5e4b97fc2af39');
+ count 
+-------
+    39
+(1 row)
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_2 WHERE a IN (NULL, '33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0', 'c51ce410c124a10e0db5e4b97fc2af39');
+                                                                        QUERY PLAN                                                                         
+-----------------------------------------------------------------------------------------------------------------------------------------------------------
+ Aggregate
+   ->  Bitmap Heap Scan on brin_in_test_bloom_2
+         Recheck Cond: (a = ANY ('{NULL,33e75ff09dd601bbe69f351039152189,f457c545a9ded88f18ecee47145a72c0,c51ce410c124a10e0db5e4b97fc2af39}'::text[]))
+         ->  Bitmap Index Scan on brin_in_test_bloom_2_idx
+               Index Cond: (a = ANY ('{NULL,33e75ff09dd601bbe69f351039152189,f457c545a9ded88f18ecee47145a72c0,c51ce410c124a10e0db5e4b97fc2af39}'::text[]))
+(5 rows)
+
+SELECT COUNT(*) FROM brin_in_test_bloom_2 WHERE a IN (NULL, '33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0', 'c51ce410c124a10e0db5e4b97fc2af39');
+ count 
+-------
+    39
+(1 row)
+
+DROP TABLE brin_in_test_bloom_2;
+RESET enable_seqscan;
diff --git a/src/test/regress/sql/brin_bloom.sql b/src/test/regress/sql/brin_bloom.sql
index 5d499208e38..d187e89a609 100644
--- a/src/test/regress/sql/brin_bloom.sql
+++ b/src/test/regress/sql/brin_bloom.sql
@@ -374,3 +374,138 @@ VACUUM ANALYZE brin_test_bloom;
 EXPLAIN (COSTS OFF) SELECT * FROM brin_test_bloom WHERE a = 1;
 -- Ensure brin index is not used when values are not correlated
 EXPLAIN (COSTS OFF) SELECT * FROM brin_test_bloom WHERE b = 1;
+
+
+-- do some tests on IN clauses for simple data types
+CREATE TABLE brin_in_test_bloom_1 (a INT, b BIGINT) WITH (fillfactor=10);
+INSERT INTO brin_in_test_bloom_1
+SELECT i/5 + mod(991 * i + 617, 20),
+       i/10 + mod(853 * i + 491, 30)
+  FROM generate_series(1,1000) s(i);
+
+CREATE INDEX brin_in_test_bloom_1_idx_1 ON brin_in_test_bloom_1 USING brin (a int4_bloom_ops(n_distinct_per_range=50)) WITH (pages_per_range=1);
+CREATE INDEX brin_in_test_bloom_1_idx_2 ON brin_in_test_bloom_1 USING brin (b int8_bloom_ops(n_distinct_per_range=50)) WITH (pages_per_range=1);
+
+SET enable_seqscan=off;
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE a IN (113);
+
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE a IN (113);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE a IN (113, NULL);
+
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE a IN (113, NULL);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE a IN (NULL, NULL);
+
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE a IN (NULL, NULL);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE a IN (113, 177);
+
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE a IN (113, 177);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE a IN (113, 177, NULL);
+
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE a IN (113, 177, NULL);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE a IN (113, 177, 25);
+
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE a IN (113, 177, 25);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE a IN (NULL, 113, 177, 25);
+
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE a IN (NULL, 113, 177, 25);
+
+-- a bit weird this requires a cast to bigint, unlike multi-value IN clause
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE b IN (82::bigint);
+
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE b IN (82::bigint);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE b IN (82, NULL);
+
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE b IN (82, NULL);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE b IN (NULL, NULL);
+
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE b IN (NULL, NULL);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE b IN (82, 41);
+
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE b IN (82, 41);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE b IN (82, 41, NULL);
+
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE b IN (82, 41, NULL);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE b IN (82, 41, 15);
+
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE b IN (82, 41, 15);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE b IN (NULL, 82, 41, 15);
+
+SELECT COUNT(*) FROM brin_in_test_bloom_1 WHERE b IN (NULL, 82, 41, 15);
+
+DROP TABLE brin_in_test_bloom_1;
+RESET enable_seqscan;
+
+
+-- do some tests on IN clauses for varlena data types
+CREATE TABLE brin_in_test_bloom_2 (a TEXT) WITH (fillfactor=10);
+INSERT INTO brin_in_test_bloom_2
+SELECT v FROM (SELECT row_number() OVER (ORDER BY v) c, v FROM (SELECT md5((i/13)::text) AS v FROM generate_series(1,1000) s(i)) foo) bar ORDER BY c + 25 * random();
+
+CREATE INDEX brin_in_test_bloom_2_idx ON brin_in_test_bloom_2 USING brin (a text_bloom_ops(n_distinct_per_range=50)) WITH (pages_per_range=1);
+
+SET enable_seqscan=off;
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189');
+
+SELECT COUNT(*) FROM brin_in_test_bloom_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189');
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', NULL);
+
+SELECT COUNT(*) FROM brin_in_test_bloom_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', NULL);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_2 WHERE a IN (NULL, NULL);
+
+SELECT COUNT(*) FROM brin_in_test_bloom_2 WHERE a IN (NULL, NULL);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0');
+
+SELECT COUNT(*) FROM brin_in_test_bloom_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0');
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0', NULL);
+
+SELECT COUNT(*) FROM brin_in_test_bloom_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0', NULL);
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0', 'c51ce410c124a10e0db5e4b97fc2af39');
+
+SELECT COUNT(*) FROM brin_in_test_bloom_2 WHERE a IN ('33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0', 'c51ce410c124a10e0db5e4b97fc2af39');
+
+EXPLAIN (COSTS OFF)
+SELECT COUNT(*) FROM brin_in_test_bloom_2 WHERE a IN (NULL, '33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0', 'c51ce410c124a10e0db5e4b97fc2af39');
+
+SELECT COUNT(*) FROM brin_in_test_bloom_2 WHERE a IN (NULL, '33e75ff09dd601bbe69f351039152189', 'f457c545a9ded88f18ecee47145a72c0', 'c51ce410c124a10e0db5e4b97fc2af39');
+
+DROP TABLE brin_in_test_bloom_2;
+RESET enable_seqscan;
-- 
2.39.1



^ permalink  raw  reply  [nested|flat] 5+ messages in thread


end of thread, other threads:[~2023-02-18 19:49 UTC | newest]

Thread overview: 5+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2020-12-03 07:54 [PATCH v5 1/2] Add a new OUTDATED filtering facility for REINDEX command. Julien Rouhaud <[email protected]>
2023-02-13 17:01 BRIN indexes vs. SK_SEARCHARRAY (and preprocessing scan keys) Tomas Vondra <[email protected]>
2023-02-16 01:35 ` Re: BRIN indexes vs. SK_SEARCHARRAY (and preprocessing scan keys) Tomas Vondra <[email protected]>
2023-02-17 02:50   ` Re: BRIN indexes vs. SK_SEARCHARRAY (and preprocessing scan keys) Tomas Vondra <[email protected]>
2023-02-18 19:49     ` Re: BRIN indexes vs. SK_SEARCHARRAY (and preprocessing scan keys) Tomas Vondra <[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