<div><div><div>From e796800a32277a29013354344ede221b89e46c27 Mon Sep 17 00:00:00 2001</div><div>From: Vlad Medveditskov <vladmedveditskov(at)yandex(dot)ru></div><div>Date: Mon, 18 May 2026 12:50:13 +0300</div><div>Subject: [PATCH] Add contrib/anyarray: intarray-style operations and indexes</div><div> for any array type</div><div> </div><div>This new contrib module generalizes contrib/intarray to arrays of any</div><div>element type with a default btree opclass. It provides:</div><div> </div><div>* Set-style helpers for anyarray / anyelement: anyarray_sort</div><div> (with optional asc/desc direction), anyarray_uniq, anyarray_idx,</div><div> anyarray_subarray (2- and 3-arg), anyarray_icount,</div><div> anyarray_intersect, anyarray_union, anyarray_union_elem,</div><div> anyarray_difference, anyarray_difference_elem, plus the</div><div> corresponding operators #, &, |, - on anyarray and anyelement.</div><div> </div><div>* A textual boolean query type "anyquery" and the operators</div><div> anyarray @@ anyquery (with commutator anyquery ~~ anyarray).</div><div> Tokens are stored as text and parsed lazily through the</div><div> element type's input function at match time, so the same</div><div> query value can be used against arrays of different element</div><div> types.</div><div> </div><div>* A signature-based polymorphic GiST opclass "anyarray_gist_ops"</div><div> on anyarray. Each indexed array is hashed into a fixed-size</div><div> bit vector; internal nodes store the bitwise union of their</div><div> children, with an ALLISTRUE short-circuit when every bit is</div><div> set. Signature length is configurable via the siglen</div><div> reloption (default 252 bytes).</div><div> </div><div>* GIN opclasses for the three element types most commonly</div><div> requested by users: int8_anyquery_gin_ops,</div><div> uuid_anyquery_gin_ops and text_anyquery_gin_ops. They are</div><div> per-concrete-type because the GIN AM does not propagate</div><div> fn_expr into extractQuery, so the element type needed to</div><div> parse anyquery tokens cannot be discovered dynamically.</div><div> Strategies 1-4 mirror the built-in array_ops; strategy 5</div><div> is @@.</div><div> </div><div>* doc/src/sgml/anyarray.sgml with descriptions of every</div><div> function, operator and opclass, plus a usage example.</div><div> </div><div>* Regression test (contrib/anyarray/sql/anyarray.sql,</div><div> expected/anyarray.out) covering int8, uuid and text inputs,</div><div> the error paths, and a cross-AM consistency block that</div><div> asserts seqscan, GiST and GIN return the same row set for</div><div> seven representative predicates.</div><div>---</div><div> contrib/Makefile | 1 +</div><div> contrib/anyarray/.gitignore | 4 +</div><div> contrib/anyarray/Makefile | 27 +</div><div> contrib/anyarray/anyarray--1.0.sql | 336 +++++++++++</div><div> contrib/anyarray/anyarray.c | 116 ++++</div><div> contrib/anyarray/anyarray.control | 6 +</div><div> contrib/anyarray/anyarray.h | 184 ++++++</div><div> contrib/anyarray/anyarray_bool.c | 664 ++++++++++++++++++++++</div><div> contrib/anyarray/anyarray_gin.c | 374 +++++++++++++</div><div> contrib/anyarray/anyarray_gist.c | 742 +++++++++++++++++++++++++</div><div> contrib/anyarray/anyarray_op.c | 613 ++++++++++++++++++++</div><div> contrib/anyarray/expected/anyarray.out | 727 ++++++++++++++++++++++++</div><div> contrib/anyarray/meson.build | 38 ++</div><div> contrib/anyarray/sql/anyarray.sql | 330 +++++++++++</div><div> contrib/meson.build | 1 +</div><div> doc/src/sgml/anyarray.sgml | 482 ++++++++++++++++</div><div> doc/src/sgml/contrib.sgml | 1 +</div><div> doc/src/sgml/filelist.sgml | 1 +</div><div> src/test/regress/regression.diffs | 0</div><div> src/test/regress/regression.out | 2 +</div><div> src/tools/pgindent/typedefs.list | 8 +</div><div> 21 files changed, 4657 insertions(+)</div><div> create mode 100644 contrib/anyarray/.gitignore</div><div> create mode 100644 contrib/anyarray/Makefile</div><div> create mode 100644 contrib/anyarray/anyarray--1.0.sql</div><div> create mode 100644 contrib/anyarray/anyarray.c</div><div> create mode 100644 contrib/anyarray/anyarray.control</div><div> create mode 100644 contrib/anyarray/anyarray.h</div><div> create mode 100644 contrib/anyarray/anyarray_bool.c</div><div> create mode 100644 contrib/anyarray/anyarray_gin.c</div><div> create mode 100644 contrib/anyarray/anyarray_gist.c</div><div> create mode 100644 contrib/anyarray/anyarray_op.c</div><div> create mode 100644 contrib/anyarray/expected/anyarray.out</div><div> create mode 100644 contrib/anyarray/meson.build</div><div> create mode 100644 contrib/anyarray/sql/anyarray.sql</div><div> create mode 100644 doc/src/sgml/anyarray.sgml</div><div> create mode 100644 src/test/regress/regression.diffs</div><div> create mode 100644 src/test/regress/regression.out</div><div> </div><div>diff --git a/contrib/Makefile b/contrib/Makefile</div><div>index 7d91fe77db3..b9586cb3a6b 100644</div><div>--- a/contrib/Makefile</div><div>+++ b/contrib/Makefile</div><div>@@ -6,6 +6,7 @@ include $(top_builddir)/src/Makefile.global</div><div> </div><div> SUBDIRS = \</div><div> amcheck \</div><div>+ anyarray \</div><div> auth_delay \</div><div> auto_explain \</div><div> basic_archive \</div><div>diff --git a/contrib/anyarray/.gitignore b/contrib/anyarray/.gitignore</div><div>new file mode 100644</div><div>index 00000000000..5dcb3ff9723</div><div>--- /dev/null</div><div>+++ b/contrib/anyarray/.gitignore</div><div>@@ -0,0 +1,4 @@</div><div>+# Generated subdirectories</div><div>+/log/</div><div>+/results/</div><div>+/tmp_check/</div><div>diff --git a/contrib/anyarray/Makefile b/contrib/anyarray/Makefile</div><div>new file mode 100644</div><div>index 00000000000..d6eb9ae10ad</div><div>--- /dev/null</div><div>+++ b/contrib/anyarray/Makefile</div><div>@@ -0,0 +1,27 @@</div><div>+# contrib/anyarray/Makefile</div><div>+</div><div>+MODULE_big = anyarray</div><div>+OBJS = \</div><div>+ $(WIN32RES) \</div><div>+ anyarray.o \</div><div>+ anyarray_bool.o \</div><div>+ anyarray_gin.o \</div><div>+ anyarray_gist.o \</div><div>+ anyarray_op.o</div><div>+</div><div>+EXTENSION = anyarray</div><div>+DATA = anyarray--1.0.sql</div><div>+PGFILEDESC = "anyarray - operations and indexes for arrays of any type"</div><div>+</div><div>+REGRESS = anyarray</div><div>+</div><div>+ifdef USE_PGXS</div><div>+PG_CONFIG = pg_config</div><div>+PGXS := $(shell $(PG_CONFIG) --pgxs)</div><div>+include $(PGXS)</div><div>+else</div><div>+subdir = contrib/anyarray</div><div>+top_builddir = ../..</div><div>+include $(top_builddir)/src/Makefile.global</div><div>+include $(top_srcdir)/contrib/contrib-global.mk</div><div>+endif</div><div>diff --git a/contrib/anyarray/anyarray--1.0.sql b/contrib/anyarray/anyarray--1.0.sql</div><div>new file mode 100644</div><div>index 00000000000..84bcff34b5b</div><div>--- /dev/null</div><div>+++ b/contrib/anyarray/anyarray--1.0.sql</div><div>@@ -0,0 +1,336 @@</div><div>+/* contrib/anyarray/anyarray--1.0.sql */</div><div>+</div><div>+-- complain if script is sourced in psql, rather than via CREATE EXTENSION</div><div>+\echo Use "CREATE EXTENSION anyarray" to load this file. \quit</div><div>+</div><div>+--</div><div>+-- Set-style operations for arrays of any btree-orderable element type.</div><div>+--</div><div>+</div><div>+CREATE FUNCTION anyarray_sort(anyarray)</div><div>+RETURNS anyarray</div><div>+AS 'MODULE_PATHNAME', 'anyarray_sort'</div><div>+LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE;</div><div>+</div><div>+CREATE FUNCTION anyarray_sort(anyarray, text)</div><div>+RETURNS anyarray</div><div>+AS 'MODULE_PATHNAME', 'anyarray_sort_dir'</div><div>+LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE;</div><div>+</div><div>+CREATE FUNCTION anyarray_uniq(anyarray)</div><div>+RETURNS anyarray</div><div>+AS 'MODULE_PATHNAME', 'anyarray_uniq'</div><div>+LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE;</div><div>+</div><div>+CREATE FUNCTION anyarray_idx(anyarray, anyelement)</div><div>+RETURNS int4</div><div>+AS 'MODULE_PATHNAME', 'anyarray_idx'</div><div>+LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE;</div><div>+</div><div>+CREATE FUNCTION anyarray_subarray(anyarray, int4, int4)</div><div>+RETURNS anyarray</div><div>+AS 'MODULE_PATHNAME', 'anyarray_subarray'</div><div>+LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE;</div><div>+</div><div>+CREATE FUNCTION anyarray_subarray(anyarray, int4)</div><div>+RETURNS anyarray</div><div>+AS 'MODULE_PATHNAME', 'anyarray_subarray_to_end'</div><div>+LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE;</div><div>+</div><div>+CREATE FUNCTION anyarray_icount(anyarray)</div><div>+RETURNS int4</div><div>+AS 'MODULE_PATHNAME', 'anyarray_icount'</div><div>+LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE;</div><div>+</div><div>+CREATE FUNCTION anyarray_intersect(anyarray, anyarray)</div><div>+RETURNS anyarray</div><div>+AS 'MODULE_PATHNAME', 'anyarray_intersect'</div><div>+LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE;</div><div>+</div><div>+CREATE FUNCTION anyarray_union(anyarray, anyarray)</div><div>+RETURNS anyarray</div><div>+AS 'MODULE_PATHNAME', 'anyarray_union'</div><div>+LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE;</div><div>+</div><div>+CREATE FUNCTION anyarray_union_elem(anyarray, anyelement)</div><div>+RETURNS anyarray</div><div>+AS 'MODULE_PATHNAME', 'anyarray_union_elem'</div><div>+LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE;</div><div>+</div><div>+CREATE FUNCTION anyarray_difference(anyarray, anyarray)</div><div>+RETURNS anyarray</div><div>+AS 'MODULE_PATHNAME', 'anyarray_difference'</div><div>+LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE;</div><div>+</div><div>+CREATE FUNCTION anyarray_difference_elem(anyarray, anyelement)</div><div>+RETURNS anyarray</div><div>+AS 'MODULE_PATHNAME', 'anyarray_difference_elem'</div><div>+LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE;</div><div>+</div><div>+--</div><div>+-- Operators</div><div>+--</div><div>+</div><div>+-- Unary prefix # : cardinality (number of elements)</div><div>+CREATE OPERATOR # (</div><div>+ RIGHTARG = anyarray,</div><div>+ PROCEDURE = anyarray_icount</div><div>+);</div><div>+</div><div>+-- array # element : 1-based index of element, or 0 if not found</div><div>+CREATE OPERATOR # (</div><div>+ LEFTARG = anyarray,</div><div>+ RIGHTARG = anyelement,</div><div>+ PROCEDURE = anyarray_idx</div><div>+);</div><div>+</div><div>+-- Set intersection</div><div>+CREATE OPERATOR & (</div><div>+ LEFTARG = anyarray,</div><div>+ RIGHTARG = anyarray,</div><div>+ COMMUTATOR = &,</div><div>+ PROCEDURE = anyarray_intersect</div><div>+);</div><div>+</div><div>+-- Set union (array | array)</div><div>+CREATE OPERATOR | (</div><div>+ LEFTARG = anyarray,</div><div>+ RIGHTARG = anyarray,</div><div>+ COMMUTATOR = |,</div><div>+ PROCEDURE = anyarray_union</div><div>+);</div><div>+</div><div>+-- Append element (array | element)</div><div>+CREATE OPERATOR | (</div><div>+ LEFTARG = anyarray,</div><div>+ RIGHTARG = anyelement,</div><div>+ PROCEDURE = anyarray_union_elem</div><div>+);</div><div>+</div><div>+-- Set difference (array - array)</div><div>+CREATE OPERATOR - (</div><div>+ LEFTARG = anyarray,</div><div>+ RIGHTARG = anyarray,</div><div>+ PROCEDURE = anyarray_difference</div><div>+);</div><div>+</div><div>+-- Remove all occurrences of element (array - element)</div><div>+CREATE OPERATOR - (</div><div>+ LEFTARG = anyarray,</div><div>+ RIGHTARG = anyelement,</div><div>+ PROCEDURE = anyarray_difference_elem</div><div>+);</div><div>+</div><div>+--</div><div>+-- Boolean query type</div><div>+--</div><div>+</div><div>+CREATE FUNCTION anyquery_in(cstring)</div><div>+RETURNS anyquery</div><div>+AS 'MODULE_PATHNAME', 'anyquery_in'</div><div>+LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE;</div><div>+</div><div>+CREATE FUNCTION anyquery_out(anyquery)</div><div>+RETURNS cstring</div><div>+AS 'MODULE_PATHNAME', 'anyquery_out'</div><div>+LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE;</div><div>+</div><div>+CREATE TYPE anyquery (</div><div>+ INTERNALLENGTH = -1,</div><div>+ INPUT = anyquery_in,</div><div>+ OUTPUT = anyquery_out,</div><div>+ STORAGE = extended</div><div>+);</div><div>+</div><div>+CREATE FUNCTION anyquery_querytree(anyquery)</div><div>+RETURNS text</div><div>+AS 'MODULE_PATHNAME', 'anyquery_querytree'</div><div>+LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE;</div><div>+</div><div>+CREATE FUNCTION anyarray_boolop(anyarray, anyquery)</div><div>+RETURNS bool</div><div>+AS 'MODULE_PATHNAME', 'anyarray_boolop'</div><div>+LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE;</div><div>+</div><div>+CREATE FUNCTION anyquery_boolop_rev(anyquery, anyarray)</div><div>+RETURNS bool</div><div>+AS 'MODULE_PATHNAME', 'anyquery_boolop_rev'</div><div>+LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE;</div><div>+</div><div>+CREATE OPERATOR @@ (</div><div>+ LEFTARG = anyarray,</div><div>+ RIGHTARG = anyquery,</div><div>+ PROCEDURE = anyarray_boolop,</div><div>+ COMMUTATOR = '~~',</div><div>+ RESTRICT = contsel,</div><div>+ JOIN = contjoinsel</div><div>+);</div><div>+</div><div>+CREATE OPERATOR ~~ (</div><div>+ LEFTARG = anyquery,</div><div>+ RIGHTARG = anyarray,</div><div>+ PROCEDURE = anyquery_boolop_rev,</div><div>+ COMMUTATOR = '@@',</div><div>+ RESTRICT = contsel,</div><div>+ JOIN = contjoinsel</div><div>+);</div><div>+</div><div>+--</div><div>+-- GiST opclass (signature-based)</div><div>+--</div><div>+</div><div>+CREATE FUNCTION anyarray_gist_key_in(cstring)</div><div>+RETURNS anyarray_gist_key</div><div>+AS 'MODULE_PATHNAME', 'anyarray_gist_key_in'</div><div>+LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE;</div><div>+</div><div>+CREATE FUNCTION anyarray_gist_key_out(anyarray_gist_key)</div><div>+RETURNS cstring</div><div>+AS 'MODULE_PATHNAME', 'anyarray_gist_key_out'</div><div>+LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE;</div><div>+</div><div>+CREATE TYPE anyarray_gist_key (</div><div>+ INTERNALLENGTH = -1,</div><div>+ INPUT = anyarray_gist_key_in,</div><div>+ OUTPUT = anyarray_gist_key_out</div><div>+);</div><div>+</div><div>+CREATE FUNCTION anyarray_gist_consistent(internal, anyarray, smallint, oid, internal)</div><div>+RETURNS bool</div><div>+AS 'MODULE_PATHNAME', 'anyarray_gist_consistent'</div><div>+LANGUAGE C IMMUTABLE PARALLEL SAFE;</div><div>+</div><div>+CREATE FUNCTION anyarray_gist_compress(internal)</div><div>+RETURNS internal</div><div>+AS 'MODULE_PATHNAME', 'anyarray_gist_compress'</div><div>+LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;</div><div>+</div><div>+CREATE FUNCTION anyarray_gist_decompress(internal)</div><div>+RETURNS internal</div><div>+AS 'MODULE_PATHNAME', 'anyarray_gist_decompress'</div><div>+LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;</div><div>+</div><div>+CREATE FUNCTION anyarray_gist_union(internal, internal)</div><div>+RETURNS anyarray_gist_key</div><div>+AS 'MODULE_PATHNAME', 'anyarray_gist_union'</div><div>+LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;</div><div>+</div><div>+CREATE FUNCTION anyarray_gist_same(anyarray_gist_key, anyarray_gist_key, internal)</div><div>+RETURNS internal</div><div>+AS 'MODULE_PATHNAME', 'anyarray_gist_same'</div><div>+LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;</div><div>+</div><div>+CREATE FUNCTION anyarray_gist_penalty(internal, internal, internal)</div><div>+RETURNS internal</div><div>+AS 'MODULE_PATHNAME', 'anyarray_gist_penalty'</div><div>+LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;</div><div>+</div><div>+CREATE FUNCTION anyarray_gist_picksplit(internal, internal)</div><div>+RETURNS internal</div><div>+AS 'MODULE_PATHNAME', 'anyarray_gist_picksplit'</div><div>+LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;</div><div>+</div><div>+CREATE FUNCTION anyarray_gist_options(internal)</div><div>+RETURNS void</div><div>+AS 'MODULE_PATHNAME', 'anyarray_gist_options'</div><div>+LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;</div><div>+</div><div>+CREATE OPERATOR CLASS anyarray_gist_ops</div><div>+FOR TYPE anyarray USING gist</div><div>+AS</div><div>+ OPERATOR 3 &&,</div><div>+ OPERATOR 7 @>,</div><div>+ OPERATOR 8 <@,</div><div>+ OPERATOR 18 =,</div><div>+ OPERATOR 20 @@ (anyarray, anyquery),</div><div>+ FUNCTION 1 anyarray_gist_consistent(internal, anyarray, smallint, oid, internal),</div><div>+ FUNCTION 2 anyarray_gist_union(internal, internal),</div><div>+ FUNCTION 3 anyarray_gist_compress(internal),</div><div>+ FUNCTION 4 anyarray_gist_decompress(internal),</div><div>+ FUNCTION 5 anyarray_gist_penalty(internal, internal, internal),</div><div>+ FUNCTION 6 anyarray_gist_picksplit(internal, internal),</div><div>+ FUNCTION 7 anyarray_gist_same(anyarray_gist_key, anyarray_gist_key, internal),</div><div>+ FUNCTION 10 anyarray_gist_options(internal),</div><div>+ STORAGE anyarray_gist_key;</div><div>+</div><div>+--</div><div>+-- GIN opclasses (per concrete element type, supporting standard ops + @@)</div><div>+--</div><div>+</div><div>+CREATE FUNCTION anyarray_gin_extract_query_int8(</div><div>+ int8[], internal, smallint, internal, internal, internal, internal)</div><div>+RETURNS internal</div><div>+AS 'MODULE_PATHNAME', 'anyarray_gin_extract_query_int8'</div><div>+LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;</div><div>+</div><div>+CREATE FUNCTION anyarray_gin_consistent_int8(</div><div>+ internal, smallint, int8[], integer, internal, internal, internal, internal)</div><div>+RETURNS bool</div><div>+AS 'MODULE_PATHNAME', 'anyarray_gin_consistent_int8'</div><div>+LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;</div><div>+</div><div>+CREATE OPERATOR CLASS int8_anyquery_gin_ops</div><div>+FOR TYPE int8[] USING gin</div><div>+AS</div><div>+ OPERATOR 1 && (anyarray, anyarray),</div><div>+ OPERATOR 2 @> (anyarray, anyarray),</div><div>+ OPERATOR 3 <@ (anyarray, anyarray),</div><div>+ OPERATOR 4 = (anyarray, anyarray),</div><div>+ OPERATOR 5 @@ (anyarray, anyquery),</div><div>+ FUNCTION 1 btint8cmp(int8, int8),</div><div>+ FUNCTION 2 ginarrayextract(anyarray, internal, internal),</div><div>+ FUNCTION 3 anyarray_gin_extract_query_int8(int8[], internal, smallint, internal, internal, internal, internal),</div><div>+ FUNCTION 4 anyarray_gin_consistent_int8(internal, smallint, int8[], integer, internal, internal, internal, internal),</div><div>+ STORAGE int8;</div><div>+</div><div>+CREATE FUNCTION anyarray_gin_extract_query_uuid(</div><div>+ uuid[], internal, smallint, internal, internal, internal, internal)</div><div>+RETURNS internal</div><div>+AS 'MODULE_PATHNAME', 'anyarray_gin_extract_query_uuid'</div><div>+LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;</div><div>+</div><div>+CREATE FUNCTION anyarray_gin_consistent_uuid(</div><div>+ internal, smallint, uuid[], integer, internal, internal, internal, internal)</div><div>+RETURNS bool</div><div>+AS 'MODULE_PATHNAME', 'anyarray_gin_consistent_uuid'</div><div>+LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;</div><div>+</div><div>+CREATE OPERATOR CLASS uuid_anyquery_gin_ops</div><div>+FOR TYPE uuid[] USING gin</div><div>+AS</div><div>+ OPERATOR 1 && (anyarray, anyarray),</div><div>+ OPERATOR 2 @> (anyarray, anyarray),</div><div>+ OPERATOR 3 <@ (anyarray, anyarray),</div><div>+ OPERATOR 4 = (anyarray, anyarray),</div><div>+ OPERATOR 5 @@ (anyarray, anyquery),</div><div>+ FUNCTION 1 uuid_cmp(uuid, uuid),</div><div>+ FUNCTION 2 ginarrayextract(anyarray, internal, internal),</div><div>+ FUNCTION 3 anyarray_gin_extract_query_uuid(uuid[], internal, smallint, internal, internal, internal, internal),</div><div>+ FUNCTION 4 anyarray_gin_consistent_uuid(internal, smallint, uuid[], integer, internal, internal, internal, internal),</div><div>+ STORAGE uuid;</div><div>+</div><div>+CREATE FUNCTION anyarray_gin_extract_query_text(</div><div>+ text[], internal, smallint, internal, internal, internal, internal)</div><div>+RETURNS internal</div><div>+AS 'MODULE_PATHNAME', 'anyarray_gin_extract_query_text'</div><div>+LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;</div><div>+</div><div>+CREATE FUNCTION anyarray_gin_consistent_text(</div><div>+ internal, smallint, text[], integer, internal, internal, internal, internal)</div><div>+RETURNS bool</div><div>+AS 'MODULE_PATHNAME', 'anyarray_gin_consistent_text'</div><div>+LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;</div><div>+</div><div>+CREATE OPERATOR CLASS text_anyquery_gin_ops</div><div>+FOR TYPE text[] USING gin</div><div>+AS</div><div>+ OPERATOR 1 && (anyarray, anyarray),</div><div>+ OPERATOR 2 @> (anyarray, anyarray),</div><div>+ OPERATOR 3 <@ (anyarray, anyarray),</div><div>+ OPERATOR 4 = (anyarray, anyarray),</div><div>+ OPERATOR 5 @@ (anyarray, anyquery),</div><div>+ FUNCTION 1 bttextcmp(text, text),</div><div>+ FUNCTION 2 ginarrayextract(anyarray, internal, internal),</div><div>+ FUNCTION 3 anyarray_gin_extract_query_text(text[], internal, smallint, internal, internal, internal, internal),</div><div>+ FUNCTION 4 anyarray_gin_consistent_text(internal, smallint, text[], integer, internal, internal, internal, internal),</div><div>+ STORAGE text;</div><div>diff --git a/contrib/anyarray/anyarray.c b/contrib/anyarray/anyarray.c</div><div>new file mode 100644</div><div>index 00000000000..0a2ab2153c8</div><div>--- /dev/null</div><div>+++ b/contrib/anyarray/anyarray.c</div><div>@@ -0,0 +1,116 @@</div><div>+/*-------------------------------------------------------------------------</div><div>+ *</div><div>+ * anyarray.c</div><div>+ * Common code for the anyarray extension.</div><div>+ *</div><div>+ * This extension extends PostgreSQL with intarray-style operations and</div><div>+ * index support that work for arrays of any element type with a default</div><div>+ * btree opclass. It is a generalization of the contrib/intarray module.</div><div>+ *</div><div>+ * Copyright (c) 2026, PostgreSQL Global Development Group</div><div>+ *</div><div>+ * IDENTIFICATION</div><div>+ * contrib/anyarray/anyarray.c</div><div>+ *</div><div>+ *-------------------------------------------------------------------------</div><div>+ */</div><div>+</div><div>+#include "postgres.h"</div><div>+</div><div>+#include "anyarray.h"</div><div>+#include "catalog/pg_type.h"</div><div>+#include "utils/builtins.h"</div><div>+#include "utils/lsyscache.h"</div><div>+</div><div>+PG_MODULE_MAGIC_EXT(</div><div>+ .name = "anyarray",</div><div>+ .version = PG_VERSION</div><div>+);</div><div>+</div><div>+/*</div><div>+ * anyarray_get_meta</div><div>+ * Fetch (and cache) type metadata for the given element type.</div><div>+ *</div><div>+ * The cache lives in fcinfo->flinfo->fn_extra so we pay the catalog lookup</div><div>+ * cost only once per call site. If "need_hash" is true the element type</div><div>+ * must additionally provide a default hash function; otherwise only a btree</div><div>+ * comparison function is required.</div><div>+ */</div><div>+AnyArrayTypeInfo *</div><div>+anyarray_get_meta(FunctionCallInfo fcinfo, Oid element_type, bool need_hash)</div><div>+{<!-- --></div><div>+ AnyArrayTypeInfo *meta = (AnyArrayTypeInfo *) fcinfo->flinfo->fn_extra;</div><div>+ TypeCacheEntry *typentry;</div><div>+ uint32 flags;</div><div>+</div><div>+ if (meta == NULL)</div><div>+ {<!-- --></div><div>+ meta = (AnyArrayTypeInfo *)</div><div>+ MemoryContextAllocZero(fcinfo->flinfo->fn_mcxt,</div><div>+ sizeof(AnyArrayTypeInfo));</div><div>+ fcinfo->flinfo->fn_extra = meta;</div><div>+ meta->element_type = InvalidOid;</div><div>+ }</div><div>+</div><div>+ if (meta->element_type == element_type && (!need_hash || meta->have_hash))</div><div>+ return meta;</div><div>+</div><div>+ flags = TYPECACHE_CMP_PROC_FINFO | TYPECACHE_EQ_OPR_FINFO;</div><div>+ if (need_hash)</div><div>+ flags |= TYPECACHE_HASH_PROC_FINFO;</div><div>+</div><div>+ typentry = lookup_type_cache(element_type, flags);</div><div>+</div><div>+ if (!OidIsValid(typentry->cmp_proc_finfo.fn_oid))</div><div>+ ereport(ERROR,</div><div>+ (errcode(ERRCODE_UNDEFINED_FUNCTION),</div><div>+ errmsg("could not identify a comparison function for type %s",</div><div>+ format_type_be(element_type))));</div><div>+</div><div>+ if (!OidIsValid(typentry->eq_opr_finfo.fn_oid))</div><div>+ ereport(ERROR,</div><div>+ (errcode(ERRCODE_UNDEFINED_FUNCTION),</div><div>+ errmsg("could not identify an equality operator for type %s",</div><div>+ format_type_be(element_type))));</div><div>+</div><div>+ if (need_hash && !OidIsValid(typentry->hash_proc_finfo.fn_oid))</div><div>+ ereport(ERROR,</div><div>+ (errcode(ERRCODE_UNDEFINED_FUNCTION),</div><div>+ errmsg("could not identify a hash function for type %s",</div><div>+ format_type_be(element_type))));</div><div>+</div><div>+ meta->element_type = element_type;</div><div>+ get_typlenbyvalalign(element_type, &meta->typlen, &meta->typbyval,</div><div>+ &meta->typalign);</div><div>+ meta->typcollation = typentry->typcollation;</div><div>+ fmgr_info_cxt(typentry->cmp_proc_finfo.fn_oid, &meta->cmp_proc,</div><div>+ fcinfo->flinfo->fn_mcxt);</div><div>+ fmgr_info_cxt(typentry->eq_opr_finfo.fn_oid, &meta->eq_proc,</div><div>+ fcinfo->flinfo->fn_mcxt);</div><div>+ if (need_hash)</div><div>+ {<!-- --></div><div>+ fmgr_info_cxt(typentry->hash_proc_finfo.fn_oid, &meta->hash_proc,</div><div>+ fcinfo->flinfo->fn_mcxt);</div><div>+ meta->have_hash = true;</div><div>+ }</div><div>+ else</div><div>+ meta->have_hash = false;</div><div>+</div><div>+ return meta;</div><div>+}</div><div>+</div><div>+/*</div><div>+ * anyarray_cmp_datum</div><div>+ * qsort_arg-compatible comparator delegating to the type's btree cmp.</div><div>+ */</div><div>+int</div><div>+anyarray_cmp_datum(const void *a, const void *b, void *arg)</div><div>+{<!-- --></div><div>+ AnyArrayTypeInfo *meta = (AnyArrayTypeInfo *) arg;</div><div>+ Datum d1 = *((const Datum *) a);</div><div>+ Datum d2 = *((const Datum *) b);</div><div>+ Datum result;</div><div>+</div><div>+ result = FunctionCall2Coll(&meta->cmp_proc, meta->typcollation, d1, d2);</div><div>+ return DatumGetInt32(result);</div><div>+}</div><div>diff --git a/contrib/anyarray/anyarray.control b/contrib/anyarray/anyarray.control</div><div>new file mode 100644</div><div>index 00000000000..2478f2d93d7</div><div>--- /dev/null</div><div>+++ b/contrib/anyarray/anyarray.control</div><div>@@ -0,0 +1,6 @@</div><div>+# anyarray extension</div><div>+comment = 'Operations and indexes for arrays of any type'</div><div>+default_version = '1.0'</div><div>+module_pathname = '$libdir/anyarray'</div><div>+relocatable = true</div><div>+trusted = false</div><div>diff --git a/contrib/anyarray/anyarray.h b/contrib/anyarray/anyarray.h</div><div>new file mode 100644</div><div>index 00000000000..f59ac9de749</div><div>--- /dev/null</div><div>+++ b/contrib/anyarray/anyarray.h</div><div>@@ -0,0 +1,184 @@</div><div>+/*-------------------------------------------------------------------------</div><div>+ *</div><div>+ * anyarray.h</div><div>+ * Shared declarations for the anyarray extension.</div><div>+ *</div><div>+ * Copyright (c) 2026, PostgreSQL Global Development Group</div><div>+ *</div><div>+ * IDENTIFICATION</div><div>+ * contrib/anyarray/anyarray.h</div><div>+ *</div><div>+ *-------------------------------------------------------------------------</div><div>+ */</div><div>+#ifndef ANYARRAY_H</div><div>+#define ANYARRAY_H</div><div>+</div><div>+#include "access/gist.h"</div><div>+#include "fmgr.h"</div><div>+#include "utils/array.h"</div><div>+#include "utils/typcache.h"</div><div>+</div><div>+/*</div><div>+ * Per-call cached element type metadata.</div><div>+ *</div><div>+ * Stored in fcinfo->flinfo->fn_extra so we look up the type once per</div><div>+ * planner-level call site. All entries are populated by anyarray_get_meta().</div><div>+ */</div><div>+typedef struct AnyArrayTypeInfo</div><div>+{<!-- --></div><div>+ Oid element_type; /* element OID, or InvalidOid if not</div><div>+ * initialized */</div><div>+ int16 typlen;</div><div>+ bool typbyval;</div><div>+ char typalign;</div><div>+ Oid typcollation; /* default collation for comparisons */</div><div>+ FmgrInfo cmp_proc; /* btree comparison function for element_type */</div><div>+ FmgrInfo eq_proc; /* equality function for element_type */</div><div>+ FmgrInfo hash_proc; /* hash function for element_type (optional) */</div><div>+ bool have_hash; /* whether hash_proc was filled in */</div><div>+} AnyArrayTypeInfo;</div><div>+</div><div>+/* Reject the kinds of input we do not handle. */</div><div>+#define ANYARRAY_CHECK_ARRAY(arr) \</div><div>+ do { \</div><div>+ if (ARR_NDIM(arr) > 1) \</div><div>+ ereport(ERROR, \</div><div>+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), \</div><div>+ errmsg("multidimensional arrays are not supported"))); \</div><div>+ if (ARR_HASNULL(arr) && array_contains_nulls(arr)) \</div><div>+ ereport(ERROR, \</div><div>+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), \</div><div>+ errmsg("array must not contain nulls"))); \</div><div>+ } while (0)</div><div>+</div><div>+#define ANYARRAY_NELEMS(arr) \</div><div>+ (ARR_NDIM(arr) > 0 ? ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr)) : 0)</div><div>+</div><div>+/*</div><div>+ * Initialize / refresh AnyArrayTypeInfo for a given element type.</div><div>+ *</div><div>+ * Allocates the struct in fcinfo->flinfo->fn_mcxt on first use; on subsequent</div><div>+ * calls reuses the cache unless the element type changed. Reports an error</div><div>+ * via ereport() if the element type lacks a btree comparison operator</div><div>+ * (need_hash == true additionally requires a hash function).</div><div>+ */</div><div>+extern AnyArrayTypeInfo *anyarray_get_meta(FunctionCallInfo fcinfo,</div><div>+ Oid element_type,</div><div>+ bool need_hash);</div><div>+</div><div>+/*</div><div>+ * Comparison helper used both as a qsort_arg callback and directly.</div><div>+ * "arg" must be a valid AnyArrayTypeInfo *.</div><div>+ */</div><div>+extern int anyarray_cmp_datum(const void *a, const void *b, void *arg);</div><div>+</div><div>+/*****************************************************************************</div><div>+ * Boolean query type</div><div>+ *</div><div>+ * An anyquery value is a boolean expression in postfix notation whose leaves</div><div>+ * are textual representations of values. The element type is not known at</div><div>+ * parse time; it is supplied at @@-time by the array operand, and the value</div><div>+ * strings are parsed lazily through the element type's text input function.</div><div>+ *</div><div>+ * On-disk layout:</div><div>+ *</div><div>+ * +-----------------------------+</div><div>+ * | varlena header |</div><div>+ * | int32 size (n items) |</div><div>+ * | int32 str_off | -- byte offset to string heap</div><div>+ * | int32 str_len |</div><div>+ * | AnyQueryItem items[size] |</div><div>+ * | <padding> |</div><div>+ * | char strings[str_len] | -- null-terminated value strings</div><div>+ * +-----------------------------+</div><div>+ *****************************************************************************/</div><div>+</div><div>+/* item.type values */</div><div>+#define ANYQ_VAL 1</div><div>+#define ANYQ_OPR 2</div><div>+</div><div>+/* operator codes (also used in the input grammar) */</div><div>+#define ANYQ_AND '&'</div><div>+#define ANYQ_OR '|'</div><div>+#define ANYQ_NOT '!'</div><div>+</div><div>+typedef struct AnyQueryItem</div><div>+{<!-- --></div><div>+ int16 type; /* ANYQ_VAL or ANYQ_OPR */</div><div>+ int16 left; /* offset to left-operand item (OPR only) */</div><div>+ int32 payload; /* VAL: offset into string heap; OPR: one of</div><div>+ * ANYQ_AND/OR/NOT */</div><div>+} AnyQueryItem;</div><div>+</div><div>+typedef struct AnyQuery</div><div>+{<!-- --></div><div>+ int32 vl_len_; /* varlena header (do not touch directly!) */</div><div>+ int32 size; /* number of items */</div><div>+ int32 str_off; /* byte offset to string heap */</div><div>+ int32 str_len; /* length of string heap */</div><div>+ AnyQueryItem items[FLEXIBLE_ARRAY_MEMBER];</div><div>+ /* followed by null-terminated value strings */</div><div>+} AnyQuery;</div><div>+</div><div>+#define ANYQUERY_HDRSIZE offsetof(AnyQuery, items)</div><div>+#define ANYQUERY_ITEMS(q) ((q)->items)</div><div>+#define ANYQUERY_STRING(q, item) ((const char *) (q) + (q)->str_off + (item)->payload)</div><div>+#define ANYQUERY_MAXITEMS \</div><div>+ ((MaxAllocSize - ANYQUERY_HDRSIZE) / sizeof(AnyQueryItem))</div><div>+</div><div>+#define DatumGetAnyQueryP(X) ((AnyQuery *) PG_DETOAST_DATUM(X))</div><div>+#define PG_GETARG_ANYQUERY_P(n) DatumGetAnyQueryP(PG_GETARG_DATUM(n))</div><div>+</div><div>+/*****************************************************************************</div><div>+ * GiST signature key</div><div>+ *</div><div>+ * Each indexed array is summarized as a fixed-size bit vector ("signature").</div><div>+ * Each element contributes a single bit, chosen as</div><div>+ * hash(element) mod (siglen * 8).</div><div>+ * Internal node keys are bitwise unions of their children; the ALLISTRUE</div><div>+ * flag short-circuits storage when every bit would be set.</div><div>+ *****************************************************************************/</div><div>+</div><div>+#define ANYARRAY_SIGLEN_DEFAULT (63 * 4) /* 252 bytes -> 2016 bits */</div><div>+#define ANYARRAY_SIGLEN_MAX GISTMaxIndexKeySize</div><div>+#define ANYARRAY_ALLISTRUE 0x01</div><div>+</div><div>+typedef struct AnyArrayGistKey</div><div>+{<!-- --></div><div>+ int32 vl_len_; /* varlena header (do not touch directly!) */</div><div>+ int32 flag;</div><div>+ char data[FLEXIBLE_ARRAY_MEMBER]; /* bit vector, siglen bytes */</div><div>+} AnyArrayGistKey;</div><div>+</div><div>+typedef struct AnyArrayGistOptions</div><div>+{<!-- --></div><div>+ int32 vl_len_;</div><div>+ int siglen; /* signature length in bytes */</div><div>+} AnyArrayGistOptions;</div><div>+</div><div>+#define ANYARRAY_GET_SIGLEN() (PG_HAS_OPCLASS_OPTIONS() ? \</div><div>+ ((AnyArrayGistOptions *) PG_GET_OPCLASS_OPTIONS())->siglen : \</div><div>+ ANYARRAY_SIGLEN_DEFAULT)</div><div>+</div><div>+#define ANYARRAY_GKEY_HDR (VARHDRSZ + sizeof(int32))</div><div>+#define ANYARRAY_GKEY_SIZE(flag, siglen) \</div><div>+ (ANYARRAY_GKEY_HDR + (((flag) & ANYARRAY_ALLISTRUE) ? 0 : (siglen)))</div><div>+#define ANYARRAY_GKEY_ISALLTRUE(k) (((AnyArrayGistKey *) (k))->flag & ANYARRAY_ALLISTRUE)</div><div>+#define ANYARRAY_GKEY_SIGN(k) ((unsigned char *) (((AnyArrayGistKey *) (k))->data))</div><div>+</div><div>+/* GIN strategy numbers; the first four match core's gin/array_ops */</div><div>+#define ANYARRAY_GIN_OVERLAP_STRATEGY 1</div><div>+#define ANYARRAY_GIN_CONTAINS_STRATEGY 2</div><div>+#define ANYARRAY_GIN_CONTAINED_STRATEGY 3</div><div>+#define ANYARRAY_GIN_EQUAL_STRATEGY 4</div><div>+#define ANYARRAY_GIN_BOOLEAN_STRATEGY 5</div><div>+</div><div>+#define ANYARRAY_HASHVAL(h, siglen) ((unsigned int) (h) % ((siglen) * BITS_PER_BYTE))</div><div>+#define ANYARRAY_SETBIT(sig, h, siglen) \</div><div>+ ((sig)[(ANYARRAY_HASHVAL((h), (siglen)) / BITS_PER_BYTE)] |= \</div><div>+ (1 << (ANYARRAY_HASHVAL((h), (siglen)) % BITS_PER_BYTE)))</div><div>+#define ANYARRAY_GETBIT(sig, h, siglen) \</div><div>+ (((sig)[(ANYARRAY_HASHVAL((h), (siglen)) / BITS_PER_BYTE)] >> \</div><div>+ (ANYARRAY_HASHVAL((h), (siglen)) % BITS_PER_BYTE)) & 1)</div><div>+</div><div>+#endif /* ANYARRAY_H */</div><div>diff --git a/contrib/anyarray/anyarray_bool.c b/contrib/anyarray/anyarray_bool.c</div><div>new file mode 100644</div><div>index 00000000000..325619837ef</div><div>--- /dev/null</div><div>+++ b/contrib/anyarray/anyarray_bool.c</div><div>@@ -0,0 +1,664 @@</div><div>+/*-------------------------------------------------------------------------</div><div>+ *</div><div>+ * anyarray_bool.c</div><div>+ * Boolean query type for anyarray.</div><div>+ *</div><div>+ * Provides the anyquery type and the @@ operator. A query value is a</div><div>+ * boolean expression whose leaves are text tokens; the element type is</div><div>+ * supplied at @@-time by the array operand, and the leaves are parsed</div><div>+ * through the element type's text input function.</div><div>+ *</div><div>+ * Grammar:</div><div>+ *</div><div>+ * expr ::= or_expr</div><div>+ * or_expr ::= and_expr ( '|' and_expr )*</div><div>+ * and_expr ::= unary_expr ( '&' unary_expr )*</div><div>+ * unary_expr ::= '!' unary_expr | atom</div><div>+ * atom ::= VALUE | '(' expr ')'</div><div>+ * VALUE ::= bare_token | quoted_string</div><div>+ * bare_token ::= any run of characters that are not whitespace,</div><div>+ * '&', '|', '!', '(', ')', or '"'</div><div>+ * quoted_string := '"' chars-with-backslash-escapes '"'</div><div>+ *</div><div>+ *</div><div>+ * Copyright (c) 2026, PostgreSQL Global Development Group</div><div>+ *</div><div>+ * IDENTIFICATION</div><div>+ * contrib/anyarray/anyarray_bool.c</div><div>+ *</div><div>+ *-------------------------------------------------------------------------</div><div>+ */</div><div>+</div><div>+#include "postgres.h"</div><div>+</div><div>+#include "anyarray.h"</div><div>+</div><div>+#include "lib/stringinfo.h"</div><div>+#include "miscadmin.h"</div><div>+#include "nodes/miscnodes.h"</div><div>+#include "utils/array.h"</div><div>+#include "utils/builtins.h"</div><div>+#include "utils/lsyscache.h"</div><div>+#include "utils/memutils.h"</div><div>+</div><div>+PG_FUNCTION_INFO_V1(anyquery_in);</div><div>+PG_FUNCTION_INFO_V1(anyquery_out);</div><div>+PG_FUNCTION_INFO_V1(anyarray_boolop);</div><div>+PG_FUNCTION_INFO_V1(anyquery_boolop_rev);</div><div>+PG_FUNCTION_INFO_V1(anyquery_querytree);</div><div>+</div><div>+</div><div>+/* ------------------------------------------------------------------------</div><div>+ * Parser</div><div>+ * ------------------------------------------------------------------------</div><div>+ */</div><div>+</div><div>+/*</div><div>+ * Working state used while building the postfix item list.</div><div>+ *</div><div>+ * Items are emitted in postfix (reverse polish) order directly into the</div><div>+ * "items" array. Value strings are concatenated into "strs"; each VAL item</div><div>+ * stores the byte offset of its string within strs->data.</div><div>+ */</div><div>+typedef struct ParserState</div><div>+{<!-- --></div><div>+ const char *cur;</div><div>+ const char *end;</div><div>+ AnyQueryItem *items;</div><div>+ int nitems;</div><div>+ int capitems;</div><div>+ StringInfoData strs;</div><div>+ struct Node *escontext;</div><div>+} ParserState;</div><div>+</div><div>+#define PARSE_FAIL(state, errc, ...) \</div><div>+ do { \</div><div>+ errsave((state)->escontext, \</div><div>+ (errcode(errc), \</div><div>+ __VA_ARGS__)); \</div><div>+ return false; \</div><div>+ } while (0)</div><div>+</div><div>+static bool parse_expr(ParserState *state);</div><div>+</div><div>+static void</div><div>+skip_ws(ParserState *state)</div><div>+{<!-- --></div><div>+ while (state->cur < state->end &&</div><div>+ (*state->cur == ' ' || *state->cur == '\t' ||</div><div>+ *state->cur == '\r' || *state->cur == '\n' ||</div><div>+ *state->cur == '\v' || *state->cur == '\f'))</div><div>+ state->cur++;</div><div>+}</div><div>+</div><div>+static bool</div><div>+is_value_char(char c)</div><div>+{<!-- --></div><div>+ if (c == '\0' || c == ' ' || c == '\t' || c == '\r' || c == '\n' ||</div><div>+ c == '\v' || c == '\f')</div><div>+ return false;</div><div>+ if (c == '&' || c == '|' || c == '!' || c == '(' || c == ')' || c == '"')</div><div>+ return false;</div><div>+ return true;</div><div>+}</div><div>+</div><div>+static bool</div><div>+emit_item(ParserState *state, int16 type, int32 payload)</div><div>+{<!-- --></div><div>+ if (state->nitems == state->capitems)</div><div>+ {<!-- --></div><div>+ if ((size_t) state->capitems * 2 > ANYQUERY_MAXITEMS)</div><div>+ PARSE_FAIL(state, ERRCODE_PROGRAM_LIMIT_EXCEEDED,</div><div>+ errmsg("anyquery expression is too complex"));</div><div>+ state->capitems *= 2;</div><div>+ state->items = (AnyQueryItem *) repalloc(state->items,</div><div>+ sizeof(AnyQueryItem) *</div><div>+ state->capitems);</div><div>+ }</div><div>+ state->items[state->nitems].type = type;</div><div>+ state->items[state->nitems].left = 0;</div><div>+ state->items[state->nitems].payload = payload;</div><div>+ state->nitems++;</div><div>+ return true;</div><div>+}</div><div>+</div><div>+/*</div><div>+ * Consume a value token, append its string (with terminating NUL) to</div><div>+ * state->strs, and emit a VAL item whose payload is the byte offset of the</div><div>+ * string within strs->data.</div><div>+ */</div><div>+static bool</div><div>+parse_value(ParserState *state)</div><div>+{<!-- --></div><div>+ int32 off;</div><div>+</div><div>+ skip_ws(state);</div><div>+</div><div>+ if (state->cur >= state->end)</div><div>+ PARSE_FAIL(state, ERRCODE_SYNTAX_ERROR,</div><div>+ errmsg("unexpected end of input in anyquery"));</div><div>+</div><div>+ off = state->strs.len;</div><div>+</div><div>+ if (*state->cur == '"')</div><div>+ {<!-- --></div><div>+ state->cur++; /* opening quote */</div><div>+ while (state->cur < state->end && *state->cur != '"')</div><div>+ {<!-- --></div><div>+ if (*state->cur == '\\' && state->cur + 1 < state->end)</div><div>+ state->cur++;</div><div>+ appendStringInfoChar(&state->strs, *state->cur);</div><div>+ state->cur++;</div><div>+ }</div><div>+ if (state->cur >= state->end)</div><div>+ PARSE_FAIL(state, ERRCODE_SYNTAX_ERROR,</div><div>+ errmsg("unterminated quoted value in anyquery"));</div><div>+ state->cur++; /* closing quote */</div><div>+ }</div><div>+ else if (is_value_char(*state->cur))</div><div>+ {<!-- --></div><div>+ while (state->cur < state->end && is_value_char(*state->cur))</div><div>+ {<!-- --></div><div>+ appendStringInfoChar(&state->strs, *state->cur);</div><div>+ state->cur++;</div><div>+ }</div><div>+ }</div><div>+ else</div><div>+ PARSE_FAIL(state, ERRCODE_SYNTAX_ERROR,</div><div>+ errmsg("expected value at character \"%c\"", *state->cur));</div><div>+</div><div>+ appendStringInfoChar(&state->strs, '\0');</div><div>+ return emit_item(state, ANYQ_VAL, off);</div><div>+}</div><div>+</div><div>+static bool</div><div>+parse_atom(ParserState *state)</div><div>+{<!-- --></div><div>+ skip_ws(state);</div><div>+ if (state->cur < state->end && *state->cur == '(')</div><div>+ {<!-- --></div><div>+ state->cur++;</div><div>+ if (!parse_expr(state))</div><div>+ return false;</div><div>+ skip_ws(state);</div><div>+ if (state->cur >= state->end || *state->cur != ')')</div><div>+ PARSE_FAIL(state, ERRCODE_SYNTAX_ERROR,</div><div>+ errmsg("missing closing parenthesis in anyquery"));</div><div>+ state->cur++;</div><div>+ return true;</div><div>+ }</div><div>+ return parse_value(state);</div><div>+}</div><div>+</div><div>+static bool</div><div>+parse_not(ParserState *state)</div><div>+{<!-- --></div><div>+ skip_ws(state);</div><div>+ if (state->cur < state->end && *state->cur == '!')</div><div>+ {<!-- --></div><div>+ state->cur++;</div><div>+ if (!parse_not(state))</div><div>+ return false;</div><div>+ return emit_item(state, ANYQ_OPR, ANYQ_NOT);</div><div>+ }</div><div>+ return parse_atom(state);</div><div>+}</div><div>+</div><div>+static bool</div><div>+parse_and(ParserState *state)</div><div>+{<!-- --></div><div>+ if (!parse_not(state))</div><div>+ return false;</div><div>+ for (;;)</div><div>+ {<!-- --></div><div>+ skip_ws(state);</div><div>+ if (state->cur >= state->end || *state->cur != '&')</div><div>+ return true;</div><div>+ state->cur++;</div><div>+ if (!parse_not(state))</div><div>+ return false;</div><div>+ if (!emit_item(state, ANYQ_OPR, ANYQ_AND))</div><div>+ return false;</div><div>+ }</div><div>+}</div><div>+</div><div>+static bool</div><div>+parse_expr(ParserState *state)</div><div>+{<!-- --></div><div>+ check_stack_depth();</div><div>+</div><div>+ if (!parse_and(state))</div><div>+ return false;</div><div>+ for (;;)</div><div>+ {<!-- --></div><div>+ skip_ws(state);</div><div>+ if (state->cur >= state->end || *state->cur != '|')</div><div>+ return true;</div><div>+ state->cur++;</div><div>+ if (!parse_and(state))</div><div>+ return false;</div><div>+ if (!emit_item(state, ANYQ_OPR, ANYQ_OR))</div><div>+ return false;</div><div>+ }</div><div>+}</div><div>+</div><div>+/*</div><div>+ * Compute the "left" field for every OPR item by walking the postfix array</div><div>+ * top-down. Returns false on overflow of the int16 left field.</div><div>+ */</div><div>+static bool</div><div>+compute_lefts(ParserState *state, int *pos)</div><div>+{<!-- --></div><div>+ int mypos;</div><div>+</div><div>+ check_stack_depth();</div><div>+</div><div>+ mypos = (*pos)--;</div><div>+ Assert(mypos >= 0);</div><div>+</div><div>+ if (state->items[mypos].type == ANYQ_VAL)</div><div>+ {<!-- --></div><div>+ state->items[mypos].left = 0;</div><div>+ return true;</div><div>+ }</div><div>+ else if (state->items[mypos].payload == ANYQ_NOT)</div><div>+ {<!-- --></div><div>+ state->items[mypos].left = -1;</div><div>+ return compute_lefts(state, pos);</div><div>+ }</div><div>+ else</div><div>+ {<!-- --></div><div>+ int delta;</div><div>+</div><div>+ /* binary operator: walk right operand */</div><div>+ if (!compute_lefts(state, pos))</div><div>+ return false;</div><div>+ delta = *pos - mypos;</div><div>+ if (delta < PG_INT16_MIN)</div><div>+ {<!-- --></div><div>+ errsave(state->escontext,</div><div>+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),</div><div>+ errmsg("anyquery expression is too complex")));</div><div>+ return false;</div><div>+ }</div><div>+ state->items[mypos].left = (int16) delta;</div><div>+ return compute_lefts(state, pos);</div><div>+ }</div><div>+}</div><div>+</div><div>+</div><div>+/* ------------------------------------------------------------------------</div><div>+ * Input / output / debug</div><div>+ * ------------------------------------------------------------------------</div><div>+ */</div><div>+</div><div>+Datum</div><div>+anyquery_in(PG_FUNCTION_ARGS)</div><div>+{<!-- --></div><div>+ char *buf = PG_GETARG_CSTRING(0);</div><div>+ ParserState state;</div><div>+ AnyQuery *out;</div><div>+ Size items_bytes;</div><div>+ Size total;</div><div>+ int pos;</div><div>+</div><div>+ state.cur = buf;</div><div>+ state.end = buf + strlen(buf);</div><div>+ state.capitems = 16;</div><div>+ state.items = (AnyQueryItem *) palloc(sizeof(AnyQueryItem) *</div><div>+ state.capitems);</div><div>+ state.nitems = 0;</div><div>+ initStringInfo(&state.strs);</div><div>+ state.escontext = fcinfo->context;</div><div>+</div><div>+ if (!parse_expr(&state))</div><div>+ PG_RETURN_NULL();</div><div>+</div><div>+ skip_ws(&state);</div><div>+ if (state.cur < state.end)</div><div>+ ereturn(state.escontext, (Datum) 0,</div><div>+ (errcode(ERRCODE_SYNTAX_ERROR),</div><div>+ errmsg("unexpected trailing input in anyquery")));</div><div>+</div><div>+ if (state.nitems == 0)</div><div>+ ereturn(state.escontext, (Datum) 0,</div><div>+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),</div><div>+ errmsg("empty anyquery")));</div><div>+</div><div>+ if ((size_t) state.nitems > ANYQUERY_MAXITEMS)</div><div>+ ereturn(state.escontext, (Datum) 0,</div><div>+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),</div><div>+ errmsg("anyquery has too many items (%d, maximum %zu)",</div><div>+ state.nitems, ANYQUERY_MAXITEMS)));</div><div>+</div><div>+ pos = state.nitems - 1;</div><div>+ if (!compute_lefts(&state, &pos))</div><div>+ PG_RETURN_NULL();</div><div>+ Assert(pos == -1);</div><div>+</div><div>+ items_bytes = MAXALIGN(state.nitems * sizeof(AnyQueryItem));</div><div>+ total = ANYQUERY_HDRSIZE + items_bytes + state.strs.len;</div><div>+</div><div>+ out = (AnyQuery *) palloc0(total);</div><div>+ SET_VARSIZE(out, total);</div><div>+ out->size = state.nitems;</div><div>+ out->str_off = ANYQUERY_HDRSIZE + items_bytes;</div><div>+ out->str_len = state.strs.len;</div><div>+ memcpy(out->items, state.items, state.nitems * sizeof(AnyQueryItem));</div><div>+ memcpy((char *) out + out->str_off, state.strs.data, state.strs.len);</div><div>+</div><div>+ pfree(state.items);</div><div>+ pfree(state.strs.data);</div><div>+</div><div>+ PG_RETURN_POINTER(out);</div><div>+}</div><div>+</div><div>+/*</div><div>+ * Quote a value string if it contains anything the parser would consider</div><div>+ * special. Otherwise emit it verbatim.</div><div>+ */</div><div>+static void</div><div>+append_value_token(StringInfo out, const char *s)</div><div>+{<!-- --></div><div>+ const char *p;</div><div>+ bool need_quote = (*s == '\0');</div><div>+</div><div>+ for (p = s; *p; p++)</div><div>+ {<!-- --></div><div>+ if (!is_value_char(*p))</div><div>+ {<!-- --></div><div>+ need_quote = true;</div><div>+ break;</div><div>+ }</div><div>+ }</div><div>+</div><div>+ if (!need_quote)</div><div>+ {<!-- --></div><div>+ appendStringInfoString(out, s);</div><div>+ return;</div><div>+ }</div><div>+</div><div>+ appendStringInfoChar(out, '"');</div><div>+ for (p = s; *p; p++)</div><div>+ {<!-- --></div><div>+ if (*p == '"' || *p == '\\')</div><div>+ appendStringInfoChar(out, '\\');</div><div>+ appendStringInfoChar(out, *p);</div><div>+ }</div><div>+ appendStringInfoChar(out, '"');</div><div>+}</div><div>+</div><div>+/*</div><div>+ * Infix walker for the output function. Returns the postfix index that</div><div>+ * "cur" decremented to so the caller can chain.</div><div>+ */</div><div>+static int</div><div>+infix_walk(StringInfo out, AnyQuery *q, int cur, bool top)</div><div>+{<!-- --></div><div>+ AnyQueryItem *it;</div><div>+</div><div>+ check_stack_depth();</div><div>+ Assert(cur >= 0);</div><div>+ it = &q->items[cur];</div><div>+</div><div>+ if (it->type == ANYQ_VAL)</div><div>+ {<!-- --></div><div>+ append_value_token(out, ANYQUERY_STRING(q, it));</div><div>+ return cur - 1;</div><div>+ }</div><div>+ else if (it->payload == ANYQ_NOT)</div><div>+ {<!-- --></div><div>+ AnyQueryItem *child = &q->items[cur - 1];</div><div>+ bool paren = (child->type == ANYQ_OPR);</div><div>+</div><div>+ appendStringInfoChar(out, '!');</div><div>+ if (paren)</div><div>+ appendStringInfoString(out, "( ");</div><div>+ cur = infix_walk(out, q, cur - 1, false);</div><div>+ if (paren)</div><div>+ appendStringInfoString(out, " )");</div><div>+ return cur;</div><div>+ }</div><div>+ else</div><div>+ {<!-- --></div><div>+ int op = it->payload;</div><div>+ StringInfoData right;</div><div>+ int next;</div><div>+</div><div>+ if (op == ANYQ_OR && !top)</div><div>+ appendStringInfoString(out, "( ");</div><div>+</div><div>+ /* right operand first into a side buffer */</div><div>+ initStringInfo(&right);</div><div>+ next = infix_walk(&right, q, cur - 1, false);</div><div>+ /* left operand into the main buffer */</div><div>+ cur = infix_walk(out, q, next, false);</div><div>+</div><div>+ appendStringInfo(out, " %c %s", op, right.data);</div><div>+ pfree(right.data);</div><div>+</div><div>+ if (op == ANYQ_OR && !top)</div><div>+ appendStringInfoString(out, " )");</div><div>+ return cur;</div><div>+ }</div><div>+}</div><div>+</div><div>+Datum</div><div>+anyquery_out(PG_FUNCTION_ARGS)</div><div>+{<!-- --></div><div>+ AnyQuery *q = PG_GETARG_ANYQUERY_P(0);</div><div>+ StringInfoData out;</div><div>+</div><div>+ if (q->size <= 0)</div><div>+ ereport(ERROR,</div><div>+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),</div><div>+ errmsg("empty anyquery")));</div><div>+</div><div>+ initStringInfo(&out);</div><div>+ (void) infix_walk(&out, q, q->size - 1, true);</div><div>+</div><div>+ PG_RETURN_CSTRING(out.data);</div><div>+}</div><div>+</div><div>+/*</div><div>+ * Debugging helper: produce a postfix string with operator codes spelled out.</div><div>+ * Mostly useful while writing tests.</div><div>+ */</div><div>+Datum</div><div>+anyquery_querytree(PG_FUNCTION_ARGS)</div><div>+{<!-- --></div><div>+ AnyQuery *q = PG_GETARG_ANYQUERY_P(0);</div><div>+ StringInfoData out;</div><div>+ int i;</div><div>+</div><div>+ initStringInfo(&out);</div><div>+ for (i = 0; i < q->size; i++)</div><div>+ {<!-- --></div><div>+ AnyQueryItem *it = &q->items[i];</div><div>+</div><div>+ if (i > 0)</div><div>+ appendStringInfoChar(&out, ' ');</div><div>+ if (it->type == ANYQ_VAL)</div><div>+ append_value_token(&out, ANYQUERY_STRING(q, it));</div><div>+ else</div><div>+ appendStringInfoChar(&out, (char) it->payload);</div><div>+ }</div><div>+ PG_RETURN_TEXT_P(cstring_to_text_with_len(out.data, out.len));</div><div>+}</div><div>+</div><div>+</div><div>+/* ------------------------------------------------------------------------</div><div>+ * Matching: anyarray @@ anyquery</div><div>+ * ------------------------------------------------------------------------</div><div>+ */</div><div>+</div><div>+typedef struct MatchContext</div><div>+{<!-- --></div><div>+ AnyArrayTypeInfo *meta;</div><div>+ Datum *sorted; /* sorted array elements (owned) */</div><div>+ int nelems;</div><div>+ /* cached parsed values for each VAL item in the query */</div><div>+ Datum *parsed;</div><div>+ bool *parsed_valid;</div><div>+} MatchContext;</div><div>+</div><div>+/*</div><div>+ * Look up val in the sorted element array via binary search.</div><div>+ */</div><div>+static bool</div><div>+contains_value(MatchContext *ctx, Datum val)</div><div>+{<!-- --></div><div>+ int lo = 0;</div><div>+ int hi = ctx->nelems;</div><div>+</div><div>+ while (lo < hi)</div><div>+ {<!-- --></div><div>+ int mid = lo + (hi - lo) / 2;</div><div>+ int c = DatumGetInt32(FunctionCall2Coll(&ctx->meta->cmp_proc,</div><div>+ ctx->meta->typcollation,</div><div>+ ctx->sorted[mid],</div><div>+ val));</div><div>+</div><div>+ if (c == 0)</div><div>+ return true;</div><div>+ if (c < 0)</div><div>+ lo = mid + 1;</div><div>+ else</div><div>+ hi = mid;</div><div>+ }</div><div>+ return false;</div><div>+}</div><div>+</div><div>+/*</div><div>+ * Evaluate one node of the postfix tree. Recursive on operators.</div><div>+ */</div><div>+static bool</div><div>+eval_item(AnyQuery *q, int idx, MatchContext *ctx, Oid input_func,</div><div>+ int input_typioparam, int32 input_typmod)</div><div>+{<!-- --></div><div>+ AnyQueryItem *it;</div><div>+</div><div>+ check_stack_depth();</div><div>+ Assert(idx >= 0);</div><div>+ it = &q->items[idx];</div><div>+</div><div>+ if (it->type == ANYQ_VAL)</div><div>+ {<!-- --></div><div>+ Datum v;</div><div>+</div><div>+ if (!ctx->parsed_valid[idx])</div><div>+ {<!-- --></div><div>+ const char *s = ANYQUERY_STRING(q, it);</div><div>+</div><div>+ v = OidInputFunctionCall(input_func, (char *) s,</div><div>+ input_typioparam, input_typmod);</div><div>+ ctx->parsed[idx] = v;</div><div>+ ctx->parsed_valid[idx] = true;</div><div>+ }</div><div>+ return contains_value(ctx, ctx->parsed[idx]);</div><div>+ }</div><div>+ else if (it->payload == ANYQ_NOT)</div><div>+ {<!-- --></div><div>+ return !eval_item(q, idx - 1, ctx, input_func,</div><div>+ input_typioparam, input_typmod);</div><div>+ }</div><div>+ else if (it->payload == ANYQ_AND)</div><div>+ {<!-- --></div><div>+ if (!eval_item(q, idx + it->left, ctx, input_func,</div><div>+ input_typioparam, input_typmod))</div><div>+ return false;</div><div>+ return eval_item(q, idx - 1, ctx, input_func,</div><div>+ input_typioparam, input_typmod);</div><div>+ }</div><div>+ else /* ANYQ_OR */</div><div>+ {<!-- --></div><div>+ if (eval_item(q, idx + it->left, ctx, input_func,</div><div>+ input_typioparam, input_typmod))</div><div>+ return true;</div><div>+ return eval_item(q, idx - 1, ctx, input_func,</div><div>+ input_typioparam, input_typmod);</div><div>+ }</div><div>+}</div><div>+</div><div>+static Datum</div><div>+do_boolop(FunctionCallInfo fcinfo, ArrayType *arr, AnyQuery *q)</div><div>+{<!-- --></div><div>+ AnyArrayTypeInfo *meta;</div><div>+ Datum *values;</div><div>+ bool *nulls;</div><div>+ int nelems;</div><div>+ Oid input_func;</div><div>+ Oid input_typioparam;</div><div>+ bool result;</div><div>+ MatchContext ctx;</div><div>+</div><div>+ ANYARRAY_CHECK_ARRAY(arr);</div><div>+</div><div>+ if (q->size <= 0)</div><div>+ ereport(ERROR,</div><div>+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),</div><div>+ errmsg("empty anyquery")));</div><div>+</div><div>+ meta = anyarray_get_meta(fcinfo, ARR_ELEMTYPE(arr), false);</div><div>+</div><div>+ if (ARR_NDIM(arr) == 0)</div><div>+ {<!-- --></div><div>+ nelems = 0;</div><div>+ values = NULL;</div><div>+ }</div><div>+ else</div><div>+ {<!-- --></div><div>+ deconstruct_array(arr, meta->element_type, meta->typlen,</div><div>+ meta->typbyval, meta->typalign,</div><div>+ &values, &nulls, &nelems);</div><div>+ pfree(nulls);</div><div>+ if (nelems > 1)</div><div>+ qsort_arg(values, nelems, sizeof(Datum),</div><div>+ anyarray_cmp_datum, meta);</div><div>+ }</div><div>+</div><div>+ getTypeInputInfo(meta->element_type, &input_func, &input_typioparam);</div><div>+</div><div>+ ctx.meta = meta;</div><div>+ ctx.sorted = values;</div><div>+ ctx.nelems = nelems;</div><div>+ ctx.parsed = (Datum *) palloc0(sizeof(Datum) * q->size);</div><div>+ ctx.parsed_valid = (bool *) palloc0(sizeof(bool) * q->size);</div><div>+</div><div>+ result = eval_item(q, q->size - 1, &ctx, input_func,</div><div>+ input_typioparam, -1);</div><div>+</div><div>+ pfree(ctx.parsed);</div><div>+ pfree(ctx.parsed_valid);</div><div>+ if (values)</div><div>+ pfree(values);</div><div>+</div><div>+ PG_RETURN_BOOL(result);</div><div>+}</div><div>+</div><div>+Datum</div><div>+anyarray_boolop(PG_FUNCTION_ARGS)</div><div>+{<!-- --></div><div>+ ArrayType *arr = PG_GETARG_ARRAYTYPE_P(0);</div><div>+ AnyQuery *q = PG_GETARG_ANYQUERY_P(1);</div><div>+ Datum result = do_boolop(fcinfo, arr, q);</div><div>+</div><div>+ PG_FREE_IF_COPY(arr, 0);</div><div>+ PG_FREE_IF_COPY(q, 1);</div><div>+ return result;</div><div>+}</div><div>+</div><div>+/*</div><div>+ * Commutator: anyquery ~~ anyarray simply swaps the arguments.</div><div>+ */</div><div>+Datum</div><div>+anyquery_boolop_rev(PG_FUNCTION_ARGS)</div><div>+{<!-- --></div><div>+ AnyQuery *q = PG_GETARG_ANYQUERY_P(0);</div><div>+ ArrayType *arr = PG_GETARG_ARRAYTYPE_P(1);</div><div>+ Datum result = do_boolop(fcinfo, arr, q);</div><div>+</div><div>+ PG_FREE_IF_COPY(q, 0);</div><div>+ PG_FREE_IF_COPY(arr, 1);</div><div>+ return result;</div><div>+}</div><div>diff --git a/contrib/anyarray/anyarray_gin.c b/contrib/anyarray/anyarray_gin.c</div><div>new file mode 100644</div><div>index 00000000000..d9d201c12d5</div><div>--- /dev/null</div><div>+++ b/contrib/anyarray/anyarray_gin.c</div><div>@@ -0,0 +1,374 @@</div><div>+/*-------------------------------------------------------------------------</div><div>+ *</div><div>+ * anyarray_gin.c</div><div>+ * GIN opclasses that add the @@ (anyarray, anyquery) boolean search</div><div>+ * operator to the existing array_ops behaviour.</div><div>+ *</div><div>+ * GIN's extractQuery support function doesn't see fn_expr, so the element</div><div>+ * type cannot be derived dynamically when the query is an anyquery (which</div><div>+ * carries only text tokens). We work around this by exposing one opclass</div><div>+ * per concrete element type; each is a thin C wrapper that dispatches on</div><div>+ * the strategy number, delegating standard operators to inline replicas of</div><div>+ * core's array_ops behaviour and parsing anyquery tokens via the type's</div><div>+ * input function for strategy 5.</div><div>+ *</div><div>+ * Copyright (c) 2026, PostgreSQL Global Development Group</div><div>+ *</div><div>+ * IDENTIFICATION</div><div>+ * contrib/anyarray/anyarray_gin.c</div><div>+ *</div><div>+ *-------------------------------------------------------------------------</div><div>+ */</div><div>+</div><div>+#include "postgres.h"</div><div>+</div><div>+#include "anyarray.h"</div><div>+</div><div>+#include "access/gin.h"</div><div>+#include "access/stratnum.h"</div><div>+#include "catalog/pg_type.h"</div><div>+#include "utils/array.h"</div><div>+#include "utils/builtins.h"</div><div>+#include "utils/lsyscache.h"</div><div>+</div><div>+PG_FUNCTION_INFO_V1(anyarray_gin_extract_query_int8);</div><div>+PG_FUNCTION_INFO_V1(anyarray_gin_extract_query_uuid);</div><div>+PG_FUNCTION_INFO_V1(anyarray_gin_extract_query_text);</div><div>+PG_FUNCTION_INFO_V1(anyarray_gin_consistent_int8);</div><div>+PG_FUNCTION_INFO_V1(anyarray_gin_consistent_uuid);</div><div>+PG_FUNCTION_INFO_V1(anyarray_gin_consistent_text);</div><div>+</div><div>+</div><div>+/* ------------------------------------------------------------------------</div><div>+ * Common: extractQuery dispatcher</div><div>+ * ------------------------------------------------------------------------</div><div>+ */</div><div>+</div><div>+/*</div><div>+ * Extract array elements as GIN keys (used for strategies 1-4).</div><div>+ *</div><div>+ * Mirrors core's ginqueryarrayextract logic, but inlined so we don't depend</div><div>+ * on it being callable. Returns a freshly-palloc'd Datum vector.</div><div>+ */</div><div>+static Datum *</div><div>+extract_array_keys(ArrayType *arr, AnyArrayTypeInfo *meta,</div><div>+ int32 *nentries, bool **nulls_out)</div><div>+{<!-- --></div><div>+ Datum *values;</div><div>+ bool *nulls;</div><div>+ int n;</div><div>+ int i;</div><div>+ int j = 0;</div><div>+</div><div>+ if (ARR_NDIM(arr) == 0)</div><div>+ {<!-- --></div><div>+ *nentries = 0;</div><div>+ *nulls_out = NULL;</div><div>+ return NULL;</div><div>+ }</div><div>+</div><div>+ deconstruct_array(arr, meta->element_type, meta->typlen,</div><div>+ meta->typbyval, meta->typalign,</div><div>+ &values, &nulls, &n);</div><div>+</div><div>+ /* compact out NULL entries (we treat NULLs as never-present) */</div><div>+ for (i = 0; i < n; i++)</div><div>+ {<!-- --></div><div>+ if (!nulls[i])</div><div>+ {<!-- --></div><div>+ if (j != i)</div><div>+ {<!-- --></div><div>+ values[j] = values[i];</div><div>+ nulls[j] = false;</div><div>+ }</div><div>+ j++;</div><div>+ }</div><div>+ }</div><div>+</div><div>+ *nentries = j;</div><div>+ *nulls_out = nulls;</div><div>+ return values;</div><div>+}</div><div>+</div><div>+/*</div><div>+ * Walk the anyquery: every VAL is a key that must be looked up in the GIN</div><div>+ * index. Operators (& | !) do not contribute keys. Returns true if at</div><div>+ * least one VAL must be present for the query to possibly match (i.e. the</div><div>+ * query is not e.g. "!foo").</div><div>+ */</div><div>+static bool</div><div>+anyquery_has_required(AnyQuery *q, int idx)</div><div>+{<!-- --></div><div>+ AnyQueryItem *it;</div><div>+</div><div>+ if (idx < 0)</div><div>+ return false;</div><div>+ it = &q->items[idx];</div><div>+</div><div>+ if (it->type == ANYQ_VAL)</div><div>+ return true;</div><div>+ if (it->payload == ANYQ_NOT)</div><div>+ return false; /* assume non-required under NOT */</div><div>+ if (it->payload == ANYQ_AND)</div><div>+ return anyquery_has_required(q, idx + it->left) ||</div><div>+ anyquery_has_required(q, idx - 1);</div><div>+ /* OR: both sides must contain required values */</div><div>+ return anyquery_has_required(q, idx + it->left) &&</div><div>+ anyquery_has_required(q, idx - 1);</div><div>+}</div><div>+</div><div>+static Datum</div><div>+do_gin_extract_query(FunctionCallInfo fcinfo, Oid elem_type)</div><div>+{<!-- --></div><div>+ Datum queryDatum = PG_GETARG_DATUM(0);</div><div>+ int32 *nentries = (int32 *) PG_GETARG_POINTER(1);</div><div>+ StrategyNumber strat = PG_GETARG_UINT16(2);</div><div>+</div><div>+ /* PG_GETARG_POINTER(3): partial_matches -- not used */</div><div>+ /* PG_GETARG_POINTER(4): extra_data -- not used */</div><div>+ bool **nullFlags = (bool **) PG_GETARG_POINTER(5);</div><div>+ int32 *searchMode = (int32 *) PG_GETARG_POINTER(6);</div><div>+ Datum *keys = NULL;</div><div>+</div><div>+ *nentries = 0;</div><div>+ *nullFlags = NULL;</div><div>+ *searchMode = GIN_SEARCH_MODE_DEFAULT;</div><div>+</div><div>+ if (strat == ANYARRAY_GIN_BOOLEAN_STRATEGY)</div><div>+ {<!-- --></div><div>+ AnyQuery *q = DatumGetAnyQueryP(queryDatum);</div><div>+ Oid input_func;</div><div>+ Oid input_typioparam;</div><div>+ int i;</div><div>+ int k = 0;</div><div>+</div><div>+ if (q->size <= 0)</div><div>+ PG_RETURN_POINTER(NULL);</div><div>+</div><div>+ /*</div><div>+ * If the query has no required VAL (e.g. just "!foo"), we must scan</div><div>+ * the whole index because rows containing NONE of the queried values</div><div>+ * are valid matches.</div><div>+ */</div><div>+ if (!anyquery_has_required(q, q->size - 1))</div><div>+ *searchMode = GIN_SEARCH_MODE_ALL;</div><div>+</div><div>+ getTypeInputInfo(elem_type, &input_func, &input_typioparam);</div><div>+ keys = (Datum *) palloc(sizeof(Datum) * q->size);</div><div>+</div><div>+ for (i = 0; i < q->size; i++)</div><div>+ {<!-- --></div><div>+ AnyQueryItem *it = &q->items[i];</div><div>+</div><div>+ if (it->type != ANYQ_VAL)</div><div>+ continue;</div><div>+ keys[k++] = OidInputFunctionCall(input_func,</div><div>+ (char *) ANYQUERY_STRING(q, it),</div><div>+ input_typioparam, -1);</div><div>+ }</div><div>+ *nentries = k;</div><div>+ PG_RETURN_POINTER(keys);</div><div>+ }</div><div>+ else</div><div>+ {<!-- --></div><div>+ ArrayType *arr = DatumGetArrayTypeP(queryDatum);</div><div>+ AnyArrayTypeInfo *meta = anyarray_get_meta(fcinfo, elem_type, false);</div><div>+ bool *nulls;</div><div>+</div><div>+ ANYARRAY_CHECK_ARRAY(arr);</div><div>+ keys = extract_array_keys(arr, meta, nentries, &nulls);</div><div>+</div><div>+ switch (strat)</div><div>+ {<!-- --></div><div>+ case ANYARRAY_GIN_OVERLAP_STRATEGY:</div><div>+ *searchMode = GIN_SEARCH_MODE_DEFAULT;</div><div>+ break;</div><div>+ case ANYARRAY_GIN_CONTAINED_STRATEGY:</div><div>+ *searchMode = GIN_SEARCH_MODE_INCLUDE_EMPTY;</div><div>+ break;</div><div>+ case ANYARRAY_GIN_EQUAL_STRATEGY:</div><div>+ *searchMode = (*nentries > 0)</div><div>+ ? GIN_SEARCH_MODE_DEFAULT</div><div>+ : GIN_SEARCH_MODE_INCLUDE_EMPTY;</div><div>+ break;</div><div>+ case ANYARRAY_GIN_CONTAINS_STRATEGY:</div><div>+ *searchMode = (*nentries > 0)</div><div>+ ? GIN_SEARCH_MODE_DEFAULT</div><div>+ : GIN_SEARCH_MODE_ALL;</div><div>+ break;</div><div>+ default:</div><div>+ elog(ERROR, "anyarray_gin: unknown strategy number: %d", strat);</div><div>+ }</div><div>+</div><div>+ (void) nulls; /* swallowed by extract_array_keys */</div><div>+ PG_RETURN_POINTER(keys);</div><div>+ }</div><div>+}</div><div>+</div><div>+</div><div>+/* ------------------------------------------------------------------------</div><div>+ * Common: consistent dispatcher</div><div>+ * ------------------------------------------------------------------------</div><div>+ */</div><div>+</div><div>+/*</div><div>+ * Evaluate an anyquery against an array of "key present" flags. The i-th</div><div>+ * VAL in postfix order corresponds to check[i] (we mapped them in</div><div>+ * extractQuery, so the j-th VAL we emitted is check[j]). We rebuild that</div><div>+ * VAL-only mapping here as we walk the postfix tree.</div><div>+ */</div><div>+typedef struct AnyQueryCheck</div><div>+{<!-- --></div><div>+ const bool *check; /* GIN's present-key flags */</div><div>+ int next; /* next index in check[] */</div><div>+} AnyQueryCheck;</div><div>+</div><div>+static bool</div><div>+eval_with_check(AnyQuery *q, int idx, bool *vals)</div><div>+{<!-- --></div><div>+ AnyQueryItem *it = &q->items[idx];</div><div>+</div><div>+ if (it->type == ANYQ_VAL)</div><div>+ return vals[idx];</div><div>+ if (it->payload == ANYQ_NOT)</div><div>+ return !eval_with_check(q, idx - 1, vals);</div><div>+ if (it->payload == ANYQ_AND)</div><div>+ return eval_with_check(q, idx + it->left, vals) &&</div><div>+ eval_with_check(q, idx - 1, vals);</div><div>+ /* OR */</div><div>+ return eval_with_check(q, idx + it->left, vals) ||</div><div>+ eval_with_check(q, idx - 1, vals);</div><div>+}</div><div>+</div><div>+static Datum</div><div>+do_gin_consistent(FunctionCallInfo fcinfo, Oid elem_type)</div><div>+{<!-- --></div><div>+ bool *check = (bool *) PG_GETARG_POINTER(0);</div><div>+ StrategyNumber strat = PG_GETARG_UINT16(1);</div><div>+ Datum queryDatum = PG_GETARG_DATUM(2);</div><div>+ int32 nkeys = PG_GETARG_INT32(3);</div><div>+</div><div>+ /* PG_GETARG_POINTER(4): extra_data -- not used */</div><div>+ bool *recheck = (bool *) PG_GETARG_POINTER(5);</div><div>+ bool result = false;</div><div>+ int i;</div><div>+</div><div>+ (void) elem_type;</div><div>+</div><div>+ if (strat == ANYARRAY_GIN_BOOLEAN_STRATEGY)</div><div>+ {<!-- --></div><div>+ AnyQuery *q = DatumGetAnyQueryP(queryDatum);</div><div>+ bool *vals;</div><div>+ int k = 0;</div><div>+</div><div>+ *recheck = false;</div><div>+</div><div>+ if (q->size <= 0)</div><div>+ PG_RETURN_BOOL(false);</div><div>+</div><div>+ /* Map each VAL postfix slot to its position in check[]. */</div><div>+ vals = (bool *) palloc(sizeof(bool) * q->size);</div><div>+ for (i = 0; i < q->size; i++)</div><div>+ {<!-- --></div><div>+ if (q->items[i].type == ANYQ_VAL)</div><div>+ vals[i] = check[k++];</div><div>+ }</div><div>+ result = eval_with_check(q, q->size - 1, vals);</div><div>+ pfree(vals);</div><div>+ PG_RETURN_BOOL(result);</div><div>+ }</div><div>+</div><div>+ switch (strat)</div><div>+ {<!-- --></div><div>+ case ANYARRAY_GIN_OVERLAP_STRATEGY:</div><div>+ *recheck = false;</div><div>+</div><div>+ /*</div><div>+ * GIN guarantees at least one true entry on entry; safe to say</div><div>+ * yes</div><div>+ */</div><div>+ for (i = 0; i < nkeys; i++)</div><div>+ {<!-- --></div><div>+ if (check[i])</div><div>+ {<!-- --></div><div>+ result = true;</div><div>+ break;</div><div>+ }</div><div>+ }</div><div>+ break;</div><div>+ case ANYARRAY_GIN_CONTAINED_STRATEGY:</div><div>+ *recheck = true;</div><div>+ result = true; /* must always recheck */</div><div>+ break;</div><div>+ case ANYARRAY_GIN_EQUAL_STRATEGY:</div><div>+ *recheck = true;</div><div>+ result = true;</div><div>+ for (i = 0; i < nkeys; i++)</div><div>+ {<!-- --></div><div>+ if (!check[i])</div><div>+ {<!-- --></div><div>+ result = false;</div><div>+ break;</div><div>+ }</div><div>+ }</div><div>+ break;</div><div>+ case ANYARRAY_GIN_CONTAINS_STRATEGY:</div><div>+ *recheck = false;</div><div>+ result = true;</div><div>+ for (i = 0; i < nkeys; i++)</div><div>+ {<!-- --></div><div>+ if (!check[i])</div><div>+ {<!-- --></div><div>+ result = false;</div><div>+ break;</div><div>+ }</div><div>+ }</div><div>+ break;</div><div>+ default:</div><div>+ elog(ERROR, "anyarray_gin: unknown strategy number: %d", strat);</div><div>+ }</div><div>+</div><div>+ PG_RETURN_BOOL(result);</div><div>+}</div><div>+</div><div>+</div><div>+/* ------------------------------------------------------------------------</div><div>+ * Per-type wrappers</div><div>+ * ------------------------------------------------------------------------</div><div>+ */</div><div>+</div><div>+Datum</div><div>+anyarray_gin_extract_query_int8(PG_FUNCTION_ARGS)</div><div>+{<!-- --></div><div>+ return do_gin_extract_query(fcinfo, INT8OID);</div><div>+}</div><div>+</div><div>+Datum</div><div>+anyarray_gin_extract_query_uuid(PG_FUNCTION_ARGS)</div><div>+{<!-- --></div><div>+ return do_gin_extract_query(fcinfo, UUIDOID);</div><div>+}</div><div>+</div><div>+Datum</div><div>+anyarray_gin_extract_query_text(PG_FUNCTION_ARGS)</div><div>+{<!-- --></div><div>+ return do_gin_extract_query(fcinfo, TEXTOID);</div><div>+}</div><div>+</div><div>+Datum</div><div>+anyarray_gin_consistent_int8(PG_FUNCTION_ARGS)</div><div>+{<!-- --></div><div>+ return do_gin_consistent(fcinfo, INT8OID);</div><div>+}</div><div>+</div><div>+Datum</div><div>+anyarray_gin_consistent_uuid(PG_FUNCTION_ARGS)</div><div>+{<!-- --></div><div>+ return do_gin_consistent(fcinfo, UUIDOID);</div><div>+}</div><div>+</div><div>+Datum</div><div>+anyarray_gin_consistent_text(PG_FUNCTION_ARGS)</div><div>+{<!-- --></div><div>+ return do_gin_consistent(fcinfo, TEXTOID);</div><div>+}</div><div>diff --git a/contrib/anyarray/anyarray_gist.c b/contrib/anyarray/anyarray_gist.c</div><div>new file mode 100644</div><div>index 00000000000..89570c63ded</div><div>--- /dev/null</div><div>+++ b/contrib/anyarray/anyarray_gist.c</div><div>@@ -0,0 +1,742 @@</div><div>+/*-------------------------------------------------------------------------</div><div>+ *</div><div>+ * anyarray_gist.c</div><div>+ * Signature-based GiST opclass for anyarray.</div><div>+ *</div><div>+ * Each indexed array is summarised as a fixed-size bit vector; each element</div><div>+ * contributes a single bit chosen by its hash modulo the signature length.</div><div>+ * Internal node keys are bitwise unions of their children, with an</div><div>+ * ALLISTRUE short-circuit when every bit would be set.</div><div>+ *</div><div>+ * The opclass supports the array operators &&, @>, <@, = and the anyarray</div><div>+ * extension's @@ operator. Because signatures are lossy, all matches are</div><div>+ * rechecked by GiST.</div><div>+ *</div><div>+ * Copyright (c) 2026, PostgreSQL Global Development Group</div><div>+ *</div><div>+ * IDENTIFICATION</div><div>+ * contrib/anyarray/anyarray_gist.c</div><div>+ *</div><div>+ *-------------------------------------------------------------------------</div><div>+ */</div><div>+</div><div>+#include "postgres.h"</div><div>+</div><div>+#include "anyarray.h"</div><div>+</div><div>+#include "access/gist.h"</div><div>+#include "access/reloptions.h"</div><div>+#include "access/stratnum.h"</div><div>+#include "catalog/pg_index.h"</div><div>+#include "catalog/pg_type.h"</div><div>+#include "miscadmin.h"</div><div>+#include "port/pg_bitutils.h"</div><div>+#include "utils/array.h"</div><div>+#include "utils/builtins.h"</div><div>+#include "utils/lsyscache.h"</div><div>+#include "utils/rel.h"</div><div>+</div><div>+/* Strategy numbers; share R-tree conventions where applicable. */</div><div>+#define ANYARRAY_OVERLAP_STRATEGY 3</div><div>+#define ANYARRAY_CONTAINS_STRATEGY 7</div><div>+#define ANYARRAY_CONTAINED_STRATEGY 8</div><div>+#define ANYARRAY_EQUAL_STRATEGY 18</div><div>+#define ANYARRAY_BOOLEAN_STRATEGY 20</div><div>+</div><div>+PG_FUNCTION_INFO_V1(anyarray_gist_key_in);</div><div>+PG_FUNCTION_INFO_V1(anyarray_gist_key_out);</div><div>+PG_FUNCTION_INFO_V1(anyarray_gist_consistent);</div><div>+PG_FUNCTION_INFO_V1(anyarray_gist_compress);</div><div>+PG_FUNCTION_INFO_V1(anyarray_gist_decompress);</div><div>+PG_FUNCTION_INFO_V1(anyarray_gist_union);</div><div>+PG_FUNCTION_INFO_V1(anyarray_gist_same);</div><div>+PG_FUNCTION_INFO_V1(anyarray_gist_penalty);</div><div>+PG_FUNCTION_INFO_V1(anyarray_gist_picksplit);</div><div>+PG_FUNCTION_INFO_V1(anyarray_gist_options);</div><div>+</div><div>+</div><div>+/* ------------------------------------------------------------------------</div><div>+ * Storage type stubs</div><div>+ *</div><div>+ * The signature key is only ever constructed internally by the index AM,</div><div>+ * so its text input/output functions reject all calls (mirroring</div><div>+ * intbig_gkey in contrib/intarray).</div><div>+ * ------------------------------------------------------------------------</div><div>+ */</div><div>+</div><div>+Datum</div><div>+anyarray_gist_key_in(PG_FUNCTION_ARGS)</div><div>+{<!-- --></div><div>+ ereport(ERROR,</div><div>+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),</div><div>+ errmsg("cannot accept a value of type %s",</div><div>+ "anyarray_gist_key")));</div><div>+ PG_RETURN_VOID();</div><div>+}</div><div>+</div><div>+Datum</div><div>+anyarray_gist_key_out(PG_FUNCTION_ARGS)</div><div>+{<!-- --></div><div>+ ereport(ERROR,</div><div>+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),</div><div>+ errmsg("cannot display a value of type %s",</div><div>+ "anyarray_gist_key")));</div><div>+ PG_RETURN_VOID();</div><div>+}</div><div>+</div><div>+</div><div>+/* ------------------------------------------------------------------------</div><div>+ * Signature helpers</div><div>+ * ------------------------------------------------------------------------</div><div>+ */</div><div>+</div><div>+static AnyArrayGistKey *</div><div>+alloc_key(bool allistrue, int siglen, const unsigned char *src)</div><div>+{<!-- --></div><div>+ int32 flag = allistrue ? ANYARRAY_ALLISTRUE : 0;</div><div>+ Size size = ANYARRAY_GKEY_SIZE(flag, siglen);</div><div>+ AnyArrayGistKey *k = (AnyArrayGistKey *) palloc(size);</div><div>+</div><div>+ SET_VARSIZE(k, size);</div><div>+ k->flag = flag;</div><div>+ if (!allistrue)</div><div>+ {<!-- --></div><div>+ if (src)</div><div>+ memcpy(k->data, src, siglen);</div><div>+ else</div><div>+ memset(k->data, 0, siglen);</div><div>+ }</div><div>+ return k;</div><div>+}</div><div>+</div><div>+/*</div><div>+ * Set the bit corresponding to "val" (a btree hash value) in "sig", a bit</div><div>+ * vector of length "siglen" bytes.</div><div>+ */</div><div>+static inline void</div><div>+set_bit(unsigned char *sig, uint32 hashval, int siglen)</div><div>+{<!-- --></div><div>+ uint32 bit = hashval % ((uint32) siglen * BITS_PER_BYTE);</div><div>+</div><div>+ sig[bit / BITS_PER_BYTE] |= (1U << (bit % BITS_PER_BYTE));</div><div>+}</div><div>+</div><div>+static inline bool</div><div>+get_bit(const unsigned char *sig, uint32 hashval, int siglen)</div><div>+{<!-- --></div><div>+ uint32 bit = hashval % ((uint32) siglen * BITS_PER_BYTE);</div><div>+</div><div>+ return (sig[bit / BITS_PER_BYTE] >> (bit % BITS_PER_BYTE)) & 1;</div><div>+}</div><div>+</div><div>+/*</div><div>+ * Hash a single Datum of the given element type, returning an unsigned 32-bit</div><div>+ * value suitable for indexing the bit vector.</div><div>+ */</div><div>+static uint32</div><div>+hash_elem(AnyArrayTypeInfo *meta, Datum value)</div><div>+{<!-- --></div><div>+ Datum h = FunctionCall1Coll(&meta->hash_proc, meta->typcollation,</div><div>+ value);</div><div>+</div><div>+ return (uint32) DatumGetInt32(h);</div><div>+}</div><div>+</div><div>+/*</div><div>+ * Hash each element of "arr" into "sig". The metadata must have a valid</div><div>+ * hash_proc (use anyarray_get_meta(..., true)).</div><div>+ */</div><div>+static void</div><div>+hash_array_into(unsigned char *sig, int siglen,</div><div>+ ArrayType *arr, AnyArrayTypeInfo *meta)</div><div>+{<!-- --></div><div>+ Datum *values;</div><div>+ bool *nulls;</div><div>+ int nelems;</div><div>+ int i;</div><div>+</div><div>+ if (ARR_NDIM(arr) == 0)</div><div>+ return;</div><div>+</div><div>+ deconstruct_array(arr, meta->element_type, meta->typlen,</div><div>+ meta->typbyval, meta->typalign,</div><div>+ &values, &nulls, &nelems);</div><div>+</div><div>+ for (i = 0; i < nelems; i++)</div><div>+ {<!-- --></div><div>+ if (!nulls[i])</div><div>+ set_bit(sig, hash_elem(meta, values[i]), siglen);</div><div>+ }</div><div>+</div><div>+ pfree(values);</div><div>+ pfree(nulls);</div><div>+}</div><div>+</div><div>+/* Count "1" bits in a buffer of "n" bytes. */</div><div>+static int</div><div>+popcount_bytes(const unsigned char *p, int n)</div><div>+{<!-- --></div><div>+ return pg_popcount((const char *) p, n);</div><div>+}</div><div>+</div><div>+</div><div>+/* ------------------------------------------------------------------------</div><div>+ * compress / decompress</div><div>+ * ------------------------------------------------------------------------</div><div>+ */</div><div>+</div><div>+Datum</div><div>+anyarray_gist_compress(PG_FUNCTION_ARGS)</div><div>+{<!-- --></div><div>+ GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0);</div><div>+ GISTENTRY *retval = entry;</div><div>+ int siglen = ANYARRAY_GET_SIGLEN();</div><div>+</div><div>+ if (entry->leafkey)</div><div>+ {<!-- --></div><div>+ ArrayType *arr = DatumGetArrayTypeP(entry->key);</div><div>+ AnyArrayGistKey *key;</div><div>+ AnyArrayTypeInfo *meta;</div><div>+</div><div>+ ANYARRAY_CHECK_ARRAY(arr);</div><div>+</div><div>+ meta = anyarray_get_meta(fcinfo, ARR_ELEMTYPE(arr), true);</div><div>+</div><div>+ key = alloc_key(false, siglen, NULL);</div><div>+ hash_array_into(ANYARRAY_GKEY_SIGN(key), siglen, arr, meta);</div><div>+</div><div>+ retval = (GISTENTRY *) palloc(sizeof(GISTENTRY));</div><div>+ gistentryinit(*retval, PointerGetDatum(key), entry->rel, entry->page,</div><div>+ entry->offset, false);</div><div>+ }</div><div>+ else if (!ANYARRAY_GKEY_ISALLTRUE(DatumGetPointer(entry->key)))</div><div>+ {<!-- --></div><div>+ AnyArrayGistKey *k = (AnyArrayGistKey *) DatumGetPointer(entry->key);</div><div>+</div><div>+ /*</div><div>+ * If every bit happens to be set, switch to ALLISTRUE storage so</div><div>+ * subsequent operations don't have to compare a full bit vector.</div><div>+ */</div><div>+ if (popcount_bytes(ANYARRAY_GKEY_SIGN(k), siglen) ==</div><div>+ siglen * BITS_PER_BYTE)</div><div>+ {<!-- --></div><div>+ AnyArrayGistKey *r = alloc_key(true, siglen, NULL);</div><div>+</div><div>+ retval = (GISTENTRY *) palloc(sizeof(GISTENTRY));</div><div>+ gistentryinit(*retval, PointerGetDatum(r), entry->rel, entry->page,</div><div>+ entry->offset, false);</div><div>+ }</div><div>+ }</div><div>+</div><div>+ PG_RETURN_POINTER(retval);</div><div>+}</div><div>+</div><div>+Datum</div><div>+anyarray_gist_decompress(PG_FUNCTION_ARGS)</div><div>+{<!-- --></div><div>+ PG_RETURN_POINTER(PG_GETARG_POINTER(0));</div><div>+}</div><div>+</div><div>+</div><div>+/* ------------------------------------------------------------------------</div><div>+ * union / same / penalty / picksplit / options</div><div>+ * ------------------------------------------------------------------------</div><div>+ */</div><div>+</div><div>+Datum</div><div>+anyarray_gist_union(PG_FUNCTION_ARGS)</div><div>+{<!-- --></div><div>+ GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0);</div><div>+ int *sizep = (int *) PG_GETARG_POINTER(1);</div><div>+ int siglen = ANYARRAY_GET_SIGLEN();</div><div>+ AnyArrayGistKey *out;</div><div>+ unsigned char *sig;</div><div>+ int n = entryvec->n;</div><div>+ int i;</div><div>+ bool all = false;</div><div>+</div><div>+ out = alloc_key(false, siglen, NULL);</div><div>+ sig = ANYARRAY_GKEY_SIGN(out);</div><div>+</div><div>+ for (i = 0; i < n; i++)</div><div>+ {<!-- --></div><div>+ AnyArrayGistKey *k = (AnyArrayGistKey *)</div><div>+ DatumGetPointer(entryvec->vector[i].key);</div><div>+</div><div>+ if (ANYARRAY_GKEY_ISALLTRUE(k))</div><div>+ {<!-- --></div><div>+ all = true;</div><div>+ break;</div><div>+ }</div><div>+ for (int j = 0; j < siglen; j++)</div><div>+ sig[j] |= ANYARRAY_GKEY_SIGN(k)[j];</div><div>+ }</div><div>+</div><div>+ if (all || popcount_bytes(sig, siglen) == siglen * BITS_PER_BYTE)</div><div>+ {<!-- --></div><div>+ pfree(out);</div><div>+ out = alloc_key(true, siglen, NULL);</div><div>+ }</div><div>+</div><div>+ *sizep = VARSIZE(out);</div><div>+ PG_RETURN_POINTER(out);</div><div>+}</div><div>+</div><div>+Datum</div><div>+anyarray_gist_same(PG_FUNCTION_ARGS)</div><div>+{<!-- --></div><div>+ AnyArrayGistKey *a = (AnyArrayGistKey *) PG_GETARG_POINTER(0);</div><div>+ AnyArrayGistKey *b = (AnyArrayGistKey *) PG_GETARG_POINTER(1);</div><div>+ bool *result = (bool *) PG_GETARG_POINTER(2);</div><div>+ int siglen = ANYARRAY_GET_SIGLEN();</div><div>+</div><div>+ if (ANYARRAY_GKEY_ISALLTRUE(a) || ANYARRAY_GKEY_ISALLTRUE(b))</div><div>+ *result = ANYARRAY_GKEY_ISALLTRUE(a) && ANYARRAY_GKEY_ISALLTRUE(b);</div><div>+ else</div><div>+ *result = (memcmp(ANYARRAY_GKEY_SIGN(a), ANYARRAY_GKEY_SIGN(b),</div><div>+ siglen) == 0);</div><div>+</div><div>+ PG_RETURN_POINTER(result);</div><div>+}</div><div>+</div><div>+/*</div><div>+ * Hamming weight of (orig OR new) - Hamming weight of orig: the number of</div><div>+ * new bits a child would introduce. ALLISTRUE entries have the maximum</div><div>+ * possible weight (siglen*8) so the answer is 0.</div><div>+ */</div><div>+Datum</div><div>+anyarray_gist_penalty(PG_FUNCTION_ARGS)</div><div>+{<!-- --></div><div>+ GISTENTRY *origentry = (GISTENTRY *) PG_GETARG_POINTER(0);</div><div>+ GISTENTRY *newentry = (GISTENTRY *) PG_GETARG_POINTER(1);</div><div>+ float *penalty = (float *) PG_GETARG_POINTER(2);</div><div>+ int siglen = ANYARRAY_GET_SIGLEN();</div><div>+ AnyArrayGistKey *orig = (AnyArrayGistKey *) DatumGetPointer(origentry->key);</div><div>+ AnyArrayGistKey *new_ = (AnyArrayGistKey *) DatumGetPointer(newentry->key);</div><div>+</div><div>+ if (ANYARRAY_GKEY_ISALLTRUE(orig))</div><div>+ {<!-- --></div><div>+ *penalty = 0.0;</div><div>+ }</div><div>+ else if (ANYARRAY_GKEY_ISALLTRUE(new_))</div><div>+ {<!-- --></div><div>+ int orig_bits = popcount_bytes(ANYARRAY_GKEY_SIGN(orig), siglen);</div><div>+</div><div>+ *penalty = (float) (siglen * BITS_PER_BYTE - orig_bits);</div><div>+ }</div><div>+ else</div><div>+ {<!-- --></div><div>+ unsigned char *o = ANYARRAY_GKEY_SIGN(orig);</div><div>+ unsigned char *n = ANYARRAY_GKEY_SIGN(new_);</div><div>+ int added = 0;</div><div>+</div><div>+ for (int i = 0; i < siglen; i++)</div><div>+ {<!-- --></div><div>+ unsigned char extra = n[i] & ~o[i];</div><div>+</div><div>+ added += pg_number_of_ones[extra];</div><div>+ }</div><div>+ *penalty = (float) added;</div><div>+ }</div><div>+</div><div>+ PG_RETURN_POINTER(penalty);</div><div>+}</div><div>+</div><div>+/*</div><div>+ * Standard signature picksplit: sort entries by Hamming weight and split</div><div>+ * down the middle. This isn't optimal but is correct and balanced; the</div><div>+ * fancier Guttman-style splits used by intbig can be added later.</div><div>+ */</div><div>+typedef struct PickSplitEntry</div><div>+{<!-- --></div><div>+ OffsetNumber offset;</div><div>+ int weight;</div><div>+} PickSplitEntry;</div><div>+</div><div>+static int</div><div>+psplit_cmp(const void *a, const void *b)</div><div>+{<!-- --></div><div>+ int wa = ((const PickSplitEntry *) a)->weight;</div><div>+ int wb = ((const PickSplitEntry *) b)->weight;</div><div>+</div><div>+ return (wa > wb) - (wa < wb);</div><div>+}</div><div>+</div><div>+static void</div><div>+or_into(unsigned char *dst, const unsigned char *src, int siglen)</div><div>+{<!-- --></div><div>+ for (int i = 0; i < siglen; i++)</div><div>+ dst[i] |= src[i];</div><div>+}</div><div>+</div><div>+Datum</div><div>+anyarray_gist_picksplit(PG_FUNCTION_ARGS)</div><div>+{<!-- --></div><div>+ GistEntryVector *entryvec = (GistEntryVector *) PG_GETARG_POINTER(0);</div><div>+ GIST_SPLITVEC *v = (GIST_SPLITVEC *) PG_GETARG_POINTER(1);</div><div>+ int siglen = ANYARRAY_GET_SIGLEN();</div><div>+ int n = entryvec->n - 1; /* entries start at index 1 */</div><div>+ PickSplitEntry *items;</div><div>+ AnyArrayGistKey *left,</div><div>+ *right;</div><div>+ int i;</div><div>+ int split;</div><div>+</div><div>+ items = (PickSplitEntry *) palloc(n * sizeof(PickSplitEntry));</div><div>+ for (i = 0; i < n; i++)</div><div>+ {<!-- --></div><div>+ AnyArrayGistKey *k = (AnyArrayGistKey *)</div><div>+ DatumGetPointer(entryvec->vector[i + 1].key);</div><div>+</div><div>+ items[i].offset = (OffsetNumber) (i + 1);</div><div>+ items[i].weight = ANYARRAY_GKEY_ISALLTRUE(k)</div><div>+ ? siglen * BITS_PER_BYTE</div><div>+ : popcount_bytes(ANYARRAY_GKEY_SIGN(k), siglen);</div><div>+ }</div><div>+ qsort(items, n, sizeof(PickSplitEntry), psplit_cmp);</div><div>+</div><div>+ v->spl_left = (OffsetNumber *) palloc(n * sizeof(OffsetNumber));</div><div>+ v->spl_right = (OffsetNumber *) palloc(n * sizeof(OffsetNumber));</div><div>+ v->spl_nleft = 0;</div><div>+ v->spl_nright = 0;</div><div>+ left = alloc_key(false, siglen, NULL);</div><div>+ right = alloc_key(false, siglen, NULL);</div><div>+</div><div>+ split = n / 2;</div><div>+ if (split == 0)</div><div>+ split = 1;</div><div>+ if (split == n)</div><div>+ split = n - 1;</div><div>+</div><div>+ for (i = 0; i < split; i++)</div><div>+ {<!-- --></div><div>+ AnyArrayGistKey *k = (AnyArrayGistKey *)</div><div>+ DatumGetPointer(entryvec->vector[items[i].offset].key);</div><div>+</div><div>+ v->spl_left[v->spl_nleft++] = items[i].offset;</div><div>+ if (ANYARRAY_GKEY_ISALLTRUE(k))</div><div>+ {<!-- --></div><div>+ pfree(left);</div><div>+ left = alloc_key(true, siglen, NULL);</div><div>+ }</div><div>+ else if (!ANYARRAY_GKEY_ISALLTRUE(left))</div><div>+ or_into(ANYARRAY_GKEY_SIGN(left), ANYARRAY_GKEY_SIGN(k), siglen);</div><div>+ }</div><div>+ for (; i < n; i++)</div><div>+ {<!-- --></div><div>+ AnyArrayGistKey *k = (AnyArrayGistKey *)</div><div>+ DatumGetPointer(entryvec->vector[items[i].offset].key);</div><div>+</div><div>+ v->spl_right[v->spl_nright++] = items[i].offset;</div><div>+ if (ANYARRAY_GKEY_ISALLTRUE(k))</div><div>+ {<!-- --></div><div>+ pfree(right);</div><div>+ right = alloc_key(true, siglen, NULL);</div><div>+ }</div><div>+ else if (!ANYARRAY_GKEY_ISALLTRUE(right))</div><div>+ or_into(ANYARRAY_GKEY_SIGN(right), ANYARRAY_GKEY_SIGN(k), siglen);</div><div>+ }</div><div>+</div><div>+ v->spl_ldatum = PointerGetDatum(left);</div><div>+ v->spl_rdatum = PointerGetDatum(right);</div><div>+</div><div>+ pfree(items);</div><div>+ PG_RETURN_POINTER(v);</div><div>+}</div><div>+</div><div>+/* opclass options: just the signature length, in bytes */</div><div>+static void</div><div>+fill_siglen_default(int *siglen)</div><div>+{<!-- --></div><div>+ *siglen = ANYARRAY_SIGLEN_DEFAULT;</div><div>+}</div><div>+</div><div>+Datum</div><div>+anyarray_gist_options(PG_FUNCTION_ARGS)</div><div>+{<!-- --></div><div>+ local_relopts *relopts = (local_relopts *) PG_GETARG_POINTER(0);</div><div>+</div><div>+ init_local_reloptions(relopts, sizeof(AnyArrayGistOptions));</div><div>+ add_local_int_reloption(relopts, "siglen",</div><div>+ "signature length in bytes",</div><div>+ ANYARRAY_SIGLEN_DEFAULT, 1, ANYARRAY_SIGLEN_MAX,</div><div>+ offsetof(AnyArrayGistOptions, siglen));</div><div>+</div><div>+ (void) fill_siglen_default;</div><div>+ PG_RETURN_VOID();</div><div>+}</div><div>+</div><div>+</div><div>+/* ------------------------------------------------------------------------</div><div>+ * consistent</div><div>+ * ------------------------------------------------------------------------</div><div>+ */</div><div>+</div><div>+/*</div><div>+ * For overlap: any single matching bit means the key MIGHT contain something</div><div>+ * the query references. Returns true if at least one element of "query"</div><div>+ * hashes to a set bit in "sig"; ALLISTRUE keys trivially pass.</div><div>+ */</div><div>+static bool</div><div>+sig_overlap_array(const unsigned char *sig, int siglen,</div><div>+ ArrayType *query, AnyArrayTypeInfo *meta)</div><div>+{<!-- --></div><div>+ Datum *values;</div><div>+ bool *nulls;</div><div>+ int nelems;</div><div>+ int i;</div><div>+ bool found = false;</div><div>+</div><div>+ if (ARR_NDIM(query) == 0)</div><div>+ return false;</div><div>+</div><div>+ deconstruct_array(query, meta->element_type, meta->typlen,</div><div>+ meta->typbyval, meta->typalign,</div><div>+ &values, &nulls, &nelems);</div><div>+</div><div>+ for (i = 0; i < nelems; i++)</div><div>+ {<!-- --></div><div>+ if (nulls[i])</div><div>+ continue;</div><div>+ if (get_bit(sig, hash_elem(meta, values[i]), siglen))</div><div>+ {<!-- --></div><div>+ found = true;</div><div>+ break;</div><div>+ }</div><div>+ }</div><div>+ pfree(values);</div><div>+ pfree(nulls);</div><div>+ return found;</div><div>+}</div><div>+</div><div>+/*</div><div>+ * For contains: every element of the query must hash to a set bit.</div><div>+ * If any required bit is missing the key cannot contain the query.</div><div>+ */</div><div>+static bool</div><div>+sig_contains_array(const unsigned char *sig, int siglen,</div><div>+ ArrayType *query, AnyArrayTypeInfo *meta)</div><div>+{<!-- --></div><div>+ Datum *values;</div><div>+ bool *nulls;</div><div>+ int nelems;</div><div>+ int i;</div><div>+ bool ok = true;</div><div>+</div><div>+ if (ARR_NDIM(query) == 0)</div><div>+ return true;</div><div>+</div><div>+ deconstruct_array(query, meta->element_type, meta->typlen,</div><div>+ meta->typbyval, meta->typalign,</div><div>+ &values, &nulls, &nelems);</div><div>+</div><div>+ for (i = 0; i < nelems; i++)</div><div>+ {<!-- --></div><div>+ if (nulls[i])</div><div>+ continue;</div><div>+ if (!get_bit(sig, hash_elem(meta, values[i]), siglen))</div><div>+ {<!-- --></div><div>+ ok = false;</div><div>+ break;</div><div>+ }</div><div>+ }</div><div>+ pfree(values);</div><div>+ pfree(nulls);</div><div>+ return ok;</div><div>+}</div><div>+</div><div>+/*</div><div>+ * Recursive postfix evaluator for sig_eval_query().</div><div>+ *</div><div>+ * "vals" holds the precomputed signature lookup for every VAL slot. NOT</div><div>+ * is non-restrictive under a signature (the bit could still be set by some</div><div>+ * other inserted array), so we conservatively report true on negation; the</div><div>+ * GiST recheck step will then filter on the real values.</div><div>+ */</div><div>+static bool</div><div>+sig_eval_walk(AnyQuery *q, int idx, const bool *vals)</div><div>+{<!-- --></div><div>+ AnyQueryItem *it;</div><div>+</div><div>+ check_stack_depth();</div><div>+ Assert(idx >= 0);</div><div>+ it = &q->items[idx];</div><div>+</div><div>+ if (it->type == ANYQ_VAL)</div><div>+ return vals[idx];</div><div>+ if (it->payload == ANYQ_NOT)</div><div>+ return true;</div><div>+ if (it->payload == ANYQ_AND)</div><div>+ return sig_eval_walk(q, idx + it->left, vals) &&</div><div>+ sig_eval_walk(q, idx - 1, vals);</div><div>+ /* ANYQ_OR */</div><div>+ return sig_eval_walk(q, idx + it->left, vals) ||</div><div>+ sig_eval_walk(q, idx - 1, vals);</div><div>+}</div><div>+</div><div>+/*</div><div>+ * Evaluate an anyquery against a signature. Each VAL is parsed using the</div><div>+ * element type's text input, hashed, then looked up in the signature.</div><div>+ */</div><div>+static bool</div><div>+sig_eval_query(const unsigned char *sig, int siglen,</div><div>+ AnyQuery *q, AnyArrayTypeInfo *meta,</div><div>+ Oid input_func, Oid input_typioparam)</div><div>+{<!-- --></div><div>+ bool *vals;</div><div>+ bool result;</div><div>+ int i;</div><div>+</div><div>+ if (q->size <= 0)</div><div>+ return false;</div><div>+</div><div>+ vals = (bool *) palloc0(sizeof(bool) * q->size);</div><div>+</div><div>+ for (i = 0; i < q->size; i++)</div><div>+ {<!-- --></div><div>+ AnyQueryItem *it = &q->items[i];</div><div>+ Datum v;</div><div>+</div><div>+ if (it->type != ANYQ_VAL)</div><div>+ continue;</div><div>+ v = OidInputFunctionCall(input_func,</div><div>+ (char *) ANYQUERY_STRING(q, it),</div><div>+ input_typioparam, -1);</div><div>+ vals[i] = get_bit(sig, hash_elem(meta, v), siglen);</div><div>+ }</div><div>+</div><div>+ result = sig_eval_walk(q, q->size - 1, vals);</div><div>+ pfree(vals);</div><div>+ return result;</div><div>+}</div><div>+</div><div>+Datum</div><div>+anyarray_gist_consistent(PG_FUNCTION_ARGS)</div><div>+{<!-- --></div><div>+ GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0);</div><div>+ StrategyNumber strat = (StrategyNumber) PG_GETARG_UINT16(2);</div><div>+ Oid subtype = PG_GETARG_OID(3);</div><div>+ bool *recheck = (bool *) PG_GETARG_POINTER(4);</div><div>+ AnyArrayGistKey *key = (AnyArrayGistKey *) DatumGetPointer(entry->key);</div><div>+ int siglen = ANYARRAY_GET_SIGLEN();</div><div>+ bool result;</div><div>+</div><div>+ /* All signature-based answers require a recheck. */</div><div>+ *recheck = true;</div><div>+</div><div>+ if (ANYARRAY_GKEY_ISALLTRUE(key))</div><div>+ {<!-- --></div><div>+ /* Cannot prune anything from an all-true signature. */</div><div>+ PG_RETURN_BOOL(true);</div><div>+ }</div><div>+</div><div>+ (void) subtype;</div><div>+</div><div>+ if (strat == ANYARRAY_BOOLEAN_STRATEGY)</div><div>+ {<!-- --></div><div>+ AnyQuery *q = PG_GETARG_ANYQUERY_P(1);</div><div>+ AnyArrayTypeInfo *meta;</div><div>+ Form_pg_index ind;</div><div>+ Oid coltype;</div><div>+ Oid elemtype;</div><div>+ Oid input_func;</div><div>+ Oid input_typioparam;</div><div>+</div><div>+ /*</div><div>+ * The query is anyquery, so we recover the element type from the</div><div>+ * indexed column's pg_index entry. The index's own rd_att gives the</div><div>+ * STORAGE type (anyarray_gist_key), not the original array type, so</div><div>+ * we read the indrelid + indkey instead.</div><div>+ */</div><div>+ ind = entry->rel->rd_index;</div><div>+ if (ind == NULL || ind->indnatts < 1)</div><div>+ ereport(ERROR,</div><div>+ (errcode(ERRCODE_INTERNAL_ERROR),</div><div>+ errmsg("anyarray GiST index has no key column")));</div><div>+ coltype = get_atttype(ind->indrelid, ind->indkey.values[0]);</div><div>+ elemtype = get_element_type(coltype);</div><div>+ if (!OidIsValid(elemtype))</div><div>+ ereport(ERROR,</div><div>+ (errcode(ERRCODE_DATATYPE_MISMATCH),</div><div>+ errmsg("cannot determine element type for anyquery match")));</div><div>+</div><div>+ meta = anyarray_get_meta(fcinfo, elemtype, true);</div><div>+ getTypeInputInfo(elemtype, &input_func, &input_typioparam);</div><div>+</div><div>+ result = sig_eval_query(ANYARRAY_GKEY_SIGN(key), siglen, q, meta,</div><div>+ input_func, input_typioparam);</div><div>+ PG_FREE_IF_COPY(q, 1);</div><div>+ PG_RETURN_BOOL(result);</div><div>+ }</div><div>+ else</div><div>+ {<!-- --></div><div>+ ArrayType *query = PG_GETARG_ARRAYTYPE_P(1);</div><div>+ AnyArrayTypeInfo *meta;</div><div>+</div><div>+ ANYARRAY_CHECK_ARRAY(query);</div><div>+ meta = anyarray_get_meta(fcinfo, ARR_ELEMTYPE(query), true);</div><div>+</div><div>+ switch (strat)</div><div>+ {<!-- --></div><div>+ case ANYARRAY_OVERLAP_STRATEGY:</div><div>+ result = sig_overlap_array(ANYARRAY_GKEY_SIGN(key), siglen,</div><div>+ query, meta);</div><div>+ break;</div><div>+ case ANYARRAY_CONTAINS_STRATEGY:</div><div>+ result = sig_contains_array(ANYARRAY_GKEY_SIGN(key), siglen,</div><div>+ query, meta);</div><div>+ break;</div><div>+ case ANYARRAY_CONTAINED_STRATEGY:</div><div>+</div><div>+ /*</div><div>+ * For "key <@ query", signatures cannot exclude a non-leaf</div><div>+ * key because its bits may come from many distinct children</div><div>+ * which need not individually be contained in query. Always</div><div>+ * recheck.</div><div>+ */</div><div>+ if (GIST_LEAF(entry))</div><div>+ {<!-- --></div><div>+ /*</div><div>+ * At the leaf, every set bit must also have been set by</div><div>+ * the query (i.e., element hashes to that bit).</div><div>+ */</div><div>+ unsigned char *qsig;</div><div>+ int i;</div><div>+</div><div>+ qsig = (unsigned char *) palloc0(siglen);</div><div>+ hash_array_into(qsig, siglen, query, meta);</div><div>+ result = true;</div><div>+ for (i = 0; i < siglen; i++)</div><div>+ {<!-- --></div><div>+ if (ANYARRAY_GKEY_SIGN(key)[i] & ~qsig[i])</div><div>+ {<!-- --></div><div>+ result = false;</div><div>+ break;</div><div>+ }</div><div>+ }</div><div>+ pfree(qsig);</div><div>+ }</div><div>+ else</div><div>+ result = true;</div><div>+ break;</div><div>+ case ANYARRAY_EQUAL_STRATEGY:</div><div>+</div><div>+ /*</div><div>+ * The leaf signature for an equal row contains every bit of</div><div>+ * the query's hash; internal unions contain at least those</div><div>+ * bits. So "key contains query bits" is a sound</div><div>+ * over-approximation that recheck will tighten.</div><div>+ */</div><div>+ result = sig_contains_array(ANYARRAY_GKEY_SIGN(key), siglen,</div><div>+ query, meta);</div><div>+ break;</div><div>+ default:</div><div>+ elog(ERROR, "unrecognized strategy number: %d", strat);</div><div>+ result = false;</div><div>+ break;</div><div>+ }</div><div>+</div><div>+ PG_FREE_IF_COPY(query, 1);</div><div>+ PG_RETURN_BOOL(result);</div><div>+ }</div><div>+}</div><div>diff --git a/contrib/anyarray/anyarray_op.c b/contrib/anyarray/anyarray_op.c</div><div>new file mode 100644</div><div>index 00000000000..47a050bb0e3</div><div>--- /dev/null</div><div>+++ b/contrib/anyarray/anyarray_op.c</div><div>@@ -0,0 +1,613 @@</div><div>+/*-------------------------------------------------------------------------</div><div>+ *</div><div>+ * anyarray_op.c</div><div>+ * Set-style operations on arrays of any type.</div><div>+ *</div><div>+ * All functions accept anyarray / anyelement inputs and dispatch to the</div><div>+ * element type's btree comparison function through anyarray_get_meta().</div><div>+ * The operations mirror the corresponding intarray operations but are</div><div>+ * type-polymorphic.</div><div>+ *</div><div>+ * Copyright (c) 2026, PostgreSQL Global Development Group</div><div>+ *</div><div>+ * IDENTIFICATION</div><div>+ * contrib/anyarray/anyarray_op.c</div><div>+ *</div><div>+ *-------------------------------------------------------------------------</div><div>+ */</div><div>+</div><div>+#include "postgres.h"</div><div>+</div><div>+#include "anyarray.h"</div><div>+</div><div>+#include "catalog/pg_type.h"</div><div>+#include "lib/qunique.h"</div><div>+#include "utils/array.h"</div><div>+#include "utils/builtins.h"</div><div>+#include "utils/lsyscache.h"</div><div>+</div><div>+</div><div>+/*</div><div>+ * deconstruct_arr_meta</div><div>+ * Pull element values out of "arr" and fill in "*meta" for its type.</div><div>+ *</div><div>+ * Returns Datum array in *out_values (palloc'd), count in *out_nelems. The</div><div>+ * input array must already have passed ANYARRAY_CHECK_ARRAY; we still gate</div><div>+ * on it defensively because all entry points feed through this helper.</div><div>+ */</div><div>+static void</div><div>+deconstruct_arr_meta(FunctionCallInfo fcinfo, ArrayType *arr,</div><div>+ AnyArrayTypeInfo **out_meta,</div><div>+ Datum **out_values, int *out_nelems)</div><div>+{<!-- --></div><div>+ AnyArrayTypeInfo *meta;</div><div>+ Datum *values;</div><div>+ bool *nulls;</div><div>+ int nelems;</div><div>+</div><div>+ ANYARRAY_CHECK_ARRAY(arr);</div><div>+</div><div>+ meta = anyarray_get_meta(fcinfo, ARR_ELEMTYPE(arr), false);</div><div>+</div><div>+ if (ARR_NDIM(arr) == 0)</div><div>+ {<!-- --></div><div>+ *out_meta = meta;</div><div>+ *out_values = NULL;</div><div>+ *out_nelems = 0;</div><div>+ return;</div><div>+ }</div><div>+</div><div>+ deconstruct_array(arr, meta->element_type,</div><div>+ meta->typlen, meta->typbyval, meta->typalign,</div><div>+ &values, &nulls, &nelems);</div><div>+ pfree(nulls); /* we've already rejected nulls above */</div><div>+</div><div>+ *out_meta = meta;</div><div>+ *out_values = values;</div><div>+ *out_nelems = nelems;</div><div>+}</div><div>+</div><div>+/*</div><div>+ * make_array_from_datums</div><div>+ * Build a 1-D array from the given Datum vector. Empty -> empty array.</div><div>+ */</div><div>+static ArrayType *</div><div>+make_array_from_datums(Datum *values, int nelems, AnyArrayTypeInfo *meta)</div><div>+{<!-- --></div><div>+ if (nelems == 0)</div><div>+ return construct_empty_array(meta->element_type);</div><div>+</div><div>+ return construct_array(values, nelems, meta->element_type,</div><div>+ meta->typlen, meta->typbyval, meta->typalign);</div><div>+}</div><div>+</div><div>+</div><div>+/* ------------------------------------------------------------------------</div><div>+ * sort / uniq</div><div>+ * ------------------------------------------------------------------------</div><div>+ */</div><div>+</div><div>+PG_FUNCTION_INFO_V1(anyarray_sort);</div><div>+PG_FUNCTION_INFO_V1(anyarray_sort_dir);</div><div>+PG_FUNCTION_INFO_V1(anyarray_uniq);</div><div>+</div><div>+/*</div><div>+ * Sort an array ascending.</div><div>+ */</div><div>+Datum</div><div>+anyarray_sort(PG_FUNCTION_ARGS)</div><div>+{<!-- --></div><div>+ ArrayType *arr = PG_GETARG_ARRAYTYPE_P(0);</div><div>+ AnyArrayTypeInfo *meta;</div><div>+ Datum *values;</div><div>+ int nelems;</div><div>+</div><div>+ deconstruct_arr_meta(fcinfo, arr, &meta, &values, &nelems);</div><div>+</div><div>+ if (nelems > 1)</div><div>+ qsort_arg(values, nelems, sizeof(Datum), anyarray_cmp_datum, meta);</div><div>+</div><div>+ PG_RETURN_ARRAYTYPE_P(make_array_from_datums(values, nelems, meta));</div><div>+}</div><div>+</div><div>+/*</div><div>+ * Sort an array. The direction text is case-insensitive and must be one of</div><div>+ * 'asc' / 'ascending' or 'desc' / 'descending'.</div><div>+ */</div><div>+Datum</div><div>+anyarray_sort_dir(PG_FUNCTION_ARGS)</div><div>+{<!-- --></div><div>+ ArrayType *arr = PG_GETARG_ARRAYTYPE_P(0);</div><div>+ text *dir = PG_GETARG_TEXT_PP(1);</div><div>+ const char *dirstr = VARDATA_ANY(dir);</div><div>+ int dirlen = VARSIZE_ANY_EXHDR(dir);</div><div>+ bool ascending;</div><div>+ AnyArrayTypeInfo *meta;</div><div>+ Datum *values;</div><div>+ int nelems;</div><div>+</div><div>+ if ((dirlen == 3 && pg_strncasecmp(dirstr, "asc", 3) == 0) ||</div><div>+ (dirlen == 9 && pg_strncasecmp(dirstr, "ascending", 9) == 0))</div><div>+ ascending = true;</div><div>+ else if ((dirlen == 4 && pg_strncasecmp(dirstr, "desc", 4) == 0) ||</div><div>+ (dirlen == 10 && pg_strncasecmp(dirstr, "descending", 10) == 0))</div><div>+ ascending = false;</div><div>+ else</div><div>+ ereport(ERROR,</div><div>+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),</div><div>+ errmsg("second parameter must be \"asc\" or \"desc\"")));</div><div>+</div><div>+ deconstruct_arr_meta(fcinfo, arr, &meta, &values, &nelems);</div><div>+</div><div>+ if (nelems > 1)</div><div>+ {<!-- --></div><div>+ qsort_arg(values, nelems, sizeof(Datum), anyarray_cmp_datum, meta);</div><div>+ if (!ascending)</div><div>+ {<!-- --></div><div>+ Datum *l = values;</div><div>+ Datum *r = values + nelems - 1;</div><div>+</div><div>+ while (l < r)</div><div>+ {<!-- --></div><div>+ Datum tmp = *l;</div><div>+</div><div>+ *l++ = *r;</div><div>+ *r-- = tmp;</div><div>+ }</div><div>+ }</div><div>+ }</div><div>+</div><div>+ PG_RETURN_ARRAYTYPE_P(make_array_from_datums(values, nelems, meta));</div><div>+}</div><div>+</div><div>+/*</div><div>+ * Remove duplicate elements; does not require pre-sorted input.</div><div>+ */</div><div>+Datum</div><div>+anyarray_uniq(PG_FUNCTION_ARGS)</div><div>+{<!-- --></div><div>+ ArrayType *arr = PG_GETARG_ARRAYTYPE_P(0);</div><div>+ AnyArrayTypeInfo *meta;</div><div>+ Datum *values;</div><div>+ int nelems;</div><div>+ int nunique;</div><div>+</div><div>+ deconstruct_arr_meta(fcinfo, arr, &meta, &values, &nelems);</div><div>+</div><div>+ if (nelems > 1)</div><div>+ {<!-- --></div><div>+ qsort_arg(values, nelems, sizeof(Datum), anyarray_cmp_datum, meta);</div><div>+ nunique = qunique_arg(values, nelems, sizeof(Datum),</div><div>+ anyarray_cmp_datum, meta);</div><div>+ }</div><div>+ else</div><div>+ nunique = nelems;</div><div>+</div><div>+ PG_RETURN_ARRAYTYPE_P(make_array_from_datums(values, nunique, meta));</div><div>+}</div><div>+</div><div>+</div><div>+/* ------------------------------------------------------------------------</div><div>+ * idx / subarray / cardinality</div><div>+ * ------------------------------------------------------------------------</div><div>+ */</div><div>+</div><div>+PG_FUNCTION_INFO_V1(anyarray_idx);</div><div>+PG_FUNCTION_INFO_V1(anyarray_subarray);</div><div>+PG_FUNCTION_INFO_V1(anyarray_subarray_to_end);</div><div>+PG_FUNCTION_INFO_V1(anyarray_icount);</div><div>+</div><div>+/*</div><div>+ * Return the 1-based index of the first occurrence of "elem" in "arr",</div><div>+ * or 0 if not found.</div><div>+ */</div><div>+Datum</div><div>+anyarray_idx(PG_FUNCTION_ARGS)</div><div>+{<!-- --></div><div>+ ArrayType *arr = PG_GETARG_ARRAYTYPE_P(0);</div><div>+ Datum elem = PG_GETARG_DATUM(1);</div><div>+ AnyArrayTypeInfo *meta;</div><div>+ Datum *values;</div><div>+ int nelems;</div><div>+ int i;</div><div>+</div><div>+ deconstruct_arr_meta(fcinfo, arr, &meta, &values, &nelems);</div><div>+</div><div>+ for (i = 0; i < nelems; i++)</div><div>+ {<!-- --></div><div>+ Datum eq = FunctionCall2Coll(&meta->eq_proc, meta->typcollation,</div><div>+ values[i], elem);</div><div>+</div><div>+ if (DatumGetBool(eq))</div><div>+ PG_RETURN_INT32(i + 1);</div><div>+ }</div><div>+</div><div>+ PG_RETURN_INT32(0);</div><div>+}</div><div>+</div><div>+/*</div><div>+ * Common subarray extraction. "start" is 1-based; if it is non-positive</div><div>+ * the function returns an empty array. "len" gives the maximum number of</div><div>+ * elements to copy; if "have_len" is false the whole tail is returned.</div><div>+ */</div><div>+static ArrayType *</div><div>+do_subarray(FunctionCallInfo fcinfo, ArrayType *arr,</div><div>+ int32 start, int32 len, bool have_len)</div><div>+{<!-- --></div><div>+ AnyArrayTypeInfo *meta;</div><div>+ Datum *values;</div><div>+ int nelems;</div><div>+ int off;</div><div>+ int n;</div><div>+</div><div>+ deconstruct_arr_meta(fcinfo, arr, &meta, &values, &nelems);</div><div>+</div><div>+ if (start < 1 || start > nelems)</div><div>+ return construct_empty_array(meta->element_type);</div><div>+</div><div>+ off = start - 1;</div><div>+ if (have_len)</div><div>+ {<!-- --></div><div>+ if (len <= 0)</div><div>+ return construct_empty_array(meta->element_type);</div><div>+ n = Min(len, nelems - off);</div><div>+ }</div><div>+ else</div><div>+ n = nelems - off;</div><div>+</div><div>+ return make_array_from_datums(values + off, n, meta);</div><div>+}</div><div>+</div><div>+/*</div><div>+ * subarray(arr, start, len)</div><div>+ */</div><div>+Datum</div><div>+anyarray_subarray(PG_FUNCTION_ARGS)</div><div>+{<!-- --></div><div>+ ArrayType *arr = PG_GETARG_ARRAYTYPE_P(0);</div><div>+ int32 start = PG_GETARG_INT32(1);</div><div>+ int32 len = PG_GETARG_INT32(2);</div><div>+</div><div>+ PG_RETURN_ARRAYTYPE_P(do_subarray(fcinfo, arr, start, len, true));</div><div>+}</div><div>+</div><div>+/*</div><div>+ * subarray(arr, start) -- to end</div><div>+ */</div><div>+Datum</div><div>+anyarray_subarray_to_end(PG_FUNCTION_ARGS)</div><div>+{<!-- --></div><div>+ ArrayType *arr = PG_GETARG_ARRAYTYPE_P(0);</div><div>+ int32 start = PG_GETARG_INT32(1);</div><div>+</div><div>+ PG_RETURN_ARRAYTYPE_P(do_subarray(fcinfo, arr, start, 0, false));</div><div>+}</div><div>+</div><div>+/*</div><div>+ * icount: total element count, intarray-style. This duplicates</div><div>+ * built-in cardinality() but we expose it under the # operator the way</div><div>+ * intarray does.</div><div>+ */</div><div>+Datum</div><div>+anyarray_icount(PG_FUNCTION_ARGS)</div><div>+{<!-- --></div><div>+ ArrayType *arr = PG_GETARG_ARRAYTYPE_P(0);</div><div>+</div><div>+ ANYARRAY_CHECK_ARRAY(arr);</div><div>+ PG_RETURN_INT32(ANYARRAY_NELEMS(arr));</div><div>+}</div><div>+</div><div>+</div><div>+/* ------------------------------------------------------------------------</div><div>+ * intersect / union / difference</div><div>+ * ------------------------------------------------------------------------</div><div>+ */</div><div>+</div><div>+PG_FUNCTION_INFO_V1(anyarray_intersect);</div><div>+PG_FUNCTION_INFO_V1(anyarray_union);</div><div>+PG_FUNCTION_INFO_V1(anyarray_union_elem);</div><div>+PG_FUNCTION_INFO_V1(anyarray_difference);</div><div>+PG_FUNCTION_INFO_V1(anyarray_difference_elem);</div><div>+</div><div>+/*</div><div>+ * deconstruct_two</div><div>+ * Like deconstruct_arr_meta() but for two array inputs that must share</div><div>+ * an element type. The second array is allowed to be a different</div><div>+ * ARRAY OID (it might come from another column) as long as the element</div><div>+ * types match.</div><div>+ */</div><div>+static AnyArrayTypeInfo *</div><div>+deconstruct_two(FunctionCallInfo fcinfo,</div><div>+ ArrayType *a, ArrayType *b,</div><div>+ Datum **avals, int *anelems,</div><div>+ Datum **bvals, int *bnelems)</div><div>+{<!-- --></div><div>+ AnyArrayTypeInfo *meta;</div><div>+ bool *nulls;</div><div>+</div><div>+ ANYARRAY_CHECK_ARRAY(a);</div><div>+ ANYARRAY_CHECK_ARRAY(b);</div><div>+</div><div>+ if (ARR_ELEMTYPE(a) != ARR_ELEMTYPE(b))</div><div>+ ereport(ERROR,</div><div>+ (errcode(ERRCODE_DATATYPE_MISMATCH),</div><div>+ errmsg("cannot operate on arrays of different element types"),</div><div>+ errdetail("Left operand has element type %s, right operand has %s.",</div><div>+ format_type_be(ARR_ELEMTYPE(a)),</div><div>+ format_type_be(ARR_ELEMTYPE(b)))));</div><div>+</div><div>+ meta = anyarray_get_meta(fcinfo, ARR_ELEMTYPE(a), false);</div><div>+</div><div>+ if (ARR_NDIM(a) == 0)</div><div>+ {<!-- --></div><div>+ *avals = NULL;</div><div>+ *anelems = 0;</div><div>+ }</div><div>+ else</div><div>+ {<!-- --></div><div>+ deconstruct_array(a, meta->element_type, meta->typlen,</div><div>+ meta->typbyval, meta->typalign,</div><div>+ avals, &nulls, anelems);</div><div>+ pfree(nulls);</div><div>+ }</div><div>+</div><div>+ if (ARR_NDIM(b) == 0)</div><div>+ {<!-- --></div><div>+ *bvals = NULL;</div><div>+ *bnelems = 0;</div><div>+ }</div><div>+ else</div><div>+ {<!-- --></div><div>+ deconstruct_array(b, meta->element_type, meta->typlen,</div><div>+ meta->typbyval, meta->typalign,</div><div>+ bvals, &nulls, bnelems);</div><div>+ pfree(nulls);</div><div>+ }</div><div>+</div><div>+ return meta;</div><div>+}</div><div>+</div><div>+/*</div><div>+ * Sort + de-dup in place. Returns the new length.</div><div>+ */</div><div>+static int</div><div>+sort_uniq(Datum *values, int nelems, AnyArrayTypeInfo *meta)</div><div>+{<!-- --></div><div>+ if (nelems <= 1)</div><div>+ return nelems;</div><div>+</div><div>+ qsort_arg(values, nelems, sizeof(Datum), anyarray_cmp_datum, meta);</div><div>+ return qunique_arg(values, nelems, sizeof(Datum),</div><div>+ anyarray_cmp_datum, meta);</div><div>+}</div><div>+</div><div>+/*</div><div>+ * Intersection: returns the sorted, deduplicated values present in both</div><div>+ * inputs.</div><div>+ */</div><div>+Datum</div><div>+anyarray_intersect(PG_FUNCTION_ARGS)</div><div>+{<!-- --></div><div>+ ArrayType *a = PG_GETARG_ARRAYTYPE_P(0);</div><div>+ ArrayType *b = PG_GETARG_ARRAYTYPE_P(1);</div><div>+ AnyArrayTypeInfo *meta;</div><div>+ Datum *av,</div><div>+ *bv,</div><div>+ *out;</div><div>+ int an,</div><div>+ bn,</div><div>+ i = 0,</div><div>+ j = 0,</div><div>+ k = 0;</div><div>+</div><div>+ meta = deconstruct_two(fcinfo, a, b, &av, &an, &bv, &bn);</div><div>+</div><div>+ an = sort_uniq(av, an, meta);</div><div>+ bn = sort_uniq(bv, bn, meta);</div><div>+</div><div>+ out = (Datum *) palloc(sizeof(Datum) * Min(an, bn));</div><div>+ while (i < an && j < bn)</div><div>+ {<!-- --></div><div>+ int cmp = anyarray_cmp_datum(&av[i], &bv[j], meta);</div><div>+</div><div>+ if (cmp == 0)</div><div>+ {<!-- --></div><div>+ out[k++] = av[i];</div><div>+ i++;</div><div>+ j++;</div><div>+ }</div><div>+ else if (cmp < 0)</div><div>+ i++;</div><div>+ else</div><div>+ j++;</div><div>+ }</div><div>+</div><div>+ PG_RETURN_ARRAYTYPE_P(make_array_from_datums(out, k, meta));</div><div>+}</div><div>+</div><div>+/*</div><div>+ * Union: returns the sorted, deduplicated values present in either input.</div><div>+ */</div><div>+Datum</div><div>+anyarray_union(PG_FUNCTION_ARGS)</div><div>+{<!-- --></div><div>+ ArrayType *a = PG_GETARG_ARRAYTYPE_P(0);</div><div>+ ArrayType *b = PG_GETARG_ARRAYTYPE_P(1);</div><div>+ AnyArrayTypeInfo *meta;</div><div>+ Datum *av,</div><div>+ *bv,</div><div>+ *out;</div><div>+ int an,</div><div>+ bn,</div><div>+ i = 0,</div><div>+ j = 0,</div><div>+ k = 0;</div><div>+</div><div>+ meta = deconstruct_two(fcinfo, a, b, &av, &an, &bv, &bn);</div><div>+</div><div>+ an = sort_uniq(av, an, meta);</div><div>+ bn = sort_uniq(bv, bn, meta);</div><div>+</div><div>+ out = (Datum *) palloc(sizeof(Datum) * (an + bn));</div><div>+ while (i < an && j < bn)</div><div>+ {<!-- --></div><div>+ int cmp = anyarray_cmp_datum(&av[i], &bv[j], meta);</div><div>+</div><div>+ if (cmp == 0)</div><div>+ {<!-- --></div><div>+ out[k++] = av[i];</div><div>+ i++;</div><div>+ j++;</div><div>+ }</div><div>+ else if (cmp < 0)</div><div>+ out[k++] = av[i++];</div><div>+ else</div><div>+ out[k++] = bv[j++];</div><div>+ }</div><div>+ while (i < an)</div><div>+ out[k++] = av[i++];</div><div>+ while (j < bn)</div><div>+ out[k++] = bv[j++];</div><div>+</div><div>+ PG_RETURN_ARRAYTYPE_P(make_array_from_datums(out, k, meta));</div><div>+}</div><div>+</div><div>+/*</div><div>+ * array | element : add element if not already present, returning the</div><div>+ * sorted, deduplicated result.</div><div>+ */</div><div>+Datum</div><div>+anyarray_union_elem(PG_FUNCTION_ARGS)</div><div>+{<!-- --></div><div>+ ArrayType *a = PG_GETARG_ARRAYTYPE_P(0);</div><div>+ Datum elem = PG_GETARG_DATUM(1);</div><div>+ Oid elem_type = get_fn_expr_argtype(fcinfo->flinfo, 1);</div><div>+ AnyArrayTypeInfo *meta;</div><div>+ Datum *values;</div><div>+ int nelems;</div><div>+ int out_n;</div><div>+</div><div>+ ANYARRAY_CHECK_ARRAY(a);</div><div>+</div><div>+ if (ARR_ELEMTYPE(a) != elem_type)</div><div>+ ereport(ERROR,</div><div>+ (errcode(ERRCODE_DATATYPE_MISMATCH),</div><div>+ errmsg("element type does not match array element type")));</div><div>+</div><div>+ meta = anyarray_get_meta(fcinfo, ARR_ELEMTYPE(a), false);</div><div>+</div><div>+ if (ARR_NDIM(a) == 0)</div><div>+ {<!-- --></div><div>+ nelems = 0;</div><div>+ values = (Datum *) palloc(sizeof(Datum));</div><div>+ }</div><div>+ else</div><div>+ {<!-- --></div><div>+ bool *nulls;</div><div>+</div><div>+ deconstruct_array(a, meta->element_type, meta->typlen,</div><div>+ meta->typbyval, meta->typalign,</div><div>+ &values, &nulls, &nelems);</div><div>+ pfree(nulls);</div><div>+ values = repalloc(values, sizeof(Datum) * (nelems + 1));</div><div>+ }</div><div>+</div><div>+ values[nelems++] = elem;</div><div>+</div><div>+ out_n = sort_uniq(values, nelems, meta);</div><div>+ PG_RETURN_ARRAYTYPE_P(make_array_from_datums(values, out_n, meta));</div><div>+}</div><div>+</div><div>+/*</div><div>+ * Difference: returns sorted, deduplicated values from "a" that are not in</div><div>+ * "b".</div><div>+ */</div><div>+Datum</div><div>+anyarray_difference(PG_FUNCTION_ARGS)</div><div>+{<!-- --></div><div>+ ArrayType *a = PG_GETARG_ARRAYTYPE_P(0);</div><div>+ ArrayType *b = PG_GETARG_ARRAYTYPE_P(1);</div><div>+ AnyArrayTypeInfo *meta;</div><div>+ Datum *av,</div><div>+ *bv,</div><div>+ *out;</div><div>+ int an,</div><div>+ bn,</div><div>+ i = 0,</div><div>+ j = 0,</div><div>+ k = 0;</div><div>+</div><div>+ meta = deconstruct_two(fcinfo, a, b, &av, &an, &bv, &bn);</div><div>+</div><div>+ an = sort_uniq(av, an, meta);</div><div>+ bn = sort_uniq(bv, bn, meta);</div><div>+</div><div>+ out = (Datum *) palloc(sizeof(Datum) * an);</div><div>+ while (i < an && j < bn)</div><div>+ {<!-- --></div><div>+ int cmp = anyarray_cmp_datum(&av[i], &bv[j], meta);</div><div>+</div><div>+ if (cmp == 0)</div><div>+ {<!-- --></div><div>+ i++;</div><div>+ j++;</div><div>+ }</div><div>+ else if (cmp < 0)</div><div>+ out[k++] = av[i++];</div><div>+ else</div><div>+ j++;</div><div>+ }</div><div>+ while (i < an)</div><div>+ out[k++] = av[i++];</div><div>+</div><div>+ PG_RETURN_ARRAYTYPE_P(make_array_from_datums(out, k, meta));</div><div>+}</div><div>+</div><div>+/*</div><div>+ * array - element : remove all occurrences of "elem", preserving order.</div><div>+ * (Note: intarray returns the input order-preserved; we do the same.)</div><div>+ */</div><div>+Datum</div><div>+anyarray_difference_elem(PG_FUNCTION_ARGS)</div><div>+{<!-- --></div><div>+ ArrayType *a = PG_GETARG_ARRAYTYPE_P(0);</div><div>+ Datum elem = PG_GETARG_DATUM(1);</div><div>+ Oid elem_type = get_fn_expr_argtype(fcinfo->flinfo, 1);</div><div>+ AnyArrayTypeInfo *meta;</div><div>+ Datum *values;</div><div>+ int nelems;</div><div>+ int i,</div><div>+ k = 0;</div><div>+</div><div>+ ANYARRAY_CHECK_ARRAY(a);</div><div>+</div><div>+ if (ARR_ELEMTYPE(a) != elem_type)</div><div>+ ereport(ERROR,</div><div>+ (errcode(ERRCODE_DATATYPE_MISMATCH),</div><div>+ errmsg("element type does not match array element type")));</div><div>+</div><div>+ meta = anyarray_get_meta(fcinfo, ARR_ELEMTYPE(a), false);</div><div>+</div><div>+ if (ARR_NDIM(a) == 0)</div><div>+ PG_RETURN_ARRAYTYPE_P(construct_empty_array(meta->element_type));</div><div>+</div><div>+ {<!-- --></div><div>+ bool *nulls;</div><div>+</div><div>+ deconstruct_array(a, meta->element_type, meta->typlen,</div><div>+ meta->typbyval, meta->typalign,</div><div>+ &values, &nulls, &nelems);</div><div>+ pfree(nulls);</div><div>+ }</div><div>+</div><div>+ for (i = 0; i < nelems; i++)</div><div>+ {<!-- --></div><div>+ Datum eq = FunctionCall2Coll(&meta->eq_proc, meta->typcollation,</div><div>+ values[i], elem);</div><div>+</div><div>+ if (!DatumGetBool(eq))</div><div>+ values[k++] = values[i];</div><div>+ }</div><div>+</div><div>+ PG_RETURN_ARRAYTYPE_P(make_array_from_datums(values, k, meta));</div><div>+}</div><div>diff --git a/contrib/anyarray/expected/anyarray.out b/contrib/anyarray/expected/anyarray.out</div><div>new file mode 100644</div><div>index 00000000000..b17c6ef033e</div><div>--- /dev/null</div><div>+++ b/contrib/anyarray/expected/anyarray.out</div><div>@@ -0,0 +1,727 @@</div><div>+CREATE EXTENSION anyarray;</div><div>+--</div><div>+-- Phase 1: set operations, helpers and operators for int8, uuid, text</div><div>+--</div><div>+-- ===== int8 =====</div><div>+SELECT anyarray_sort(ARRAY[3, 1, 2, 1]::int8[]);</div><div>+ anyarray_sort </div><div>+---------------</div><div>+ {1,1,2,3}</div><div>+(1 row)</div><div>+</div><div>+SELECT anyarray_sort(ARRAY[3, 1, 2, 1]::int8[], 'desc');</div><div>+ anyarray_sort </div><div>+---------------</div><div>+ {3,2,1,1}</div><div>+(1 row)</div><div>+</div><div>+SELECT anyarray_sort(ARRAY[]::int8[]);</div><div>+ anyarray_sort </div><div>+---------------</div><div>+ {}</div><div>+(1 row)</div><div>+</div><div>+SELECT anyarray_uniq(ARRAY[1, 1, 2, 3, 3, 2]::int8[]);</div><div>+ anyarray_uniq </div><div>+---------------</div><div>+ {1,2,3}</div><div>+(1 row)</div><div>+</div><div>+SELECT anyarray_idx(ARRAY[10, 20, 30]::int8[], 20::int8);</div><div>+ anyarray_idx </div><div>+--------------</div><div>+ 2</div><div>+(1 row)</div><div>+</div><div>+SELECT anyarray_idx(ARRAY[10, 20, 30]::int8[], 99::int8);</div><div>+ anyarray_idx </div><div>+--------------</div><div>+ 0</div><div>+(1 row)</div><div>+</div><div>+SELECT anyarray_subarray(ARRAY[1,2,3,4,5]::int8[], 2, 2);</div><div>+ anyarray_subarray </div><div>+-------------------</div><div>+ {2,3}</div><div>+(1 row)</div><div>+</div><div>+SELECT anyarray_subarray(ARRAY[1,2,3,4,5]::int8[], 3);</div><div>+ anyarray_subarray </div><div>+-------------------</div><div>+ {3,4,5}</div><div>+(1 row)</div><div>+</div><div>+SELECT anyarray_subarray(ARRAY[1,2,3]::int8[], 5, 1);</div><div>+ anyarray_subarray </div><div>+-------------------</div><div>+ {}</div><div>+(1 row)</div><div>+</div><div>+SELECT ARRAY[1,2,3]::int8[] & ARRAY[2,3,4]::int8[];</div><div>+ ?column? </div><div>+----------</div><div>+ {2,3}</div><div>+(1 row)</div><div>+</div><div>+SELECT ARRAY[1,2,3]::int8[] | ARRAY[3,4,5]::int8[];</div><div>+ ?column? </div><div>+-------------</div><div>+ {1,2,3,4,5}</div><div>+(1 row)</div><div>+</div><div>+SELECT ARRAY[1,2,3]::int8[] | 4::int8;</div><div>+ ?column? </div><div>+-----------</div><div>+ {1,2,3,4}</div><div>+(1 row)</div><div>+</div><div>+SELECT ARRAY[1,2,3,4]::int8[] - ARRAY[2,4]::int8[];</div><div>+ ?column? </div><div>+----------</div><div>+ {1,3}</div><div>+(1 row)</div><div>+</div><div>+SELECT ARRAY[1,2,2,3,2]::int8[] - 2::int8;</div><div>+ ?column? </div><div>+----------</div><div>+ {1,3}</div><div>+(1 row)</div><div>+</div><div>+SELECT # ARRAY[10,20,30]::int8[];</div><div>+ ?column? </div><div>+----------</div><div>+ 3</div><div>+(1 row)</div><div>+</div><div>+SELECT ARRAY[10,20,30]::int8[] # 20::int8;</div><div>+ ?column? </div><div>+----------</div><div>+ 2</div><div>+(1 row)</div><div>+</div><div>+-- ===== uuid =====</div><div>+SELECT anyarray_sort(ARRAY[</div><div>+ '00000000-0000-0000-0000-000000000003'::uuid,</div><div>+ '00000000-0000-0000-0000-000000000001',</div><div>+ '00000000-0000-0000-0000-000000000002']);</div><div>+ anyarray_sort </div><div>+------------------------------------------------------------------------------------------------------------------</div><div>+ {00000000-0000-0000-0000-000000000001,00000000-0000-0000-0000-000000000002,00000000-0000-0000-0000-000000000003}</div><div>+(1 row)</div><div>+</div><div>+SELECT anyarray_uniq(ARRAY[</div><div>+ '00000000-0000-0000-0000-000000000001'::uuid,</div><div>+ '00000000-0000-0000-0000-000000000001',</div><div>+ '00000000-0000-0000-0000-000000000002']);</div><div>+ anyarray_uniq </div><div>+-----------------------------------------------------------------------------</div><div>+ {00000000-0000-0000-0000-000000000001,00000000-0000-0000-0000-000000000002}</div><div>+(1 row)</div><div>+</div><div>+SELECT anyarray_idx(ARRAY[</div><div>+ '00000000-0000-0000-0000-000000000001'::uuid,</div><div>+ '00000000-0000-0000-0000-000000000002'],</div><div>+ '00000000-0000-0000-0000-000000000002'::uuid);</div><div>+ anyarray_idx </div><div>+--------------</div><div>+ 2</div><div>+(1 row)</div><div>+</div><div>+SELECT ARRAY['00000000-0000-0000-0000-000000000001'::uuid,</div><div>+ '00000000-0000-0000-0000-000000000002']</div><div>+ & ARRAY['00000000-0000-0000-0000-000000000002'::uuid,</div><div>+ '00000000-0000-0000-0000-000000000003'];</div><div>+ ?column? </div><div>+----------------------------------------</div><div>+ {00000000-0000-0000-0000-000000000002}</div><div>+(1 row)</div><div>+</div><div>+SELECT ARRAY['00000000-0000-0000-0000-000000000001'::uuid]</div><div>+ | '00000000-0000-0000-0000-000000000002'::uuid;</div><div>+ ?column? </div><div>+-----------------------------------------------------------------------------</div><div>+ {00000000-0000-0000-0000-000000000001,00000000-0000-0000-0000-000000000002}</div><div>+(1 row)</div><div>+</div><div>+-- ===== text =====</div><div>+SELECT anyarray_sort(ARRAY['banana','apple','cherry']);</div><div>+ anyarray_sort </div><div>+-----------------------</div><div>+ {apple,banana,cherry}</div><div>+(1 row)</div><div>+</div><div>+SELECT anyarray_sort(ARRAY['banana','apple','cherry'], 'desc');</div><div>+ anyarray_sort </div><div>+-----------------------</div><div>+ {cherry,banana,apple}</div><div>+(1 row)</div><div>+</div><div>+SELECT anyarray_uniq(ARRAY['a','b','a','c','b']);</div><div>+ anyarray_uniq </div><div>+---------------</div><div>+ {a,b,c}</div><div>+(1 row)</div><div>+</div><div>+SELECT anyarray_idx(ARRAY['a','b','c'], 'b'::text);</div><div>+ anyarray_idx </div><div>+--------------</div><div>+ 2</div><div>+(1 row)</div><div>+</div><div>+SELECT ARRAY['a','b','c']::text[] & ARRAY['b','c','d']::text[];</div><div>+ ?column? </div><div>+----------</div><div>+ {b,c}</div><div>+(1 row)</div><div>+</div><div>+SELECT ARRAY['a','b','c']::text[] | 'd'::text;</div><div>+ ?column? </div><div>+-----------</div><div>+ {a,b,c,d}</div><div>+(1 row)</div><div>+</div><div>+SELECT ARRAY['a','b','c','b']::text[] - 'b'::text;</div><div>+ ?column? </div><div>+----------</div><div>+ {a,c}</div><div>+(1 row)</div><div>+</div><div>+-- ===== error cases =====</div><div>+-- multi-dim</div><div>+SELECT anyarray_sort(ARRAY[[1,2],[3,4]]::int8[]);</div><div>+ERROR: multidimensional arrays are not supported</div><div>+-- nulls</div><div>+SELECT anyarray_sort(ARRAY[1, NULL, 2]::int8[]);</div><div>+ERROR: array must not contain nulls</div><div>+-- bad direction</div><div>+SELECT anyarray_sort(ARRAY[1,2,3]::int8[], 'bogus');</div><div>+ERROR: second parameter must be "asc" or "desc"</div><div>+--</div><div>+-- Phase 2: anyquery and @@ operator</div><div>+--</div><div>+-- parsing + output round-trip</div><div>+SELECT '1 & 2'::anyquery::text;</div><div>+ text </div><div>+-------</div><div>+ 1 & 2</div><div>+(1 row)</div><div>+</div><div>+SELECT '1 & 2 | 3'::anyquery::text;</div><div>+ text </div><div>+-----------</div><div>+ 1 & 2 | 3</div><div>+(1 row)</div><div>+</div><div>+SELECT '(1 | 2) & 3'::anyquery::text;</div><div>+ text </div><div>+---------------</div><div>+ ( 1 | 2 ) & 3</div><div>+(1 row)</div><div>+</div><div>+SELECT '!1 & 2'::anyquery::text;</div><div>+ text </div><div>+--------</div><div>+ !1 & 2</div><div>+(1 row)</div><div>+</div><div>+SELECT '!(1 | 2) & 3'::anyquery::text;</div><div>+ text </div><div>+--------------------</div><div>+ !( ( 1 | 2 ) ) & 3</div><div>+(1 row)</div><div>+</div><div>+SELECT '"hello world" | foo'::anyquery::text;</div><div>+ text </div><div>+---------------------</div><div>+ "hello world" | foo</div><div>+(1 row)</div><div>+</div><div>+SELECT anyquery_querytree('1 & 2 | 3'::anyquery);</div><div>+ anyquery_querytree </div><div>+--------------------</div><div>+ 1 2 & 3 |</div><div>+(1 row)</div><div>+</div><div>+-- int8 matching</div><div>+SELECT ARRAY[1,2,3]::int8[] @@ '1'::anyquery;</div><div>+ ?column? </div><div>+----------</div><div>+ t</div><div>+(1 row)</div><div>+</div><div>+SELECT ARRAY[1,2,3]::int8[] @@ '1 & 2'::anyquery;</div><div>+ ?column? </div><div>+----------</div><div>+ t</div><div>+(1 row)</div><div>+</div><div>+SELECT ARRAY[1,2,3]::int8[] @@ '1 & 4'::anyquery;</div><div>+ ?column? </div><div>+----------</div><div>+ f</div><div>+(1 row)</div><div>+</div><div>+SELECT ARRAY[1,2,3]::int8[] @@ '1 | 4'::anyquery;</div><div>+ ?column? </div><div>+----------</div><div>+ t</div><div>+(1 row)</div><div>+</div><div>+SELECT ARRAY[1,2,3]::int8[] @@ '!4'::anyquery;</div><div>+ ?column? </div><div>+----------</div><div>+ t</div><div>+(1 row)</div><div>+</div><div>+SELECT ARRAY[1,2,3]::int8[] @@ '!1'::anyquery;</div><div>+ ?column? </div><div>+----------</div><div>+ f</div><div>+(1 row)</div><div>+</div><div>+SELECT ARRAY[1,2,3]::int8[] @@ '!4 & 1'::anyquery;</div><div>+ ?column? </div><div>+----------</div><div>+ t</div><div>+(1 row)</div><div>+</div><div>+SELECT ARRAY[1,2,3]::int8[] @@ '(1 | 4) & (2 | 5)'::anyquery;</div><div>+ ?column? </div><div>+----------</div><div>+ t</div><div>+(1 row)</div><div>+</div><div>+-- commutator</div><div>+SELECT '1 & 2'::anyquery ~~ ARRAY[1,2,3]::int8[];</div><div>+ ?column? </div><div>+----------</div><div>+ t</div><div>+(1 row)</div><div>+</div><div>+SELECT '1 & 4'::anyquery ~~ ARRAY[1,2,3]::int8[];</div><div>+ ?column? </div><div>+----------</div><div>+ f</div><div>+(1 row)</div><div>+</div><div>+-- uuid</div><div>+SELECT ARRAY['00000000-0000-0000-0000-000000000001'::uuid,</div><div>+ '00000000-0000-0000-0000-000000000002']</div><div>+ @@ '00000000-0000-0000-0000-000000000001 & 00000000-0000-0000-0000-000000000002'::anyquery;</div><div>+ ?column? </div><div>+----------</div><div>+ t</div><div>+(1 row)</div><div>+</div><div>+SELECT ARRAY['00000000-0000-0000-0000-000000000001'::uuid]</div><div>+ @@ '00000000-0000-0000-0000-000000000002'::anyquery;</div><div>+ ?column? </div><div>+----------</div><div>+ f</div><div>+(1 row)</div><div>+</div><div>+-- text</div><div>+SELECT ARRAY['apple','banana','cherry']::text[] @@ 'apple & banana'::anyquery;</div><div>+ ?column? </div><div>+----------</div><div>+ t</div><div>+(1 row)</div><div>+</div><div>+SELECT ARRAY['apple','banana','cherry']::text[] @@ '"banana" | grape'::anyquery;</div><div>+ ?column? </div><div>+----------</div><div>+ t</div><div>+(1 row)</div><div>+</div><div>+SELECT ARRAY['apple','banana','cherry']::text[] @@ 'durian | grape'::anyquery;</div><div>+ ?column? </div><div>+----------</div><div>+ f</div><div>+(1 row)</div><div>+</div><div>+-- empty array</div><div>+SELECT ARRAY[]::int8[] @@ '1'::anyquery;</div><div>+ ?column? </div><div>+----------</div><div>+ f</div><div>+(1 row)</div><div>+</div><div>+SELECT ARRAY[]::int8[] @@ '!1'::anyquery;</div><div>+ ?column? </div><div>+----------</div><div>+ t</div><div>+(1 row)</div><div>+</div><div>+-- parse errors</div><div>+SELECT ''::anyquery;</div><div>+ERROR: unexpected end of input in anyquery</div><div>+LINE 1: SELECT ''::anyquery;</div><div>+ ^</div><div>+SELECT '1 &'::anyquery;</div><div>+ERROR: unexpected end of input in anyquery</div><div>+LINE 1: SELECT '1 &'::anyquery;</div><div>+ ^</div><div>+SELECT '1 & (2'::anyquery;</div><div>+ERROR: missing closing parenthesis in anyquery</div><div>+LINE 1: SELECT '1 & (2'::anyquery;</div><div>+ ^</div><div>+SELECT '1 ) 2'::anyquery;</div><div>+ERROR: unexpected trailing input in anyquery</div><div>+LINE 1: SELECT '1 ) 2'::anyquery;</div><div>+ ^</div><div>+-- runtime errors: token cannot be parsed as element type</div><div>+SELECT ARRAY[1,2]::int8[] @@ 'abc'::anyquery;</div><div>+ERROR: invalid input syntax for type bigint: "abc"</div><div>+--</div><div>+-- Phase 3: GiST signature index</div><div>+--</div><div>+-- amvalidate: well-formed opclass</div><div>+SELECT amname, opcname FROM pg_opclass opc</div><div>+LEFT JOIN pg_am am ON am.oid = opcmethod</div><div>+WHERE opc.opcname = 'anyarray_gist_ops' AND NOT amvalidate(opc.oid);</div><div>+ amname | opcname </div><div>+--------+---------</div><div>+(0 rows)</div><div>+</div><div>+-- int8 GiST: build deterministic table</div><div>+CREATE TABLE anyarray_gist_int8 (id int, a int8[]);</div><div>+INSERT INTO anyarray_gist_int8 VALUES</div><div>+ (1, ARRAY[1,2,3]::int8[]),</div><div>+ (2, ARRAY[10,20,30]::int8[]),</div><div>+ (3, ARRAY[10,20,30]::int8[]),</div><div>+ (4, ARRAY[1,2,3,4,5]::int8[]),</div><div>+ (5, ARRAY[100,200]::int8[]);</div><div>+CREATE INDEX anyarray_gist_int8_idx</div><div>+ ON anyarray_gist_int8 USING gist(a anyarray_gist_ops);</div><div>+SET enable_seqscan = off;</div><div>+SELECT id FROM anyarray_gist_int8 WHERE a @> ARRAY[1,2]::int8[] ORDER BY id;</div><div>+ id </div><div>+----</div><div>+ 1</div><div>+ 4</div><div>+(2 rows)</div><div>+</div><div>+SELECT id FROM anyarray_gist_int8 WHERE a && ARRAY[10,200]::int8[] ORDER BY id;</div><div>+ id </div><div>+----</div><div>+ 2</div><div>+ 3</div><div>+ 5</div><div>+(3 rows)</div><div>+</div><div>+SELECT id FROM anyarray_gist_int8 WHERE a = ARRAY[10,20,30]::int8[] ORDER BY id;</div><div>+ id </div><div>+----</div><div>+ 2</div><div>+ 3</div><div>+(2 rows)</div><div>+</div><div>+SELECT id FROM anyarray_gist_int8 WHERE a <@ ARRAY[1,2,3,4,5]::int8[] ORDER BY id;</div><div>+ id </div><div>+----</div><div>+ 1</div><div>+ 4</div><div>+(2 rows)</div><div>+</div><div>+SELECT id FROM anyarray_gist_int8 WHERE a @@ '10 & 20'::anyquery ORDER BY id;</div><div>+ id </div><div>+----</div><div>+ 2</div><div>+ 3</div><div>+(2 rows)</div><div>+</div><div>+SELECT id FROM anyarray_gist_int8 WHERE a @@ '1 | 100'::anyquery ORDER BY id;</div><div>+ id </div><div>+----</div><div>+ 1</div><div>+ 4</div><div>+ 5</div><div>+(3 rows)</div><div>+</div><div>+SELECT id FROM anyarray_gist_int8 WHERE a @@ '!1 & 10'::anyquery ORDER BY id;</div><div>+ id </div><div>+----</div><div>+ 2</div><div>+ 3</div><div>+(2 rows)</div><div>+</div><div>+-- result consistency: GiST must agree with seqscan</div><div>+SELECT (</div><div>+ SELECT count(*) FROM anyarray_gist_int8 WHERE a @> ARRAY[10]::int8[]</div><div>+) = (</div><div>+ SELECT count(*) FROM (SELECT 1 FROM anyarray_gist_int8 WHERE a @> ARRAY[10]::int8[]) s</div><div>+) AS gist_matches_seqscan;</div><div>+ gist_matches_seqscan </div><div>+----------------------</div><div>+ t</div><div>+(1 row)</div><div>+</div><div>+RESET enable_seqscan;</div><div>+-- text GiST</div><div>+CREATE TABLE anyarray_gist_text (id int, a text[]);</div><div>+INSERT INTO anyarray_gist_text VALUES</div><div>+ (1, ARRAY['apple','banana']),</div><div>+ (2, ARRAY['banana','cherry']),</div><div>+ (3, ARRAY['apple','cherry','durian']);</div><div>+CREATE INDEX anyarray_gist_text_idx</div><div>+ ON anyarray_gist_text USING gist(a anyarray_gist_ops(siglen=64));</div><div>+SET enable_seqscan = off;</div><div>+SELECT id FROM anyarray_gist_text WHERE a @> ARRAY['apple']::text[] ORDER BY id;</div><div>+ id </div><div>+----</div><div>+ 1</div><div>+ 3</div><div>+(2 rows)</div><div>+</div><div>+SELECT id FROM anyarray_gist_text WHERE a @@ 'apple & cherry'::anyquery ORDER BY id;</div><div>+ id </div><div>+----</div><div>+ 3</div><div>+(1 row)</div><div>+</div><div>+RESET enable_seqscan;</div><div>+-- siglen bounds</div><div>+CREATE INDEX ON anyarray_gist_text USING gist(a anyarray_gist_ops(siglen=0));</div><div>+ERROR: value 0 out of bounds for option "siglen"</div><div>+DETAIL: Valid values are between "1" and "2024".</div><div>+CREATE INDEX ON anyarray_gist_text USING gist(a anyarray_gist_ops(siglen=8193));</div><div>+ERROR: value 8193 out of bounds for option "siglen"</div><div>+DETAIL: Valid values are between "1" and "2024".</div><div>+--</div><div>+-- Phase 4: GIN index (per-type, supports standard ops + @@)</div><div>+--</div><div>+-- amvalidate: all three opclasses</div><div>+SELECT amname, opcname FROM pg_opclass opc</div><div>+LEFT JOIN pg_am am ON am.oid = opcmethod</div><div>+WHERE opc.opcname LIKE '%anyquery_gin_ops' AND NOT amvalidate(opc.oid);</div><div>+ amname | opcname </div><div>+--------+---------</div><div>+(0 rows)</div><div>+</div><div>+-- int8 GIN</div><div>+CREATE TABLE anyarray_gin_int8 (id int, a int8[]);</div><div>+INSERT INTO anyarray_gin_int8 VALUES</div><div>+ (1, ARRAY[1,2,3]::int8[]),</div><div>+ (2, ARRAY[10,20,30]::int8[]),</div><div>+ (3, ARRAY[10,20,30]::int8[]),</div><div>+ (4, ARRAY[1,2,3,4,5]::int8[]),</div><div>+ (5, ARRAY[100,200]::int8[]);</div><div>+CREATE INDEX anyarray_gin_int8_idx</div><div>+ ON anyarray_gin_int8 USING gin(a int8_anyquery_gin_ops);</div><div>+SET enable_seqscan = off;</div><div>+SELECT id FROM anyarray_gin_int8 WHERE a @> ARRAY[1,2]::int8[] ORDER BY id;</div><div>+ id </div><div>+----</div><div>+ 1</div><div>+ 4</div><div>+(2 rows)</div><div>+</div><div>+SELECT id FROM anyarray_gin_int8 WHERE a && ARRAY[10,200]::int8[] ORDER BY id;</div><div>+ id </div><div>+----</div><div>+ 2</div><div>+ 3</div><div>+ 5</div><div>+(3 rows)</div><div>+</div><div>+SELECT id FROM anyarray_gin_int8 WHERE a @@ '10 & 20'::anyquery ORDER BY id;</div><div>+ id </div><div>+----</div><div>+ 2</div><div>+ 3</div><div>+(2 rows)</div><div>+</div><div>+SELECT id FROM anyarray_gin_int8 WHERE a @@ '(1 & 2) | 100'::anyquery ORDER BY id;</div><div>+ id </div><div>+----</div><div>+ 1</div><div>+ 4</div><div>+ 5</div><div>+(3 rows)</div><div>+</div><div>+SELECT id FROM anyarray_gin_int8 WHERE a @@ '!1'::anyquery ORDER BY id;</div><div>+ id </div><div>+----</div><div>+ 2</div><div>+ 3</div><div>+ 5</div><div>+(3 rows)</div><div>+</div><div>+-- uuid GIN</div><div>+CREATE TABLE anyarray_gin_uuid (id int, a uuid[]);</div><div>+INSERT INTO anyarray_gin_uuid VALUES</div><div>+ (1, ARRAY['11111111-1111-1111-1111-111111111111'::uuid,</div><div>+ '22222222-2222-2222-2222-222222222222']),</div><div>+ (2, ARRAY['33333333-3333-3333-3333-333333333333'::uuid]),</div><div>+ (3, ARRAY['11111111-1111-1111-1111-111111111111'::uuid,</div><div>+ '33333333-3333-3333-3333-333333333333']);</div><div>+CREATE INDEX anyarray_gin_uuid_idx</div><div>+ ON anyarray_gin_uuid USING gin(a uuid_anyquery_gin_ops);</div><div>+SET enable_seqscan = off;</div><div>+SELECT id FROM anyarray_gin_uuid</div><div>+ WHERE a @@ '11111111-1111-1111-1111-111111111111 & 22222222-2222-2222-2222-222222222222'::anyquery</div><div>+ ORDER BY id;</div><div>+ id </div><div>+----</div><div>+ 1</div><div>+(1 row)</div><div>+</div><div>+SELECT id FROM anyarray_gin_uuid</div><div>+ WHERE a @> ARRAY['11111111-1111-1111-1111-111111111111'::uuid]</div><div>+ ORDER BY id;</div><div>+ id </div><div>+----</div><div>+ 1</div><div>+ 3</div><div>+(2 rows)</div><div>+</div><div>+RESET enable_seqscan;</div><div>+-- text GIN</div><div>+CREATE TABLE anyarray_gin_text (id int, a text[]);</div><div>+INSERT INTO anyarray_gin_text VALUES</div><div>+ (1, ARRAY['apple','banana']),</div><div>+ (2, ARRAY['banana','cherry']),</div><div>+ (3, ARRAY['apple','cherry','durian']);</div><div>+CREATE INDEX anyarray_gin_text_idx</div><div>+ ON anyarray_gin_text USING gin(a text_anyquery_gin_ops);</div><div>+SET enable_seqscan = off;</div><div>+SELECT id FROM anyarray_gin_text WHERE a @@ 'apple & cherry'::anyquery ORDER BY id;</div><div>+ id </div><div>+----</div><div>+ 3</div><div>+(1 row)</div><div>+</div><div>+SELECT id FROM anyarray_gin_text WHERE a @@ '"durian"'::anyquery ORDER BY id;</div><div>+ id </div><div>+----</div><div>+ 3</div><div>+(1 row)</div><div>+</div><div>+SELECT id FROM anyarray_gin_text WHERE a @@ 'apple | grape'::anyquery ORDER BY id;</div><div>+ id </div><div>+----</div><div>+ 1</div><div>+ 3</div><div>+(2 rows)</div><div>+</div><div>+SELECT id FROM anyarray_gin_text WHERE a @@ '!banana'::anyquery ORDER BY id;</div><div>+ id </div><div>+----</div><div>+ 3</div><div>+(1 row)</div><div>+</div><div>+RESET enable_seqscan;</div><div>+--</div><div>+-- Phase 5: cross-AM consistency and edge cases</div><div>+--</div><div>+-- Build a larger deterministic int8 table and index it with both GiST and</div><div>+-- GIN. Then run each candidate query under seqscan, GiST and GIN and check</div><div>+-- that all three plans return the same row set.</div><div>+--</div><div>+CREATE TABLE anyarray_xcheck (id int, a int8[]);</div><div>+INSERT INTO anyarray_xcheck</div><div>+SELECT g,</div><div>+ ARRAY[(g % 7)::int8, ((g * 3) % 11)::int8, ((g + 1) % 5)::int8]</div><div>+FROM generate_series(1, 200) g;</div><div>+-- a few hand-picked rows to exercise common values</div><div>+INSERT INTO anyarray_xcheck VALUES</div><div>+ (1001, ARRAY[1,2,3,4,5]::int8[]),</div><div>+ (1002, ARRAY[1,2,3,4,5]::int8[]),</div><div>+ (1003, ARRAY[100,200,300]::int8[]),</div><div>+ (1004, ARRAY[]::int8[]),</div><div>+ (1005, NULL);</div><div>+CREATE INDEX anyarray_xcheck_gist ON anyarray_xcheck</div><div>+ USING gist(a anyarray_gist_ops);</div><div>+CREATE INDEX anyarray_xcheck_gin ON anyarray_xcheck</div><div>+ USING gin(a int8_anyquery_gin_ops);</div><div>+-- Run each predicate three times: pure seqscan, GiST-only, GIN-only.</div><div>+-- All three result sets must match.</div><div>+CREATE FUNCTION anyarray_xcheck_match(pred text)</div><div>+RETURNS TABLE(gist_ok bool, gin_ok bool)</div><div>+LANGUAGE plpgsql AS $$</div><div>+DECLARE</div><div>+ seq int[]; gst int[]; gin int[];</div><div>+BEGIN</div><div>+ SET LOCAL enable_seqscan = on;</div><div>+ SET LOCAL enable_indexscan = off;</div><div>+ SET LOCAL enable_bitmapscan = off;</div><div>+ EXECUTE 'SELECT array_agg(id ORDER BY id) FROM anyarray_xcheck WHERE '</div><div>+ || pred INTO seq;</div><div>+</div><div>+ -- Force GiST by hiding the GIN index.</div><div>+ ALTER INDEX anyarray_xcheck_gin SET (fastupdate = off);</div><div>+ SET LOCAL enable_seqscan = off;</div><div>+ SET LOCAL enable_indexscan = on;</div><div>+ SET LOCAL enable_bitmapscan = on;</div><div>+ DROP INDEX anyarray_xcheck_gin;</div><div>+ EXECUTE 'SELECT array_agg(id ORDER BY id) FROM anyarray_xcheck WHERE '</div><div>+ || pred INTO gst;</div><div>+ CREATE INDEX anyarray_xcheck_gin ON anyarray_xcheck</div><div>+ USING gin(a int8_anyquery_gin_ops);</div><div>+</div><div>+ -- Force GIN by hiding GiST.</div><div>+ DROP INDEX anyarray_xcheck_gist;</div><div>+ EXECUTE 'SELECT array_agg(id ORDER BY id) FROM anyarray_xcheck WHERE '</div><div>+ || pred INTO gin;</div><div>+ CREATE INDEX anyarray_xcheck_gist ON anyarray_xcheck</div><div>+ USING gist(a anyarray_gist_ops);</div><div>+</div><div>+ gist_ok := seq IS NOT DISTINCT FROM gst;</div><div>+ gin_ok := seq IS NOT DISTINCT FROM gin;</div><div>+ RETURN NEXT;</div><div>+END $$;</div><div>+SELECT pred, gist_ok, gin_ok FROM (VALUES</div><div>+ ('a @> ARRAY[1,2]::int8[]'),</div><div>+ ('a @> ARRAY[100,200]::int8[]'),</div><div>+ ('a && ARRAY[5,99]::int8[]'),</div><div>+ ('a = ARRAY[1,2,3,4,5]::int8[]'),</div><div>+ ('a @@ ''1 & 2''::anyquery'),</div><div>+ ('a @@ ''100 | 999''::anyquery'),</div><div>+ ('a @@ ''!1 & 2''::anyquery')</div><div>+) v(pred), LATERAL anyarray_xcheck_match(pred);</div><div>+ pred | gist_ok | gin_ok </div><div>+------------------------------+---------+--------</div><div>+ a @> ARRAY[1,2]::int8[] | t | t</div><div>+ a @> ARRAY[100,200]::int8[] | t | t</div><div>+ a && ARRAY[5,99]::int8[] | t | t</div><div>+ a = ARRAY[1,2,3,4,5]::int8[] | t | t</div><div>+ a @@ '1 & 2'::anyquery | t | t</div><div>+ a @@ '100 | 999'::anyquery | t | t</div><div>+ a @@ '!1 & 2'::anyquery | t | t</div><div>+(7 rows)</div><div>+</div><div>+-- Edge cases: empty / NULL arrays via index</div><div>+SET enable_seqscan = off;</div><div>+SELECT id FROM anyarray_xcheck WHERE a @> ARRAY[]::int8[] AND id = 1004;</div><div>+ id </div><div>+------</div><div>+ 1004</div><div>+(1 row)</div><div>+</div><div>+SELECT id FROM anyarray_xcheck WHERE a <@ ARRAY[1,2,3]::int8[] AND id < 100 ORDER BY id LIMIT 3;</div><div>+ id </div><div>+----</div><div>+ 1</div><div>+ 15</div><div>+ 30</div><div>+(3 rows)</div><div>+</div><div>+SELECT count(*) FROM anyarray_xcheck WHERE a IS NULL;</div><div>+ count </div><div>+-------</div><div>+ 1</div><div>+(1 row)</div><div>+</div><div>+RESET enable_seqscan;</div><div>+-- DELETE / VACUUM / re-query through index</div><div>+DELETE FROM anyarray_xcheck WHERE id BETWEEN 1 AND 50;</div><div>+VACUUM anyarray_xcheck;</div><div>+SET enable_seqscan = off;</div><div>+SELECT count(*) FROM anyarray_xcheck WHERE a @@ '1 | 2'::anyquery;</div><div>+ count </div><div>+-------</div><div>+ 98</div><div>+(1 row)</div><div>+</div><div>+RESET enable_seqscan;</div><div>+DROP FUNCTION anyarray_xcheck_match(text);</div><div>diff --git a/contrib/anyarray/meson.build b/contrib/anyarray/meson.build</div><div>new file mode 100644</div><div>index 00000000000..a20646b82b2</div><div>--- /dev/null</div><div>+++ b/contrib/anyarray/meson.build</div><div>@@ -0,0 +1,38 @@</div><div>+# Copyright (c) 2026, PostgreSQL Global Development Group</div><div>+</div><div>+anyarray_sources = files(</div><div>+ 'anyarray.c',</div><div>+ 'anyarray_bool.c',</div><div>+ 'anyarray_gin.c',</div><div>+ 'anyarray_gist.c',</div><div>+ 'anyarray_op.c',</div><div>+)</div><div>+</div><div>+if host_system == 'windows'</div><div>+ anyarray_sources += rc_lib_gen.process(win32ver_rc, extra_args: [</div><div>+ '--NAME', 'anyarray',</div><div>+ '--FILEDESC', 'anyarray - operations and indexes for arrays of any type',])</div><div>+endif</div><div>+</div><div>+anyarray = shared_module('anyarray',</div><div>+ anyarray_sources,</div><div>+ kwargs: contrib_mod_args,</div><div>+)</div><div>+contrib_targets += anyarray</div><div>+</div><div>+install_data(</div><div>+ 'anyarray--1.0.sql',</div><div>+ 'anyarray.control',</div><div>+ kwargs: contrib_data_args,</div><div>+)</div><div>+</div><div>+tests += {<!-- --></div><div>+ 'name': 'anyarray',</div><div>+ 'sd': meson.current_source_dir(),</div><div>+ 'bd': meson.current_build_dir(),</div><div>+ 'regress': {<!-- --></div><div>+ 'sql': [</div><div>+ 'anyarray',</div><div>+ ],</div><div>+ },</div><div>+}</div><div>diff --git a/contrib/anyarray/sql/anyarray.sql b/contrib/anyarray/sql/anyarray.sql</div><div>new file mode 100644</div><div>index 00000000000..90cc061fbde</div><div>--- /dev/null</div><div>+++ b/contrib/anyarray/sql/anyarray.sql</div><div>@@ -0,0 +1,330 @@</div><div>+CREATE EXTENSION anyarray;</div><div>+</div><div>+--</div><div>+-- Phase 1: set operations, helpers and operators for int8, uuid, text</div><div>+--</div><div>+</div><div>+-- ===== int8 =====</div><div>+SELECT anyarray_sort(ARRAY[3, 1, 2, 1]::int8[]);</div><div>+SELECT anyarray_sort(ARRAY[3, 1, 2, 1]::int8[], 'desc');</div><div>+SELECT anyarray_sort(ARRAY[]::int8[]);</div><div>+SELECT anyarray_uniq(ARRAY[1, 1, 2, 3, 3, 2]::int8[]);</div><div>+SELECT anyarray_idx(ARRAY[10, 20, 30]::int8[], 20::int8);</div><div>+SELECT anyarray_idx(ARRAY[10, 20, 30]::int8[], 99::int8);</div><div>+SELECT anyarray_subarray(ARRAY[1,2,3,4,5]::int8[], 2, 2);</div><div>+SELECT anyarray_subarray(ARRAY[1,2,3,4,5]::int8[], 3);</div><div>+SELECT anyarray_subarray(ARRAY[1,2,3]::int8[], 5, 1);</div><div>+</div><div>+SELECT ARRAY[1,2,3]::int8[] & ARRAY[2,3,4]::int8[];</div><div>+SELECT ARRAY[1,2,3]::int8[] | ARRAY[3,4,5]::int8[];</div><div>+SELECT ARRAY[1,2,3]::int8[] | 4::int8;</div><div>+SELECT ARRAY[1,2,3,4]::int8[] - ARRAY[2,4]::int8[];</div><div>+SELECT ARRAY[1,2,2,3,2]::int8[] - 2::int8;</div><div>+SELECT # ARRAY[10,20,30]::int8[];</div><div>+SELECT ARRAY[10,20,30]::int8[] # 20::int8;</div><div>+</div><div>+-- ===== uuid =====</div><div>+SELECT anyarray_sort(ARRAY[</div><div>+ '00000000-0000-0000-0000-000000000003'::uuid,</div><div>+ '00000000-0000-0000-0000-000000000001',</div><div>+ '00000000-0000-0000-0000-000000000002']);</div><div>+SELECT anyarray_uniq(ARRAY[</div><div>+ '00000000-0000-0000-0000-000000000001'::uuid,</div><div>+ '00000000-0000-0000-0000-000000000001',</div><div>+ '00000000-0000-0000-0000-000000000002']);</div><div>+SELECT anyarray_idx(ARRAY[</div><div>+ '00000000-0000-0000-0000-000000000001'::uuid,</div><div>+ '00000000-0000-0000-0000-000000000002'],</div><div>+ '00000000-0000-0000-0000-000000000002'::uuid);</div><div>+</div><div>+SELECT ARRAY['00000000-0000-0000-0000-000000000001'::uuid,</div><div>+ '00000000-0000-0000-0000-000000000002']</div><div>+ & ARRAY['00000000-0000-0000-0000-000000000002'::uuid,</div><div>+ '00000000-0000-0000-0000-000000000003'];</div><div>+</div><div>+SELECT ARRAY['00000000-0000-0000-0000-000000000001'::uuid]</div><div>+ | '00000000-0000-0000-0000-000000000002'::uuid;</div><div>+</div><div>+-- ===== text =====</div><div>+SELECT anyarray_sort(ARRAY['banana','apple','cherry']);</div><div>+SELECT anyarray_sort(ARRAY['banana','apple','cherry'], 'desc');</div><div>+SELECT anyarray_uniq(ARRAY['a','b','a','c','b']);</div><div>+SELECT anyarray_idx(ARRAY['a','b','c'], 'b'::text);</div><div>+SELECT ARRAY['a','b','c']::text[] & ARRAY['b','c','d']::text[];</div><div>+SELECT ARRAY['a','b','c']::text[] | 'd'::text;</div><div>+SELECT ARRAY['a','b','c','b']::text[] - 'b'::text;</div><div>+</div><div>+-- ===== error cases =====</div><div>+-- multi-dim</div><div>+SELECT anyarray_sort(ARRAY[[1,2],[3,4]]::int8[]);</div><div>+-- nulls</div><div>+SELECT anyarray_sort(ARRAY[1, NULL, 2]::int8[]);</div><div>+-- bad direction</div><div>+SELECT anyarray_sort(ARRAY[1,2,3]::int8[], 'bogus');</div><div>+</div><div>+--</div><div>+-- Phase 2: anyquery and @@ operator</div><div>+--</div><div>+</div><div>+-- parsing + output round-trip</div><div>+SELECT '1 & 2'::anyquery::text;</div><div>+SELECT '1 & 2 | 3'::anyquery::text;</div><div>+SELECT '(1 | 2) & 3'::anyquery::text;</div><div>+SELECT '!1 & 2'::anyquery::text;</div><div>+SELECT '!(1 | 2) & 3'::anyquery::text;</div><div>+SELECT '"hello world" | foo'::anyquery::text;</div><div>+SELECT anyquery_querytree('1 & 2 | 3'::anyquery);</div><div>+</div><div>+-- int8 matching</div><div>+SELECT ARRAY[1,2,3]::int8[] @@ '1'::anyquery;</div><div>+SELECT ARRAY[1,2,3]::int8[] @@ '1 & 2'::anyquery;</div><div>+SELECT ARRAY[1,2,3]::int8[] @@ '1 & 4'::anyquery;</div><div>+SELECT ARRAY[1,2,3]::int8[] @@ '1 | 4'::anyquery;</div><div>+SELECT ARRAY[1,2,3]::int8[] @@ '!4'::anyquery;</div><div>+SELECT ARRAY[1,2,3]::int8[] @@ '!1'::anyquery;</div><div>+SELECT ARRAY[1,2,3]::int8[] @@ '!4 & 1'::anyquery;</div><div>+SELECT ARRAY[1,2,3]::int8[] @@ '(1 | 4) & (2 | 5)'::anyquery;</div><div>+</div><div>+-- commutator</div><div>+SELECT '1 & 2'::anyquery ~~ ARRAY[1,2,3]::int8[];</div><div>+SELECT '1 & 4'::anyquery ~~ ARRAY[1,2,3]::int8[];</div><div>+</div><div>+-- uuid</div><div>+SELECT ARRAY['00000000-0000-0000-0000-000000000001'::uuid,</div><div>+ '00000000-0000-0000-0000-000000000002']</div><div>+ @@ '00000000-0000-0000-0000-000000000001 & 00000000-0000-0000-0000-000000000002'::anyquery;</div><div>+SELECT ARRAY['00000000-0000-0000-0000-000000000001'::uuid]</div><div>+ @@ '00000000-0000-0000-0000-000000000002'::anyquery;</div><div>+</div><div>+-- text</div><div>+SELECT ARRAY['apple','banana','cherry']::text[] @@ 'apple & banana'::anyquery;</div><div>+SELECT ARRAY['apple','banana','cherry']::text[] @@ '"banana" | grape'::anyquery;</div><div>+SELECT ARRAY['apple','banana','cherry']::text[] @@ 'durian | grape'::anyquery;</div><div>+</div><div>+-- empty array</div><div>+SELECT ARRAY[]::int8[] @@ '1'::anyquery;</div><div>+SELECT ARRAY[]::int8[] @@ '!1'::anyquery;</div><div>+</div><div>+-- parse errors</div><div>+SELECT ''::anyquery;</div><div>+SELECT '1 &'::anyquery;</div><div>+SELECT '1 & (2'::anyquery;</div><div>+SELECT '1 ) 2'::anyquery;</div><div>+</div><div>+-- runtime errors: token cannot be parsed as element type</div><div>+SELECT ARRAY[1,2]::int8[] @@ 'abc'::anyquery;</div><div>+</div><div>+--</div><div>+-- Phase 3: GiST signature index</div><div>+--</div><div>+</div><div>+-- amvalidate: well-formed opclass</div><div>+SELECT amname, opcname FROM pg_opclass opc</div><div>+LEFT JOIN pg_am am ON am.oid = opcmethod</div><div>+WHERE opc.opcname = 'anyarray_gist_ops' AND NOT amvalidate(opc.oid);</div><div>+</div><div>+-- int8 GiST: build deterministic table</div><div>+CREATE TABLE anyarray_gist_int8 (id int, a int8[]);</div><div>+INSERT INTO anyarray_gist_int8 VALUES</div><div>+ (1, ARRAY[1,2,3]::int8[]),</div><div>+ (2, ARRAY[10,20,30]::int8[]),</div><div>+ (3, ARRAY[10,20,30]::int8[]),</div><div>+ (4, ARRAY[1,2,3,4,5]::int8[]),</div><div>+ (5, ARRAY[100,200]::int8[]);</div><div>+</div><div>+CREATE INDEX anyarray_gist_int8_idx</div><div>+ ON anyarray_gist_int8 USING gist(a anyarray_gist_ops);</div><div>+</div><div>+SET enable_seqscan = off;</div><div>+</div><div>+SELECT id FROM anyarray_gist_int8 WHERE a @> ARRAY[1,2]::int8[] ORDER BY id;</div><div>+SELECT id FROM anyarray_gist_int8 WHERE a && ARRAY[10,200]::int8[] ORDER BY id;</div><div>+SELECT id FROM anyarray_gist_int8 WHERE a = ARRAY[10,20,30]::int8[] ORDER BY id;</div><div>+SELECT id FROM anyarray_gist_int8 WHERE a <@ ARRAY[1,2,3,4,5]::int8[] ORDER BY id;</div><div>+SELECT id FROM anyarray_gist_int8 WHERE a @@ '10 & 20'::anyquery ORDER BY id;</div><div>+SELECT id FROM anyarray_gist_int8 WHERE a @@ '1 | 100'::anyquery ORDER BY id;</div><div>+SELECT id FROM anyarray_gist_int8 WHERE a @@ '!1 & 10'::anyquery ORDER BY id;</div><div>+</div><div>+-- result consistency: GiST must agree with seqscan</div><div>+SELECT (</div><div>+ SELECT count(*) FROM anyarray_gist_int8 WHERE a @> ARRAY[10]::int8[]</div><div>+) = (</div><div>+ SELECT count(*) FROM (SELECT 1 FROM anyarray_gist_int8 WHERE a @> ARRAY[10]::int8[]) s</div><div>+) AS gist_matches_seqscan;</div><div>+</div><div>+RESET enable_seqscan;</div><div>+</div><div>+-- text GiST</div><div>+CREATE TABLE anyarray_gist_text (id int, a text[]);</div><div>+INSERT INTO anyarray_gist_text VALUES</div><div>+ (1, ARRAY['apple','banana']),</div><div>+ (2, ARRAY['banana','cherry']),</div><div>+ (3, ARRAY['apple','cherry','durian']);</div><div>+</div><div>+CREATE INDEX anyarray_gist_text_idx</div><div>+ ON anyarray_gist_text USING gist(a anyarray_gist_ops(siglen=64));</div><div>+</div><div>+SET enable_seqscan = off;</div><div>+SELECT id FROM anyarray_gist_text WHERE a @> ARRAY['apple']::text[] ORDER BY id;</div><div>+SELECT id FROM anyarray_gist_text WHERE a @@ 'apple & cherry'::anyquery ORDER BY id;</div><div>+RESET enable_seqscan;</div><div>+</div><div>+-- siglen bounds</div><div>+CREATE INDEX ON anyarray_gist_text USING gist(a anyarray_gist_ops(siglen=0));</div><div>+CREATE INDEX ON anyarray_gist_text USING gist(a anyarray_gist_ops(siglen=8193));</div><div>+</div><div>+--</div><div>+-- Phase 4: GIN index (per-type, supports standard ops + @@)</div><div>+--</div><div>+</div><div>+-- amvalidate: all three opclasses</div><div>+SELECT amname, opcname FROM pg_opclass opc</div><div>+LEFT JOIN pg_am am ON am.oid = opcmethod</div><div>+WHERE opc.opcname LIKE '%anyquery_gin_ops' AND NOT amvalidate(opc.oid);</div><div>+</div><div>+-- int8 GIN</div><div>+CREATE TABLE anyarray_gin_int8 (id int, a int8[]);</div><div>+INSERT INTO anyarray_gin_int8 VALUES</div><div>+ (1, ARRAY[1,2,3]::int8[]),</div><div>+ (2, ARRAY[10,20,30]::int8[]),</div><div>+ (3, ARRAY[10,20,30]::int8[]),</div><div>+ (4, ARRAY[1,2,3,4,5]::int8[]),</div><div>+ (5, ARRAY[100,200]::int8[]);</div><div>+</div><div>+CREATE INDEX anyarray_gin_int8_idx</div><div>+ ON anyarray_gin_int8 USING gin(a int8_anyquery_gin_ops);</div><div>+</div><div>+SET enable_seqscan = off;</div><div>+SELECT id FROM anyarray_gin_int8 WHERE a @> ARRAY[1,2]::int8[] ORDER BY id;</div><div>+SELECT id FROM anyarray_gin_int8 WHERE a && ARRAY[10,200]::int8[] ORDER BY id;</div><div>+SELECT id FROM anyarray_gin_int8 WHERE a @@ '10 & 20'::anyquery ORDER BY id;</div><div>+SELECT id FROM anyarray_gin_int8 WHERE a @@ '(1 & 2) | 100'::anyquery ORDER BY id;</div><div>+SELECT id FROM anyarray_gin_int8 WHERE a @@ '!1'::anyquery ORDER BY id;</div><div>+</div><div>+-- uuid GIN</div><div>+CREATE TABLE anyarray_gin_uuid (id int, a uuid[]);</div><div>+INSERT INTO anyarray_gin_uuid VALUES</div><div>+ (1, ARRAY['11111111-1111-1111-1111-111111111111'::uuid,</div><div>+ '22222222-2222-2222-2222-222222222222']),</div><div>+ (2, ARRAY['33333333-3333-3333-3333-333333333333'::uuid]),</div><div>+ (3, ARRAY['11111111-1111-1111-1111-111111111111'::uuid,</div><div>+ '33333333-3333-3333-3333-333333333333']);</div><div>+</div><div>+CREATE INDEX anyarray_gin_uuid_idx</div><div>+ ON anyarray_gin_uuid USING gin(a uuid_anyquery_gin_ops);</div><div>+</div><div>+SET enable_seqscan = off;</div><div>+SELECT id FROM anyarray_gin_uuid</div><div>+ WHERE a @@ '11111111-1111-1111-1111-111111111111 & 22222222-2222-2222-2222-222222222222'::anyquery</div><div>+ ORDER BY id;</div><div>+SELECT id FROM anyarray_gin_uuid</div><div>+ WHERE a @> ARRAY['11111111-1111-1111-1111-111111111111'::uuid]</div><div>+ ORDER BY id;</div><div>+RESET enable_seqscan;</div><div>+</div><div>+-- text GIN</div><div>+CREATE TABLE anyarray_gin_text (id int, a text[]);</div><div>+INSERT INTO anyarray_gin_text VALUES</div><div>+ (1, ARRAY['apple','banana']),</div><div>+ (2, ARRAY['banana','cherry']),</div><div>+ (3, ARRAY['apple','cherry','durian']);</div><div>+</div><div>+CREATE INDEX anyarray_gin_text_idx</div><div>+ ON anyarray_gin_text USING gin(a text_anyquery_gin_ops);</div><div>+</div><div>+SET enable_seqscan = off;</div><div>+SELECT id FROM anyarray_gin_text WHERE a @@ 'apple & cherry'::anyquery ORDER BY id;</div><div>+SELECT id FROM anyarray_gin_text WHERE a @@ '"durian"'::anyquery ORDER BY id;</div><div>+SELECT id FROM anyarray_gin_text WHERE a @@ 'apple | grape'::anyquery ORDER BY id;</div><div>+SELECT id FROM anyarray_gin_text WHERE a @@ '!banana'::anyquery ORDER BY id;</div><div>+RESET enable_seqscan;</div><div>+</div><div>+--</div><div>+-- Phase 5: cross-AM consistency and edge cases</div><div>+--</div><div>+-- Build a larger deterministic int8 table and index it with both GiST and</div><div>+-- GIN. Then run each candidate query under seqscan, GiST and GIN and check</div><div>+-- that all three plans return the same row set.</div><div>+--</div><div>+</div><div>+CREATE TABLE anyarray_xcheck (id int, a int8[]);</div><div>+</div><div>+INSERT INTO anyarray_xcheck</div><div>+SELECT g,</div><div>+ ARRAY[(g % 7)::int8, ((g * 3) % 11)::int8, ((g + 1) % 5)::int8]</div><div>+FROM generate_series(1, 200) g;</div><div>+-- a few hand-picked rows to exercise common values</div><div>+INSERT INTO anyarray_xcheck VALUES</div><div>+ (1001, ARRAY[1,2,3,4,5]::int8[]),</div><div>+ (1002, ARRAY[1,2,3,4,5]::int8[]),</div><div>+ (1003, ARRAY[100,200,300]::int8[]),</div><div>+ (1004, ARRAY[]::int8[]),</div><div>+ (1005, NULL);</div><div>+</div><div>+CREATE INDEX anyarray_xcheck_gist ON anyarray_xcheck</div><div>+ USING gist(a anyarray_gist_ops);</div><div>+CREATE INDEX anyarray_xcheck_gin ON anyarray_xcheck</div><div>+ USING gin(a int8_anyquery_gin_ops);</div><div>+</div><div>+-- Run each predicate three times: pure seqscan, GiST-only, GIN-only.</div><div>+-- All three result sets must match.</div><div>+CREATE FUNCTION anyarray_xcheck_match(pred text)</div><div>+RETURNS TABLE(gist_ok bool, gin_ok bool)</div><div>+LANGUAGE plpgsql AS $$</div><div>+DECLARE</div><div>+ seq int[]; gst int[]; gin int[];</div><div>+BEGIN</div><div>+ SET LOCAL enable_seqscan = on;</div><div>+ SET LOCAL enable_indexscan = off;</div><div>+ SET LOCAL enable_bitmapscan = off;</div><div>+ EXECUTE 'SELECT array_agg(id ORDER BY id) FROM anyarray_xcheck WHERE '</div><div>+ || pred INTO seq;</div><div>+</div><div>+ -- Force GiST by hiding the GIN index.</div><div>+ ALTER INDEX anyarray_xcheck_gin SET (fastupdate = off);</div><div>+ SET LOCAL enable_seqscan = off;</div><div>+ SET LOCAL enable_indexscan = on;</div><div>+ SET LOCAL enable_bitmapscan = on;</div><div>+ DROP INDEX anyarray_xcheck_gin;</div><div>+ EXECUTE 'SELECT array_agg(id ORDER BY id) FROM anyarray_xcheck WHERE '</div><div>+ || pred INTO gst;</div><div>+ CREATE INDEX anyarray_xcheck_gin ON anyarray_xcheck</div><div>+ USING gin(a int8_anyquery_gin_ops);</div><div>+</div><div>+ -- Force GIN by hiding GiST.</div><div>+ DROP INDEX anyarray_xcheck_gist;</div><div>+ EXECUTE 'SELECT array_agg(id ORDER BY id) FROM anyarray_xcheck WHERE '</div><div>+ || pred INTO gin;</div><div>+ CREATE INDEX anyarray_xcheck_gist ON anyarray_xcheck</div><div>+ USING gist(a anyarray_gist_ops);</div><div>+</div><div>+ gist_ok := seq IS NOT DISTINCT FROM gst;</div><div>+ gin_ok := seq IS NOT DISTINCT FROM gin;</div><div>+ RETURN NEXT;</div><div>+END $$;</div><div>+</div><div>+SELECT pred, gist_ok, gin_ok FROM (VALUES</div><div>+ ('a @> ARRAY[1,2]::int8[]'),</div><div>+ ('a @> ARRAY[100,200]::int8[]'),</div><div>+ ('a && ARRAY[5,99]::int8[]'),</div><div>+ ('a = ARRAY[1,2,3,4,5]::int8[]'),</div><div>+ ('a @@ ''1 & 2''::anyquery'),</div><div>+ ('a @@ ''100 | 999''::anyquery'),</div><div>+ ('a @@ ''!1 & 2''::anyquery')</div><div>+) v(pred), LATERAL anyarray_xcheck_match(pred);</div><div>+</div><div>+-- Edge cases: empty / NULL arrays via index</div><div>+SET enable_seqscan = off;</div><div>+SELECT id FROM anyarray_xcheck WHERE a @> ARRAY[]::int8[] AND id = 1004;</div><div>+SELECT id FROM anyarray_xcheck WHERE a <@ ARRAY[1,2,3]::int8[] AND id < 100 ORDER BY id LIMIT 3;</div><div>+SELECT count(*) FROM anyarray_xcheck WHERE a IS NULL;</div><div>+RESET enable_seqscan;</div><div>+</div><div>+-- DELETE / VACUUM / re-query through index</div><div>+DELETE FROM anyarray_xcheck WHERE id BETWEEN 1 AND 50;</div><div>+VACUUM anyarray_xcheck;</div><div>+SET enable_seqscan = off;</div><div>+SELECT count(*) FROM anyarray_xcheck WHERE a @@ '1 | 2'::anyquery;</div><div>+RESET enable_seqscan;</div><div>+</div><div>+DROP FUNCTION anyarray_xcheck_match(text);</div><div>diff --git a/contrib/meson.build b/contrib/meson.build</div><div>index ebb7f83d8c5..e158f18fa7e 100644</div><div>--- a/contrib/meson.build</div><div>+++ b/contrib/meson.build</div><div>@@ -13,6 +13,7 @@ contrib_doc_args = {<!-- --></div><div> }</div><div> </div><div> subdir('amcheck')</div><div>+subdir('anyarray')</div><div> subdir('auth_delay')</div><div> subdir('auto_explain')</div><div> subdir('basic_archive')</div><div>diff --git a/doc/src/sgml/anyarray.sgml b/doc/src/sgml/anyarray.sgml</div><div>new file mode 100644</div><div>index 00000000000..4f376e0868c</div><div>--- /dev/null</div><div>+++ b/doc/src/sgml/anyarray.sgml</div><div>@@ -0,0 +1,482 @@</div><div>+<!-- doc/src/sgml/anyarray.sgml --></div><div>+</div><div>+<sect1 id="anyarray" xreflabel="anyarray"></div><div>+ <title>anyarray &mdash; operations and indexes for arrays of any type</title></div><div>+</div><div>+ <indexterm zone="anyarray"></div><div>+ <primary>anyarray</primary></div><div>+ </indexterm></div><div>+</div><div>+ <para></div><div>+ The <filename>anyarray</filename> module generalizes</div><div>+ <xref linkend="intarray"/>-style operations to arrays of any element type</div><div>+ that provides a default B-tree opclass. It adds set-style helpers, a</div><div>+ textual boolean query type (<type>anyquery</type>), and index support that</div><div>+ works for arbitrary element types.</div><div>+ </para></div><div>+</div><div>+ <para></div><div>+ All operations reject arrays that contain <literal>NULL</literal> elements,</div><div>+ and they are defined only for one-dimensional arrays.</div><div>+ </para></div><div>+</div><div>+ <para></div><div>+ This module is not marked as <quote>trusted</quote>: installing it</div><div>+ requires database-level privileges, because the index operator classes</div><div>+ it defines refer to internal storage types.</div><div>+ </para></div><div>+</div><div>+ <sect2 id="anyarray-funcs-ops"></div><div>+ <title><filename>anyarray</filename> Functions and Operators</title></div><div>+</div><div>+ <para></div><div>+ The functions provided by <filename>anyarray</filename> are listed in</div><div>+ <xref linkend="anyarray-func-table"/>, the operators in</div><div>+ <xref linkend="anyarray-op-table"/>.</div><div>+ </para></div><div>+</div><div>+ <table id="anyarray-func-table"></div><div>+ <title><filename>anyarray</filename> Functions</title></div><div>+ <tgroup cols="1"></div><div>+ <thead></div><div>+ <row></div><div>+ <entry role="func_table_entry"><para role="func_signature"></div><div>+ Function</div><div>+ </para></div><div>+ <para></div><div>+ Description</div><div>+ </para></div><div>+ <para></div><div>+ Example(s)</div><div>+ </para></entry></div><div>+ </row></div><div>+ </thead></div><div>+</div><div>+ <tbody></div><div>+ <row></div><div>+ <entry role="func_table_entry"><para role="func_signature"></div><div>+ <function>anyarray_sort</function> ( <type>anyarray</type> [, <type>text</type> ] )</div><div>+ <returnvalue>anyarray</returnvalue></div><div>+ </para></div><div>+ <para></div><div>+ Sorts an array. The optional second argument is</div><div>+ <literal>asc</literal> (default) or <literal>desc</literal>.</div><div>+ </para></div><div>+ <para></div><div>+ <literal>anyarray_sort('{3,1,2}'::int8[])</literal></div><div>+ <returnvalue>{1,2,3}</returnvalue></div><div>+ </para></entry></div><div>+ </row></div><div>+</div><div>+ <row></div><div>+ <entry role="func_table_entry"><para role="func_signature"></div><div>+ <function>anyarray_uniq</function> ( <type>anyarray</type> )</div><div>+ <returnvalue>anyarray</returnvalue></div><div>+ </para></div><div>+ <para></div><div>+ Removes duplicates from the array, returning the sorted unique</div><div>+ elements.</div><div>+ </para></div><div>+ <para></div><div>+ <literal>anyarray_uniq('{1,1,2,3,3}'::int8[])</literal></div><div>+ <returnvalue>{1,2,3}</returnvalue></div><div>+ </para></entry></div><div>+ </row></div><div>+</div><div>+ <row></div><div>+ <entry role="func_table_entry"><para role="func_signature"></div><div>+ <function>anyarray_idx</function> ( <type>anyarray</type>, <type>anyelement</type> )</div><div>+ <returnvalue>integer</returnvalue></div><div>+ </para></div><div>+ <para></div><div>+ Returns the 1-based index of the first occurrence of the element,</div><div>+ or 0 if not found.</div><div>+ </para></div><div>+ <para></div><div>+ <literal>anyarray_idx(ARRAY['a','b','c'], 'b')</literal></div><div>+ <returnvalue>2</returnvalue></div><div>+ </para></entry></div><div>+ </row></div><div>+</div><div>+ <row></div><div>+ <entry role="func_table_entry"><para role="func_signature"></div><div>+ <function>anyarray_subarray</function> ( <type>anyarray</type>, <parameter>start</parameter> <type>integer</type> [, <parameter>length</parameter> <type>integer</type> ] )</div><div>+ <returnvalue>anyarray</returnvalue></div><div>+ </para></div><div>+ <para></div><div>+ Returns a contiguous slice starting at <parameter>start</parameter></div><div>+ (1-based). If <parameter>length</parameter> is omitted the slice</div><div>+ extends to the end of the array. Out-of-range positions or</div><div>+ non-positive lengths produce an empty array.</div><div>+ </para></div><div>+ <para></div><div>+ <literal>anyarray_subarray('{1,2,3,4,5}'::int8[], 2, 2)</literal></div><div>+ <returnvalue>{2,3}</returnvalue></div><div>+ </para></entry></div><div>+ </row></div><div>+</div><div>+ <row></div><div>+ <entry role="func_table_entry"><para role="func_signature"></div><div>+ <function>anyarray_icount</function> ( <type>anyarray</type> )</div><div>+ <returnvalue>integer</returnvalue></div><div>+ </para></div><div>+ <para></div><div>+ Returns the number of elements in the array (exposed under the</div><div>+ <literal>#</literal> operator).</div><div>+ </para></div><div>+ <para></div><div>+ <literal>anyarray_icount('{10,20,30}'::int8[])</literal></div><div>+ <returnvalue>3</returnvalue></div><div>+ </para></entry></div><div>+ </row></div><div>+</div><div>+ <row></div><div>+ <entry role="func_table_entry"><para role="func_signature"></div><div>+ <function>anyarray_intersect</function> ( <type>anyarray</type>, <type>anyarray</type> )</div><div>+ <returnvalue>anyarray</returnvalue></div><div>+ </para></div><div>+ <para></div><div>+ Returns the sorted, deduplicated values present in both inputs.</div><div>+ </para></div><div>+ <para></div><div>+ <literal>anyarray_intersect('{1,2,3}'::int8[], '{2,3,4}')</literal></div><div>+ <returnvalue>{2,3}</returnvalue></div><div>+ </para></entry></div><div>+ </row></div><div>+</div><div>+ <row></div><div>+ <entry role="func_table_entry"><para role="func_signature"></div><div>+ <function>anyarray_union</function> ( <type>anyarray</type>, <type>anyarray</type> )</div><div>+ <returnvalue>anyarray</returnvalue></div><div>+ </para></div><div>+ <para></div><div>+ Returns the sorted, deduplicated union of both inputs.</div><div>+ </para></div><div>+ <para></div><div>+ <literal>anyarray_union('{1,2}'::int8[], '{2,3}')</literal></div><div>+ <returnvalue>{1,2,3}</returnvalue></div><div>+ </para></entry></div><div>+ </row></div><div>+</div><div>+ <row></div><div>+ <entry role="func_table_entry"><para role="func_signature"></div><div>+ <function>anyarray_union_elem</function> ( <type>anyarray</type>, <type>anyelement</type> )</div><div>+ <returnvalue>anyarray</returnvalue></div><div>+ </para></div><div>+ <para></div><div>+ Adds an element to the array if not already present; result is</div><div>+ sorted and deduplicated.</div><div>+ </para></div><div>+ <para></div><div>+ <literal>anyarray_union_elem('{1,2,3}'::int8[], 4::int8)</literal></div><div>+ <returnvalue>{1,2,3,4}</returnvalue></div><div>+ </para></entry></div><div>+ </row></div><div>+</div><div>+ <row></div><div>+ <entry role="func_table_entry"><para role="func_signature"></div><div>+ <function>anyarray_difference</function> ( <type>anyarray</type>, <type>anyarray</type> )</div><div>+ <returnvalue>anyarray</returnvalue></div><div>+ </para></div><div>+ <para></div><div>+ Returns the sorted, deduplicated values from the first array that</div><div>+ are not in the second.</div><div>+ </para></div><div>+ <para></div><div>+ <literal>anyarray_difference('{1,2,3,4}'::int8[], '{2,4}')</literal></div><div>+ <returnvalue>{1,3}</returnvalue></div><div>+ </para></entry></div><div>+ </row></div><div>+</div><div>+ <row></div><div>+ <entry role="func_table_entry"><para role="func_signature"></div><div>+ <function>anyarray_difference_elem</function> ( <type>anyarray</type>, <type>anyelement</type> )</div><div>+ <returnvalue>anyarray</returnvalue></div><div>+ </para></div><div>+ <para></div><div>+ Removes all occurrences of the given element, preserving the</div><div>+ relative order of the surviving elements.</div><div>+ </para></div><div>+ <para></div><div>+ <literal>anyarray_difference_elem('{1,2,2,3,2}'::int8[], 2::int8)</literal></div><div>+ <returnvalue>{1,3}</returnvalue></div><div>+ </para></entry></div><div>+ </row></div><div>+</div><div>+ <row></div><div>+ <entry role="func_table_entry"><para role="func_signature"></div><div>+ <function>anyquery_querytree</function> ( <type>anyquery</type> )</div><div>+ <returnvalue>text</returnvalue></div><div>+ </para></div><div>+ <para></div><div>+ Returns the query in postfix form; useful for debugging.</div><div>+ </para></div><div>+ <para></div><div>+ <literal>anyquery_querytree('1 &amp; 2 | 3'::anyquery)</literal></div><div>+ <returnvalue>1 2 &amp; 3 |</returnvalue></div><div>+ </para></entry></div><div>+ </row></div><div>+ </tbody></div><div>+ </tgroup></div><div>+ </table></div><div>+</div><div>+ <table id="anyarray-op-table"></div><div>+ <title><filename>anyarray</filename> Operators</title></div><div>+ <tgroup cols="1"></div><div>+ <thead></div><div>+ <row></div><div>+ <entry role="func_table_entry"><para role="func_signature"></div><div>+ Operator</div><div>+ </para></div><div>+ <para></div><div>+ Description</div><div>+ </para></entry></div><div>+ </row></div><div>+ </thead></div><div>+</div><div>+ <tbody></div><div>+ <row></div><div>+ <entry role="func_table_entry"><para role="func_signature"></div><div>+ <type></type> <literal>#</literal> <type>anyarray</type></div><div>+ <returnvalue>integer</returnvalue></div><div>+ </para></div><div>+ <para></div><div>+ Returns the number of elements in the array (prefix operator).</div><div>+ </para></entry></div><div>+ </row></div><div>+</div><div>+ <row></div><div>+ <entry role="func_table_entry"><para role="func_signature"></div><div>+ <type>anyarray</type> <literal>#</literal> <type>anyelement</type></div><div>+ <returnvalue>integer</returnvalue></div><div>+ </para></div><div>+ <para></div><div>+ Returns the 1-based index of the right operand in the array, or 0</div><div>+ if not found. Same as <function>anyarray_idx</function>.</div><div>+ </para></entry></div><div>+ </row></div><div>+</div><div>+ <row></div><div>+ <entry role="func_table_entry"><para role="func_signature"></div><div>+ <type>anyarray</type> <literal>&amp;</literal> <type>anyarray</type></div><div>+ <returnvalue>anyarray</returnvalue></div><div>+ </para></div><div>+ <para></div><div>+ Set intersection of the operands.</div><div>+ </para></entry></div><div>+ </row></div><div>+</div><div>+ <row></div><div>+ <entry role="func_table_entry"><para role="func_signature"></div><div>+ <type>anyarray</type> <literal>|</literal> <type>anyarray</type></div><div>+ <returnvalue>anyarray</returnvalue></div><div>+ </para></div><div>+ <para></div><div>+ Set union of the operands.</div><div>+ </para></entry></div><div>+ </row></div><div>+</div><div>+ <row></div><div>+ <entry role="func_table_entry"><para role="func_signature"></div><div>+ <type>anyarray</type> <literal>|</literal> <type>anyelement</type></div><div>+ <returnvalue>anyarray</returnvalue></div><div>+ </para></div><div>+ <para></div><div>+ Adds the right operand to the array if not already present.</div><div>+ </para></entry></div><div>+ </row></div><div>+</div><div>+ <row></div><div>+ <entry role="func_table_entry"><para role="func_signature"></div><div>+ <type>anyarray</type> <literal>-</literal> <type>anyarray</type></div><div>+ <returnvalue>anyarray</returnvalue></div><div>+ </para></div><div>+ <para></div><div>+ Set difference: elements of the left array not present in the</div><div>+ right.</div><div>+ </para></entry></div><div>+ </row></div><div>+</div><div>+ <row></div><div>+ <entry role="func_table_entry"><para role="func_signature"></div><div>+ <type>anyarray</type> <literal>-</literal> <type>anyelement</type></div><div>+ <returnvalue>anyarray</returnvalue></div><div>+ </para></div><div>+ <para></div><div>+ Removes all occurrences of the right operand from the array.</div><div>+ </para></entry></div><div>+ </row></div><div>+</div><div>+ <row></div><div>+ <entry role="func_table_entry"><para role="func_signature"></div><div>+ <type>anyarray</type> <literal>@@</literal> <type>anyquery</type></div><div>+ <returnvalue>boolean</returnvalue></div><div>+ </para></div><div>+ <para></div><div>+ True if the array satisfies the query (see below).</div><div>+ </para></entry></div><div>+ </row></div><div>+</div><div>+ <row></div><div>+ <entry role="func_table_entry"><para role="func_signature"></div><div>+ <type>anyquery</type> <literal>~~</literal> <type>anyarray</type></div><div>+ <returnvalue>boolean</returnvalue></div><div>+ </para></div><div>+ <para></div><div>+ Commutator of <literal>@@</literal>.</div><div>+ </para></entry></div><div>+ </row></div><div>+ </tbody></div><div>+ </tgroup></div><div>+ </table></div><div>+</div><div>+ <para></div><div>+ The operators <literal>&amp;&amp;</literal>, <literal>@&gt;</literal>,</div><div>+ <literal>&lt;@</literal> and <literal>=</literal> on arrays are</div><div>+ <emphasis>not</emphasis> redefined by this extension; the built-in</div><div>+ PostgreSQL operators are used.</div><div>+ </para></div><div>+ </sect2></div><div>+</div><div>+ <sect2 id="anyarray-query"></div><div>+ <title>The <type>anyquery</type> Type</title></div><div>+</div><div>+ <para></div><div>+ <type>anyquery</type> represents a boolean expression over array</div><div>+ elements. Values are textual tokens; the element type used to interpret</div><div>+ them is determined by the left-hand side of <literal>@@</literal> at</div><div>+ query time. Tokens are combined with the operators</div><div>+ <literal>&amp;</literal> (AND), <literal>|</literal> (OR) and</div><div>+ <literal>!</literal> (NOT). Parentheses control precedence; without</div><div>+ them <literal>&amp;</literal> binds tighter than <literal>|</literal>.</div><div>+ </para></div><div>+</div><div>+ <para></div><div>+ A token consists of any sequence of characters that are not whitespace</div><div>+ or one of <literal>&amp; | ! ( ) "</literal>. To include those</div><div>+ characters or embedded whitespace, wrap the token in double quotes:</div><div>+ <literal>"hello world"</literal>. Inside quotes, a backslash escapes</div><div>+ the next character.</div><div>+ </para></div><div>+</div><div>+ <para></div><div>+ At evaluation time, each token is parsed by the input function of the</div><div>+ array's element type. This means</div><div>+ <literal>'1 &amp; 2'::anyquery @@ ARRAY[1,2,3]::bigint[]</literal></div><div>+ parses <quote>1</quote> and <quote>2</quote> as <type>bigint</type>; the</div><div>+ same query against a <type>text[]</type> column compares the literal</div><div>+ strings <quote>1</quote> and <quote>2</quote>. An unparseable token</div><div>+ raises an error at <literal>@@</literal>-time, not at parse time.</div><div>+ </para></div><div>+ </sect2></div><div>+</div><div>+ <sect2 id="anyarray-index"></div><div>+ <title>Index Support</title></div><div>+</div><div>+ <para></div><div>+ <filename>anyarray</filename> provides a polymorphic GiST opclass and</div><div>+ per-element-type GIN opclasses. All of them support the standard array</div><div>+ operators (<literal>&amp;&amp;</literal>, <literal>@&gt;</literal>,</div><div>+ <literal>&lt;@</literal>, <literal>=</literal>) as well as</div><div>+ <literal>@@</literal>.</div><div>+ </para></div><div>+</div><div>+ <sect3 id="anyarray-index-gist"></div><div>+ <title>GiST: <literal>anyarray_gist_ops</literal></title></div><div>+</div><div>+ <para></div><div>+ A single opclass on <type>anyarray</type> works for any element type</div><div>+ that has a default hash function. Each indexed array is summarised as</div><div>+ a fixed-size bit vector by hashing every element into one bit; internal</div><div>+ nodes store the union of their children. All matches are rechecked</div><div>+ because signatures are lossy.</div><div>+ </para></div><div>+</div><div>+ <para></div><div>+ The optional integer parameter <literal>siglen</literal> sets the</div><div>+ signature length in bytes. The default is 252 bytes (2016 bits);</div><div>+ valid values are between 1 and the index-key size limit. Longer</div><div>+ signatures produce more precise indexes at the cost of more storage.</div><div>+ </para></div><div>+</div><div>+ <programlisting></div><div>+CREATE INDEX my_idx ON my_table USING gist (col anyarray_gist_ops);</div><div>+CREATE INDEX my_idx ON my_table USING gist (col anyarray_gist_ops(siglen=64));</div><div>+ </programlisting></div><div>+ </sect3></div><div>+</div><div>+ <sect3 id="anyarray-index-gin"></div><div>+ <title>GIN: per-type opclasses</title></div><div>+</div><div>+ <para></div><div>+ Because the GIN extractQuery API does not expose the indexed column's</div><div>+ element type when the query is an <type>anyquery</type>, GIN support</div><div>+ is provided as one opclass per concrete element type. Currently</div><div>+ supplied:</div><div>+ </para></div><div>+</div><div>+ <itemizedlist></div><div>+ <listitem><para></div><div>+ <literal>int8_anyquery_gin_ops</literal> for <type>bigint[]</type></div><div>+ </para></listitem></div><div>+ <listitem><para></div><div>+ <literal>uuid_anyquery_gin_ops</literal> for <type>uuid[]</type></div><div>+ </para></listitem></div><div>+ <listitem><para></div><div>+ <literal>text_anyquery_gin_ops</literal> for <type>text[]</type></div><div>+ </para></listitem></div><div>+ </itemizedlist></div><div>+</div><div>+ <para></div><div>+ These opclasses are drop-in replacements for the built-in</div><div>+ <literal>array_ops</literal>; they additionally support</div><div>+ <literal>@@</literal>.</div><div>+ </para></div><div>+</div><div>+ <programlisting></div><div>+CREATE INDEX my_idx ON my_table USING gin (col int8_anyquery_gin_ops);</div><div>+ </programlisting></div><div>+</div><div>+ <para></div><div>+ For element types not in the list above, use the built-in GIN</div><div>+ <literal>array_ops</literal> for set queries and rely on</div><div>+ <literal>anyarray_gist_ops</literal> for <literal>@@</literal>.</div><div>+ </para></div><div>+ </sect3></div><div>+ </sect2></div><div>+</div><div>+ <sect2 id="anyarray-example"></div><div>+ <title>Example</title></div><div>+</div><div>+<programlisting></div><div>+CREATE EXTENSION anyarray;</div><div>+</div><div>+CREATE TABLE message (mid INT PRIMARY KEY, tags TEXT[]);</div><div>+</div><div>+CREATE INDEX message_tags_gist ON message USING gist (tags anyarray_gist_ops);</div><div>+CREATE INDEX message_tags_gin ON message USING gin (tags text_anyquery_gin_ops);</div><div>+</div><div>+-- Messages tagged with both 'urgent' and 'bug':</div><div>+SELECT mid FROM message WHERE tags @&gt; ARRAY['urgent','bug'];</div><div>+</div><div>+-- Same, with the query syntax:</div><div>+SELECT mid FROM message WHERE tags @@ 'urgent &amp; bug'::anyquery;</div><div>+</div><div>+-- Messages tagged 'urgent' but not 'wontfix':</div><div>+SELECT mid FROM message WHERE tags @@ 'urgent &amp; !wontfix'::anyquery;</div><div>+</div><div>+-- Sort and deduplicate a tag list:</div><div>+SELECT anyarray_uniq(tags) FROM message WHERE mid = 42;</div><div>+</programlisting></div><div>+ </sect2></div><div>+</div><div>+ <sect2 id="anyarray-authors"></div><div>+ <title>Authors</title></div><div>+</div><div>+ <para></div><div>+ The <filename>anyarray</filename> module is a generalization of</div><div>+ <xref linkend="intarray"/> by Teodor Sigaev and Oleg Bartunov.</div><div>+ </para></div><div>+ </sect2></div><div>+</div><div>+</sect1></div><div>diff --git a/doc/src/sgml/contrib.sgml b/doc/src/sgml/contrib.sgml</div><div>index b9b03654aad..c9aaf79cb69 100644</div><div>--- a/doc/src/sgml/contrib.sgml</div><div>+++ b/doc/src/sgml/contrib.sgml</div><div>@@ -128,6 +128,7 @@ CREATE EXTENSION <replaceable>extension_name</replaceable>;</div><div> </para></div><div> </div><div> &amcheck;</div><div>+ &anyarray;</div><div> &auth-delay;</div><div> &auto-explain;</div><div> &basebackup-to-shell;</div><div>diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml</div><div>index 25a85082759..0530342e74b 100644</div><div>--- a/doc/src/sgml/filelist.sgml</div><div>+++ b/doc/src/sgml/filelist.sgml</div><div>@@ -119,6 +119,7 @@</div><div> <!-- contrib information --></div><div> <!ENTITY contrib SYSTEM "contrib.sgml"></div><div> <!ENTITY amcheck SYSTEM "amcheck.sgml"></div><div>+<!ENTITY anyarray SYSTEM "anyarray.sgml"></div><div> <!ENTITY auth-delay SYSTEM "auth-delay.sgml"></div><div> <!ENTITY auto-explain SYSTEM "auto-explain.sgml"></div><div> <!ENTITY basic-archive SYSTEM "basic-archive.sgml"></div><div>diff --git a/src/test/regress/regression.diffs b/src/test/regress/regression.diffs</div><div>new file mode 100644</div><div>index 00000000000..e69de29bb2d</div><div>diff --git a/src/test/regress/regression.out b/src/test/regress/regression.out</div><div>new file mode 100644</div><div>index 00000000000..9d8d484179c</div><div>--- /dev/null</div><div>+++ b/src/test/regress/regression.out</div><div>@@ -0,0 +1,2 @@</div><div>+# using postmaster on Unix socket, default port</div><div>+# command failed: "/Users/vlad/pgsql/bin/psql" -X -q -c "SET client_min_messages = warning" -c "DROP DATABASE IF EXISTS \"regression\"" "postgres"Bail out!</div><div>diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list</div><div>index cbd9e10fc1d..049485f5c24 100644</div><div>--- a/src/tools/pgindent/typedefs.list</div><div>+++ b/src/tools/pgindent/typedefs.list</div><div>@@ -126,7 +126,13 @@ AnalyzeAttrFetchFunc</div><div> AnalyzeForeignTable_function</div><div> AnlExprData</div><div> AnlIndexData</div><div>+AnyArrayGistKey</div><div>+AnyArrayGistOptions</div><div> AnyArrayType</div><div>+AnyArrayTypeInfo</div><div>+AnyQuery</div><div>+AnyQueryCheck</div><div>+AnyQueryItem</div><div> Append</div><div> AppendPath</div><div> AppendPathInput</div><div>@@ -1744,6 +1750,7 @@ MVNDistinct</div><div> MVNDistinctItem</div><div> ManyTestResource</div><div> ManyTestResourceKind</div><div>+MatchContext</div><div> Material</div><div> MaterialPath</div><div> MaterialState</div><div>@@ -2372,6 +2379,7 @@ PgXmlStrictness</div><div> Pg_abi_values</div><div> Pg_finfo_record</div><div> Pg_magic_struct</div><div>+PickSplitEntry</div><div> PipeProtoChunk</div><div> PipeProtoHeader</div><div> PlaceHolderInfo</div><div>-- </div><div>2.39.5 (Apple Git-154)</div><div> </div></div></div>